Merge branch 'generate-token' into 2.1

This commit is contained in:
mark_story 2011-12-04 13:47:26 -05:00
commit c6f492d947
2 changed files with 67 additions and 11 deletions

View file

@ -157,6 +157,19 @@ class SecurityComponent extends Component {
*/ */
public $csrfUseOnce = true; public $csrfUseOnce = true;
/**
* Control the number of tokens a user can keep open.
* This is most useful with one-time use tokens. Since new tokens
* are created on each request, having a hard limit on the number of open tokens
* can be useful in controlling the size of the session file.
*
* When tokens are evicted, the oldest ones will be removed, as they are the most likely
* to be dead/expired.
*
* @var integer
*/
public $csrfLimit = 100;
/** /**
* Other components used by the Security component * Other components used by the Security component
* *
@ -207,7 +220,7 @@ class SecurityComponent extends Component {
return $this->blackHole($controller, 'csrf'); return $this->blackHole($controller, 'csrf');
} }
} }
$this->_generateToken($controller); $this->generateToken($controller->request);
if ($isPost) { if ($isPost) {
unset($controller->request->data['_Token']); unset($controller->request->data['_Token']);
} }
@ -469,16 +482,15 @@ class SecurityComponent extends Component {
} }
/** /**
* Add authentication key for new form posts * Manually add CSRF token information into the provided request object.
* *
* @param Controller $controller Instantiating controller * @param CakeRequest $request The request object to add into.
* @return boolean Success * @return boolean
*/ */
protected function _generateToken($controller) { public function generateToken(CakeRequest $request) {
if (isset($controller->request->params['requested']) && $controller->request->params['requested'] === 1) { if (isset($request->params['requested']) && $request->params['requested'] === 1) {
if ($this->Session->check('_Token')) { if ($this->Session->check('_Token')) {
$tokenData = $this->Session->read('_Token'); $request->params['_Token'] = $this->Session->read('_Token');
$controller->request->params['_Token'] = $tokenData;
} }
return false; return false;
} }
@ -498,15 +510,15 @@ class SecurityComponent extends Component {
$token['csrfTokens'] = $this->_expireTokens($tokenData['csrfTokens']); $token['csrfTokens'] = $this->_expireTokens($tokenData['csrfTokens']);
} }
} }
if ($this->csrfCheck && ($this->csrfUseOnce || empty($token['csrfTokens'])) ) { if ($this->csrfUseOnce || empty($token['csrfTokens'])) {
$token['csrfTokens'][$authKey] = strtotime($this->csrfExpires); $token['csrfTokens'][$authKey] = strtotime($this->csrfExpires);
} }
if ($this->csrfCheck && $this->csrfUseOnce == false) { if (!$this->csrfUseOnce) {
$csrfTokens = array_keys($token['csrfTokens']); $csrfTokens = array_keys($token['csrfTokens']);
$token['key'] = $csrfTokens[0]; $token['key'] = $csrfTokens[0];
} }
$this->Session->write('_Token', $token); $this->Session->write('_Token', $token);
$controller->request->params['_Token'] = array( $request->params['_Token'] = array(
'key' => $token['key'], 'key' => $token['key'],
'unlockedFields' => $token['unlockedFields'] 'unlockedFields' => $token['unlockedFields']
); );
@ -542,6 +554,10 @@ class SecurityComponent extends Component {
*/ */
protected function _expireTokens($tokens) { protected function _expireTokens($tokens) {
$now = time(); $now = time();
$overflow = count($tokens) - $this->csrfLimit;
if ($overflow > 0) {
$tokens = array_slice($tokens, $overflow + 1, null, true);
}
foreach ($tokens as $nonce => $expires) { foreach ($tokens as $nonce => $expires) {
if ($expires < $now) { if ($expires < $now) {
unset($tokens[$nonce]); unset($tokens[$nonce]);

View file

@ -1277,4 +1277,44 @@ class SecurityComponentTest extends CakeTestCase {
$token = $this->Security->Session->read('_Token'); $token = $this->Security->Session->read('_Token');
$this->assertTrue(isset($token['csrfTokens']['nonce1']), 'Token was consumed'); $this->assertTrue(isset($token['csrfTokens']['nonce1']), 'Token was consumed');
} }
/**
* Test generateToken()
*
* @return void
*/
public function testGenerateToken() {
$request = $this->Controller->request;
$this->Security->generateToken($request);
$this->assertNotEmpty($request->params['_Token']);
$this->assertTrue(isset($request->params['_Token']['unlockedFields']));
$this->assertTrue(isset($request->params['_Token']['key']));
}
/**
* Test the limiting of CSRF tokens.
*
* @return void
*/
public function testCsrfLimit() {
$this->Security->csrfLimit = 3;
$time = strtotime('+10 minutes');
$tokens = array(
'1' => $time,
'2' => $time,
'3' => $time,
'4' => $time,
'5' => $time,
);
$this->Security->Session->write('_Token', array('csrfTokens' => $tokens));
$this->Security->generateToken($this->Controller->request);
$result = $this->Security->Session->read('_Token.csrfTokens');
$this->assertFalse(isset($result['1']));
$this->assertFalse(isset($result['2']));
$this->assertFalse(isset($result['3']));
$this->assertTrue(isset($result['4']));
$this->assertTrue(isset($result['5']));
}
} }