@@ -23,213 +23,213 @@ |
||
| 23 | 23 | |
| 24 | 24 | class PageForgotPassword extends InternalPageBase |
| 25 | 25 | { |
| 26 | - /** |
|
| 27 | - * Main function for this page, when no specific actions are called. |
|
| 28 | - * |
|
| 29 | - * This is the forgotten password reset form |
|
| 30 | - * @category Security-Critical |
|
| 31 | - */ |
|
| 32 | - protected function main() |
|
| 33 | - { |
|
| 34 | - if (WebRequest::wasPosted()) { |
|
| 35 | - $this->validateCSRFToken(); |
|
| 36 | - $username = WebRequest::postString('username'); |
|
| 37 | - $email = WebRequest::postEmail('email'); |
|
| 38 | - $database = $this->getDatabase(); |
|
| 39 | - |
|
| 40 | - if ($username === null || trim($username) === "" || $email === null || trim($email) === "") { |
|
| 41 | - throw new ApplicationLogicException("Both username and email address must be specified!"); |
|
| 42 | - } |
|
| 43 | - |
|
| 44 | - $user = User::getByUsername($username, $database); |
|
| 45 | - $this->sendResetMail($user, $email); |
|
| 46 | - |
|
| 47 | - SessionAlert::success('<strong>Your password reset request has been completed.</strong> If the details you have provided match our records, you should receive an email shortly.'); |
|
| 48 | - |
|
| 49 | - $this->redirect('login'); |
|
| 50 | - } |
|
| 51 | - else { |
|
| 52 | - $this->assignCSRFToken(); |
|
| 53 | - $this->setTemplate('forgot-password/forgotpw.tpl'); |
|
| 54 | - } |
|
| 55 | - } |
|
| 56 | - |
|
| 57 | - /** |
|
| 58 | - * Sends a reset email if the user is authenticated |
|
| 59 | - * |
|
| 60 | - * @param User|boolean $user The user located from the database, or false. Doesn't really matter, since we do the |
|
| 61 | - * check anyway within this method and silently skip if we don't have a user. |
|
| 62 | - * @param string $email The provided email address |
|
| 63 | - */ |
|
| 64 | - private function sendResetMail($user, $email) |
|
| 65 | - { |
|
| 66 | - // If the user isn't found, or the email address is wrong, skip sending the details silently. |
|
| 67 | - if (!$user instanceof User) { |
|
| 68 | - return; |
|
| 69 | - } |
|
| 70 | - |
|
| 71 | - if (strtolower($user->getEmail()) === strtolower($email)) { |
|
| 72 | - $clientIp = $this->getXffTrustProvider() |
|
| 73 | - ->getTrustedClientIp(WebRequest::remoteAddress(), WebRequest::forwardedAddress()); |
|
| 74 | - |
|
| 75 | - $this->cleanExistingTokens($user); |
|
| 76 | - |
|
| 77 | - $hash = Base32::encodeUpper(openssl_random_pseudo_bytes(30)); |
|
| 78 | - |
|
| 79 | - $encryptionHelper = new EncryptionHelper($this->getSiteConfiguration()); |
|
| 80 | - |
|
| 81 | - $cred = new Credential(); |
|
| 82 | - $cred->setDatabase($this->getDatabase()); |
|
| 83 | - $cred->setFactor(-1); |
|
| 84 | - $cred->setUserId($user->getId()); |
|
| 85 | - $cred->setType('reset'); |
|
| 86 | - $cred->setData($encryptionHelper->encryptData($hash)); |
|
| 87 | - $cred->setVersion(0); |
|
| 88 | - $cred->setDisabled(0); |
|
| 89 | - $cred->setTimeout(new DateTimeImmutable('+ 1 hour')); |
|
| 90 | - $cred->setPriority(9); |
|
| 91 | - $cred->save(); |
|
| 92 | - |
|
| 93 | - $this->assign("user", $user); |
|
| 94 | - $this->assign("hash", $hash); |
|
| 95 | - $this->assign("remoteAddress", $clientIp); |
|
| 96 | - |
|
| 97 | - $emailContent = $this->fetchTemplate('forgot-password/reset-mail.tpl'); |
|
| 98 | - |
|
| 99 | - // FIXME: domains! |
|
| 100 | - /** @var Domain $domain */ |
|
| 101 | - $domain = Domain::getById(1, $this->getDatabase()); |
|
| 102 | - $this->getEmailHelper()->sendMail( |
|
| 103 | - null, $user->getEmail(), "WP:ACC password reset", $emailContent); |
|
| 104 | - } |
|
| 105 | - } |
|
| 106 | - |
|
| 107 | - /** |
|
| 108 | - * Entry point for the reset action |
|
| 109 | - * |
|
| 110 | - * This is the reset password part of the form. |
|
| 111 | - * @category Security-Critical |
|
| 112 | - */ |
|
| 113 | - protected function reset() |
|
| 114 | - { |
|
| 115 | - $si = WebRequest::getString('si'); |
|
| 116 | - $id = WebRequest::getString('id'); |
|
| 117 | - |
|
| 118 | - if ($si === null || trim($si) === "" || $id === null || trim($id) === "") { |
|
| 119 | - throw new ApplicationLogicException("Link not valid, please ensure it has copied correctly"); |
|
| 120 | - } |
|
| 121 | - |
|
| 122 | - $database = $this->getDatabase(); |
|
| 123 | - $user = $this->getResettingUser($id, $database, $si); |
|
| 124 | - |
|
| 125 | - // Dual mode |
|
| 126 | - if (WebRequest::wasPosted()) { |
|
| 127 | - $this->validateCSRFToken(); |
|
| 128 | - try { |
|
| 129 | - $this->doReset($user); |
|
| 130 | - $this->cleanExistingTokens($user); |
|
| 131 | - } |
|
| 132 | - catch (ApplicationLogicException $ex) { |
|
| 133 | - SessionAlert::error($ex->getMessage()); |
|
| 134 | - $this->redirect('forgotPassword', 'reset', array('si' => $si, 'id' => $id)); |
|
| 135 | - |
|
| 136 | - return; |
|
| 137 | - } |
|
| 138 | - } |
|
| 139 | - else { |
|
| 140 | - $this->assignCSRFToken(); |
|
| 141 | - $this->assign('user', $user); |
|
| 142 | - $this->setTemplate('forgot-password/forgotpwreset.tpl'); |
|
| 143 | - $this->addJs("/vendor/dropbox/zxcvbn/dist/zxcvbn.js"); |
|
| 144 | - } |
|
| 145 | - } |
|
| 146 | - |
|
| 147 | - /** |
|
| 148 | - * Gets the user resetting their password from the database, or throwing an exception if that is not possible. |
|
| 149 | - * |
|
| 150 | - * @param integer $id The ID of the user to retrieve |
|
| 151 | - * @param PdoDatabase $database The database object to use |
|
| 152 | - * @param string $si The reset hash provided |
|
| 153 | - * |
|
| 154 | - * @return User |
|
| 155 | - * @throws ApplicationLogicException |
|
| 156 | - */ |
|
| 157 | - private function getResettingUser($id, $database, $si) |
|
| 158 | - { |
|
| 159 | - $user = User::getById($id, $database); |
|
| 160 | - |
|
| 161 | - if ($user === false || $user->isCommunityUser()) { |
|
| 162 | - throw new ApplicationLogicException("Password reset failed. Please try again."); |
|
| 163 | - } |
|
| 164 | - |
|
| 165 | - $statement = $database->prepare("SELECT * FROM credential WHERE type = 'reset' AND user = :user;"); |
|
| 166 | - $statement->execute([':user' => $user->getId()]); |
|
| 167 | - |
|
| 168 | - /** @var Credential $credential */ |
|
| 169 | - $credential = $statement->fetchObject(Credential::class); |
|
| 170 | - |
|
| 171 | - $statement->closeCursor(); |
|
| 172 | - |
|
| 173 | - if ($credential === false) { |
|
| 174 | - throw new ApplicationLogicException("Password reset failed. Please try again."); |
|
| 175 | - } |
|
| 176 | - |
|
| 177 | - $credential->setDatabase($database); |
|
| 178 | - |
|
| 179 | - $encryptionHelper = new EncryptionHelper($this->getSiteConfiguration()); |
|
| 180 | - if ($encryptionHelper->decryptData($credential->getData()) != $si) { |
|
| 181 | - throw new ApplicationLogicException("Password reset failed. Please try again."); |
|
| 182 | - } |
|
| 183 | - |
|
| 184 | - if ($credential->getTimeout() < new DateTimeImmutable()) { |
|
| 185 | - $credential->delete(); |
|
| 186 | - throw new ApplicationLogicException("Password reset token expired. Please try again."); |
|
| 187 | - } |
|
| 188 | - |
|
| 189 | - return $user; |
|
| 190 | - } |
|
| 191 | - |
|
| 192 | - /** |
|
| 193 | - * Performs the setting of the new password |
|
| 194 | - * |
|
| 195 | - * @param User $user The user to set the password for |
|
| 196 | - * |
|
| 197 | - * @throws ApplicationLogicException |
|
| 198 | - */ |
|
| 199 | - private function doReset(User $user) |
|
| 200 | - { |
|
| 201 | - $pw = WebRequest::postString('newpassword'); |
|
| 202 | - $pw2 = WebRequest::postString('newpasswordconfirm'); |
|
| 203 | - |
|
| 204 | - if ($pw !== $pw2) { |
|
| 205 | - throw new ApplicationLogicException('Passwords do not match!'); |
|
| 206 | - } |
|
| 207 | - |
|
| 208 | - $passwordCredentialProvider = new PasswordCredentialProvider($user->getDatabase(), $this->getSiteConfiguration()); |
|
| 209 | - $passwordCredentialProvider->setCredential($user, 1, $pw); |
|
| 210 | - |
|
| 211 | - SessionAlert::success('You may now log in!'); |
|
| 212 | - $this->redirect('login'); |
|
| 213 | - } |
|
| 214 | - |
|
| 215 | - protected function isProtectedPage() |
|
| 216 | - { |
|
| 217 | - return false; |
|
| 218 | - } |
|
| 219 | - |
|
| 220 | - /** |
|
| 221 | - * @param $user |
|
| 222 | - */ |
|
| 223 | - private function cleanExistingTokens($user): void |
|
| 224 | - { |
|
| 225 | - // clean out existing reset tokens |
|
| 226 | - $statement = $this->getDatabase()->prepare("SELECT * FROM credential WHERE type = 'reset' AND user = :user;"); |
|
| 227 | - $statement->execute([':user' => $user->getId()]); |
|
| 228 | - $existing = $statement->fetchAll(PdoDatabase::FETCH_CLASS, Credential::class); |
|
| 229 | - |
|
| 230 | - foreach ($existing as $c) { |
|
| 231 | - $c->setDatabase($this->getDatabase()); |
|
| 232 | - $c->delete(); |
|
| 233 | - } |
|
| 234 | - } |
|
| 26 | + /** |
|
| 27 | + * Main function for this page, when no specific actions are called. |
|
| 28 | + * |
|
| 29 | + * This is the forgotten password reset form |
|
| 30 | + * @category Security-Critical |
|
| 31 | + */ |
|
| 32 | + protected function main() |
|
| 33 | + { |
|
| 34 | + if (WebRequest::wasPosted()) { |
|
| 35 | + $this->validateCSRFToken(); |
|
| 36 | + $username = WebRequest::postString('username'); |
|
| 37 | + $email = WebRequest::postEmail('email'); |
|
| 38 | + $database = $this->getDatabase(); |
|
| 39 | + |
|
| 40 | + if ($username === null || trim($username) === "" || $email === null || trim($email) === "") { |
|
| 41 | + throw new ApplicationLogicException("Both username and email address must be specified!"); |
|
| 42 | + } |
|
| 43 | + |
|
| 44 | + $user = User::getByUsername($username, $database); |
|
| 45 | + $this->sendResetMail($user, $email); |
|
| 46 | + |
|
| 47 | + SessionAlert::success('<strong>Your password reset request has been completed.</strong> If the details you have provided match our records, you should receive an email shortly.'); |
|
| 48 | + |
|
| 49 | + $this->redirect('login'); |
|
| 50 | + } |
|
| 51 | + else { |
|
| 52 | + $this->assignCSRFToken(); |
|
| 53 | + $this->setTemplate('forgot-password/forgotpw.tpl'); |
|
| 54 | + } |
|
| 55 | + } |
|
| 56 | + |
|
| 57 | + /** |
|
| 58 | + * Sends a reset email if the user is authenticated |
|
| 59 | + * |
|
| 60 | + * @param User|boolean $user The user located from the database, or false. Doesn't really matter, since we do the |
|
| 61 | + * check anyway within this method and silently skip if we don't have a user. |
|
| 62 | + * @param string $email The provided email address |
|
| 63 | + */ |
|
| 64 | + private function sendResetMail($user, $email) |
|
| 65 | + { |
|
| 66 | + // If the user isn't found, or the email address is wrong, skip sending the details silently. |
|
| 67 | + if (!$user instanceof User) { |
|
| 68 | + return; |
|
| 69 | + } |
|
| 70 | + |
|
| 71 | + if (strtolower($user->getEmail()) === strtolower($email)) { |
|
| 72 | + $clientIp = $this->getXffTrustProvider() |
|
| 73 | + ->getTrustedClientIp(WebRequest::remoteAddress(), WebRequest::forwardedAddress()); |
|
| 74 | + |
|
| 75 | + $this->cleanExistingTokens($user); |
|
| 76 | + |
|
| 77 | + $hash = Base32::encodeUpper(openssl_random_pseudo_bytes(30)); |
|
| 78 | + |
|
| 79 | + $encryptionHelper = new EncryptionHelper($this->getSiteConfiguration()); |
|
| 80 | + |
|
| 81 | + $cred = new Credential(); |
|
| 82 | + $cred->setDatabase($this->getDatabase()); |
|
| 83 | + $cred->setFactor(-1); |
|
| 84 | + $cred->setUserId($user->getId()); |
|
| 85 | + $cred->setType('reset'); |
|
| 86 | + $cred->setData($encryptionHelper->encryptData($hash)); |
|
| 87 | + $cred->setVersion(0); |
|
| 88 | + $cred->setDisabled(0); |
|
| 89 | + $cred->setTimeout(new DateTimeImmutable('+ 1 hour')); |
|
| 90 | + $cred->setPriority(9); |
|
| 91 | + $cred->save(); |
|
| 92 | + |
|
| 93 | + $this->assign("user", $user); |
|
| 94 | + $this->assign("hash", $hash); |
|
| 95 | + $this->assign("remoteAddress", $clientIp); |
|
| 96 | + |
|
| 97 | + $emailContent = $this->fetchTemplate('forgot-password/reset-mail.tpl'); |
|
| 98 | + |
|
| 99 | + // FIXME: domains! |
|
| 100 | + /** @var Domain $domain */ |
|
| 101 | + $domain = Domain::getById(1, $this->getDatabase()); |
|
| 102 | + $this->getEmailHelper()->sendMail( |
|
| 103 | + null, $user->getEmail(), "WP:ACC password reset", $emailContent); |
|
| 104 | + } |
|
| 105 | + } |
|
| 106 | + |
|
| 107 | + /** |
|
| 108 | + * Entry point for the reset action |
|
| 109 | + * |
|
| 110 | + * This is the reset password part of the form. |
|
| 111 | + * @category Security-Critical |
|
| 112 | + */ |
|
| 113 | + protected function reset() |
|
| 114 | + { |
|
| 115 | + $si = WebRequest::getString('si'); |
|
| 116 | + $id = WebRequest::getString('id'); |
|
| 117 | + |
|
| 118 | + if ($si === null || trim($si) === "" || $id === null || trim($id) === "") { |
|
| 119 | + throw new ApplicationLogicException("Link not valid, please ensure it has copied correctly"); |
|
| 120 | + } |
|
| 121 | + |
|
| 122 | + $database = $this->getDatabase(); |
|
| 123 | + $user = $this->getResettingUser($id, $database, $si); |
|
| 124 | + |
|
| 125 | + // Dual mode |
|
| 126 | + if (WebRequest::wasPosted()) { |
|
| 127 | + $this->validateCSRFToken(); |
|
| 128 | + try { |
|
| 129 | + $this->doReset($user); |
|
| 130 | + $this->cleanExistingTokens($user); |
|
| 131 | + } |
|
| 132 | + catch (ApplicationLogicException $ex) { |
|
| 133 | + SessionAlert::error($ex->getMessage()); |
|
| 134 | + $this->redirect('forgotPassword', 'reset', array('si' => $si, 'id' => $id)); |
|
| 135 | + |
|
| 136 | + return; |
|
| 137 | + } |
|
| 138 | + } |
|
| 139 | + else { |
|
| 140 | + $this->assignCSRFToken(); |
|
| 141 | + $this->assign('user', $user); |
|
| 142 | + $this->setTemplate('forgot-password/forgotpwreset.tpl'); |
|
| 143 | + $this->addJs("/vendor/dropbox/zxcvbn/dist/zxcvbn.js"); |
|
| 144 | + } |
|
| 145 | + } |
|
| 146 | + |
|
| 147 | + /** |
|
| 148 | + * Gets the user resetting their password from the database, or throwing an exception if that is not possible. |
|
| 149 | + * |
|
| 150 | + * @param integer $id The ID of the user to retrieve |
|
| 151 | + * @param PdoDatabase $database The database object to use |
|
| 152 | + * @param string $si The reset hash provided |
|
| 153 | + * |
|
| 154 | + * @return User |
|
| 155 | + * @throws ApplicationLogicException |
|
| 156 | + */ |
|
| 157 | + private function getResettingUser($id, $database, $si) |
|
| 158 | + { |
|
| 159 | + $user = User::getById($id, $database); |
|
| 160 | + |
|
| 161 | + if ($user === false || $user->isCommunityUser()) { |
|
| 162 | + throw new ApplicationLogicException("Password reset failed. Please try again."); |
|
| 163 | + } |
|
| 164 | + |
|
| 165 | + $statement = $database->prepare("SELECT * FROM credential WHERE type = 'reset' AND user = :user;"); |
|
| 166 | + $statement->execute([':user' => $user->getId()]); |
|
| 167 | + |
|
| 168 | + /** @var Credential $credential */ |
|
| 169 | + $credential = $statement->fetchObject(Credential::class); |
|
| 170 | + |
|
| 171 | + $statement->closeCursor(); |
|
| 172 | + |
|
| 173 | + if ($credential === false) { |
|
| 174 | + throw new ApplicationLogicException("Password reset failed. Please try again."); |
|
| 175 | + } |
|
| 176 | + |
|
| 177 | + $credential->setDatabase($database); |
|
| 178 | + |
|
| 179 | + $encryptionHelper = new EncryptionHelper($this->getSiteConfiguration()); |
|
| 180 | + if ($encryptionHelper->decryptData($credential->getData()) != $si) { |
|
| 181 | + throw new ApplicationLogicException("Password reset failed. Please try again."); |
|
| 182 | + } |
|
| 183 | + |
|
| 184 | + if ($credential->getTimeout() < new DateTimeImmutable()) { |
|
| 185 | + $credential->delete(); |
|
| 186 | + throw new ApplicationLogicException("Password reset token expired. Please try again."); |
|
| 187 | + } |
|
| 188 | + |
|
| 189 | + return $user; |
|
| 190 | + } |
|
| 191 | + |
|
| 192 | + /** |
|
| 193 | + * Performs the setting of the new password |
|
| 194 | + * |
|
| 195 | + * @param User $user The user to set the password for |
|
| 196 | + * |
|
| 197 | + * @throws ApplicationLogicException |
|
| 198 | + */ |
|
| 199 | + private function doReset(User $user) |
|
| 200 | + { |
|
| 201 | + $pw = WebRequest::postString('newpassword'); |
|
| 202 | + $pw2 = WebRequest::postString('newpasswordconfirm'); |
|
| 203 | + |
|
| 204 | + if ($pw !== $pw2) { |
|
| 205 | + throw new ApplicationLogicException('Passwords do not match!'); |
|
| 206 | + } |
|
| 207 | + |
|
| 208 | + $passwordCredentialProvider = new PasswordCredentialProvider($user->getDatabase(), $this->getSiteConfiguration()); |
|
| 209 | + $passwordCredentialProvider->setCredential($user, 1, $pw); |
|
| 210 | + |
|
| 211 | + SessionAlert::success('You may now log in!'); |
|
| 212 | + $this->redirect('login'); |
|
| 213 | + } |
|
| 214 | + |
|
| 215 | + protected function isProtectedPage() |
|
| 216 | + { |
|
| 217 | + return false; |
|
| 218 | + } |
|
| 219 | + |
|
| 220 | + /** |
|
| 221 | + * @param $user |
|
| 222 | + */ |
|
| 223 | + private function cleanExistingTokens($user): void |
|
| 224 | + { |
|
| 225 | + // clean out existing reset tokens |
|
| 226 | + $statement = $this->getDatabase()->prepare("SELECT * FROM credential WHERE type = 'reset' AND user = :user;"); |
|
| 227 | + $statement->execute([':user' => $user->getId()]); |
|
| 228 | + $existing = $statement->fetchAll(PdoDatabase::FETCH_CLASS, Credential::class); |
|
| 229 | + |
|
| 230 | + foreach ($existing as $c) { |
|
| 231 | + $c->setDatabase($this->getDatabase()); |
|
| 232 | + $c->delete(); |
|
| 233 | + } |
|
| 234 | + } |
|
| 235 | 235 | } |
@@ -13,31 +13,31 @@ |
||
| 13 | 13 | |
| 14 | 14 | class PagePasswordLogin extends LoginCredentialPageBase |
| 15 | 15 | { |
| 16 | - protected function providerSpecificSetup() |
|
| 17 | - { |
|
| 18 | - list($partialId, $partialStage) = WebRequest::getAuthPartialLogin(); |
|
| 19 | - |
|
| 20 | - if ($partialId !== null && $partialStage > 1) { |
|
| 21 | - $sql = 'SELECT type FROM credential WHERE user = :user AND factor = :stage AND disabled = 0 ORDER BY priority'; |
|
| 22 | - $statement = $this->getDatabase()->prepare($sql); |
|
| 23 | - $statement->execute(array(':user' => $partialId, ':stage' => $partialStage)); |
|
| 24 | - $nextStage = $statement->fetchColumn(); |
|
| 25 | - $statement->closeCursor(); |
|
| 26 | - |
|
| 27 | - $this->redirect("login/" . $this->nextPageMap[$nextStage]); |
|
| 28 | - return; |
|
| 29 | - } |
|
| 30 | - |
|
| 31 | - $this->setTemplate('login/password.tpl'); |
|
| 32 | - } |
|
| 33 | - |
|
| 34 | - protected function getProviderCredentials() |
|
| 35 | - { |
|
| 36 | - $password = WebRequest::postString("password"); |
|
| 37 | - if ($password === null || $password === "") { |
|
| 38 | - throw new ApplicationLogicException("No password specified"); |
|
| 39 | - } |
|
| 40 | - |
|
| 41 | - return $password; |
|
| 42 | - } |
|
| 16 | + protected function providerSpecificSetup() |
|
| 17 | + { |
|
| 18 | + list($partialId, $partialStage) = WebRequest::getAuthPartialLogin(); |
|
| 19 | + |
|
| 20 | + if ($partialId !== null && $partialStage > 1) { |
|
| 21 | + $sql = 'SELECT type FROM credential WHERE user = :user AND factor = :stage AND disabled = 0 ORDER BY priority'; |
|
| 22 | + $statement = $this->getDatabase()->prepare($sql); |
|
| 23 | + $statement->execute(array(':user' => $partialId, ':stage' => $partialStage)); |
|
| 24 | + $nextStage = $statement->fetchColumn(); |
|
| 25 | + $statement->closeCursor(); |
|
| 26 | + |
|
| 27 | + $this->redirect("login/" . $this->nextPageMap[$nextStage]); |
|
| 28 | + return; |
|
| 29 | + } |
|
| 30 | + |
|
| 31 | + $this->setTemplate('login/password.tpl'); |
|
| 32 | + } |
|
| 33 | + |
|
| 34 | + protected function getProviderCredentials() |
|
| 35 | + { |
|
| 36 | + $password = WebRequest::postString("password"); |
|
| 37 | + if ($password === null || $password === "") { |
|
| 38 | + throw new ApplicationLogicException("No password specified"); |
|
| 39 | + } |
|
| 40 | + |
|
| 41 | + return $password; |
|
| 42 | + } |
|
| 43 | 43 | } |
| 44 | 44 | \ No newline at end of file |
@@ -21,310 +21,310 @@ |
||
| 21 | 21 | |
| 22 | 22 | abstract class LoginCredentialPageBase extends InternalPageBase |
| 23 | 23 | { |
| 24 | - /** @var User */ |
|
| 25 | - protected $partialUser = null; |
|
| 26 | - protected $nextPageMap = array( |
|
| 27 | - 'yubikeyotp' => 'otp', |
|
| 28 | - 'totp' => 'otp', |
|
| 29 | - 'scratch' => 'otp', |
|
| 30 | - ); |
|
| 31 | - protected $names = array( |
|
| 32 | - 'yubikeyotp' => 'Yubikey OTP', |
|
| 33 | - 'totp' => 'TOTP (phone code generator)', |
|
| 34 | - 'scratch' => 'scratch token', |
|
| 35 | - ); |
|
| 36 | - |
|
| 37 | - /** |
|
| 38 | - * Main function for this page, when no specific actions are called. |
|
| 39 | - * @return void |
|
| 40 | - */ |
|
| 41 | - protected function main() |
|
| 42 | - { |
|
| 43 | - if (!$this->enforceHttps()) { |
|
| 44 | - return; |
|
| 45 | - } |
|
| 46 | - |
|
| 47 | - if (WebRequest::wasPosted()) { |
|
| 48 | - $this->validateCSRFToken(); |
|
| 49 | - |
|
| 50 | - $database = $this->getDatabase(); |
|
| 51 | - try { |
|
| 52 | - list($partialId, $partialStage) = WebRequest::getAuthPartialLogin(); |
|
| 53 | - |
|
| 54 | - if ($partialStage === null) { |
|
| 55 | - $partialStage = 1; |
|
| 56 | - } |
|
| 57 | - |
|
| 58 | - if ($partialId === null) { |
|
| 59 | - $username = WebRequest::postString('username'); |
|
| 60 | - |
|
| 61 | - if ($username === null || trim($username) === '') { |
|
| 62 | - throw new ApplicationLogicException('No username specified.'); |
|
| 63 | - } |
|
| 64 | - |
|
| 65 | - $user = User::getByUsername($username, $database); |
|
| 66 | - } |
|
| 67 | - else { |
|
| 68 | - $user = User::getById($partialId, $database); |
|
| 69 | - } |
|
| 70 | - |
|
| 71 | - if ($user === false) { |
|
| 72 | - throw new ApplicationLogicException("Authentication failed"); |
|
| 73 | - } |
|
| 74 | - |
|
| 75 | - $authMan = new AuthenticationManager($database, $this->getSiteConfiguration(), |
|
| 76 | - $this->getHttpHelper()); |
|
| 77 | - |
|
| 78 | - $credential = $this->getProviderCredentials(); |
|
| 79 | - |
|
| 80 | - $authResult = $authMan->authenticate($user, $credential, $partialStage); |
|
| 81 | - |
|
| 82 | - if ($authResult === AuthenticationManager::AUTH_FAIL) { |
|
| 83 | - throw new ApplicationLogicException("Authentication failed"); |
|
| 84 | - } |
|
| 85 | - |
|
| 86 | - if ($authResult === AuthenticationManager::AUTH_REQUIRE_NEXT_STAGE) { |
|
| 87 | - $this->processJumpNextStage($user, $partialStage, $database); |
|
| 88 | - |
|
| 89 | - return; |
|
| 90 | - } |
|
| 91 | - |
|
| 92 | - if ($authResult === AuthenticationManager::AUTH_OK) { |
|
| 93 | - $this->processLoginSuccess($user); |
|
| 94 | - |
|
| 95 | - return; |
|
| 96 | - } |
|
| 97 | - } |
|
| 98 | - catch (ApplicationLogicException $ex) { |
|
| 99 | - WebRequest::clearAuthPartialLogin(); |
|
| 100 | - |
|
| 101 | - SessionAlert::error($ex->getMessage()); |
|
| 102 | - $this->redirect('login'); |
|
| 103 | - |
|
| 104 | - return; |
|
| 105 | - } |
|
| 106 | - } |
|
| 107 | - else { |
|
| 108 | - $this->assign('showSignIn', true); |
|
| 109 | - |
|
| 110 | - $this->setupPartial(); |
|
| 111 | - $this->assignCSRFToken(); |
|
| 112 | - $this->providerSpecificSetup(); |
|
| 113 | - } |
|
| 114 | - } |
|
| 115 | - |
|
| 116 | - protected function isProtectedPage() |
|
| 117 | - { |
|
| 118 | - return false; |
|
| 119 | - } |
|
| 120 | - |
|
| 121 | - /** |
|
| 122 | - * Enforces HTTPS on the login form |
|
| 123 | - * |
|
| 124 | - * @return bool |
|
| 125 | - */ |
|
| 126 | - private function enforceHttps() |
|
| 127 | - { |
|
| 128 | - if ($this->getSiteConfiguration()->getUseStrictTransportSecurity() !== false) { |
|
| 129 | - if (WebRequest::isHttps()) { |
|
| 130 | - // Client can clearly use HTTPS, so let's enforce it for all connections. |
|
| 131 | - $this->headerQueue[] = "Strict-Transport-Security: max-age=15768000"; |
|
| 132 | - } |
|
| 133 | - else { |
|
| 134 | - // This is the login form, not the request form. We need protection here. |
|
| 135 | - $this->redirectUrl('https://' . WebRequest::serverName() . WebRequest::requestUri()); |
|
| 136 | - |
|
| 137 | - return false; |
|
| 138 | - } |
|
| 139 | - } |
|
| 140 | - |
|
| 141 | - return true; |
|
| 142 | - } |
|
| 143 | - |
|
| 144 | - protected abstract function providerSpecificSetup(); |
|
| 145 | - |
|
| 146 | - protected function setupPartial() |
|
| 147 | - { |
|
| 148 | - $database = $this->getDatabase(); |
|
| 149 | - |
|
| 150 | - // default stuff |
|
| 151 | - $this->assign('alternatives', array()); // 'u2f' => array('U2F token'), 'otp' => array('TOTP', 'scratch', 'yubiotp'))); |
|
| 152 | - |
|
| 153 | - // is this stage one? |
|
| 154 | - list($partialId, $partialStage) = WebRequest::getAuthPartialLogin(); |
|
| 155 | - if ($partialStage === null || $partialId === null) { |
|
| 156 | - WebRequest::clearAuthPartialLogin(); |
|
| 157 | - } |
|
| 158 | - |
|
| 159 | - // Check to see if we have a partial login in progress |
|
| 160 | - $username = null; |
|
| 161 | - if ($partialId !== null) { |
|
| 162 | - // Yes, enforce this username |
|
| 163 | - $this->partialUser = User::getById($partialId, $database); |
|
| 164 | - $username = $this->partialUser->getUsername(); |
|
| 165 | - |
|
| 166 | - $this->setupAlternates($this->partialUser, $partialStage, $database); |
|
| 167 | - } |
|
| 168 | - else { |
|
| 169 | - // No, see if we've preloaded a username |
|
| 170 | - $preloadUsername = WebRequest::getString('tplUsername'); |
|
| 171 | - if ($preloadUsername !== null) { |
|
| 172 | - $username = $preloadUsername; |
|
| 173 | - } |
|
| 174 | - } |
|
| 175 | - |
|
| 176 | - if ($partialStage === null) { |
|
| 177 | - $partialStage = 1; |
|
| 178 | - } |
|
| 179 | - |
|
| 180 | - $this->assign('partialStage', $partialStage); |
|
| 181 | - $this->assign('username', $username); |
|
| 182 | - } |
|
| 183 | - |
|
| 184 | - /** |
|
| 185 | - * Redirect the user back to wherever they came from after a successful login |
|
| 186 | - * |
|
| 187 | - * @param User $user |
|
| 188 | - */ |
|
| 189 | - protected function goBackWhenceYouCame(User $user) |
|
| 190 | - { |
|
| 191 | - // Redirect to wherever the user came from |
|
| 192 | - $redirectDestination = WebRequest::clearPostLoginRedirect(); |
|
| 193 | - if ($redirectDestination !== null) { |
|
| 194 | - $this->redirectUrl($redirectDestination); |
|
| 195 | - } |
|
| 196 | - else { |
|
| 197 | - if ($user->isNewUser()) { |
|
| 198 | - // home page isn't allowed, go to preferences instead |
|
| 199 | - $this->redirect('preferences'); |
|
| 200 | - } |
|
| 201 | - else { |
|
| 202 | - // go to the home page |
|
| 203 | - $this->redirect(''); |
|
| 204 | - } |
|
| 205 | - } |
|
| 206 | - } |
|
| 207 | - |
|
| 208 | - private function processLoginSuccess(User $user) |
|
| 209 | - { |
|
| 210 | - // Touch force logout |
|
| 211 | - $user->setForceLogout(false); |
|
| 212 | - $user->save(); |
|
| 213 | - |
|
| 214 | - $oauth = new OAuthUserHelper($user, $this->getDatabase(), $this->getOAuthProtocolHelper(), |
|
| 215 | - $this->getSiteConfiguration()); |
|
| 216 | - |
|
| 217 | - if ($oauth->isFullyLinked()) { |
|
| 218 | - try { |
|
| 219 | - // Reload the user's identity ticket. |
|
| 220 | - $oauth->refreshIdentity(); |
|
| 221 | - |
|
| 222 | - // Check for blocks |
|
| 223 | - if ($oauth->getIdentity()->getBlocked()) { |
|
| 224 | - // blocked! |
|
| 225 | - SessionAlert::error("You are currently blocked on-wiki. You will not be able to log in until you are unblocked."); |
|
| 226 | - $this->redirect('login'); |
|
| 227 | - |
|
| 228 | - return; |
|
| 229 | - } |
|
| 230 | - } |
|
| 231 | - catch (OAuthException $ex) { |
|
| 232 | - // Oops. Refreshing ticket failed. Force a re-auth. |
|
| 233 | - $authoriseUrl = $oauth->getRequestToken(); |
|
| 234 | - WebRequest::setOAuthPartialLogin($user); |
|
| 235 | - $this->redirectUrl($authoriseUrl); |
|
| 236 | - |
|
| 237 | - return; |
|
| 238 | - } |
|
| 239 | - } |
|
| 240 | - |
|
| 241 | - if (($this->getSiteConfiguration()->getEnforceOAuth() && !$oauth->isFullyLinked()) |
|
| 242 | - || $oauth->isPartiallyLinked() |
|
| 243 | - ) { |
|
| 244 | - $authoriseUrl = $oauth->getRequestToken(); |
|
| 245 | - WebRequest::setOAuthPartialLogin($user); |
|
| 246 | - $this->redirectUrl($authoriseUrl); |
|
| 247 | - |
|
| 248 | - return; |
|
| 249 | - } |
|
| 250 | - |
|
| 251 | - WebRequest::setLoggedInUser($user); |
|
| 252 | - $this->getDomainAccessManager()->switchToDefaultDomain($user); |
|
| 253 | - |
|
| 254 | - $this->goBackWhenceYouCame($user); |
|
| 255 | - } |
|
| 256 | - |
|
| 257 | - protected abstract function getProviderCredentials(); |
|
| 258 | - |
|
| 259 | - /** |
|
| 260 | - * @param User $user |
|
| 261 | - * @param int $partialStage |
|
| 262 | - * @param PdoDatabase $database |
|
| 263 | - * |
|
| 264 | - * @throws ApplicationLogicException |
|
| 265 | - */ |
|
| 266 | - private function processJumpNextStage(User $user, $partialStage, PdoDatabase $database) |
|
| 267 | - { |
|
| 268 | - WebRequest::setAuthPartialLogin($user->getId(), $partialStage + 1); |
|
| 269 | - |
|
| 270 | - $sql = 'SELECT type FROM credential WHERE user = :user AND factor = :stage AND disabled = 0 ORDER BY priority'; |
|
| 271 | - $statement = $database->prepare($sql); |
|
| 272 | - $statement->execute(array(':user' => $user->getId(), ':stage' => $partialStage + 1)); |
|
| 273 | - $nextStage = $statement->fetchColumn(); |
|
| 274 | - $statement->closeCursor(); |
|
| 275 | - |
|
| 276 | - if (!isset($this->nextPageMap[$nextStage])) { |
|
| 277 | - throw new ApplicationLogicException('Unknown page handler for next authentication stage.'); |
|
| 278 | - } |
|
| 279 | - |
|
| 280 | - $this->redirect("login/" . $this->nextPageMap[$nextStage]); |
|
| 281 | - } |
|
| 282 | - |
|
| 283 | - private function setupAlternates(User $user, $partialStage, PdoDatabase $database) |
|
| 284 | - { |
|
| 285 | - // get the providers available |
|
| 286 | - $sql = 'SELECT type FROM credential WHERE user = :user AND factor = :stage AND disabled = 0'; |
|
| 287 | - $statement = $database->prepare($sql); |
|
| 288 | - $statement->execute(array(':user' => $user->getId(), ':stage' => $partialStage)); |
|
| 289 | - $alternates = $statement->fetchAll(PDO::FETCH_COLUMN); |
|
| 290 | - |
|
| 291 | - $types = array(); |
|
| 292 | - foreach ($alternates as $item) { |
|
| 293 | - $type = $this->nextPageMap[$item]; |
|
| 294 | - if (!isset($types[$type])) { |
|
| 295 | - $types[$type] = array(); |
|
| 296 | - } |
|
| 297 | - |
|
| 298 | - $types[$type][] = $item; |
|
| 299 | - } |
|
| 300 | - |
|
| 301 | - $userOptions = array(); |
|
| 302 | - if (get_called_class() !== PageOtpLogin::class) { |
|
| 303 | - $userOptions = array_merge($userOptions, $this->setupUserOptionsForType($types, 'otp', $userOptions)); |
|
| 304 | - } |
|
| 305 | - |
|
| 306 | - $this->assign('alternatives', $userOptions); |
|
| 307 | - } |
|
| 308 | - |
|
| 309 | - /** |
|
| 310 | - * @param $types |
|
| 311 | - * @param $type |
|
| 312 | - * @param $userOptions |
|
| 313 | - * |
|
| 314 | - * @return mixed |
|
| 315 | - */ |
|
| 316 | - private function setupUserOptionsForType($types, $type, $userOptions) |
|
| 317 | - { |
|
| 318 | - if (isset($types[$type])) { |
|
| 319 | - $options = $types[$type]; |
|
| 320 | - |
|
| 321 | - array_walk($options, function(&$val) { |
|
| 322 | - $val = $this->names[$val]; |
|
| 323 | - }); |
|
| 324 | - |
|
| 325 | - $userOptions[$type] = $options; |
|
| 326 | - } |
|
| 327 | - |
|
| 328 | - return $userOptions; |
|
| 329 | - } |
|
| 24 | + /** @var User */ |
|
| 25 | + protected $partialUser = null; |
|
| 26 | + protected $nextPageMap = array( |
|
| 27 | + 'yubikeyotp' => 'otp', |
|
| 28 | + 'totp' => 'otp', |
|
| 29 | + 'scratch' => 'otp', |
|
| 30 | + ); |
|
| 31 | + protected $names = array( |
|
| 32 | + 'yubikeyotp' => 'Yubikey OTP', |
|
| 33 | + 'totp' => 'TOTP (phone code generator)', |
|
| 34 | + 'scratch' => 'scratch token', |
|
| 35 | + ); |
|
| 36 | + |
|
| 37 | + /** |
|
| 38 | + * Main function for this page, when no specific actions are called. |
|
| 39 | + * @return void |
|
| 40 | + */ |
|
| 41 | + protected function main() |
|
| 42 | + { |
|
| 43 | + if (!$this->enforceHttps()) { |
|
| 44 | + return; |
|
| 45 | + } |
|
| 46 | + |
|
| 47 | + if (WebRequest::wasPosted()) { |
|
| 48 | + $this->validateCSRFToken(); |
|
| 49 | + |
|
| 50 | + $database = $this->getDatabase(); |
|
| 51 | + try { |
|
| 52 | + list($partialId, $partialStage) = WebRequest::getAuthPartialLogin(); |
|
| 53 | + |
|
| 54 | + if ($partialStage === null) { |
|
| 55 | + $partialStage = 1; |
|
| 56 | + } |
|
| 57 | + |
|
| 58 | + if ($partialId === null) { |
|
| 59 | + $username = WebRequest::postString('username'); |
|
| 60 | + |
|
| 61 | + if ($username === null || trim($username) === '') { |
|
| 62 | + throw new ApplicationLogicException('No username specified.'); |
|
| 63 | + } |
|
| 64 | + |
|
| 65 | + $user = User::getByUsername($username, $database); |
|
| 66 | + } |
|
| 67 | + else { |
|
| 68 | + $user = User::getById($partialId, $database); |
|
| 69 | + } |
|
| 70 | + |
|
| 71 | + if ($user === false) { |
|
| 72 | + throw new ApplicationLogicException("Authentication failed"); |
|
| 73 | + } |
|
| 74 | + |
|
| 75 | + $authMan = new AuthenticationManager($database, $this->getSiteConfiguration(), |
|
| 76 | + $this->getHttpHelper()); |
|
| 77 | + |
|
| 78 | + $credential = $this->getProviderCredentials(); |
|
| 79 | + |
|
| 80 | + $authResult = $authMan->authenticate($user, $credential, $partialStage); |
|
| 81 | + |
|
| 82 | + if ($authResult === AuthenticationManager::AUTH_FAIL) { |
|
| 83 | + throw new ApplicationLogicException("Authentication failed"); |
|
| 84 | + } |
|
| 85 | + |
|
| 86 | + if ($authResult === AuthenticationManager::AUTH_REQUIRE_NEXT_STAGE) { |
|
| 87 | + $this->processJumpNextStage($user, $partialStage, $database); |
|
| 88 | + |
|
| 89 | + return; |
|
| 90 | + } |
|
| 91 | + |
|
| 92 | + if ($authResult === AuthenticationManager::AUTH_OK) { |
|
| 93 | + $this->processLoginSuccess($user); |
|
| 94 | + |
|
| 95 | + return; |
|
| 96 | + } |
|
| 97 | + } |
|
| 98 | + catch (ApplicationLogicException $ex) { |
|
| 99 | + WebRequest::clearAuthPartialLogin(); |
|
| 100 | + |
|
| 101 | + SessionAlert::error($ex->getMessage()); |
|
| 102 | + $this->redirect('login'); |
|
| 103 | + |
|
| 104 | + return; |
|
| 105 | + } |
|
| 106 | + } |
|
| 107 | + else { |
|
| 108 | + $this->assign('showSignIn', true); |
|
| 109 | + |
|
| 110 | + $this->setupPartial(); |
|
| 111 | + $this->assignCSRFToken(); |
|
| 112 | + $this->providerSpecificSetup(); |
|
| 113 | + } |
|
| 114 | + } |
|
| 115 | + |
|
| 116 | + protected function isProtectedPage() |
|
| 117 | + { |
|
| 118 | + return false; |
|
| 119 | + } |
|
| 120 | + |
|
| 121 | + /** |
|
| 122 | + * Enforces HTTPS on the login form |
|
| 123 | + * |
|
| 124 | + * @return bool |
|
| 125 | + */ |
|
| 126 | + private function enforceHttps() |
|
| 127 | + { |
|
| 128 | + if ($this->getSiteConfiguration()->getUseStrictTransportSecurity() !== false) { |
|
| 129 | + if (WebRequest::isHttps()) { |
|
| 130 | + // Client can clearly use HTTPS, so let's enforce it for all connections. |
|
| 131 | + $this->headerQueue[] = "Strict-Transport-Security: max-age=15768000"; |
|
| 132 | + } |
|
| 133 | + else { |
|
| 134 | + // This is the login form, not the request form. We need protection here. |
|
| 135 | + $this->redirectUrl('https://' . WebRequest::serverName() . WebRequest::requestUri()); |
|
| 136 | + |
|
| 137 | + return false; |
|
| 138 | + } |
|
| 139 | + } |
|
| 140 | + |
|
| 141 | + return true; |
|
| 142 | + } |
|
| 143 | + |
|
| 144 | + protected abstract function providerSpecificSetup(); |
|
| 145 | + |
|
| 146 | + protected function setupPartial() |
|
| 147 | + { |
|
| 148 | + $database = $this->getDatabase(); |
|
| 149 | + |
|
| 150 | + // default stuff |
|
| 151 | + $this->assign('alternatives', array()); // 'u2f' => array('U2F token'), 'otp' => array('TOTP', 'scratch', 'yubiotp'))); |
|
| 152 | + |
|
| 153 | + // is this stage one? |
|
| 154 | + list($partialId, $partialStage) = WebRequest::getAuthPartialLogin(); |
|
| 155 | + if ($partialStage === null || $partialId === null) { |
|
| 156 | + WebRequest::clearAuthPartialLogin(); |
|
| 157 | + } |
|
| 158 | + |
|
| 159 | + // Check to see if we have a partial login in progress |
|
| 160 | + $username = null; |
|
| 161 | + if ($partialId !== null) { |
|
| 162 | + // Yes, enforce this username |
|
| 163 | + $this->partialUser = User::getById($partialId, $database); |
|
| 164 | + $username = $this->partialUser->getUsername(); |
|
| 165 | + |
|
| 166 | + $this->setupAlternates($this->partialUser, $partialStage, $database); |
|
| 167 | + } |
|
| 168 | + else { |
|
| 169 | + // No, see if we've preloaded a username |
|
| 170 | + $preloadUsername = WebRequest::getString('tplUsername'); |
|
| 171 | + if ($preloadUsername !== null) { |
|
| 172 | + $username = $preloadUsername; |
|
| 173 | + } |
|
| 174 | + } |
|
| 175 | + |
|
| 176 | + if ($partialStage === null) { |
|
| 177 | + $partialStage = 1; |
|
| 178 | + } |
|
| 179 | + |
|
| 180 | + $this->assign('partialStage', $partialStage); |
|
| 181 | + $this->assign('username', $username); |
|
| 182 | + } |
|
| 183 | + |
|
| 184 | + /** |
|
| 185 | + * Redirect the user back to wherever they came from after a successful login |
|
| 186 | + * |
|
| 187 | + * @param User $user |
|
| 188 | + */ |
|
| 189 | + protected function goBackWhenceYouCame(User $user) |
|
| 190 | + { |
|
| 191 | + // Redirect to wherever the user came from |
|
| 192 | + $redirectDestination = WebRequest::clearPostLoginRedirect(); |
|
| 193 | + if ($redirectDestination !== null) { |
|
| 194 | + $this->redirectUrl($redirectDestination); |
|
| 195 | + } |
|
| 196 | + else { |
|
| 197 | + if ($user->isNewUser()) { |
|
| 198 | + // home page isn't allowed, go to preferences instead |
|
| 199 | + $this->redirect('preferences'); |
|
| 200 | + } |
|
| 201 | + else { |
|
| 202 | + // go to the home page |
|
| 203 | + $this->redirect(''); |
|
| 204 | + } |
|
| 205 | + } |
|
| 206 | + } |
|
| 207 | + |
|
| 208 | + private function processLoginSuccess(User $user) |
|
| 209 | + { |
|
| 210 | + // Touch force logout |
|
| 211 | + $user->setForceLogout(false); |
|
| 212 | + $user->save(); |
|
| 213 | + |
|
| 214 | + $oauth = new OAuthUserHelper($user, $this->getDatabase(), $this->getOAuthProtocolHelper(), |
|
| 215 | + $this->getSiteConfiguration()); |
|
| 216 | + |
|
| 217 | + if ($oauth->isFullyLinked()) { |
|
| 218 | + try { |
|
| 219 | + // Reload the user's identity ticket. |
|
| 220 | + $oauth->refreshIdentity(); |
|
| 221 | + |
|
| 222 | + // Check for blocks |
|
| 223 | + if ($oauth->getIdentity()->getBlocked()) { |
|
| 224 | + // blocked! |
|
| 225 | + SessionAlert::error("You are currently blocked on-wiki. You will not be able to log in until you are unblocked."); |
|
| 226 | + $this->redirect('login'); |
|
| 227 | + |
|
| 228 | + return; |
|
| 229 | + } |
|
| 230 | + } |
|
| 231 | + catch (OAuthException $ex) { |
|
| 232 | + // Oops. Refreshing ticket failed. Force a re-auth. |
|
| 233 | + $authoriseUrl = $oauth->getRequestToken(); |
|
| 234 | + WebRequest::setOAuthPartialLogin($user); |
|
| 235 | + $this->redirectUrl($authoriseUrl); |
|
| 236 | + |
|
| 237 | + return; |
|
| 238 | + } |
|
| 239 | + } |
|
| 240 | + |
|
| 241 | + if (($this->getSiteConfiguration()->getEnforceOAuth() && !$oauth->isFullyLinked()) |
|
| 242 | + || $oauth->isPartiallyLinked() |
|
| 243 | + ) { |
|
| 244 | + $authoriseUrl = $oauth->getRequestToken(); |
|
| 245 | + WebRequest::setOAuthPartialLogin($user); |
|
| 246 | + $this->redirectUrl($authoriseUrl); |
|
| 247 | + |
|
| 248 | + return; |
|
| 249 | + } |
|
| 250 | + |
|
| 251 | + WebRequest::setLoggedInUser($user); |
|
| 252 | + $this->getDomainAccessManager()->switchToDefaultDomain($user); |
|
| 253 | + |
|
| 254 | + $this->goBackWhenceYouCame($user); |
|
| 255 | + } |
|
| 256 | + |
|
| 257 | + protected abstract function getProviderCredentials(); |
|
| 258 | + |
|
| 259 | + /** |
|
| 260 | + * @param User $user |
|
| 261 | + * @param int $partialStage |
|
| 262 | + * @param PdoDatabase $database |
|
| 263 | + * |
|
| 264 | + * @throws ApplicationLogicException |
|
| 265 | + */ |
|
| 266 | + private function processJumpNextStage(User $user, $partialStage, PdoDatabase $database) |
|
| 267 | + { |
|
| 268 | + WebRequest::setAuthPartialLogin($user->getId(), $partialStage + 1); |
|
| 269 | + |
|
| 270 | + $sql = 'SELECT type FROM credential WHERE user = :user AND factor = :stage AND disabled = 0 ORDER BY priority'; |
|
| 271 | + $statement = $database->prepare($sql); |
|
| 272 | + $statement->execute(array(':user' => $user->getId(), ':stage' => $partialStage + 1)); |
|
| 273 | + $nextStage = $statement->fetchColumn(); |
|
| 274 | + $statement->closeCursor(); |
|
| 275 | + |
|
| 276 | + if (!isset($this->nextPageMap[$nextStage])) { |
|
| 277 | + throw new ApplicationLogicException('Unknown page handler for next authentication stage.'); |
|
| 278 | + } |
|
| 279 | + |
|
| 280 | + $this->redirect("login/" . $this->nextPageMap[$nextStage]); |
|
| 281 | + } |
|
| 282 | + |
|
| 283 | + private function setupAlternates(User $user, $partialStage, PdoDatabase $database) |
|
| 284 | + { |
|
| 285 | + // get the providers available |
|
| 286 | + $sql = 'SELECT type FROM credential WHERE user = :user AND factor = :stage AND disabled = 0'; |
|
| 287 | + $statement = $database->prepare($sql); |
|
| 288 | + $statement->execute(array(':user' => $user->getId(), ':stage' => $partialStage)); |
|
| 289 | + $alternates = $statement->fetchAll(PDO::FETCH_COLUMN); |
|
| 290 | + |
|
| 291 | + $types = array(); |
|
| 292 | + foreach ($alternates as $item) { |
|
| 293 | + $type = $this->nextPageMap[$item]; |
|
| 294 | + if (!isset($types[$type])) { |
|
| 295 | + $types[$type] = array(); |
|
| 296 | + } |
|
| 297 | + |
|
| 298 | + $types[$type][] = $item; |
|
| 299 | + } |
|
| 300 | + |
|
| 301 | + $userOptions = array(); |
|
| 302 | + if (get_called_class() !== PageOtpLogin::class) { |
|
| 303 | + $userOptions = array_merge($userOptions, $this->setupUserOptionsForType($types, 'otp', $userOptions)); |
|
| 304 | + } |
|
| 305 | + |
|
| 306 | + $this->assign('alternatives', $userOptions); |
|
| 307 | + } |
|
| 308 | + |
|
| 309 | + /** |
|
| 310 | + * @param $types |
|
| 311 | + * @param $type |
|
| 312 | + * @param $userOptions |
|
| 313 | + * |
|
| 314 | + * @return mixed |
|
| 315 | + */ |
|
| 316 | + private function setupUserOptionsForType($types, $type, $userOptions) |
|
| 317 | + { |
|
| 318 | + if (isset($types[$type])) { |
|
| 319 | + $options = $types[$type]; |
|
| 320 | + |
|
| 321 | + array_walk($options, function(&$val) { |
|
| 322 | + $val = $this->names[$val]; |
|
| 323 | + }); |
|
| 324 | + |
|
| 325 | + $userOptions[$type] = $options; |
|
| 326 | + } |
|
| 327 | + |
|
| 328 | + return $userOptions; |
|
| 329 | + } |
|
| 330 | 330 | } |
@@ -26,297 +26,297 @@ |
||
| 26 | 26 | |
| 27 | 27 | class PageMultiFactor extends InternalPageBase |
| 28 | 28 | { |
| 29 | - /** |
|
| 30 | - * Main function for this page, when no specific actions are called. |
|
| 31 | - * @return void |
|
| 32 | - */ |
|
| 33 | - protected function main() |
|
| 34 | - { |
|
| 35 | - $database = $this->getDatabase(); |
|
| 36 | - $currentUser = User::getCurrent($database); |
|
| 37 | - |
|
| 38 | - $yubikeyOtpCredentialProvider = new YubikeyOtpCredentialProvider($database, $this->getSiteConfiguration(), |
|
| 39 | - $this->getHttpHelper()); |
|
| 40 | - $this->assign('yubikeyOtpIdentity', $yubikeyOtpCredentialProvider->getYubikeyData($currentUser->getId())); |
|
| 41 | - $this->assign('yubikeyOtpEnrolled', $yubikeyOtpCredentialProvider->userIsEnrolled($currentUser->getId())); |
|
| 42 | - |
|
| 43 | - $totpCredentialProvider = new TotpCredentialProvider($database, $this->getSiteConfiguration()); |
|
| 44 | - $this->assign('totpEnrolled', $totpCredentialProvider->userIsEnrolled($currentUser->getId())); |
|
| 45 | - |
|
| 46 | - $scratchCredentialProvider = new ScratchTokenCredentialProvider($database, $this->getSiteConfiguration()); |
|
| 47 | - $this->assign('scratchEnrolled', $scratchCredentialProvider->userIsEnrolled($currentUser->getId())); |
|
| 48 | - $this->assign('scratchRemaining', $scratchCredentialProvider->getRemaining($currentUser->getId())); |
|
| 49 | - |
|
| 50 | - $this->assign('allowedTotp', $this->barrierTest('enableTotp', $currentUser)); |
|
| 51 | - $this->assign('allowedYubikey', $this->barrierTest('enableYubikeyOtp', $currentUser)); |
|
| 52 | - |
|
| 53 | - $this->setTemplate('mfa/mfa.tpl'); |
|
| 54 | - } |
|
| 55 | - |
|
| 56 | - protected function enableYubikeyOtp() |
|
| 57 | - { |
|
| 58 | - $database = $this->getDatabase(); |
|
| 59 | - $currentUser = User::getCurrent($database); |
|
| 60 | - |
|
| 61 | - $otpCredentialProvider = new YubikeyOtpCredentialProvider($database, |
|
| 62 | - $this->getSiteConfiguration(), $this->getHttpHelper()); |
|
| 63 | - |
|
| 64 | - if (WebRequest::wasPosted()) { |
|
| 65 | - $this->validateCSRFToken(); |
|
| 66 | - |
|
| 67 | - $passwordCredentialProvider = new PasswordCredentialProvider($database, |
|
| 68 | - $this->getSiteConfiguration()); |
|
| 69 | - |
|
| 70 | - $password = WebRequest::postString('password'); |
|
| 71 | - $otp = WebRequest::postString('otp'); |
|
| 72 | - |
|
| 73 | - $result = $passwordCredentialProvider->authenticate($currentUser, $password); |
|
| 74 | - |
|
| 75 | - if ($result) { |
|
| 76 | - try { |
|
| 77 | - $otpCredentialProvider->setCredential($currentUser, 2, $otp); |
|
| 78 | - SessionAlert::success('Enabled YubiKey OTP.'); |
|
| 79 | - |
|
| 80 | - $scratchProvider = new ScratchTokenCredentialProvider($database, $this->getSiteConfiguration()); |
|
| 81 | - if ($scratchProvider->getRemaining($currentUser->getId()) < 3) { |
|
| 82 | - $scratchProvider->setCredential($currentUser, 2, null); |
|
| 83 | - $tokens = $scratchProvider->getTokens(); |
|
| 84 | - $this->assign('tokens', $tokens); |
|
| 85 | - $this->setTemplate('mfa/regenScratchTokens.tpl'); |
|
| 86 | - return; |
|
| 87 | - } |
|
| 88 | - } |
|
| 89 | - catch (ApplicationLogicException $ex) { |
|
| 90 | - SessionAlert::error('Error enabling YubiKey OTP: ' . $ex->getMessage()); |
|
| 91 | - } |
|
| 92 | - |
|
| 93 | - $this->redirect('multiFactor'); |
|
| 94 | - } |
|
| 95 | - else { |
|
| 96 | - SessionAlert::error('Error enabling YubiKey OTP - invalid credentials.'); |
|
| 97 | - $this->redirect('multiFactor'); |
|
| 98 | - } |
|
| 99 | - } |
|
| 100 | - else { |
|
| 101 | - if ($otpCredentialProvider->userIsEnrolled($currentUser->getId())) { |
|
| 102 | - // user is not enrolled, we shouldn't have got here. |
|
| 103 | - throw new ApplicationLogicException('User is already enrolled in the selected MFA mechanism'); |
|
| 104 | - } |
|
| 105 | - |
|
| 106 | - $this->assignCSRFToken(); |
|
| 107 | - $this->setTemplate('mfa/enableYubikey.tpl'); |
|
| 108 | - } |
|
| 109 | - } |
|
| 110 | - |
|
| 111 | - protected function disableYubikeyOtp() |
|
| 112 | - { |
|
| 113 | - $database = $this->getDatabase(); |
|
| 114 | - $currentUser = User::getCurrent($database); |
|
| 115 | - |
|
| 116 | - $otpCredentialProvider = new YubikeyOtpCredentialProvider($database, |
|
| 117 | - $this->getSiteConfiguration(), $this->getHttpHelper()); |
|
| 118 | - |
|
| 119 | - $factorType = 'YubiKey OTP'; |
|
| 120 | - |
|
| 121 | - $this->deleteCredential($database, $currentUser, $otpCredentialProvider, $factorType); |
|
| 122 | - } |
|
| 123 | - |
|
| 124 | - protected function enableTotp() |
|
| 125 | - { |
|
| 126 | - $database = $this->getDatabase(); |
|
| 127 | - $currentUser = User::getCurrent($database); |
|
| 128 | - |
|
| 129 | - $otpCredentialProvider = new TotpCredentialProvider($database, $this->getSiteConfiguration()); |
|
| 130 | - |
|
| 131 | - if (WebRequest::wasPosted()) { |
|
| 132 | - $this->validateCSRFToken(); |
|
| 133 | - |
|
| 134 | - // used for routing only, not security |
|
| 135 | - $stage = WebRequest::postString('stage'); |
|
| 136 | - |
|
| 137 | - if ($stage === "auth") { |
|
| 138 | - $password = WebRequest::postString('password'); |
|
| 139 | - |
|
| 140 | - $passwordCredentialProvider = new PasswordCredentialProvider($database, |
|
| 141 | - $this->getSiteConfiguration()); |
|
| 142 | - $result = $passwordCredentialProvider->authenticate($currentUser, $password); |
|
| 143 | - |
|
| 144 | - if ($result) { |
|
| 145 | - $otpCredentialProvider->setCredential($currentUser, 2, null); |
|
| 146 | - |
|
| 147 | - $provisioningUrl = $otpCredentialProvider->getProvisioningUrl($currentUser); |
|
| 148 | - |
|
| 149 | - $renderer = new ImageRenderer( |
|
| 150 | - new RendererStyle(256), |
|
| 151 | - new SvgImageBackEnd() |
|
| 152 | - ); |
|
| 153 | - |
|
| 154 | - $writer = new Writer($renderer); |
|
| 155 | - $svg = $writer->writeString($provisioningUrl); |
|
| 156 | - |
|
| 157 | - $this->assign('svg', $svg); |
|
| 158 | - $this->assign('secret', $otpCredentialProvider->getSecret($currentUser)); |
|
| 159 | - |
|
| 160 | - $this->assignCSRFToken(); |
|
| 161 | - $this->setTemplate('mfa/enableTotpEnroll.tpl'); |
|
| 162 | - |
|
| 163 | - return; |
|
| 164 | - } |
|
| 165 | - else { |
|
| 166 | - SessionAlert::error('Error enabling TOTP - invalid credentials.'); |
|
| 167 | - $this->redirect('multiFactor'); |
|
| 168 | - |
|
| 169 | - return; |
|
| 170 | - } |
|
| 171 | - } |
|
| 172 | - |
|
| 173 | - if ($stage === "enroll") { |
|
| 174 | - // we *must* have a defined credential already here, |
|
| 175 | - if ($otpCredentialProvider->isPartiallyEnrolled($currentUser)) { |
|
| 176 | - $otp = WebRequest::postString('otp'); |
|
| 177 | - $result = $otpCredentialProvider->verifyEnable($currentUser, $otp); |
|
| 178 | - |
|
| 179 | - if ($result) { |
|
| 180 | - SessionAlert::success('Enabled TOTP.'); |
|
| 181 | - |
|
| 182 | - $scratchProvider = new ScratchTokenCredentialProvider($database, $this->getSiteConfiguration()); |
|
| 183 | - if ($scratchProvider->getRemaining($currentUser->getId()) < 3) { |
|
| 184 | - $scratchProvider->setCredential($currentUser, 2, null); |
|
| 185 | - $tokens = $scratchProvider->getTokens(); |
|
| 186 | - $this->assign('tokens', $tokens); |
|
| 187 | - $this->setTemplate('mfa/regenScratchTokens.tpl'); |
|
| 188 | - return; |
|
| 189 | - } |
|
| 190 | - } |
|
| 191 | - else { |
|
| 192 | - $otpCredentialProvider->deleteCredential($currentUser); |
|
| 193 | - SessionAlert::error('Error enabling TOTP: invalid token provided'); |
|
| 194 | - } |
|
| 195 | - |
|
| 196 | - |
|
| 197 | - $this->redirect('multiFactor'); |
|
| 198 | - return; |
|
| 199 | - } |
|
| 200 | - else { |
|
| 201 | - SessionAlert::error('Error enabling TOTP - no enrollment found or enrollment expired.'); |
|
| 202 | - $this->redirect('multiFactor'); |
|
| 203 | - |
|
| 204 | - return; |
|
| 205 | - } |
|
| 206 | - } |
|
| 207 | - |
|
| 208 | - // urgh, dunno what happened, but it's not something expected. |
|
| 209 | - throw new ApplicationLogicException(); |
|
| 210 | - } |
|
| 211 | - else { |
|
| 212 | - if ($otpCredentialProvider->userIsEnrolled($currentUser->getId())) { |
|
| 213 | - // user is not enrolled, we shouldn't have got here. |
|
| 214 | - throw new ApplicationLogicException('User is already enrolled in the selected MFA mechanism'); |
|
| 215 | - } |
|
| 216 | - |
|
| 217 | - $this->assignCSRFToken(); |
|
| 218 | - |
|
| 219 | - $this->assign('alertmessage', 'To enable your multi-factor credentials, please prove you are who you say you are by providing your tool password below.'); |
|
| 220 | - $this->assign('alertheader', 'Provide credentials'); |
|
| 221 | - $this->assign('continueText', 'Verify password'); |
|
| 222 | - $this->setTemplate('mfa/enableAuth.tpl'); |
|
| 223 | - } |
|
| 224 | - } |
|
| 225 | - |
|
| 226 | - protected function disableTotp() |
|
| 227 | - { |
|
| 228 | - $database = $this->getDatabase(); |
|
| 229 | - $currentUser = User::getCurrent($database); |
|
| 230 | - |
|
| 231 | - $otpCredentialProvider = new TotpCredentialProvider($database, $this->getSiteConfiguration()); |
|
| 232 | - |
|
| 233 | - $factorType = 'TOTP'; |
|
| 234 | - |
|
| 235 | - $this->deleteCredential($database, $currentUser, $otpCredentialProvider, $factorType); |
|
| 236 | - } |
|
| 237 | - |
|
| 238 | - protected function scratch() |
|
| 239 | - { |
|
| 240 | - $database = $this->getDatabase(); |
|
| 241 | - $currentUser = User::getCurrent($database); |
|
| 242 | - |
|
| 243 | - if (WebRequest::wasPosted()) { |
|
| 244 | - $this->validateCSRFToken(); |
|
| 245 | - |
|
| 246 | - $passwordCredentialProvider = new PasswordCredentialProvider($database, |
|
| 247 | - $this->getSiteConfiguration()); |
|
| 248 | - |
|
| 249 | - $otpCredentialProvider = new ScratchTokenCredentialProvider($database, |
|
| 250 | - $this->getSiteConfiguration()); |
|
| 251 | - |
|
| 252 | - $password = WebRequest::postString('password'); |
|
| 253 | - |
|
| 254 | - $result = $passwordCredentialProvider->authenticate($currentUser, $password); |
|
| 255 | - |
|
| 256 | - if ($result) { |
|
| 257 | - $otpCredentialProvider->setCredential($currentUser, 2, null); |
|
| 258 | - $tokens = $otpCredentialProvider->getTokens(); |
|
| 259 | - $this->assign('tokens', $tokens); |
|
| 260 | - $this->setTemplate('mfa/regenScratchTokens.tpl'); |
|
| 261 | - } |
|
| 262 | - else { |
|
| 263 | - SessionAlert::error('Error refreshing scratch tokens - invalid credentials.'); |
|
| 264 | - $this->redirect('multiFactor'); |
|
| 265 | - } |
|
| 266 | - } |
|
| 267 | - else { |
|
| 268 | - $this->assignCSRFToken(); |
|
| 269 | - |
|
| 270 | - $this->assign('alertmessage', 'To regenerate your emergency scratch tokens, please prove you are who you say you are by providing your tool password below. Note that continuing will invalidate all remaining scratch tokens, and provide a set of new ones.'); |
|
| 271 | - $this->assign('alertheader', 'Re-generate scratch tokens'); |
|
| 272 | - $this->assign('continueText', 'Regenerate Scratch Tokens'); |
|
| 273 | - |
|
| 274 | - $this->setTemplate('mfa/enableAuth.tpl'); |
|
| 275 | - } |
|
| 276 | - } |
|
| 277 | - |
|
| 278 | - /** |
|
| 279 | - * @param PdoDatabase $database |
|
| 280 | - * @param User $currentUser |
|
| 281 | - * @param ICredentialProvider $otpCredentialProvider |
|
| 282 | - * @param string $factorType |
|
| 283 | - * |
|
| 284 | - * @throws ApplicationLogicException |
|
| 285 | - */ |
|
| 286 | - private function deleteCredential( |
|
| 287 | - PdoDatabase $database, |
|
| 288 | - User $currentUser, |
|
| 289 | - ICredentialProvider $otpCredentialProvider, |
|
| 290 | - $factorType |
|
| 291 | - ) { |
|
| 292 | - if (WebRequest::wasPosted()) { |
|
| 293 | - $passwordCredentialProvider = new PasswordCredentialProvider($database, |
|
| 294 | - $this->getSiteConfiguration()); |
|
| 295 | - |
|
| 296 | - $this->validateCSRFToken(); |
|
| 297 | - |
|
| 298 | - $password = WebRequest::postString('password'); |
|
| 299 | - $result = $passwordCredentialProvider->authenticate($currentUser, $password); |
|
| 300 | - |
|
| 301 | - if ($result) { |
|
| 302 | - $otpCredentialProvider->deleteCredential($currentUser); |
|
| 303 | - SessionAlert::success('Disabled ' . $factorType . '.'); |
|
| 304 | - $this->redirect('multiFactor'); |
|
| 305 | - } |
|
| 306 | - else { |
|
| 307 | - SessionAlert::error('Error disabling ' . $factorType . ' - invalid credentials.'); |
|
| 308 | - $this->redirect('multiFactor'); |
|
| 309 | - } |
|
| 310 | - } |
|
| 311 | - else { |
|
| 312 | - if (!$otpCredentialProvider->userIsEnrolled($currentUser->getId())) { |
|
| 313 | - // user is not enrolled, we shouldn't have got here. |
|
| 314 | - throw new ApplicationLogicException('User is not enrolled in the selected MFA mechanism'); |
|
| 315 | - } |
|
| 316 | - |
|
| 317 | - $this->assignCSRFToken(); |
|
| 318 | - $this->assign('otpType', $factorType); |
|
| 319 | - $this->setTemplate('mfa/disableOtp.tpl'); |
|
| 320 | - } |
|
| 321 | - } |
|
| 29 | + /** |
|
| 30 | + * Main function for this page, when no specific actions are called. |
|
| 31 | + * @return void |
|
| 32 | + */ |
|
| 33 | + protected function main() |
|
| 34 | + { |
|
| 35 | + $database = $this->getDatabase(); |
|
| 36 | + $currentUser = User::getCurrent($database); |
|
| 37 | + |
|
| 38 | + $yubikeyOtpCredentialProvider = new YubikeyOtpCredentialProvider($database, $this->getSiteConfiguration(), |
|
| 39 | + $this->getHttpHelper()); |
|
| 40 | + $this->assign('yubikeyOtpIdentity', $yubikeyOtpCredentialProvider->getYubikeyData($currentUser->getId())); |
|
| 41 | + $this->assign('yubikeyOtpEnrolled', $yubikeyOtpCredentialProvider->userIsEnrolled($currentUser->getId())); |
|
| 42 | + |
|
| 43 | + $totpCredentialProvider = new TotpCredentialProvider($database, $this->getSiteConfiguration()); |
|
| 44 | + $this->assign('totpEnrolled', $totpCredentialProvider->userIsEnrolled($currentUser->getId())); |
|
| 45 | + |
|
| 46 | + $scratchCredentialProvider = new ScratchTokenCredentialProvider($database, $this->getSiteConfiguration()); |
|
| 47 | + $this->assign('scratchEnrolled', $scratchCredentialProvider->userIsEnrolled($currentUser->getId())); |
|
| 48 | + $this->assign('scratchRemaining', $scratchCredentialProvider->getRemaining($currentUser->getId())); |
|
| 49 | + |
|
| 50 | + $this->assign('allowedTotp', $this->barrierTest('enableTotp', $currentUser)); |
|
| 51 | + $this->assign('allowedYubikey', $this->barrierTest('enableYubikeyOtp', $currentUser)); |
|
| 52 | + |
|
| 53 | + $this->setTemplate('mfa/mfa.tpl'); |
|
| 54 | + } |
|
| 55 | + |
|
| 56 | + protected function enableYubikeyOtp() |
|
| 57 | + { |
|
| 58 | + $database = $this->getDatabase(); |
|
| 59 | + $currentUser = User::getCurrent($database); |
|
| 60 | + |
|
| 61 | + $otpCredentialProvider = new YubikeyOtpCredentialProvider($database, |
|
| 62 | + $this->getSiteConfiguration(), $this->getHttpHelper()); |
|
| 63 | + |
|
| 64 | + if (WebRequest::wasPosted()) { |
|
| 65 | + $this->validateCSRFToken(); |
|
| 66 | + |
|
| 67 | + $passwordCredentialProvider = new PasswordCredentialProvider($database, |
|
| 68 | + $this->getSiteConfiguration()); |
|
| 69 | + |
|
| 70 | + $password = WebRequest::postString('password'); |
|
| 71 | + $otp = WebRequest::postString('otp'); |
|
| 72 | + |
|
| 73 | + $result = $passwordCredentialProvider->authenticate($currentUser, $password); |
|
| 74 | + |
|
| 75 | + if ($result) { |
|
| 76 | + try { |
|
| 77 | + $otpCredentialProvider->setCredential($currentUser, 2, $otp); |
|
| 78 | + SessionAlert::success('Enabled YubiKey OTP.'); |
|
| 79 | + |
|
| 80 | + $scratchProvider = new ScratchTokenCredentialProvider($database, $this->getSiteConfiguration()); |
|
| 81 | + if ($scratchProvider->getRemaining($currentUser->getId()) < 3) { |
|
| 82 | + $scratchProvider->setCredential($currentUser, 2, null); |
|
| 83 | + $tokens = $scratchProvider->getTokens(); |
|
| 84 | + $this->assign('tokens', $tokens); |
|
| 85 | + $this->setTemplate('mfa/regenScratchTokens.tpl'); |
|
| 86 | + return; |
|
| 87 | + } |
|
| 88 | + } |
|
| 89 | + catch (ApplicationLogicException $ex) { |
|
| 90 | + SessionAlert::error('Error enabling YubiKey OTP: ' . $ex->getMessage()); |
|
| 91 | + } |
|
| 92 | + |
|
| 93 | + $this->redirect('multiFactor'); |
|
| 94 | + } |
|
| 95 | + else { |
|
| 96 | + SessionAlert::error('Error enabling YubiKey OTP - invalid credentials.'); |
|
| 97 | + $this->redirect('multiFactor'); |
|
| 98 | + } |
|
| 99 | + } |
|
| 100 | + else { |
|
| 101 | + if ($otpCredentialProvider->userIsEnrolled($currentUser->getId())) { |
|
| 102 | + // user is not enrolled, we shouldn't have got here. |
|
| 103 | + throw new ApplicationLogicException('User is already enrolled in the selected MFA mechanism'); |
|
| 104 | + } |
|
| 105 | + |
|
| 106 | + $this->assignCSRFToken(); |
|
| 107 | + $this->setTemplate('mfa/enableYubikey.tpl'); |
|
| 108 | + } |
|
| 109 | + } |
|
| 110 | + |
|
| 111 | + protected function disableYubikeyOtp() |
|
| 112 | + { |
|
| 113 | + $database = $this->getDatabase(); |
|
| 114 | + $currentUser = User::getCurrent($database); |
|
| 115 | + |
|
| 116 | + $otpCredentialProvider = new YubikeyOtpCredentialProvider($database, |
|
| 117 | + $this->getSiteConfiguration(), $this->getHttpHelper()); |
|
| 118 | + |
|
| 119 | + $factorType = 'YubiKey OTP'; |
|
| 120 | + |
|
| 121 | + $this->deleteCredential($database, $currentUser, $otpCredentialProvider, $factorType); |
|
| 122 | + } |
|
| 123 | + |
|
| 124 | + protected function enableTotp() |
|
| 125 | + { |
|
| 126 | + $database = $this->getDatabase(); |
|
| 127 | + $currentUser = User::getCurrent($database); |
|
| 128 | + |
|
| 129 | + $otpCredentialProvider = new TotpCredentialProvider($database, $this->getSiteConfiguration()); |
|
| 130 | + |
|
| 131 | + if (WebRequest::wasPosted()) { |
|
| 132 | + $this->validateCSRFToken(); |
|
| 133 | + |
|
| 134 | + // used for routing only, not security |
|
| 135 | + $stage = WebRequest::postString('stage'); |
|
| 136 | + |
|
| 137 | + if ($stage === "auth") { |
|
| 138 | + $password = WebRequest::postString('password'); |
|
| 139 | + |
|
| 140 | + $passwordCredentialProvider = new PasswordCredentialProvider($database, |
|
| 141 | + $this->getSiteConfiguration()); |
|
| 142 | + $result = $passwordCredentialProvider->authenticate($currentUser, $password); |
|
| 143 | + |
|
| 144 | + if ($result) { |
|
| 145 | + $otpCredentialProvider->setCredential($currentUser, 2, null); |
|
| 146 | + |
|
| 147 | + $provisioningUrl = $otpCredentialProvider->getProvisioningUrl($currentUser); |
|
| 148 | + |
|
| 149 | + $renderer = new ImageRenderer( |
|
| 150 | + new RendererStyle(256), |
|
| 151 | + new SvgImageBackEnd() |
|
| 152 | + ); |
|
| 153 | + |
|
| 154 | + $writer = new Writer($renderer); |
|
| 155 | + $svg = $writer->writeString($provisioningUrl); |
|
| 156 | + |
|
| 157 | + $this->assign('svg', $svg); |
|
| 158 | + $this->assign('secret', $otpCredentialProvider->getSecret($currentUser)); |
|
| 159 | + |
|
| 160 | + $this->assignCSRFToken(); |
|
| 161 | + $this->setTemplate('mfa/enableTotpEnroll.tpl'); |
|
| 162 | + |
|
| 163 | + return; |
|
| 164 | + } |
|
| 165 | + else { |
|
| 166 | + SessionAlert::error('Error enabling TOTP - invalid credentials.'); |
|
| 167 | + $this->redirect('multiFactor'); |
|
| 168 | + |
|
| 169 | + return; |
|
| 170 | + } |
|
| 171 | + } |
|
| 172 | + |
|
| 173 | + if ($stage === "enroll") { |
|
| 174 | + // we *must* have a defined credential already here, |
|
| 175 | + if ($otpCredentialProvider->isPartiallyEnrolled($currentUser)) { |
|
| 176 | + $otp = WebRequest::postString('otp'); |
|
| 177 | + $result = $otpCredentialProvider->verifyEnable($currentUser, $otp); |
|
| 178 | + |
|
| 179 | + if ($result) { |
|
| 180 | + SessionAlert::success('Enabled TOTP.'); |
|
| 181 | + |
|
| 182 | + $scratchProvider = new ScratchTokenCredentialProvider($database, $this->getSiteConfiguration()); |
|
| 183 | + if ($scratchProvider->getRemaining($currentUser->getId()) < 3) { |
|
| 184 | + $scratchProvider->setCredential($currentUser, 2, null); |
|
| 185 | + $tokens = $scratchProvider->getTokens(); |
|
| 186 | + $this->assign('tokens', $tokens); |
|
| 187 | + $this->setTemplate('mfa/regenScratchTokens.tpl'); |
|
| 188 | + return; |
|
| 189 | + } |
|
| 190 | + } |
|
| 191 | + else { |
|
| 192 | + $otpCredentialProvider->deleteCredential($currentUser); |
|
| 193 | + SessionAlert::error('Error enabling TOTP: invalid token provided'); |
|
| 194 | + } |
|
| 195 | + |
|
| 196 | + |
|
| 197 | + $this->redirect('multiFactor'); |
|
| 198 | + return; |
|
| 199 | + } |
|
| 200 | + else { |
|
| 201 | + SessionAlert::error('Error enabling TOTP - no enrollment found or enrollment expired.'); |
|
| 202 | + $this->redirect('multiFactor'); |
|
| 203 | + |
|
| 204 | + return; |
|
| 205 | + } |
|
| 206 | + } |
|
| 207 | + |
|
| 208 | + // urgh, dunno what happened, but it's not something expected. |
|
| 209 | + throw new ApplicationLogicException(); |
|
| 210 | + } |
|
| 211 | + else { |
|
| 212 | + if ($otpCredentialProvider->userIsEnrolled($currentUser->getId())) { |
|
| 213 | + // user is not enrolled, we shouldn't have got here. |
|
| 214 | + throw new ApplicationLogicException('User is already enrolled in the selected MFA mechanism'); |
|
| 215 | + } |
|
| 216 | + |
|
| 217 | + $this->assignCSRFToken(); |
|
| 218 | + |
|
| 219 | + $this->assign('alertmessage', 'To enable your multi-factor credentials, please prove you are who you say you are by providing your tool password below.'); |
|
| 220 | + $this->assign('alertheader', 'Provide credentials'); |
|
| 221 | + $this->assign('continueText', 'Verify password'); |
|
| 222 | + $this->setTemplate('mfa/enableAuth.tpl'); |
|
| 223 | + } |
|
| 224 | + } |
|
| 225 | + |
|
| 226 | + protected function disableTotp() |
|
| 227 | + { |
|
| 228 | + $database = $this->getDatabase(); |
|
| 229 | + $currentUser = User::getCurrent($database); |
|
| 230 | + |
|
| 231 | + $otpCredentialProvider = new TotpCredentialProvider($database, $this->getSiteConfiguration()); |
|
| 232 | + |
|
| 233 | + $factorType = 'TOTP'; |
|
| 234 | + |
|
| 235 | + $this->deleteCredential($database, $currentUser, $otpCredentialProvider, $factorType); |
|
| 236 | + } |
|
| 237 | + |
|
| 238 | + protected function scratch() |
|
| 239 | + { |
|
| 240 | + $database = $this->getDatabase(); |
|
| 241 | + $currentUser = User::getCurrent($database); |
|
| 242 | + |
|
| 243 | + if (WebRequest::wasPosted()) { |
|
| 244 | + $this->validateCSRFToken(); |
|
| 245 | + |
|
| 246 | + $passwordCredentialProvider = new PasswordCredentialProvider($database, |
|
| 247 | + $this->getSiteConfiguration()); |
|
| 248 | + |
|
| 249 | + $otpCredentialProvider = new ScratchTokenCredentialProvider($database, |
|
| 250 | + $this->getSiteConfiguration()); |
|
| 251 | + |
|
| 252 | + $password = WebRequest::postString('password'); |
|
| 253 | + |
|
| 254 | + $result = $passwordCredentialProvider->authenticate($currentUser, $password); |
|
| 255 | + |
|
| 256 | + if ($result) { |
|
| 257 | + $otpCredentialProvider->setCredential($currentUser, 2, null); |
|
| 258 | + $tokens = $otpCredentialProvider->getTokens(); |
|
| 259 | + $this->assign('tokens', $tokens); |
|
| 260 | + $this->setTemplate('mfa/regenScratchTokens.tpl'); |
|
| 261 | + } |
|
| 262 | + else { |
|
| 263 | + SessionAlert::error('Error refreshing scratch tokens - invalid credentials.'); |
|
| 264 | + $this->redirect('multiFactor'); |
|
| 265 | + } |
|
| 266 | + } |
|
| 267 | + else { |
|
| 268 | + $this->assignCSRFToken(); |
|
| 269 | + |
|
| 270 | + $this->assign('alertmessage', 'To regenerate your emergency scratch tokens, please prove you are who you say you are by providing your tool password below. Note that continuing will invalidate all remaining scratch tokens, and provide a set of new ones.'); |
|
| 271 | + $this->assign('alertheader', 'Re-generate scratch tokens'); |
|
| 272 | + $this->assign('continueText', 'Regenerate Scratch Tokens'); |
|
| 273 | + |
|
| 274 | + $this->setTemplate('mfa/enableAuth.tpl'); |
|
| 275 | + } |
|
| 276 | + } |
|
| 277 | + |
|
| 278 | + /** |
|
| 279 | + * @param PdoDatabase $database |
|
| 280 | + * @param User $currentUser |
|
| 281 | + * @param ICredentialProvider $otpCredentialProvider |
|
| 282 | + * @param string $factorType |
|
| 283 | + * |
|
| 284 | + * @throws ApplicationLogicException |
|
| 285 | + */ |
|
| 286 | + private function deleteCredential( |
|
| 287 | + PdoDatabase $database, |
|
| 288 | + User $currentUser, |
|
| 289 | + ICredentialProvider $otpCredentialProvider, |
|
| 290 | + $factorType |
|
| 291 | + ) { |
|
| 292 | + if (WebRequest::wasPosted()) { |
|
| 293 | + $passwordCredentialProvider = new PasswordCredentialProvider($database, |
|
| 294 | + $this->getSiteConfiguration()); |
|
| 295 | + |
|
| 296 | + $this->validateCSRFToken(); |
|
| 297 | + |
|
| 298 | + $password = WebRequest::postString('password'); |
|
| 299 | + $result = $passwordCredentialProvider->authenticate($currentUser, $password); |
|
| 300 | + |
|
| 301 | + if ($result) { |
|
| 302 | + $otpCredentialProvider->deleteCredential($currentUser); |
|
| 303 | + SessionAlert::success('Disabled ' . $factorType . '.'); |
|
| 304 | + $this->redirect('multiFactor'); |
|
| 305 | + } |
|
| 306 | + else { |
|
| 307 | + SessionAlert::error('Error disabling ' . $factorType . ' - invalid credentials.'); |
|
| 308 | + $this->redirect('multiFactor'); |
|
| 309 | + } |
|
| 310 | + } |
|
| 311 | + else { |
|
| 312 | + if (!$otpCredentialProvider->userIsEnrolled($currentUser->getId())) { |
|
| 313 | + // user is not enrolled, we shouldn't have got here. |
|
| 314 | + throw new ApplicationLogicException('User is not enrolled in the selected MFA mechanism'); |
|
| 315 | + } |
|
| 316 | + |
|
| 317 | + $this->assignCSRFToken(); |
|
| 318 | + $this->assign('otpType', $factorType); |
|
| 319 | + $this->setTemplate('mfa/disableOtp.tpl'); |
|
| 320 | + } |
|
| 321 | + } |
|
| 322 | 322 | } |
@@ -14,22 +14,22 @@ |
||
| 14 | 14 | |
| 15 | 15 | class PageLogout extends InternalPageBase |
| 16 | 16 | { |
| 17 | - /** |
|
| 18 | - * Main function for this page, when no specific actions are called. |
|
| 19 | - */ |
|
| 20 | - protected function main() |
|
| 21 | - { |
|
| 22 | - if (WebRequest::wasPosted()) { |
|
| 23 | - Session::destroy(); |
|
| 24 | - $this->redirect("login"); |
|
| 25 | - return; |
|
| 26 | - } |
|
| 17 | + /** |
|
| 18 | + * Main function for this page, when no specific actions are called. |
|
| 19 | + */ |
|
| 20 | + protected function main() |
|
| 21 | + { |
|
| 22 | + if (WebRequest::wasPosted()) { |
|
| 23 | + Session::destroy(); |
|
| 24 | + $this->redirect("login"); |
|
| 25 | + return; |
|
| 26 | + } |
|
| 27 | 27 | |
| 28 | - $this->redirect(); |
|
| 29 | - } |
|
| 28 | + $this->redirect(); |
|
| 29 | + } |
|
| 30 | 30 | |
| 31 | - protected function isProtectedPage() |
|
| 32 | - { |
|
| 33 | - return false; |
|
| 34 | - } |
|
| 31 | + protected function isProtectedPage() |
|
| 32 | + { |
|
| 33 | + return false; |
|
| 34 | + } |
|
| 35 | 35 | } |
@@ -17,71 +17,71 @@ |
||
| 17 | 17 | |
| 18 | 18 | class PageChangePassword extends InternalPageBase |
| 19 | 19 | { |
| 20 | - /** |
|
| 21 | - * Main function for this page, when no specific actions are called. |
|
| 22 | - * @return void |
|
| 23 | - */ |
|
| 24 | - protected function main() |
|
| 25 | - { |
|
| 26 | - $this->setHtmlTitle('Change Password'); |
|
| 20 | + /** |
|
| 21 | + * Main function for this page, when no specific actions are called. |
|
| 22 | + * @return void |
|
| 23 | + */ |
|
| 24 | + protected function main() |
|
| 25 | + { |
|
| 26 | + $this->setHtmlTitle('Change Password'); |
|
| 27 | 27 | |
| 28 | - if (WebRequest::wasPosted()) { |
|
| 29 | - $this->validateCSRFToken(); |
|
| 30 | - try { |
|
| 31 | - $oldPassword = WebRequest::postString('password'); |
|
| 32 | - $newPassword = WebRequest::postString('newpassword'); |
|
| 33 | - $newPasswordConfirmation = WebRequest::postString('newpasswordconfirm'); |
|
| 28 | + if (WebRequest::wasPosted()) { |
|
| 29 | + $this->validateCSRFToken(); |
|
| 30 | + try { |
|
| 31 | + $oldPassword = WebRequest::postString('password'); |
|
| 32 | + $newPassword = WebRequest::postString('newpassword'); |
|
| 33 | + $newPasswordConfirmation = WebRequest::postString('newpasswordconfirm'); |
|
| 34 | 34 | |
| 35 | - $user = User::getCurrent($this->getDatabase()); |
|
| 36 | - if (!$user instanceof User) { |
|
| 37 | - throw new ApplicationLogicException('User not found'); |
|
| 38 | - } |
|
| 35 | + $user = User::getCurrent($this->getDatabase()); |
|
| 36 | + if (!$user instanceof User) { |
|
| 37 | + throw new ApplicationLogicException('User not found'); |
|
| 38 | + } |
|
| 39 | 39 | |
| 40 | - $this->validateNewPassword($oldPassword, $newPassword, $newPasswordConfirmation, $user); |
|
| 40 | + $this->validateNewPassword($oldPassword, $newPassword, $newPasswordConfirmation, $user); |
|
| 41 | 41 | |
| 42 | - $passwordProvider = new PasswordCredentialProvider($this->getDatabase(), $this->getSiteConfiguration()); |
|
| 43 | - $passwordProvider->setCredential($user, 1, $newPassword); |
|
| 44 | - } |
|
| 45 | - catch (ApplicationLogicException $ex) { |
|
| 46 | - SessionAlert::error($ex->getMessage()); |
|
| 47 | - $this->redirect('changePassword'); |
|
| 42 | + $passwordProvider = new PasswordCredentialProvider($this->getDatabase(), $this->getSiteConfiguration()); |
|
| 43 | + $passwordProvider->setCredential($user, 1, $newPassword); |
|
| 44 | + } |
|
| 45 | + catch (ApplicationLogicException $ex) { |
|
| 46 | + SessionAlert::error($ex->getMessage()); |
|
| 47 | + $this->redirect('changePassword'); |
|
| 48 | 48 | |
| 49 | - return; |
|
| 50 | - } |
|
| 49 | + return; |
|
| 50 | + } |
|
| 51 | 51 | |
| 52 | - SessionAlert::success('Password changed successfully!'); |
|
| 52 | + SessionAlert::success('Password changed successfully!'); |
|
| 53 | 53 | |
| 54 | - $this->redirect('preferences'); |
|
| 55 | - } |
|
| 56 | - else { |
|
| 57 | - $this->assignCSRFToken(); |
|
| 58 | - $this->setTemplate('preferences/changePassword.tpl'); |
|
| 59 | - $this->addJs("/vendor/dropbox/zxcvbn/dist/zxcvbn.js"); |
|
| 60 | - } |
|
| 61 | - } |
|
| 54 | + $this->redirect('preferences'); |
|
| 55 | + } |
|
| 56 | + else { |
|
| 57 | + $this->assignCSRFToken(); |
|
| 58 | + $this->setTemplate('preferences/changePassword.tpl'); |
|
| 59 | + $this->addJs("/vendor/dropbox/zxcvbn/dist/zxcvbn.js"); |
|
| 60 | + } |
|
| 61 | + } |
|
| 62 | 62 | |
| 63 | - /** |
|
| 64 | - * @param string $oldPassword |
|
| 65 | - * @param string $newPassword |
|
| 66 | - * @param string $newPasswordConfirmation |
|
| 67 | - * @param User $user |
|
| 68 | - * |
|
| 69 | - * @throws ApplicationLogicException |
|
| 70 | - */ |
|
| 71 | - protected function validateNewPassword($oldPassword, $newPassword, $newPasswordConfirmation, User $user) |
|
| 72 | - { |
|
| 73 | - if ($oldPassword === null || $newPassword === null || $newPasswordConfirmation === null) { |
|
| 74 | - throw new ApplicationLogicException('All three fields must be completed to change your password'); |
|
| 75 | - } |
|
| 63 | + /** |
|
| 64 | + * @param string $oldPassword |
|
| 65 | + * @param string $newPassword |
|
| 66 | + * @param string $newPasswordConfirmation |
|
| 67 | + * @param User $user |
|
| 68 | + * |
|
| 69 | + * @throws ApplicationLogicException |
|
| 70 | + */ |
|
| 71 | + protected function validateNewPassword($oldPassword, $newPassword, $newPasswordConfirmation, User $user) |
|
| 72 | + { |
|
| 73 | + if ($oldPassword === null || $newPassword === null || $newPasswordConfirmation === null) { |
|
| 74 | + throw new ApplicationLogicException('All three fields must be completed to change your password'); |
|
| 75 | + } |
|
| 76 | 76 | |
| 77 | - if ($newPassword !== $newPasswordConfirmation) { |
|
| 78 | - throw new ApplicationLogicException('Your new passwords did not match!'); |
|
| 79 | - } |
|
| 77 | + if ($newPassword !== $newPasswordConfirmation) { |
|
| 78 | + throw new ApplicationLogicException('Your new passwords did not match!'); |
|
| 79 | + } |
|
| 80 | 80 | |
| 81 | - // TODO: adapt for MFA support |
|
| 82 | - $passwordProvider = new PasswordCredentialProvider($this->getDatabase(), $this->getSiteConfiguration()); |
|
| 83 | - if (!$passwordProvider->authenticate($user, $oldPassword)) { |
|
| 84 | - throw new ApplicationLogicException('The password you entered was incorrect.'); |
|
| 85 | - } |
|
| 86 | - } |
|
| 81 | + // TODO: adapt for MFA support |
|
| 82 | + $passwordProvider = new PasswordCredentialProvider($this->getDatabase(), $this->getSiteConfiguration()); |
|
| 83 | + if (!$passwordProvider->authenticate($user, $oldPassword)) { |
|
| 84 | + throw new ApplicationLogicException('The password you entered was incorrect.'); |
|
| 85 | + } |
|
| 86 | + } |
|
| 87 | 87 | } |
@@ -18,93 +18,93 @@ |
||
| 18 | 18 | |
| 19 | 19 | class PageOAuthCallback extends InternalPageBase |
| 20 | 20 | { |
| 21 | - /** |
|
| 22 | - * @return bool |
|
| 23 | - */ |
|
| 24 | - protected function isProtectedPage() |
|
| 25 | - { |
|
| 26 | - // This page is critical to ensuring OAuth functionality is operational. |
|
| 27 | - return false; |
|
| 28 | - } |
|
| 21 | + /** |
|
| 22 | + * @return bool |
|
| 23 | + */ |
|
| 24 | + protected function isProtectedPage() |
|
| 25 | + { |
|
| 26 | + // This page is critical to ensuring OAuth functionality is operational. |
|
| 27 | + return false; |
|
| 28 | + } |
|
| 29 | 29 | |
| 30 | - /** |
|
| 31 | - * Main function for this page, when no specific actions are called. |
|
| 32 | - * @return void |
|
| 33 | - */ |
|
| 34 | - protected function main() |
|
| 35 | - { |
|
| 36 | - // This should never get hit except by URL manipulation. |
|
| 37 | - $this->redirect(''); |
|
| 38 | - } |
|
| 30 | + /** |
|
| 31 | + * Main function for this page, when no specific actions are called. |
|
| 32 | + * @return void |
|
| 33 | + */ |
|
| 34 | + protected function main() |
|
| 35 | + { |
|
| 36 | + // This should never get hit except by URL manipulation. |
|
| 37 | + $this->redirect(''); |
|
| 38 | + } |
|
| 39 | 39 | |
| 40 | - /** |
|
| 41 | - * Registered endpoint for the account creation callback. |
|
| 42 | - * |
|
| 43 | - * If this ever gets hit, something is wrong somewhere. |
|
| 44 | - */ |
|
| 45 | - protected function create() |
|
| 46 | - { |
|
| 47 | - throw new Exception('OAuth account creation endpoint triggered.'); |
|
| 48 | - } |
|
| 40 | + /** |
|
| 41 | + * Registered endpoint for the account creation callback. |
|
| 42 | + * |
|
| 43 | + * If this ever gets hit, something is wrong somewhere. |
|
| 44 | + */ |
|
| 45 | + protected function create() |
|
| 46 | + { |
|
| 47 | + throw new Exception('OAuth account creation endpoint triggered.'); |
|
| 48 | + } |
|
| 49 | 49 | |
| 50 | - /** |
|
| 51 | - * Callback entry point |
|
| 52 | - * @throws ApplicationLogicException |
|
| 53 | - * @throws OptimisticLockFailedException |
|
| 54 | - */ |
|
| 55 | - protected function authorise() |
|
| 56 | - { |
|
| 57 | - $oauthToken = WebRequest::getString('oauth_token'); |
|
| 58 | - $oauthVerifier = WebRequest::getString('oauth_verifier'); |
|
| 50 | + /** |
|
| 51 | + * Callback entry point |
|
| 52 | + * @throws ApplicationLogicException |
|
| 53 | + * @throws OptimisticLockFailedException |
|
| 54 | + */ |
|
| 55 | + protected function authorise() |
|
| 56 | + { |
|
| 57 | + $oauthToken = WebRequest::getString('oauth_token'); |
|
| 58 | + $oauthVerifier = WebRequest::getString('oauth_verifier'); |
|
| 59 | 59 | |
| 60 | - $this->doCallbackValidation($oauthToken, $oauthVerifier); |
|
| 60 | + $this->doCallbackValidation($oauthToken, $oauthVerifier); |
|
| 61 | 61 | |
| 62 | - $database = $this->getDatabase(); |
|
| 62 | + $database = $this->getDatabase(); |
|
| 63 | 63 | |
| 64 | - $user = OAuthUserHelper::findUserByRequestToken($oauthToken, $database); |
|
| 65 | - $oauth = new OAuthUserHelper($user, $database, $this->getOAuthProtocolHelper(), $this->getSiteConfiguration()); |
|
| 64 | + $user = OAuthUserHelper::findUserByRequestToken($oauthToken, $database); |
|
| 65 | + $oauth = new OAuthUserHelper($user, $database, $this->getOAuthProtocolHelper(), $this->getSiteConfiguration()); |
|
| 66 | 66 | |
| 67 | - try { |
|
| 68 | - $oauth->completeHandshake($oauthVerifier); |
|
| 69 | - } |
|
| 70 | - catch (CurlException $ex) { |
|
| 71 | - throw new ApplicationLogicException($ex->getMessage(), 0, $ex); |
|
| 72 | - } |
|
| 67 | + try { |
|
| 68 | + $oauth->completeHandshake($oauthVerifier); |
|
| 69 | + } |
|
| 70 | + catch (CurlException $ex) { |
|
| 71 | + throw new ApplicationLogicException($ex->getMessage(), 0, $ex); |
|
| 72 | + } |
|
| 73 | 73 | |
| 74 | - // OK, we're the same session that just did a partial login that was redirected to OAuth. Let's upgrade the |
|
| 75 | - // login to a full login |
|
| 76 | - if (WebRequest::getOAuthPartialLogin() === $user->getId()) { |
|
| 77 | - WebRequest::setLoggedInUser($user); |
|
| 78 | - $this->getDomainAccessManager()->switchToDefaultDomain($user); |
|
| 79 | - } |
|
| 74 | + // OK, we're the same session that just did a partial login that was redirected to OAuth. Let's upgrade the |
|
| 75 | + // login to a full login |
|
| 76 | + if (WebRequest::getOAuthPartialLogin() === $user->getId()) { |
|
| 77 | + WebRequest::setLoggedInUser($user); |
|
| 78 | + $this->getDomainAccessManager()->switchToDefaultDomain($user); |
|
| 79 | + } |
|
| 80 | 80 | |
| 81 | - // My thinking is there are three cases here: |
|
| 82 | - // a) new user => redirect to prefs - it's the only thing they can access other than stats |
|
| 83 | - // b) existing user hit the connect button in prefs => redirect to prefs since it's where they were |
|
| 84 | - // c) existing user logging in => redirect to wherever they came from |
|
| 85 | - $redirectDestination = WebRequest::clearPostLoginRedirect(); |
|
| 86 | - if ($redirectDestination !== null && !$user->isNewUser()) { |
|
| 87 | - $this->redirectUrl($redirectDestination); |
|
| 88 | - } |
|
| 89 | - else { |
|
| 90 | - $this->redirect('preferences', null, null, 'internal.php'); |
|
| 91 | - } |
|
| 92 | - } |
|
| 81 | + // My thinking is there are three cases here: |
|
| 82 | + // a) new user => redirect to prefs - it's the only thing they can access other than stats |
|
| 83 | + // b) existing user hit the connect button in prefs => redirect to prefs since it's where they were |
|
| 84 | + // c) existing user logging in => redirect to wherever they came from |
|
| 85 | + $redirectDestination = WebRequest::clearPostLoginRedirect(); |
|
| 86 | + if ($redirectDestination !== null && !$user->isNewUser()) { |
|
| 87 | + $this->redirectUrl($redirectDestination); |
|
| 88 | + } |
|
| 89 | + else { |
|
| 90 | + $this->redirect('preferences', null, null, 'internal.php'); |
|
| 91 | + } |
|
| 92 | + } |
|
| 93 | 93 | |
| 94 | - /** |
|
| 95 | - * @param string $oauthToken |
|
| 96 | - * @param string $oauthVerifier |
|
| 97 | - * |
|
| 98 | - * @throws ApplicationLogicException |
|
| 99 | - */ |
|
| 100 | - private function doCallbackValidation($oauthToken, $oauthVerifier) |
|
| 101 | - { |
|
| 102 | - if ($oauthToken === null) { |
|
| 103 | - throw new ApplicationLogicException('No token provided'); |
|
| 104 | - } |
|
| 94 | + /** |
|
| 95 | + * @param string $oauthToken |
|
| 96 | + * @param string $oauthVerifier |
|
| 97 | + * |
|
| 98 | + * @throws ApplicationLogicException |
|
| 99 | + */ |
|
| 100 | + private function doCallbackValidation($oauthToken, $oauthVerifier) |
|
| 101 | + { |
|
| 102 | + if ($oauthToken === null) { |
|
| 103 | + throw new ApplicationLogicException('No token provided'); |
|
| 104 | + } |
|
| 105 | 105 | |
| 106 | - if ($oauthVerifier === null) { |
|
| 107 | - throw new ApplicationLogicException('No oauth verifier provided.'); |
|
| 108 | - } |
|
| 109 | - } |
|
| 106 | + if ($oauthVerifier === null) { |
|
| 107 | + throw new ApplicationLogicException('No oauth verifier provided.'); |
|
| 108 | + } |
|
| 109 | + } |
|
| 110 | 110 | } |
| 111 | 111 | \ No newline at end of file |
@@ -22,81 +22,81 @@ |
||
| 22 | 22 | |
| 23 | 23 | class PageOAuth extends InternalPageBase |
| 24 | 24 | { |
| 25 | - /** |
|
| 26 | - * Attach entry point |
|
| 27 | - * |
|
| 28 | - * must be posted, or will redirect to preferences |
|
| 29 | - */ |
|
| 30 | - protected function attach() |
|
| 31 | - { |
|
| 32 | - if (!WebRequest::wasPosted()) { |
|
| 33 | - $this->redirect('preferences'); |
|
| 34 | - |
|
| 35 | - return; |
|
| 36 | - } |
|
| 37 | - |
|
| 38 | - $database = $this->getDatabase(); |
|
| 39 | - |
|
| 40 | - $this->validateCSRFToken(); |
|
| 41 | - |
|
| 42 | - $oauthProtocolHelper = $this->getOAuthProtocolHelper(); |
|
| 43 | - $user = User::getCurrent($database); |
|
| 44 | - $oauth = new OAuthUserHelper($user, $database, $oauthProtocolHelper, $this->getSiteConfiguration()); |
|
| 45 | - |
|
| 46 | - try { |
|
| 47 | - $authoriseUrl = $oauth->getRequestToken(); |
|
| 48 | - $this->redirectUrl($authoriseUrl); |
|
| 49 | - } |
|
| 50 | - catch (CurlException $ex) { |
|
| 51 | - throw new ApplicationLogicException($ex->getMessage(), 0, $ex); |
|
| 52 | - } |
|
| 53 | - } |
|
| 54 | - |
|
| 55 | - /** |
|
| 56 | - * Detach account entry point |
|
| 57 | - * @throws Exception |
|
| 58 | - */ |
|
| 59 | - protected function detach() |
|
| 60 | - { |
|
| 61 | - if ($this->getSiteConfiguration()->getEnforceOAuth()) { |
|
| 62 | - throw new AccessDeniedException($this->getSecurityManager(), $this->getDomainAccessManager()); |
|
| 63 | - } |
|
| 64 | - |
|
| 65 | - $database = $this->getDatabase(); |
|
| 66 | - $user = User::getCurrent($database); |
|
| 67 | - $oauth = new OAuthUserHelper($user, $database, $this->getOAuthProtocolHelper(), $this->getSiteConfiguration()); |
|
| 68 | - |
|
| 69 | - try { |
|
| 70 | - $oauth->refreshIdentity(); |
|
| 71 | - } |
|
| 72 | - catch (CurlException $ex) { |
|
| 73 | - // do nothing. The user's already revoked this access anyway. |
|
| 74 | - } |
|
| 75 | - catch (OAuthException $ex) { |
|
| 76 | - // do nothing. The user's already revoked this access anyway. |
|
| 77 | - } |
|
| 78 | - catch (OptimisticLockFailedException $e) { |
|
| 79 | - // do nothing. The user's already revoked this access anyway. |
|
| 80 | - } |
|
| 81 | - |
|
| 82 | - $oauth->detach(); |
|
| 83 | - |
|
| 84 | - // TODO: figure out why we need to force logout after a detach. |
|
| 85 | - $user->setForcelogout(true); |
|
| 86 | - $user->save(); |
|
| 87 | - |
|
| 88 | - // force the user to log out |
|
| 89 | - Session::destroy(); |
|
| 90 | - |
|
| 91 | - $this->redirect('login'); |
|
| 92 | - } |
|
| 93 | - |
|
| 94 | - /** |
|
| 95 | - * Main function for this page, when no specific actions are called. |
|
| 96 | - * @return void |
|
| 97 | - */ |
|
| 98 | - protected function main() |
|
| 99 | - { |
|
| 100 | - $this->redirect('preferences'); |
|
| 101 | - } |
|
| 25 | + /** |
|
| 26 | + * Attach entry point |
|
| 27 | + * |
|
| 28 | + * must be posted, or will redirect to preferences |
|
| 29 | + */ |
|
| 30 | + protected function attach() |
|
| 31 | + { |
|
| 32 | + if (!WebRequest::wasPosted()) { |
|
| 33 | + $this->redirect('preferences'); |
|
| 34 | + |
|
| 35 | + return; |
|
| 36 | + } |
|
| 37 | + |
|
| 38 | + $database = $this->getDatabase(); |
|
| 39 | + |
|
| 40 | + $this->validateCSRFToken(); |
|
| 41 | + |
|
| 42 | + $oauthProtocolHelper = $this->getOAuthProtocolHelper(); |
|
| 43 | + $user = User::getCurrent($database); |
|
| 44 | + $oauth = new OAuthUserHelper($user, $database, $oauthProtocolHelper, $this->getSiteConfiguration()); |
|
| 45 | + |
|
| 46 | + try { |
|
| 47 | + $authoriseUrl = $oauth->getRequestToken(); |
|
| 48 | + $this->redirectUrl($authoriseUrl); |
|
| 49 | + } |
|
| 50 | + catch (CurlException $ex) { |
|
| 51 | + throw new ApplicationLogicException($ex->getMessage(), 0, $ex); |
|
| 52 | + } |
|
| 53 | + } |
|
| 54 | + |
|
| 55 | + /** |
|
| 56 | + * Detach account entry point |
|
| 57 | + * @throws Exception |
|
| 58 | + */ |
|
| 59 | + protected function detach() |
|
| 60 | + { |
|
| 61 | + if ($this->getSiteConfiguration()->getEnforceOAuth()) { |
|
| 62 | + throw new AccessDeniedException($this->getSecurityManager(), $this->getDomainAccessManager()); |
|
| 63 | + } |
|
| 64 | + |
|
| 65 | + $database = $this->getDatabase(); |
|
| 66 | + $user = User::getCurrent($database); |
|
| 67 | + $oauth = new OAuthUserHelper($user, $database, $this->getOAuthProtocolHelper(), $this->getSiteConfiguration()); |
|
| 68 | + |
|
| 69 | + try { |
|
| 70 | + $oauth->refreshIdentity(); |
|
| 71 | + } |
|
| 72 | + catch (CurlException $ex) { |
|
| 73 | + // do nothing. The user's already revoked this access anyway. |
|
| 74 | + } |
|
| 75 | + catch (OAuthException $ex) { |
|
| 76 | + // do nothing. The user's already revoked this access anyway. |
|
| 77 | + } |
|
| 78 | + catch (OptimisticLockFailedException $e) { |
|
| 79 | + // do nothing. The user's already revoked this access anyway. |
|
| 80 | + } |
|
| 81 | + |
|
| 82 | + $oauth->detach(); |
|
| 83 | + |
|
| 84 | + // TODO: figure out why we need to force logout after a detach. |
|
| 85 | + $user->setForcelogout(true); |
|
| 86 | + $user->save(); |
|
| 87 | + |
|
| 88 | + // force the user to log out |
|
| 89 | + Session::destroy(); |
|
| 90 | + |
|
| 91 | + $this->redirect('login'); |
|
| 92 | + } |
|
| 93 | + |
|
| 94 | + /** |
|
| 95 | + * Main function for this page, when no specific actions are called. |
|
| 96 | + * @return void |
|
| 97 | + */ |
|
| 98 | + protected function main() |
|
| 99 | + { |
|
| 100 | + $this->redirect('preferences'); |
|
| 101 | + } |
|
| 102 | 102 | } |
@@ -19,81 +19,81 @@ |
||
| 19 | 19 | |
| 20 | 20 | class PageBreakReservation extends RequestActionBase |
| 21 | 21 | { |
| 22 | - protected function main() |
|
| 23 | - { |
|
| 24 | - $this->checkPosted(); |
|
| 25 | - $database = $this->getDatabase(); |
|
| 26 | - $request = $this->getRequest($database); |
|
| 22 | + protected function main() |
|
| 23 | + { |
|
| 24 | + $this->checkPosted(); |
|
| 25 | + $database = $this->getDatabase(); |
|
| 26 | + $request = $this->getRequest($database); |
|
| 27 | 27 | |
| 28 | - if ($request->getReserved() === null) { |
|
| 29 | - throw new ApplicationLogicException('Request is not reserved!'); |
|
| 30 | - } |
|
| 28 | + if ($request->getReserved() === null) { |
|
| 29 | + throw new ApplicationLogicException('Request is not reserved!'); |
|
| 30 | + } |
|
| 31 | 31 | |
| 32 | - $currentUser = User::getCurrent($database); |
|
| 32 | + $currentUser = User::getCurrent($database); |
|
| 33 | 33 | |
| 34 | - if ($currentUser->getId() === $request->getReserved()) { |
|
| 35 | - $this->doUnreserve($request, $database); |
|
| 36 | - } |
|
| 37 | - else { |
|
| 38 | - // not the same user! |
|
| 39 | - if ($this->barrierTest('force', $currentUser)) { |
|
| 40 | - $this->doBreakReserve($request, $database); |
|
| 41 | - } |
|
| 42 | - else { |
|
| 43 | - throw new AccessDeniedException($this->getSecurityManager(), $this->getDomainAccessManager()); |
|
| 44 | - } |
|
| 45 | - } |
|
| 46 | - } |
|
| 34 | + if ($currentUser->getId() === $request->getReserved()) { |
|
| 35 | + $this->doUnreserve($request, $database); |
|
| 36 | + } |
|
| 37 | + else { |
|
| 38 | + // not the same user! |
|
| 39 | + if ($this->barrierTest('force', $currentUser)) { |
|
| 40 | + $this->doBreakReserve($request, $database); |
|
| 41 | + } |
|
| 42 | + else { |
|
| 43 | + throw new AccessDeniedException($this->getSecurityManager(), $this->getDomainAccessManager()); |
|
| 44 | + } |
|
| 45 | + } |
|
| 46 | + } |
|
| 47 | 47 | |
| 48 | - /** |
|
| 49 | - * @param Request $request |
|
| 50 | - * @param PdoDatabase $database |
|
| 51 | - * |
|
| 52 | - * @throws Exception |
|
| 53 | - */ |
|
| 54 | - protected function doUnreserve(Request $request, PdoDatabase $database) |
|
| 55 | - { |
|
| 56 | - // same user! we allow people to unreserve their own stuff |
|
| 57 | - $request->setReserved(null); |
|
| 58 | - $request->setUpdateVersion(WebRequest::postInt('updateversion')); |
|
| 59 | - $request->save(); |
|
| 48 | + /** |
|
| 49 | + * @param Request $request |
|
| 50 | + * @param PdoDatabase $database |
|
| 51 | + * |
|
| 52 | + * @throws Exception |
|
| 53 | + */ |
|
| 54 | + protected function doUnreserve(Request $request, PdoDatabase $database) |
|
| 55 | + { |
|
| 56 | + // same user! we allow people to unreserve their own stuff |
|
| 57 | + $request->setReserved(null); |
|
| 58 | + $request->setUpdateVersion(WebRequest::postInt('updateversion')); |
|
| 59 | + $request->save(); |
|
| 60 | 60 | |
| 61 | - Logger::unreserve($database, $request); |
|
| 62 | - $this->getNotificationHelper()->requestUnreserved($request); |
|
| 61 | + Logger::unreserve($database, $request); |
|
| 62 | + $this->getNotificationHelper()->requestUnreserved($request); |
|
| 63 | 63 | |
| 64 | - // Redirect home! |
|
| 65 | - $this->redirect(); |
|
| 66 | - } |
|
| 64 | + // Redirect home! |
|
| 65 | + $this->redirect(); |
|
| 66 | + } |
|
| 67 | 67 | |
| 68 | - /** |
|
| 69 | - * @param Request $request |
|
| 70 | - * @param PdoDatabase $database |
|
| 71 | - * |
|
| 72 | - * @throws Exception |
|
| 73 | - */ |
|
| 74 | - protected function doBreakReserve(Request $request, PdoDatabase $database) |
|
| 75 | - { |
|
| 76 | - if (!WebRequest::postBoolean("confirm")) { |
|
| 77 | - $this->assignCSRFToken(); |
|
| 68 | + /** |
|
| 69 | + * @param Request $request |
|
| 70 | + * @param PdoDatabase $database |
|
| 71 | + * |
|
| 72 | + * @throws Exception |
|
| 73 | + */ |
|
| 74 | + protected function doBreakReserve(Request $request, PdoDatabase $database) |
|
| 75 | + { |
|
| 76 | + if (!WebRequest::postBoolean("confirm")) { |
|
| 77 | + $this->assignCSRFToken(); |
|
| 78 | 78 | |
| 79 | - $this->assign("request", $request->getId()); |
|
| 80 | - $this->assign("reservedUser", User::getById($request->getReserved(), $database)); |
|
| 81 | - $this->assign("updateversion", WebRequest::postInt('updateversion')); |
|
| 79 | + $this->assign("request", $request->getId()); |
|
| 80 | + $this->assign("reservedUser", User::getById($request->getReserved(), $database)); |
|
| 81 | + $this->assign("updateversion", WebRequest::postInt('updateversion')); |
|
| 82 | 82 | |
| 83 | - $this->skipAlerts(); |
|
| 83 | + $this->skipAlerts(); |
|
| 84 | 84 | |
| 85 | - $this->setTemplate("confirmations/breakreserve.tpl"); |
|
| 86 | - } |
|
| 87 | - else { |
|
| 88 | - $request->setReserved(null); |
|
| 89 | - $request->setUpdateVersion(WebRequest::postInt('updateversion')); |
|
| 90 | - $request->save(); |
|
| 85 | + $this->setTemplate("confirmations/breakreserve.tpl"); |
|
| 86 | + } |
|
| 87 | + else { |
|
| 88 | + $request->setReserved(null); |
|
| 89 | + $request->setUpdateVersion(WebRequest::postInt('updateversion')); |
|
| 90 | + $request->save(); |
|
| 91 | 91 | |
| 92 | - Logger::breakReserve($database, $request); |
|
| 93 | - $this->getNotificationHelper()->requestReserveBroken($request); |
|
| 92 | + Logger::breakReserve($database, $request); |
|
| 93 | + $this->getNotificationHelper()->requestReserveBroken($request); |
|
| 94 | 94 | |
| 95 | - // Redirect home! |
|
| 96 | - $this->redirect(); |
|
| 97 | - } |
|
| 98 | - } |
|
| 95 | + // Redirect home! |
|
| 96 | + $this->redirect(); |
|
| 97 | + } |
|
| 98 | + } |
|
| 99 | 99 | } |