PageRenderTime 40ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Controller/Traits/OneTimePasswordVerifyTrait.php

http://github.com/CakeDC/users
PHP | 197 lines | 125 code | 27 blank | 45 comment | 12 complexity | f021fff1ca0cc56f6d5b916c748810c1 MD5 | raw file
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * Copyright 2010 - 2019, Cake Development Corporation (https://www.cakedc.com)
  5. *
  6. * Licensed under The MIT License
  7. * Redistributions of files must retain the above copyright notice.
  8. *
  9. * @copyright Copyright 2010 - 2018, Cake Development Corporation (https://www.cakedc.com)
  10. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  11. */
  12. namespace CakeDC\Users\Controller\Traits;
  13. use Cake\Core\Configure;
  14. use CakeDC\Auth\Authentication\AuthenticationService;
  15. use CakeDC\Auth\Authenticator\TwoFactorAuthenticator;
  16. trait OneTimePasswordVerifyTrait
  17. {
  18. /**
  19. * Verify for Google Authenticator
  20. * If Google Authenticator's enabled we need to verify
  21. * authenticated user. To avoid accidental access to
  22. * other URL's we store auth'ed used into temporary session
  23. * to perform code verification.
  24. *
  25. * @return mixed
  26. */
  27. public function verify()
  28. {
  29. $loginAction = array_merge(
  30. Configure::read('Auth.AuthenticationComponent.loginAction'),
  31. [
  32. '?' => $this->getRequest()->getQueryParams(),
  33. ]
  34. );
  35. if (!$this->isVerifyAllowed()) {
  36. return $this->redirect($loginAction);
  37. }
  38. $temporarySession = $this->getRequest()->getSession()->read(
  39. AuthenticationService::TWO_FACTOR_VERIFY_SESSION_KEY
  40. );
  41. $secretVerified = $temporarySession['secret_verified'] ?? null;
  42. // showing QR-code until shared secret is verified
  43. if (!$secretVerified) {
  44. $secret = $this->onVerifyGetSecret($temporarySession);
  45. if (empty($secret)) {
  46. return $this->redirect($loginAction);
  47. }
  48. $secretDataUri = $this->OneTimePasswordAuthenticator->getQRCodeImageAsDataUri(
  49. $temporarySession['email'],
  50. $secret
  51. );
  52. $this->set(compact('secretDataUri'));
  53. }
  54. if ($this->getRequest()->is('post')) {
  55. return $this->onPostVerifyCode($loginAction);
  56. }
  57. }
  58. /**
  59. * Check If Google Authenticator's enabled we need to verify
  60. * authenticated user and if temporySession is present
  61. *
  62. * @return bool
  63. */
  64. protected function isVerifyAllowed()
  65. {
  66. if (!Configure::read('OneTimePasswordAuthenticator.login')) {
  67. $message = __d('cake_d_c/users', 'Please enable Google Authenticator first.');
  68. $this->Flash->error($message, [
  69. 'key' => 'auth',
  70. 'element' => 'default',
  71. 'params' => [],
  72. ]);
  73. return false;
  74. }
  75. $temporarySession = $this->getRequest()->getSession()->read(
  76. AuthenticationService::TWO_FACTOR_VERIFY_SESSION_KEY
  77. );
  78. if (empty($temporarySession) || !isset($temporarySession['id'])) {
  79. $message = __d('cake_d_c/users', 'Could not find user data');
  80. $this->Flash->error($message, [
  81. 'key' => 'auth',
  82. 'element' => 'default',
  83. 'params' => [],
  84. ]);
  85. return false;
  86. }
  87. return true;
  88. }
  89. /**
  90. * Get the Google Authenticator secret of user, if not exists try to create one and save
  91. *
  92. * @param \CakeDC\Users\Model\Entity\User $user user data present on session
  93. * @return string if empty the creation has failed
  94. */
  95. protected function onVerifyGetSecret($user)
  96. {
  97. if (isset($user['secret']) && $user['secret']) {
  98. return $user['secret'];
  99. }
  100. $secret = $this->OneTimePasswordAuthenticator->createSecret();
  101. // catching sql exception in case of any sql inconsistencies
  102. try {
  103. $query = $this->getUsersTable()->query();
  104. $query->update()
  105. ->set(['secret' => $secret])
  106. ->where(['id' => $user['id']]);
  107. $query->execute();
  108. $user['secret'] = $secret;
  109. $this->getRequest()->getSession()->write(AuthenticationService::TWO_FACTOR_VERIFY_SESSION_KEY, $user);
  110. } catch (\Exception $e) {
  111. $this->getRequest()->getSession()->destroy();
  112. $this->log($e);
  113. $message = __d('cake_d_c/users', 'Could not verify, please try again');
  114. $this->Flash->error($message, [
  115. 'key' => 'auth',
  116. 'element' => 'default',
  117. 'params' => [],
  118. ]);
  119. return '';
  120. }
  121. return $secret;
  122. }
  123. /**
  124. * Handle the action when user post the form with code
  125. *
  126. * @param array $loginAction url to login page used in redirect
  127. * @return \Cake\Http\Response
  128. */
  129. protected function onPostVerifyCode($loginAction)
  130. {
  131. $codeVerified = false;
  132. $verificationCode = $this->getRequest()->getData('code');
  133. $user = $this->getRequest()->getSession()->read(AuthenticationService::TWO_FACTOR_VERIFY_SESSION_KEY);
  134. $entity = $this->getUsersTable()->get($user['id']);
  135. if (!empty($entity['secret'])) {
  136. $codeVerified = $this->OneTimePasswordAuthenticator->verifyCode($entity['secret'], $verificationCode);
  137. }
  138. if (!$codeVerified) {
  139. $this->getRequest()->getSession()->destroy();
  140. $message = __d('cake_d_c/users', 'Verification code is invalid. Try again');
  141. $this->Flash->error($message, [
  142. 'key' => 'auth',
  143. 'element' => 'default',
  144. 'params' => [],
  145. ]);
  146. return $this->redirect($loginAction);
  147. }
  148. return $this->onPostVerifyCodeOkay($loginAction, $user);
  149. }
  150. /**
  151. * Handle the part of action when user post the form with valid code
  152. *
  153. * @param array $loginAction url to login page used in redirect
  154. * @param \CakeDC\Users\Model\Entity\User $user user data present on session
  155. * @return \Cake\Http\Response
  156. */
  157. protected function onPostVerifyCodeOkay($loginAction, $user)
  158. {
  159. unset($user['secret']);
  160. if (!$user['secret_verified']) {
  161. $this->getUsersTable()->query()->update()
  162. ->set(['secret_verified' => true])
  163. ->where(['id' => $user['id']])
  164. ->execute();
  165. }
  166. $this->getRequest()->getSession()->delete(AuthenticationService::TWO_FACTOR_VERIFY_SESSION_KEY);
  167. $this->getRequest()->getSession()->write(TwoFactorAuthenticator::USER_SESSION_KEY, $user);
  168. return $this->redirect($loginAction);
  169. }
  170. }