Skip to content

Commit

Permalink
Merge pull request #4 from linna/v1.1.0
Browse files Browse the repository at this point in the history
v1.1.0 release
  • Loading branch information
s3b4stian authored Aug 20, 2017
2 parents 84ad641 + 4aeddd4 commit 95abf09
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 18 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
87 changes: 75 additions & 12 deletions src/CsrfGuard.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 '<input type="hidden" name="'.$token['name'].'" value="'.$token['token'].'" />';
return '<input type="hidden" name="'.$token['name'].'" value="'.$token['value'].'" />';
}

/**
Expand All @@ -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;
}
}
37 changes: 32 additions & 5 deletions tests/CsrfGuardTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand All @@ -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 type="hidden" name="'.$key.'" value="'.$token.'" />', $input);

Expand All @@ -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]));
Expand Down

0 comments on commit 95abf09

Please sign in to comment.