Move token generation to Security namespace

This commit is contained in:
Frederic Guillot
2015-10-25 15:05:19 -04:00
parent 06e9486c59
commit 6756ef2301
15 changed files with 80 additions and 86 deletions

View File

@@ -5,7 +5,7 @@ namespace Kanboard\Auth;
use Kanboard\Core\Base; use Kanboard\Core\Base;
use Kanboard\Core\Request; use Kanboard\Core\Request;
use Kanboard\Event\AuthEvent; use Kanboard\Event\AuthEvent;
use Kanboard\Core\Security; use Kanboard\Core\Security\Token;
/** /**
* RememberMe model * RememberMe model
@@ -165,8 +165,8 @@ class RememberMe extends Base
*/ */
public function create($user_id, $ip, $user_agent) public function create($user_id, $ip, $user_agent)
{ {
$token = hash('sha256', $user_id.$user_agent.$ip.Security::generateToken()); $token = hash('sha256', $user_id.$user_agent.$ip.Token::getToken());
$sequence = Security::generateToken(); $sequence = Token::getToken();
$expiration = time() + self::EXPIRATION; $expiration = time() + self::EXPIRATION;
$this->cleanup($user_id); $this->cleanup($user_id);
@@ -216,7 +216,7 @@ class RememberMe extends Base
*/ */
public function update($token) public function update($token)
{ {
$new_sequence = Security::generateToken(); $new_sequence = Token::getToken();
$this->db $this->db
->table(self::TABLE) ->table(self::TABLE)

View File

@@ -3,9 +3,6 @@
namespace Kanboard\Controller; namespace Kanboard\Controller;
use Pimple\Container; use Pimple\Container;
use Kanboard\Core\Security;
use Kanboard\Core\Request;
use Kanboard\Core\Response;
use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\Event;
/** /**
@@ -16,22 +13,6 @@ use Symfony\Component\EventDispatcher\Event;
*/ */
abstract class Base extends \Kanboard\Core\Base abstract class Base extends \Kanboard\Core\Base
{ {
/**
* Request instance
*
* @accesss protected
* @var \Kanboard\Core\Request
*/
protected $request;
/**
* Response instance
*
* @accesss protected
* @var \Kanboard\Core\Response
*/
protected $response;
/** /**
* Constructor * Constructor
* *
@@ -41,11 +22,9 @@ abstract class Base extends \Kanboard\Core\Base
public function __construct(Container $container) public function __construct(Container $container)
{ {
$this->container = $container; $this->container = $container;
$this->request = new Request;
$this->response = new Response;
if (DEBUG) { if (DEBUG) {
$this->container['logger']->debug('START_REQUEST='.$_SERVER['REQUEST_URI']); $this->logger->debug('START_REQUEST='.$_SERVER['REQUEST_URI']);
} }
} }
@@ -57,14 +36,14 @@ abstract class Base extends \Kanboard\Core\Base
public function __destruct() public function __destruct()
{ {
if (DEBUG) { if (DEBUG) {
foreach ($this->container['db']->getLogMessages() as $message) { foreach ($this->db->getLogMessages() as $message) {
$this->container['logger']->debug($message); $this->logger->debug($message);
} }
$this->container['logger']->debug('SQL_QUERIES={nb}', array('nb' => $this->container['db']->nbQueries)); $this->logger->debug('SQL_QUERIES={nb}', array('nb' => $this->container['db']->nbQueries));
$this->container['logger']->debug('RENDERING={time}', array('time' => microtime(true) - @$_SERVER['REQUEST_TIME_FLOAT'])); $this->logger->debug('RENDERING={time}', array('time' => microtime(true) - @$_SERVER['REQUEST_TIME_FLOAT']));
$this->container['logger']->debug('MEMORY='.$this->helper->text->bytes(memory_get_usage())); $this->logger->debug('MEMORY='.$this->helper->text->bytes(memory_get_usage()));
$this->container['logger']->debug('END_REQUEST='.$_SERVER['REQUEST_URI']); $this->logger->debug('END_REQUEST='.$_SERVER['REQUEST_URI']);
} }
} }
@@ -201,7 +180,7 @@ abstract class Base extends \Kanboard\Core\Base
*/ */
protected function checkCSRFParam() protected function checkCSRFParam()
{ {
if (! Security::validateCSRFToken($this->request->getStringParam('csrf_token'))) { if (! $this->token->validateCSRFToken($this->request->getStringParam('csrf_token'))) {
$this->forbidden(); $this->forbidden();
} }
} }

View File

@@ -8,7 +8,7 @@ namespace Kanboard\Core;
* @package core * @package core
* @author Frederic Guillot * @author Frederic Guillot
*/ */
class Request class Request extends Base
{ {
/** /**
* Get URL string parameter * Get URL string parameter
@@ -57,7 +57,8 @@ class Request
*/ */
public function getValues() public function getValues()
{ {
if (! empty($_POST) && Security::validateCSRFFormToken($_POST)) { if (! empty($_POST) && ! empty($_POST['csrf_token']) && $this->token->validateCSRFToken($_POST['csrf_token'])) {
unset($_POST['csrf_token']);
return $_POST; return $_POST;
} }

View File

@@ -8,7 +8,7 @@ namespace Kanboard\Core;
* @package core * @package core
* @author Frederic Guillot * @author Frederic Guillot
*/ */
class Response class Response extends Base
{ {
/** /**
* Send no cache headers * Send no cache headers

View File

@@ -1,14 +1,16 @@
<?php <?php
namespace Kanboard\Core; namespace Kanboard\Core\Security;
use Kanboard\Core\Base;
/** /**
* Security class * Token Handler
* *
* @package core * @package security
* @author Frederic Guillot * @author Frederic Guillot
*/ */
class Security class Token extends Base
{ {
/** /**
* Generate a random token with different methods: openssl or /dev/urandom or fallback to uniqid() * Generate a random token with different methods: openssl or /dev/urandom or fallback to uniqid()
@@ -17,7 +19,7 @@ class Security
* @access public * @access public
* @return string Random token * @return string Random token
*/ */
public static function generateToken() public static function getToken()
{ {
if (function_exists('openssl_random_pseudo_bytes')) { if (function_exists('openssl_random_pseudo_bytes')) {
return bin2hex(\openssl_random_pseudo_bytes(30)); return bin2hex(\openssl_random_pseudo_bytes(30));
@@ -31,18 +33,16 @@ class Security
/** /**
* Generate and store a CSRF token in the current session * Generate and store a CSRF token in the current session
* *
* @static
* @access public * @access public
* @return string Random token * @return string Random token
*/ */
public static function getCSRFToken() public function getCSRFToken()
{ {
$nonce = self::generateToken(); if (! isset($_SESSION['csrf_tokens'])) {
if (empty($_SESSION['csrf_tokens'])) {
$_SESSION['csrf_tokens'] = array(); $_SESSION['csrf_tokens'] = array();
} }
$nonce = self::getToken();
$_SESSION['csrf_tokens'][$nonce] = true; $_SESSION['csrf_tokens'][$nonce] = true;
return $nonce; return $nonce;
@@ -51,12 +51,11 @@ class Security
/** /**
* Check if the token exists for the current session (a token can be used only one time) * Check if the token exists for the current session (a token can be used only one time)
* *
* @static
* @access public * @access public
* @param string $token CSRF token * @param string $token CSRF token
* @return bool * @return bool
*/ */
public static function validateCSRFToken($token) public function validateCSRFToken($token)
{ {
if (isset($_SESSION['csrf_tokens'][$token])) { if (isset($_SESSION['csrf_tokens'][$token])) {
unset($_SESSION['csrf_tokens'][$token]); unset($_SESSION['csrf_tokens'][$token]);
@@ -65,22 +64,4 @@ class Security
return false; return false;
} }
/**
* Check if the token used in a form is correct and then remove the value
*
* @static
* @access public
* @param array $values Form values
* @return bool
*/
public static function validateCSRFFormToken(array &$values)
{
if (! empty($values['csrf_token']) && self::validateCSRFToken($values['csrf_token'])) {
unset($values['csrf_token']);
return true;
}
return false;
}
} }

View File

@@ -2,7 +2,7 @@
namespace Kanboard\Helper; namespace Kanboard\Helper;
use Kanboard\Core\Security; use Kanboard\Core\Base;
/** /**
* Form helpers * Form helpers
@@ -10,7 +10,7 @@ use Kanboard\Core\Security;
* @package helper * @package helper
* @author Frederic Guillot * @author Frederic Guillot
*/ */
class Form extends \Kanboard\Core\Base class Form extends Base
{ {
/** /**
* Hidden CSRF token field * Hidden CSRF token field
@@ -20,7 +20,7 @@ class Form extends \Kanboard\Core\Base
*/ */
public function csrf() public function csrf()
{ {
return '<input type="hidden" name="csrf_token" value="'.Security::getCSRFToken().'"/>'; return '<input type="hidden" name="csrf_token" value="'.$this->token->getCSRFToken().'"/>';
} }
/** /**

View File

@@ -3,7 +3,7 @@
namespace Kanboard\Helper; namespace Kanboard\Helper;
use Kanboard\Core\Request; use Kanboard\Core\Request;
use Kanboard\Core\Security; use Kanboard\Core\Base;
/** /**
* Url helpers * Url helpers
@@ -11,7 +11,7 @@ use Kanboard\Core\Security;
* @package helper * @package helper
* @author Frederic Guillot * @author Frederic Guillot
*/ */
class Url extends \Kanboard\Core\Base class Url extends Base
{ {
private $base = ''; private $base = '';
private $directory = ''; private $directory = '';
@@ -158,7 +158,7 @@ class Url extends \Kanboard\Core\Base
} }
if ($csrf) { if ($csrf) {
$qs['csrf_token'] = Security::getCSRFToken(); $qs['csrf_token'] = $this->token->getCSRFToken();
} }
if (! empty($qs)) { if (! empty($qs)) {

View File

@@ -3,7 +3,7 @@
namespace Kanboard\Model; namespace Kanboard\Model;
use Kanboard\Core\Translator; use Kanboard\Core\Translator;
use Kanboard\Core\Security; use Kanboard\Core\Security\Token;
use Kanboard\Core\Session; use Kanboard\Core\Session;
/** /**
@@ -265,7 +265,7 @@ class Config extends Setting
*/ */
public function regenerateToken($option) public function regenerateToken($option)
{ {
$this->save(array($option => Security::generateToken())); $this->save(array($option => Token::getToken()));
} }
/** /**

View File

@@ -4,7 +4,7 @@ namespace Kanboard\Model;
use SimpleValidator\Validator; use SimpleValidator\Validator;
use SimpleValidator\Validators; use SimpleValidator\Validators;
use Kanboard\Core\Security; use Kanboard\Core\Security\Token;
/** /**
* Project model * Project model
@@ -491,7 +491,7 @@ class Project extends Base
$this->db $this->db
->table(self::TABLE) ->table(self::TABLE)
->eq('id', $project_id) ->eq('id', $project_id)
->save(array('is_public' => 1, 'token' => Security::generateToken())); ->save(array('is_public' => 1, 'token' => Token::getToken()));
} }
/** /**

View File

@@ -6,7 +6,7 @@ use PicoDb\Database;
use SimpleValidator\Validator; use SimpleValidator\Validator;
use SimpleValidator\Validators; use SimpleValidator\Validators;
use Kanboard\Core\Session; use Kanboard\Core\Session;
use Kanboard\Core\Security; use Kanboard\Core\Security\Token;
/** /**
* User model * User model
@@ -383,7 +383,7 @@ class User extends Base
return $this->db return $this->db
->table(self::TABLE) ->table(self::TABLE)
->eq('id', $user_id) ->eq('id', $user_id)
->save(array('token' => Security::generateToken())); ->save(array('token' => Token::getToken()));
} }
/** /**

View File

@@ -3,7 +3,7 @@
namespace Schema; namespace Schema;
use PDO; use PDO;
use Kanboard\Core\Security; use Kanboard\Core\Security\Token;
const VERSION = 93; const VERSION = 93;
@@ -869,7 +869,7 @@ function version_20(PDO $pdo)
function version_19(PDO $pdo) function version_19(PDO $pdo)
{ {
$pdo->exec("ALTER TABLE config ADD COLUMN api_token VARCHAR(255) DEFAULT ''"); $pdo->exec("ALTER TABLE config ADD COLUMN api_token VARCHAR(255) DEFAULT ''");
$pdo->exec("UPDATE config SET api_token='".Security::generateToken()."'"); $pdo->exec("UPDATE config SET api_token='".Token::getToken()."'");
} }
function version_18(PDO $pdo) function version_18(PDO $pdo)
@@ -1091,6 +1091,6 @@ function version_1(PDO $pdo)
$pdo->exec(" $pdo->exec("
INSERT INTO config INSERT INTO config
(webhooks_token) (webhooks_token)
VALUES ('".Security::generateToken()."') VALUES ('".Token::getToken()."')
"); ");
} }

View File

@@ -3,7 +3,7 @@
namespace Schema; namespace Schema;
use PDO; use PDO;
use Kanboard\Core\Security; use Kanboard\Core\Security\Token;
const VERSION = 73; const VERSION = 73;
@@ -994,6 +994,6 @@ function version_1(PDO $pdo)
$pdo->exec(" $pdo->exec("
INSERT INTO config INSERT INTO config
(webhooks_token, api_token) (webhooks_token, api_token)
VALUES ('".Security::generateToken()."', '".Security::generateToken()."') VALUES ('".Token::getToken()."', '".Token::getToken()."')
"); ");
} }

View File

@@ -2,7 +2,7 @@
namespace Schema; namespace Schema;
use Kanboard\Core\Security; use Kanboard\Core\Security\Token;
use PDO; use PDO;
const VERSION = 88; const VERSION = 88;
@@ -799,7 +799,7 @@ function version_20(PDO $pdo)
function version_19(PDO $pdo) function version_19(PDO $pdo)
{ {
$pdo->exec("ALTER TABLE config ADD COLUMN api_token TEXT DEFAULT ''"); $pdo->exec("ALTER TABLE config ADD COLUMN api_token TEXT DEFAULT ''");
$pdo->exec("UPDATE config SET api_token='".Security::generateToken()."'"); $pdo->exec("UPDATE config SET api_token='".Token::getToken()."'");
} }
function version_18(PDO $pdo) function version_18(PDO $pdo)
@@ -1068,6 +1068,6 @@ function version_1(PDO $pdo)
$pdo->exec(" $pdo->exec("
INSERT INTO config INSERT INTO config
(webhooks_token) (webhooks_token)
VALUES ('".Security::generateToken()."') VALUES ('".Token::getToken()."')
"); ");
} }

View File

@@ -84,6 +84,7 @@ class ClassProvider implements ServiceProviderInterface
'HttpClient', 'HttpClient',
'Lexer', 'Lexer',
'Request', 'Request',
'Response',
'Router', 'Router',
'Session', 'Session',
'Template', 'Template',
@@ -94,6 +95,9 @@ class ClassProvider implements ServiceProviderInterface
'Core\Plugin' => array( 'Core\Plugin' => array(
'Hook', 'Hook',
), ),
'Core\Security' => array(
'Token',
),
'Integration' => array( 'Integration' => array(
'BitbucketWebhook', 'BitbucketWebhook',
'GithubWebhook', 'GithubWebhook',

View File

@@ -0,0 +1,29 @@
<?php
require_once __DIR__.'/../../Base.php';
use Kanboard\Core\Security\Token;
class TokenTest extends Base
{
public function testGenerateToken()
{
$t1 = Token::getToken();
$t2 = Token::getToken();
$this->assertNotEmpty($t1);
$this->assertNotEmpty($t2);
$this->assertNotEquals($t1, $t2);
}
public function testCSRFTokens()
{
$token = new Token($this->container);
$t1 = $token->getCSRFToken();
$this->assertNotEmpty($t1);
$this->assertTrue($token->validateCSRFToken($t1));
$this->assertFalse($token->validateCSRFToken($t1));
}
}