Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/Controller/Traits/LoginTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ public function verify()
->set(['secret' => $secret])
->where(['id' => $temporarySession['id']]);
$query->execute();

$this->request->getSession()->write('temporarySession.secret', $secret);
} catch (\Exception $e) {
$this->request->getSession()->destroy();
$message = $e->getMessage();
Expand Down Expand Up @@ -273,10 +275,12 @@ public function verify()
->set(['secret_verified' => true])
->where(['id' => $user['id']])
->execute();

$user['secret_verified'] = true;
}

$this->request->getSession()->delete('temporarySession');
$this->request->getSession()->write('Auth.User', $user);
$this->Auth->setUser($user);
$url = $this->Auth->redirectUrl();

return $this->redirect($url);
Expand Down
291 changes: 290 additions & 1 deletion tests/TestCase/Controller/Traits/LoginTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -485,10 +485,297 @@ public function testVerifyGetShowQR()
$this->Trait->verify();
}

/**
* Tests that a GET request causes a a new secret to be generated in case it's
* not already present in the session.
*/
public function testVerifyGetGeneratesNewSecret()
{
Configure::write('Users.GoogleAuthenticator.login', true);

$this->Trait->GoogleAuthenticator = $this
->getMockBuilder(GoogleAuthenticatorComponent::class)
->disableOriginalConstructor()
->setMethods(['createSecret', 'getQRCodeImageAsDataUri'])
->getMock();

$this->Trait->request = $this
->getMockBuilder(ServerRequest::class)
->setMethods(['is', 'getData', 'allow', 'getSession'])
->getMock();
$this->Trait->request
->expects($this->once())
->method('is')
->with('post')
->will($this->returnValue(false));

$this->Trait->GoogleAuthenticator
->expects($this->at(0))
->method('createSecret')
->will($this->returnValue('newSecret'));
$this->Trait->GoogleAuthenticator
->expects($this->at(1))
->method('getQRCodeImageAsDataUri')
->with('email@example.com', 'newSecret')
->will($this->returnValue('newDataUriGenerated'));

$session = $this->_mockSession([
'temporarySession' => [
'id' => '00000000-0000-0000-0000-000000000001',
'email' => 'email@example.com',
'secret_verified' => false,
]
]);
$this->Trait->verify();

$this->assertEquals(
[
'temporarySession' => [
'id' => '00000000-0000-0000-0000-000000000001',
'email' => 'email@example.com',
'secret_verified' => false,
'secret' => 'newSecret'
]
],
$session->read()
);
}

/**
* Tests that a GET request does not cause a new secret to be generated in case
* it's already present in the session.
*/
public function testVerifyGetDoesNotGenerateNewSecret()
{
Configure::write('Users.GoogleAuthenticator.login', true);

$this->Trait->GoogleAuthenticator = $this
->getMockBuilder(GoogleAuthenticatorComponent::class)
->disableOriginalConstructor()
->setMethods(['createSecret', 'getQRCodeImageAsDataUri'])
->getMock();

$this->Trait->request = $this
->getMockBuilder(ServerRequest::class)
->setMethods(['is', 'getData', 'allow', 'getSession'])
->getMock();
$this->Trait->request
->expects($this->once())
->method('is')
->with('post')
->will($this->returnValue(false));

$this->Trait->GoogleAuthenticator
->expects($this->never())
->method('createSecret');
$this->Trait->GoogleAuthenticator
->expects($this->at(0))
->method('getQRCodeImageAsDataUri')
->with('email@example.com', 'alreadyPresentSecret')
->will($this->returnValue('newDataUriGenerated'));

$session = $this->_mockSession([
'temporarySession' => [
'id' => '00000000-0000-0000-0000-000000000001',
'email' => 'email@example.com',
'secret_verified' => false,
'secret' => 'alreadyPresentSecret'
]
]);
$this->Trait->verify();

$this->assertEquals(
[
'temporarySession' => [
'id' => '00000000-0000-0000-0000-000000000001',
'email' => 'email@example.com',
'secret_verified' => false,
'secret' => 'alreadyPresentSecret'
]
],
$session->read()
);
}

/**
* Tests that posting a valid code causes verification to succeed.
*/
public function testVerifyPostValidCode()
{
Configure::write('Users.GoogleAuthenticator.login', true);

$this->Trait->GoogleAuthenticator = $this->getMockBuilder(GoogleAuthenticatorComponent::class)
->disableOriginalConstructor()
->setMethods(['createSecret', 'verifyCode', 'getQRCodeImageAsDataUri'])
->getMock();

$this->Trait->Auth = $this->getMockBuilder('Cake\Controller\Component\AuthComponent')
->setMethods(['setUser', 'redirectUrl'])
->disableOriginalConstructor()
->getMock();

$this->Trait->request = $this->getMockBuilder(ServerRequest::class)
->setMethods(['is', 'getData', 'allow', 'getSession'])
->getMock();
$this->Trait->request->expects($this->once())
->method('is')
->with('post')
->will($this->returnValue(true));
$this->Trait->request->expects($this->once())
->method('getData')
->with('code')
->will($this->returnValue('123456'));

$this->Trait->GoogleAuthenticator
->expects($this->never())
->method('createSecret');
$this->Trait->GoogleAuthenticator
->expects($this->at(0))
->method('getQRCodeImageAsDataUri')
->with('email@example.com', 'yyy')
->will($this->returnValue('newDataUriGenerated'));
$this->Trait->GoogleAuthenticator
->expects($this->at(1))
->method('verifyCode')
->with('yyy', '123456')
->will($this->returnValue(true));

$this->Trait->Auth
->expects($this->at(0))
->method('setUser')
->with([
'id' => '00000000-0000-0000-0000-000000000001',
'email' => 'email@example.com',
'secret_verified' => true
]);
$this->Trait->Auth
->expects($this->at(1))
->method('redirectUrl')
->will($this->returnValue('/'));

$this->assertFalse($this->table->exists([
'id' => '00000000-0000-0000-0000-000000000001',
'secret_verified' => true
]));

$session = $this->_mockSession([
'temporarySession' => [
'id' => '00000000-0000-0000-0000-000000000001',
'email' => 'email@example.com',
'secret_verified' => false,
'secret' => 'yyy'
]
]);
$this->Trait->verify();

$this->assertTrue($this->table->exists([
'id' => '00000000-0000-0000-0000-000000000001',
'secret_verified' => true
]));

$this->assertEmpty($session->read());
}

/**
* Tests that posting and invalid code causes verification to fail.
*/
public function testVerifyPostInvalidCode()
{
Configure::write('Users.GoogleAuthenticator.login', true);

$this->Trait->GoogleAuthenticator = $this
->getMockBuilder(GoogleAuthenticatorComponent::class)
->disableOriginalConstructor()
->setMethods(['createSecret', 'verifyCode', 'getQRCodeImageAsDataUri'])
->getMock();

$this->Trait->Auth = $this
->getMockBuilder('Cake\Controller\Component\AuthComponent')
->setMethods(['setUser'])
->disableOriginalConstructor()
->getMock();

$this->Trait->Flash = $this
->getMockBuilder('Cake\Controller\Component\FlashComponent')
->setMethods(['error'])
->disableOriginalConstructor()
->getMock();

$this->Trait->request = $this
->getMockBuilder(ServerRequest::class)
->setMethods(['is', 'getData', 'allow', 'getSession'])
->getMock();
$this->Trait->request
->expects($this->once())
->method('is')
->with('post')
->will($this->returnValue(true));
$this->Trait->request
->expects($this->once())
->method('getData')
->with('code')
->will($this->returnValue('invalid'));

$this->Trait->GoogleAuthenticator
->expects($this->never())
->method('createSecret');
$this->Trait->GoogleAuthenticator
->expects($this->at(0))
->method('getQRCodeImageAsDataUri')
->with('email@example.com', 'yyy')
->will($this->returnValue('newDataUriGenerated'));
$this->Trait->GoogleAuthenticator
->expects($this->at(1))
->method('verifyCode')
->with('yyy', 'invalid')
->will($this->returnValue(false));

$this->Trait->Auth
->expects($this->never())
->method('setUser');

$this->Trait->Flash
->expects($this->once())
->method('error')
->with('Verification code is invalid. Try again', 'default', [], 'auth');

$this->Trait
->expects($this->once())
->method('redirect')
->with([
'plugin' => 'CakeDC/Users',
'controller' => 'Users',
'action' => 'login',
'prefix' => false
]);

$this->assertFalse($this->table->exists([
'id' => '00000000-0000-0000-0000-000000000001',
'secret_verified' => true
]));

$session = $this->_mockSession([
'temporarySession' => [
'id' => '00000000-0000-0000-0000-000000000001',
'email' => 'email@example.com',
'secret_verified' => false,
'secret' => 'yyy'
]
]);
$this->Trait->verify();

$this->assertFalse($this->table->exists([
'id' => '00000000-0000-0000-0000-000000000001',
'secret_verified' => true
]));

$this->assertEmpty($session->read());
}

/**
* Mock session and mock session attributes
*
* @return void
* @return \Cake\Http\Session
*/
protected function _mockSession($attributes)
{
Expand All @@ -502,5 +789,7 @@ protected function _mockSession($attributes)
->expects($this->any())
->method('getSession')
->willReturn($session);

return $session;
}
}