Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New feature: add afterGenerateToken event with customToken plugin #1308

Merged
merged 2 commits into from Jul 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Expand Up @@ -72,7 +72,7 @@
!/plugins/Demo/
!/plugins/AuditLog/
!/plugins/mailSenderToFrom/

!/plugins/customToken/

#ignore old styles
/styles/
Expand Down
10 changes: 1 addition & 9 deletions application/controllers/admin/tokens.php
Expand Up @@ -985,15 +985,7 @@ public function addDummies($iSurveyId, $subaction = '')
$token->lastname = str_replace('{TOKEN_COUNTER}', $newDummyToken, $token->lastname);
$token->email = str_replace('{TOKEN_COUNTER}', $newDummyToken, $token->email);

$attempts = 0;
do {
$token->token = Token::generateRandomToken($aData['tokenlength']);
$attempts++;
} while (isset($existingtokens[$token->token]) && $attempts < 50);

if ($attempts == 50) {
throw new Exception('Something is wrong with your random generator.');
}
$token->generateToken($aData['tokenlength']);

$existingtokens[$token->token] = true;
$token->encryptSave();
Expand Down
26 changes: 17 additions & 9 deletions application/models/Token.php
Expand Up @@ -220,17 +220,17 @@ public function findByToken($token)
* Generates a token for this object.
* @throws CHttpException
*/
public function generateToken()
public function generateToken($tokenlength = NULL)
{
$iTokenLength = $this->survey->tokenlength;
$this->token = $this::generateRandomToken($iTokenLength);
$iTokenLength = $tokenlength ? $tokenlength : $this->survey->tokenlength;
$this->token = $this->_generateRandomToken($iTokenLength);
$counter = 0;
while (!$this->validate(array('token'))) {
$this->token = $this::generateRandomToken($iTokenLength);
$this->token = $this->_generateRandomToken($iTokenLength);
$counter++;
// This is extremely unlikely.
if ($counter > 10) {
throw new CHttpException(500, 'Failed to create unique token in 10 attempts.');
if ($counter > 50) {
throw new CHttpException(500, 'Failed to create unique token in 50 attempts.');
}
}
}
Expand All @@ -241,9 +241,17 @@ public function generateToken()
* @param integer $iTokenLength
* @return string
*/
public static function generateRandomToken($iTokenLength)
private function _generateRandomToken($iTokenLength)
{
return str_replace(array('~', '_'), array('a', 'z'), Yii::app()->securityManager->generateRandomString($iTokenLength));
$token = str_replace(array('~', '_'), array('a', 'z'), Yii::app()->securityManager->generateRandomString($iTokenLength));
$event = new PluginEvent('afterGenerateToken');
$event->set('surveyId', $this->getSurveyId());
$event->set('iTokenLength', $iTokenLength);
$event->set('oToken', $this);
$event->set('token', $token);
App()->pluginManager->dispatchEvent($event);
$token = $event->get('token');
return $token;
}

/**
Expand Down Expand Up @@ -293,7 +301,7 @@ public function generateTokens()
foreach ($tkresult as $tkrow) {
$bIsValidToken = false;
while ($bIsValidToken == false && $invalidtokencount < 50) {
$newtoken = $this::generateRandomToken($iTokenLength);
$newtoken = $this->_generateRandomToken($iTokenLength);
if (!isset($existingtokens[$newtoken])) {
$existingtokens[$newtoken] = true;
$bIsValidToken = true;
Expand Down
21 changes: 21 additions & 0 deletions plugins/customToken/LICENSE.txt
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2019 Tools for Research

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
31 changes: 31 additions & 0 deletions plugins/customToken/README.md
@@ -0,0 +1,31 @@
# customToken: numeric, non-ambiguous, CAPITAL token formats #

## Installation

In LimeSurvey 4: go to Configuration > Plugin manager > Scan files > Install
In LimeSurvey 3: the plugin in /plugins/customToken should be recognized automatically.

## Activation

After activation a new item will be in the Simple plugins menu.

## Configuration on the survey level
You can select one out of 4 options on the survey level:
* No custom function
* Numeric tokens
* Without ambiguous characters
* CAPITALS ONLY

## Deactivation
On deactivating: the settings for this plugin will be removed from the plugin_settings DB table.

## Uninstallation

In LimeSurvey 4: go to Configuration > Plugin manager > Uninstall the plugin
In LimeSurvey 3: remove the directory /plugins/customToken
Note: the files will be deleted from your filesystem, but will be re-installed on a LS update

## Home page & Copyright
- HomePage [Tools for Research](https://www.toolsforresearch.com)
- Copyright � 2019 [Tools for Research](https://www.toolsforresearch.com)
- Licence : MIT <https://en.wikipedia.org/wiki/MIT_License>
21 changes: 21 additions & 0 deletions plugins/customToken/config.xml
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<config>
<metadata>
<name>customToken</name>
<type>plugin</type>
<creationDate>2019-07-22</creationDate>
<last_update>2019-07-22</last_update>
<author>Tools for Research</author>
<authorUrl>https://www.toolsforresearch.com</authorUrl>
<version>1.0.0</version>
<license>MIT</license>
<description><![CDATA[Numeric, non-ambiguous or CAPITAL tokens]]></description>
</metadata>

<compatibility>
<version>4.0</version>
</compatibility>

<updaters disabled="disabled">
</updaters>
</config>
126 changes: 126 additions & 0 deletions plugins/customToken/customToken.php
@@ -0,0 +1,126 @@
<?php
class customToken extends PluginBase {

protected $storage = 'DbStorage';
static protected $name = 'customToken';
static protected $description = 'Numeric, non-ambiguous or CAPITAL tokens';

public function init()
{
/**
* Here you should handle subscribing to the events your plugin will handle
*/
$this->subscribe('afterGenerateToken', 'generateCustomToken');

// Provides survey specific settings.
$this->subscribe('beforeSurveySettings');

// Saves survey specific settings.
$this->subscribe('newSurveySettings');

// Clean up on deactivate
$this->subscribe('beforeDeactivate');
}

/**
* The custom generate function
*/
public function generateCustomToken()
{
$event = $this->getEvent();
$iSurveyID=$event->get('surveyId');
$iTokenLength = $event->get('iTokenLength');
$token = "";
if ($this->get('customToken', 'Survey', $iSurveyID) == 0) {
// 0 = No custom function for this survey: return without changes in $event
return;
}
else if ($this->get('customToken', 'Survey', $iSurveyID) == 1) {
// 1 = Numeric tokens
$token = randomChars($iTokenLength, '123456789');
}
else if ($this->get('customToken', 'Survey', $iSurveyID) == 2) {
// 2 = Without ambiguous characters including 'hard to manually enter'
// https://github.com/LimeSurvey/LimeSurvey/commit/154e026fbe6e53037e46a8c30f2b837459235acc
$token = str_replace(
array('~','_','0','O','1','l','I'),
array('a','z','7','P','8','k','K'), Yii::app()->securityManager->generateRandomString($iTokenLength));
}
else if ($this->get('customToken', 'Survey', $iSurveyID) == 3) {
// 3 = CAPITALS ONLY
if (function_exists('crypto_rand_secure')) {
/**
* Adjusted from Yii::app()->securityManager->generateRandomString($length=32)
* https://github.com/LimeSurvey/LimeSurvey/blob/master/application/core/web/LSYii_SecurityManager.php#L71
* Use crypto_rand_secure($min, $max) defined in application/helpers/common_helper.php
*/
$codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for($i=0;$i<$iTokenLength;$i++){
$token .= $codeAlphabet[crypto_rand_secure(0,strlen($codeAlphabet))];
}
} else {
/**
* Secure enough, although not cryptographically secure
* https://www.php.net/manual/en/function.rand.php
*/
for($i=0;$i<$iTokenLength;$i++){
$token .= chr(64+rand(1, 26));
}
}
}
$event->set('token', $token);
}

/**
* This event is fired by the administration panel to gather extra settings
* available for a survey. Example URL in LS 3.17:
* /index.php/admin/survey/sa/rendersidemenulink/subaction/plugins/surveyid/46159
*/
public function beforeSurveySettings()
{
$pluginsettings = $this->getPluginSettings(true);

$event = $this->getEvent();
$iSurveyID=$event->get('iSurveyID');
$event->set("surveysettings.{$iSurveyID}", array(
'name' => get_class($this),
'settings' => array(
'customToken' => array(
'type' => 'select',
'options'=>array(
0=>'No custom function for this survey',
1=>'Numeric tokens',
2=>'Without ambiguous characters',
3=>'CAPITALS ONLY'
),
'default' => 0,
'label' => 'Custom token:',
'current' => $this->get('customToken', 'Survey', $event->get('survey'))
)
)
));
}

/**
* Save the settings
*/
public function newSurveySettings()
{
$event = $this->getEvent();
foreach ($event->get('settings') as $name => $value)
{
$this->set($name, $value, 'Survey', $event->get('survey'));
}
}

/**
* Clean up the plugin settings table
*/
public function beforeDeactivate()
{
$sDBPrefix = Yii::app()->db->tablePrefix;
$sql = "DELETE FROM {$sDBPrefix}plugin_settings WHERE `key` LIKE :key";
Yii::app()->db->createCommand($sql)->execute(array(':key' => "customToken"));
}

}