diff --git a/.travis.yml b/.travis.yml
index b88ac90..0230d90 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,11 +2,13 @@ language: php
php:
- 7.0
- 7.1
+- 7.2
- nightly
matrix:
allow_failures:
- php: nightly
+ - php: 7.2
before_script:
- composer install
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f679653..7dcc63d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
-## [v1.0.1](https://github.com/linna/csrf-guard/compare/v1.0.0...v1.0.1) - 2017-XX-XX
+## [v1.1.0](https://github.com/linna/csrf-guard/compare/v1.0.0...v1.1.0) - 2017-08-20
+
+### Added
+* `getTimedToken()` method for expiring tokens
+
+### Changed
+* `validate()` naw can validate for timed tokens
+* Tests updated
+* Internal methods refactor
+
+### Deprecated
+* `getHiddenInput()` method
### Fixed
* `private $session;` docblock
diff --git a/src/CsrfGuard.php b/src/CsrfGuard.php
index 1e82b6f..784b65f 100644
--- a/src/CsrfGuard.php
+++ b/src/CsrfGuard.php
@@ -77,30 +77,67 @@ private function dequeue(array &$array)
*/
public function getToken() : array
{
- $tokenName = 'csrf_'.bin2hex(random_bytes(8));
- $token = bin2hex(random_bytes($this->tokenStrength));
+ $token = $this->generateToken();
- $this->session['CSRF'][$tokenName] = $token;
+ $name = $token['name'];
+
+ $this->session['CSRF'][$name] = $token;
//storage cleaning!
//warning!! if you get in a page more token of maximun storage,
//will there a leak of token, the firsts generated
//in future I think throw and exception.
$this->dequeue($this->session['CSRF']);
-
- return ['name' => $tokenName, 'token' => $token];
+
+ return $token;
}
+ /**
+ * Return timed csrf token as array.
+ *
+ * @param int $ttl Time to live for the token.
+ *
+ * @return array
+ */
+ public function getTimedToken(int $ttl) : array
+ {
+ $token = $this->generateToken();
+ $token['time'] = time() + $ttl;
+
+ $name = $token['name'];
+
+ $this->session['CSRF'][$name] = $token;
+
+ $this->dequeue($this->session['CSRF']);
+
+ return $token;
+ }
+
+ /**
+ * Generate a random token.
+ *
+ * @return array
+ */
+ private function generateToken() : array
+ {
+ $name = 'csrf_'.bin2hex(random_bytes(8));
+ $value = bin2hex(random_bytes($this->tokenStrength));
+
+ return ['name' => $name, 'value' => $value];
+ }
+
/**
* Return csrf token as hidden input form.
*
* @return string
+ *
+ * @deprecated since version 1.1.0
*/
public function getHiddenInput() : string
{
$token = $this->getToken();
- return '';
+ return '';
}
/**
@@ -113,14 +150,40 @@ public function getHiddenInput() : string
*/
public function validate(array $requestData) : bool
{
- $arrayToken = $this->session['CSRF'];
+ //apply matchToken method elements of passed data,
+ //using this instead of forach for code shortness.
+ $array = array_filter($requestData, array($this, 'matchToken'), ARRAY_FILTER_USE_BOTH);
+
+ return (bool) count($array);
+ }
+
+ /**
+ * Tests for valid token.
+ *
+ * @param string $value
+ * @param string $key
+ *
+ * @return bool
+ */
+ private function matchToken(string $value, string $key) : bool
+ {
+ $tokens = $this->session['CSRF'];
+
+ //check if token exist
+ if (!isset($tokens[$key])) {
+ return false;
+ }
- foreach ($requestData as $key => $value) {
- if (isset($arrayToken[$key]) && hash_equals($arrayToken[$key], $value)) {
- return true;
- }
+ //check if token is valid
+ if (!hash_equals($tokens[$key]['value'], $value)) {
+ return false;
}
- return false;
+ //check if token is expired if timed
+ if (isset($tokens[$key]['time']) && $tokens[$key]['time'] < time()) {
+ return false;
+ }
+
+ return true;
}
}
diff --git a/tests/CsrfGuardTest.php b/tests/CsrfGuardTest.php
index a132954..8e8105e 100644
--- a/tests/CsrfGuardTest.php
+++ b/tests/CsrfGuardTest.php
@@ -113,15 +113,42 @@ public function testDequeue(int $sizeLimit)
public function testGetToken()
{
session_start();
-
+
$csrf = new CsrfGuard(32, 16);
-
+
$token = $csrf->getToken();
+
+ $key = key($_SESSION['CSRF']);
+ $value = $_SESSION['CSRF'][$key]['value'];
+
+ $this->assertEquals($key, $token['name']);
+ $this->assertEquals($value, $token['value']);
+
+ session_destroy();
+ }
+
+ /**
+ * Test get timed token.
+ *
+ * @runInSeparateProcess
+ */
+ public function testGetTimedToken()
+ {
+ session_start();
+
+ $csrf = new CsrfGuard(32, 16);
+
+ $token = $csrf->getTimedToken(5);
+ $tokenTime = time() + 5;
$key = key($_SESSION['CSRF']);
+ $value = $_SESSION['CSRF'][$key]['value'];
+ $time = $_SESSION['CSRF'][$key]['time'];
$this->assertEquals($key, $token['name']);
- $this->assertEquals($_SESSION['CSRF'][$key], $token['token']);
+ $this->assertEquals($value, $token['value']);
+ $this->assertEquals($time, $token['time']);
+ $this->assertEquals($tokenTime, $token['time']);
session_destroy();
}
@@ -140,7 +167,7 @@ public function testGetHiddenInput()
$input = $csrf->getHiddenInput();
$key = key($_SESSION['CSRF']);
- $token = $_SESSION['CSRF'][$key];
+ $token = $_SESSION['CSRF'][$key]['value'];
$this->assertEquals('', $input);
@@ -160,7 +187,7 @@ public function testValidate()
$csrf->getToken();
$key = key($_SESSION['CSRF']);
- $token = $_SESSION['CSRF'][$key];
+ $token = $_SESSION['CSRF'][$key]['value'];
$this->assertEquals(true, $csrf->validate([$key => $token]));
$this->assertEquals(false, $csrf->validate(['foo' => $token]));