Browse Source

Refactor tokens.

mscherer 5 years ago
parent
commit
625011451c

+ 2 - 2
composer.json

@@ -50,8 +50,8 @@
 		"test": "php phpunit.phar",
 		"test": "php phpunit.phar",
 		"test-setup": "[ ! -f phpunit.phar ] && wget https://phar.phpunit.de/phpunit-8.5.1.phar && mv phpunit-8.5.1.phar phpunit.phar || true",
 		"test-setup": "[ ! -f phpunit.phar ] && wget https://phar.phpunit.de/phpunit-8.5.1.phar && mv phpunit-8.5.1.phar phpunit.phar || true",
 		"test-coverage": "php phpunit.phar --log-junit webroot/coverage/unitreport.xml --coverage-html webroot/coverage --coverage-clover webroot/coverage/coverage.xml",
 		"test-coverage": "php phpunit.phar --log-junit webroot/coverage/unitreport.xml --coverage-html webroot/coverage --coverage-clover webroot/coverage/coverage.xml",
-		"cs-check": "phpcs -p --standard=vendor/fig-r/psr2r-sniffer/PSR2R/ruleset.xml --extensions=php --ignore=/tests/test_files/ src/ tests/ config/",
-		"cs-fix": "phpcbf -v --standard=vendor/fig-r/psr2r-sniffer/PSR2R/ruleset.xml --extensions=php --ignore=/tests/test_files/ src/ tests/ config/"
+		"cs-check": "phpcs -p --standard=vendor/fig-r/psr2r-sniffer/PSR2R/ruleset.xml --extensions=php --ignore=/config/Migrations/,/tests/test_files/ src/ tests/ config/",
+		"cs-fix": "phpcbf -v --standard=vendor/fig-r/psr2r-sniffer/PSR2R/ruleset.xml --extensions=php --ignore=/config/Migrations/,/tests/test_files/ src/ tests/ config/"
 	},
 	},
 	"prefer-stable": true,
 	"prefer-stable": true,
 	"config": {
 	"config": {

+ 3 - 7
config/Migrations/20200430170235_MigrationToolsTokens.php

@@ -23,7 +23,7 @@ class MigrationToolsTokens extends AbstractMigration {
 				'limit' => 20,
 				'limit' => 20,
 				'null' => false,
 				'null' => false,
 			])
 			])
-			->addColumn('token', 'string', [
+			->addColumn('token_key', 'string', [
 				'default' => null,
 				'default' => null,
 				'limit' => 60,
 				'limit' => 60,
 				'null' => false,
 				'null' => false,
@@ -49,12 +49,8 @@ class MigrationToolsTokens extends AbstractMigration {
 				'limit' => null,
 				'limit' => null,
 				'null' => true,
 				'null' => true,
 			])
 			])
-			->addIndex(
-				[
-					'user_id',
-				]
-			)
-			->addIndex(['token'], ['unique' => true])
+			->addIndex(['user_id'])
+			->addIndex(['token_key'], ['unique' => true])
 			->create();
 			->create();
 	}
 	}
 
 

+ 30 - 4
docs/Model/Tokens.md

@@ -10,7 +10,10 @@ The main methods of the model are
 * spendKey(int $id)
 * spendKey(int $id)
 
 
 User and security relevant token usage should always be bound to the id of the user (user_id).
 User and security relevant token usage should always be bound to the id of the user (user_id).
+Here you should also use one-time tokens only.
+
 Other operations can also omit this field.
 Other operations can also omit this field.
+Here you could also use unlimited tokens if needed.
 
 
 ## Install
 ## Install
 ```
 ```
@@ -24,16 +27,18 @@ over the migration file and customize. In that case execute it then without the
 ### Register action
 ### Register action
 ```php
 ```php
 $this->loadModel('Tools.Tokens');
 $this->loadModel('Tools.Tokens');
-$cCode = $this->Tokens->newKey('activate', null, $user->id);
+$tokenKey = $this->Tokens->newKey('activate', null, $user->id);
 ```
 ```
 
 
+As 4th parameter any string content can be stored.
+
 ### Activate action
 ### Activate action
 ```php
 ```php
 $this->loadModel('Tools.Tokens');
 $this->loadModel('Tools.Tokens');
-$token = $this->Tokens->useKey('activate', $keyToCheck);
+$token = $this->Tokens->useKey('activate', $tokenKey);
 
 
 if ($token && $token->used) {
 if ($token && $token->used) {
-    $this->Flash->warning(__('alreadyActivatedMessage'));
+    $this->Flash->warning(__('Already activated'));
 } elseif ($token) {
 } elseif ($token) {
     $uid = $token->user_id;
     $uid = $token->user_id;
     // Confirm activation and redirect to home
     // Confirm activation and redirect to home
@@ -42,6 +47,13 @@ if ($token && $token->used) {
 
 
 ## Other usage
 ## Other usage
 
 
+### Changing email
+Here the 4th argument comes in handy.
+The new email address one will be stored in content until
+validation is complete and will then replace the old one.
+
+### One time email links
+Login or otherwise.
 
 
 
 
 ## Garbage Collect
 ## Garbage Collect
@@ -51,7 +63,21 @@ $this->loadModel('Tools.Tokens');
 $this->Tokens->garbageCollector();
 $this->Tokens->garbageCollector();
 ```
 ```
 
 
+## Stats
+There is also a method `stats()` to retrieve statistics if required/useful to you.
+
+## Security notes
+By default, the tokens have a validity of one week.
+You can modify this value in your model.
+
+Do not send plain passwords with your emails or print them out anywhere.
+That's why you should send the expiring tokens.
+
+If you feel like you need more information on the implementation process,
+read [this article at troyhunt.com](http://www.troyhunt.com/2012/05/everything-you-ever-wanted-to-know.html).
+It describes in a very verbose way what to do and what better not to do.
+
 ## Upgrade Notes from 3.x
 ## Upgrade Notes from 3.x
 If you come from 3.x:
 If you come from 3.x:
-- The field `key` is now `token` to avoid SQL reserved keyword issue.
+- The field `key` is now `token_key` to avoid SQL reserved keyword issue.
 - Typehints are now in place.
 - Typehints are now in place.

+ 5 - 5
src/Model/Entity/Token.php

@@ -6,7 +6,7 @@ namespace Tools\Model\Entity;
  * @property int $id
  * @property int $id
  * @property int $user_id
  * @property int $user_id
  * @property string $type
  * @property string $type
- * @property string $token
+ * @property string $token_key
  * @property-read string $key
  * @property-read string $key
  * @property string $content
  * @property string $content
  * @property int $used
  * @property int $used
@@ -17,16 +17,16 @@ namespace Tools\Model\Entity;
 class Token extends Entity {
 class Token extends Entity {
 
 
 	/**
 	/**
-	 * Shim to allow ->key access for ->token.
+	 * Shim to allow ->key access for ->token_key.
 	 *
 	 *
-	 * @deprecated Use token instead.
+	 * @deprecated Use token_key instead.
 	 *
 	 *
 	 * @return string|null
 	 * @return string|null
 	 */
 	 */
 	public function _getKey(): ?string {
 	public function _getKey(): ?string {
-		trigger_error('Deprecated. Use ->token instead.', E_USER_DEPRECATED);
+		trigger_error('Deprecated. Use ->token_key instead.', E_USER_DEPRECATED);
 
 
-		return $this->token;
+		return $this->token_key;
 	}
 	}
 
 
 }
 }

+ 10 - 10
src/Model/Table/TokensTable.php

@@ -24,7 +24,7 @@ class TokensTable extends Table {
 	/**
 	/**
 	 * @var string
 	 * @var string
 	 */
 	 */
-	public $displayField = 'key';
+	public $displayField = 'token_key';
 
 
 	/**
 	/**
 	 * @var array
 	 * @var array
@@ -51,7 +51,7 @@ class TokensTable extends Table {
 				'message' => 'valErrMandatoryField',
 				'message' => 'valErrMandatoryField',
 			],
 			],
 		],
 		],
-		'token' => [
+		'token_key' => [
 			'notBlank' => [
 			'notBlank' => [
 				'rule' => ['notBlank'],
 				'rule' => ['notBlank'],
 				'message' => 'valErrMandatoryField',
 				'message' => 'valErrMandatoryField',
@@ -79,18 +79,18 @@ class TokensTable extends Table {
 	 * Checks if this key is already used (should be unique in table)
 	 * Checks if this key is already used (should be unique in table)
 	 *
 	 *
 	 * @param string $type Type: necessary
 	 * @param string $type Type: necessary
-	 * @param string|null $token Key: optional key, otherwise a key will be generated
+	 * @param string|null $key Key: optional key, otherwise a key will be generated
 	 * @param mixed|null $uid Uid: optional (if used, only this user can use this key)
 	 * @param mixed|null $uid Uid: optional (if used, only this user can use this key)
 	 * @param string|array|null $content Content: up to 255 characters of content may be added (optional)
 	 * @param string|array|null $content Content: up to 255 characters of content may be added (optional)
 	 *
 	 *
 	 * @return string|null Key on success, null otherwise
 	 * @return string|null Key on success, null otherwise
 	 */
 	 */
-	public function newKey(string $type, ?string $token = null, $uid = null, $content = null): ?string {
-		if (!$token) {
-			$token = $this->generateKey($this->defaultLength);
+	public function newKey(string $type, ?string $key = null, $uid = null, $content = null): ?string {
+		if (!$key) {
+			$key = $this->generateKey($this->defaultLength);
 			$keyLength = $this->defaultLength;
 			$keyLength = $this->defaultLength;
 		} else {
 		} else {
-			$keyLength = mb_strlen($token);
+			$keyLength = mb_strlen($key);
 		}
 		}
 
 
 		if (is_array($content)) {
 		if (is_array($content)) {
@@ -101,7 +101,7 @@ class TokensTable extends Table {
 			'type' => $type,
 			'type' => $type,
 			'user_id' => $uid,
 			'user_id' => $uid,
 			'content' => (string)$content,
 			'content' => (string)$content,
-			'token' => $token,
+			'token_key' => $key,
 		];
 		];
 
 
 		$entity = $this->newEntity($data);
 		$entity = $this->newEntity($data);
@@ -114,7 +114,7 @@ class TokensTable extends Table {
 			}
 			}
 		}
 		}
 
 
-		return $entity->token;
+		return $entity->token_key;
 	}
 	}
 
 
 	/**
 	/**
@@ -127,7 +127,7 @@ class TokensTable extends Table {
 	 * @return \Tools\Model\Entity\Token|null Content - if successfully used or if already used (used=1), NULL otherwise.
 	 * @return \Tools\Model\Entity\Token|null Content - if successfully used or if already used (used=1), NULL otherwise.
 	 */
 	 */
 	public function useKey(string $type, string $key, $uid = null, $treatUsedAsInvalid = false) {
 	public function useKey(string $type, string $key, $uid = null, $treatUsedAsInvalid = false) {
-		$options = ['conditions' => [$this->getAlias() . '.token' => $key, $this->getAlias() . '.type' => $type]];
+		$options = ['conditions' => [$this->getAlias() . '.token_key' => $key, $this->getAlias() . '.type' => $type]];
 		if ($uid) {
 		if ($uid) {
 			$options['conditions'][$this->getAlias() . '.user_id'] = $uid;
 			$options['conditions'][$this->getAlias() . '.user_id'] = $uid;
 		}
 		}

+ 11 - 11
tests/Fixture/TokensFixture.php

@@ -18,7 +18,7 @@ class TokensFixture extends TestFixture {
 		'id' => ['type' => 'integer', 'null' => false, 'default' => null, 'length' => 10],
 		'id' => ['type' => 'integer', 'null' => false, 'default' => null, 'length' => 10],
 		'user_id' => ['type' => 'integer', 'null' => true, 'length' => 10, 'comment' => ''],
 		'user_id' => ['type' => 'integer', 'null' => true, 'length' => 10, 'comment' => ''],
 		'type' => ['type' => 'string', 'null' => true, 'default' => null, 'length' => 20, 'comment' => 'e.g.:activate,reactivate'],
 		'type' => ['type' => 'string', 'null' => true, 'default' => null, 'length' => 20, 'comment' => 'e.g.:activate,reactivate'],
-		'token' => ['type' => 'string', 'null' => true, 'default' => null, 'length' => 60, 'comment' => ''],
+		'token_key' => ['type' => 'string', 'null' => true, 'default' => null, 'length' => 60, 'comment' => ''],
 		'content' => ['type' => 'string', 'null' => true, 'length' => 255, 'default' => null, 'comment' => 'can transport some information'],
 		'content' => ['type' => 'string', 'null' => true, 'length' => 255, 'default' => null, 'comment' => 'can transport some information'],
 		'used' => ['type' => 'integer', 'null' => false, 'default' => '0', 'collate' => null, 'comment' => ''],
 		'used' => ['type' => 'integer', 'null' => false, 'default' => '0', 'collate' => null, 'comment' => ''],
 		'created' => ['type' => 'datetime', 'null' => true, 'default' => null, 'collate' => null, 'comment' => ''],
 		'created' => ['type' => 'datetime', 'null' => true, 'default' => null, 'collate' => null, 'comment' => ''],
@@ -36,7 +36,7 @@ class TokensFixture extends TestFixture {
 		[
 		[
 			'user_id' => 1,
 			'user_id' => 1,
 			'type' => 'qlogin',
 			'type' => 'qlogin',
-			'token' => '7k8qdcizigtudvxn2v9zep',
+			'token_key' => '7k8qdcizigtudvxn2v9zep',
 			'content' => 'i:1;',
 			'content' => 'i:1;',
 			'used' => 0,
 			'used' => 0,
 			'created' => '2011-08-02 18:00:41',
 			'created' => '2011-08-02 18:00:41',
@@ -46,7 +46,7 @@ class TokensFixture extends TestFixture {
 		[
 		[
 			'user_id' => '2',
 			'user_id' => '2',
 			'type' => 'qlogin',
 			'type' => 'qlogin',
-			'token' => '23e32tpkcmdn8x9j8n0n00',
+			'token_key' => '23e32tpkcmdn8x9j8n0n00',
 			'content' => 'i:2;',
 			'content' => 'i:2;',
 			'used' => 0,
 			'used' => 0,
 			'created' => '2011-08-02 18:00:41',
 			'created' => '2011-08-02 18:00:41',
@@ -56,7 +56,7 @@ class TokensFixture extends TestFixture {
 		[
 		[
 			'user_id' => '1',
 			'user_id' => '1',
 			'type' => 'qlogin',
 			'type' => 'qlogin',
-			'token' => '3mpzed7eoewsjvyvg4vy35',
+			'token_key' => '3mpzed7eoewsjvyvg4vy35',
 			'content' => 'a:3:{s:10:"controller";s:4:"test";s:6:"action";s:3:"foo";i:0;s:3:"bar";}',
 			'content' => 'a:3:{s:10:"controller";s:4:"test";s:6:"action";s:3:"foo";i:0;s:3:"bar";}',
 			'used' => 1,
 			'used' => 1,
 			'created' => '2011-08-02 18:00:41',
 			'created' => '2011-08-02 18:00:41',
@@ -66,7 +66,7 @@ class TokensFixture extends TestFixture {
 		[
 		[
 			'user_id' => '2',
 			'user_id' => '2',
 			'type' => 'qlogin',
 			'type' => 'qlogin',
-			'token' => 'af8ww4y7jxzq5n6npmjpxx',
+			'token_key' => 'af8ww4y7jxzq5n6npmjpxx',
 			'content' => 's:13:"/test/foo/bar";',
 			'content' => 's:13:"/test/foo/bar";',
 			'used' => 1,
 			'used' => 1,
 			'created' => '2011-08-02 18:00:41',
 			'created' => '2011-08-02 18:00:41',
@@ -76,7 +76,7 @@ class TokensFixture extends TestFixture {
 		[
 		[
 			'user_id' => '1',
 			'user_id' => '1',
 			'type' => 'qlogin',
 			'type' => 'qlogin',
-			'token' => '2s7i3zjw0rn009j4no552b',
+			'token_key' => '2s7i3zjw0rn009j4no552b',
 			'content' => 'i:1;',
 			'content' => 'i:1;',
 			'used' => 0,
 			'used' => 0,
 			'created' => '2011-08-02 18:01:16',
 			'created' => '2011-08-02 18:01:16',
@@ -86,7 +86,7 @@ class TokensFixture extends TestFixture {
 		[
 		[
 			'user_id' => '2',
 			'user_id' => '2',
 			'type' => 'qlogin',
 			'type' => 'qlogin',
-			'token' => 'tro596dig63cay0ps09vre',
+			'token_key' => 'tro596dig63cay0ps09vre',
 			'content' => 'i:2;',
 			'content' => 'i:2;',
 			'used' => 0,
 			'used' => 0,
 			'created' => '2011-08-02 18:01:16',
 			'created' => '2011-08-02 18:01:16',
@@ -96,7 +96,7 @@ class TokensFixture extends TestFixture {
 		[
 		[
 			'user_id' => '1',
 			'user_id' => '1',
 			'type' => 'qlogin',
 			'type' => 'qlogin',
-			'token' => 'penfangwc40x550wwvgfmu',
+			'token_key' => 'penfangwc40x550wwvgfmu',
 			'content' => 'a:3:{s:10:"controller";s:4:"test";s:6:"action";s:3:"foo";i:0;s:3:"bar";}',
 			'content' => 'a:3:{s:10:"controller";s:4:"test";s:6:"action";s:3:"foo";i:0;s:3:"bar";}',
 			'used' => 1,
 			'used' => 1,
 			'created' => '2011-08-02 18:01:16',
 			'created' => '2011-08-02 18:01:16',
@@ -106,7 +106,7 @@ class TokensFixture extends TestFixture {
 		[
 		[
 			'user_id' => '2',
 			'user_id' => '2',
 			'type' => 'qlogin',
 			'type' => 'qlogin',
-			'token' => '2y7m5srasm3ozej0izxbhe',
+			'token_key' => '2y7m5srasm3ozej0izxbhe',
 			'content' => 's:13:"/test/foo/bar";',
 			'content' => 's:13:"/test/foo/bar";',
 			'used' => 1,
 			'used' => 1,
 			'created' => '2011-08-02 18:01:16',
 			'created' => '2011-08-02 18:01:16',
@@ -116,7 +116,7 @@ class TokensFixture extends TestFixture {
 		[
 		[
 			'user_id' => '1',
 			'user_id' => '1',
 			'type' => 'qlogin',
 			'type' => 'qlogin',
-			'token' => '5c6dp2w54ynxii2xo3c50m',
+			'token_key' => '5c6dp2w54ynxii2xo3c50m',
 			'content' => 'i:1;',
 			'content' => 'i:1;',
 			'used' => 0,
 			'used' => 0,
 			'created' => '2011-08-02 18:01:54',
 			'created' => '2011-08-02 18:01:54',
@@ -126,7 +126,7 @@ class TokensFixture extends TestFixture {
 		[
 		[
 			'user_id' => '2',
 			'user_id' => '2',
 			'type' => 'qlogin',
 			'type' => 'qlogin',
-			'token' => 'fr6a0d4waue2v6hmqeyek5',
+			'token_key' => 'fr6a0d4waue2v6hmqeyek5',
 			'content' => 'i:2;',
 			'content' => 'i:2;',
 			'used' => 0,
 			'used' => 0,
 			'created' => '2011-08-02 18:01:54',
 			'created' => '2011-08-02 18:01:54',