public function processRequest(AphrontRequest $request) { $user = $request->getUser(); $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession($user, $request, '/settings/'); $min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length'); $min_len = (int) $min_len; // NOTE: To change your password, you need to prove you own the account, // either by providing the old password or by carrying a token to // the workflow from a password reset email. $key = $request->getStr('key'); $token = null; if ($key) { $token = id(new PhabricatorAuthTemporaryTokenQuery())->setViewer($user)->withObjectPHIDs(array($user->getPHID()))->withTokenTypes(array(PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE))->withTokenCodes(array(PhabricatorHash::digest($key)))->withExpired(false)->executeOne(); } $e_old = true; $e_new = true; $e_conf = true; $errors = array(); if ($request->isFormPost()) { if (!$token) { $envelope = new PhutilOpaqueEnvelope($request->getStr('old_pw')); if (!$user->comparePassword($envelope)) { $errors[] = pht('The old password you entered is incorrect.'); $e_old = pht('Invalid'); } } $pass = $request->getStr('new_pw'); $conf = $request->getStr('conf_pw'); if (strlen($pass) < $min_len) { $errors[] = pht('Your new password is too short.'); $e_new = pht('Too Short'); } else { if ($pass !== $conf) { $errors[] = pht('New password and confirmation do not match.'); $e_conf = pht('Invalid'); } else { if (PhabricatorCommonPasswords::isCommonPassword($pass)) { $e_new = pht('Very Weak'); $e_conf = pht('Very Weak'); $errors[] = pht('Your new password is very weak: it is one of the most common ' . 'passwords in use. Choose a stronger password.'); } } } if (!$errors) { // This write is unguarded because the CSRF token has already // been checked in the call to $request->isFormPost() and // the CSRF token depends on the password hash, so when it // is changed here the CSRF token check will fail. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $envelope = new PhutilOpaqueEnvelope($pass); id(new PhabricatorUserEditor())->setActor($user)->changePassword($user, $envelope); unset($unguarded); if ($token) { // Destroy the token. $token->delete(); // If this is a password set/reset, kick the user to the home page // after we update their account. $next = '/'; } else { $next = $this->getPanelURI('?saved=true'); } id(new PhabricatorAuthSessionEngine())->terminateLoginSessions($user, $request->getCookie(PhabricatorCookies::COOKIE_SESSION)); return id(new AphrontRedirectResponse())->setURI($next); } } $hash_envelope = new PhutilOpaqueEnvelope($user->getPasswordHash()); if (strlen($hash_envelope->openEnvelope())) { try { $can_upgrade = PhabricatorPasswordHasher::canUpgradeHash($hash_envelope); } catch (PhabricatorPasswordHasherUnavailableException $ex) { $can_upgrade = false; // Only show this stuff if we aren't on the reset workflow. We can // do resets regardless of the old hasher's availability. if (!$token) { $errors[] = pht('Your password is currently hashed using an algorithm which is ' . 'no longer available on this install.'); $errors[] = pht('Because the algorithm implementation is missing, your password ' . 'can not be used or updated.'); $errors[] = pht('To set a new password, request a password reset link from the ' . 'login screen and then follow the instructions.'); } } if ($can_upgrade) { $errors[] = pht('The strength of your stored password hash can be upgraded. ' . 'To upgrade, either: log out and log in using your password; or ' . 'change your password.'); } } $len_caption = null; if ($min_len) { $len_caption = pht('Minimum password length: %d characters.', $min_len); } $form = new AphrontFormView(); $form->setUser($user)->addHiddenInput('key', $key); if (!$token) { $form->appendChild(id(new AphrontFormPasswordControl())->setLabel(pht('Old Password'))->setError($e_old)->setName('old_pw')); } $form->appendChild(id(new AphrontFormPasswordControl())->setDisableAutocomplete(true)->setLabel(pht('New Password'))->setError($e_new)->setName('new_pw')); $form->appendChild(id(new AphrontFormPasswordControl())->setDisableAutocomplete(true)->setLabel(pht('Confirm Password'))->setCaption($len_caption)->setError($e_conf)->setName('conf_pw')); $form->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Change Password'))); $form->appendChild(id(new AphrontFormStaticControl())->setLabel(pht('Current Algorithm'))->setValue(PhabricatorPasswordHasher::getCurrentAlgorithmName(new PhutilOpaqueEnvelope($user->getPasswordHash())))); $form->appendChild(id(new AphrontFormStaticControl())->setLabel(pht('Best Available Algorithm'))->setValue(PhabricatorPasswordHasher::getBestAlgorithmName())); $form->appendRemarkupInstructions(pht('NOTE: Changing your password will terminate any other outstanding ' . 'login sessions.')); $form_box = id(new PHUIObjectBoxView())->setHeaderText(pht('Change Password'))->setFormSaved($request->getStr('saved'))->setFormErrors($errors)->setForm($form); return array($form_box); }
public function processRequest() { $request = $this->getRequest(); if ($request->getUser()->isLoggedIn()) { return $this->renderError(pht('You are already logged in.')); } $is_setup = false; if (strlen($this->accountKey)) { $result = $this->loadAccountForRegistrationOrLinking($this->accountKey); list($account, $provider, $response) = $result; $is_default = false; } else { if ($this->isFirstTimeSetup()) { list($account, $provider, $response) = $this->loadSetupAccount(); $is_default = true; $is_setup = true; } else { list($account, $provider, $response) = $this->loadDefaultAccount(); $is_default = true; } } if ($response) { return $response; } $invite = $this->loadInvite(); if (!$provider->shouldAllowRegistration()) { if ($invite) { // If the user has an invite, we allow them to register with any // provider, even a login-only provider. } else { // TODO: This is a routine error if you click "Login" on an external // auth source which doesn't allow registration. The error should be // more tailored. return $this->renderError(pht('The account you are attempting to register with uses an ' . 'authentication provider ("%s") which does not allow ' . 'registration. An administrator may have recently disabled ' . 'registration with this provider.', $provider->getProviderName())); } } $user = new PhabricatorUser(); $default_username = $account->getUsername(); $default_realname = $account->getRealName(); $default_email = $account->getEmail(); if ($invite) { $default_email = $invite->getEmailAddress(); } if (!PhabricatorUserEmail::isValidAddress($default_email)) { $default_email = null; } if ($default_email !== null) { // We should bypass policy here becase e.g. limiting an application use // to a subset of users should not allow the others to overwrite // configured application emails $application_email = id(new PhabricatorMetaMTAApplicationEmailQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withAddresses(array($default_email))->executeOne(); if ($application_email) { $default_email = null; } } if ($default_email !== null) { // If the account source provided an email, but it's not allowed by // the configuration, roadblock the user. Previously, we let the user // pick a valid email address instead, but this does not align well with // user expectation and it's not clear the cases it enables are valuable. // See discussion in T3472. if (!PhabricatorUserEmail::isAllowedAddress($default_email)) { return $this->renderError(array(pht('The account you are attempting to register with has an invalid ' . 'email address (%s). This Phabricator install only allows ' . 'registration with specific email addresses:', $default_email), phutil_tag('br'), phutil_tag('br'), PhabricatorUserEmail::describeAllowedAddresses())); } // If the account source provided an email, but another account already // has that email, just pretend we didn't get an email. // TODO: See T3472. if ($default_email !== null) { $same_email = id(new PhabricatorUserEmail())->loadOneWhere('address = %s', $default_email); if ($same_email) { if ($invite) { // We're allowing this to continue. The fact that we loaded the // invite means that the address is nonprimary and unverified and // we're OK to steal it. } else { $default_email = null; } } } } $profile = id(new PhabricatorRegistrationProfile())->setDefaultUsername($default_username)->setDefaultEmail($default_email)->setDefaultRealName($default_realname)->setCanEditUsername(true)->setCanEditEmail($default_email === null)->setCanEditRealName(true)->setShouldVerifyEmail(false); $event_type = PhabricatorEventType::TYPE_AUTH_WILLREGISTERUSER; $event_data = array('account' => $account, 'profile' => $profile); $event = id(new PhabricatorEvent($event_type, $event_data))->setUser($user); PhutilEventEngine::dispatchEvent($event); $default_username = $profile->getDefaultUsername(); $default_email = $profile->getDefaultEmail(); $default_realname = $profile->getDefaultRealName(); $can_edit_username = $profile->getCanEditUsername(); $can_edit_email = $profile->getCanEditEmail(); $can_edit_realname = $profile->getCanEditRealName(); $must_set_password = $provider->shouldRequireRegistrationPassword(); $can_edit_anything = $profile->getCanEditAnything() || $must_set_password; $force_verify = $profile->getShouldVerifyEmail(); // Automatically verify the administrator's email address during first-time // setup. if ($is_setup) { $force_verify = true; } $value_username = $default_username; $value_realname = $default_realname; $value_email = $default_email; $value_password = null; $errors = array(); $require_real_name = PhabricatorEnv::getEnvConfig('user.require-real-name'); $e_username = strlen($value_username) ? null : true; $e_realname = $require_real_name ? true : null; $e_email = strlen($value_email) ? null : true; $e_password = true; $e_captcha = true; $skip_captcha = false; if ($invite) { // If the user is accepting an invite, assume they're trustworthy enough // that we don't need to CAPTCHA them. $skip_captcha = true; } $min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length'); $min_len = (int) $min_len; $from_invite = $request->getStr('invite'); if ($from_invite && $can_edit_username) { $value_username = $request->getStr('username'); $e_username = null; } if (($request->isFormPost() || !$can_edit_anything) && !$from_invite) { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); if ($must_set_password && !$skip_captcha) { $e_captcha = pht('Again'); $captcha_ok = AphrontFormRecaptchaControl::processCaptcha($request); if (!$captcha_ok) { $errors[] = pht('Captcha response is incorrect, try again.'); $e_captcha = pht('Invalid'); } } if ($can_edit_username) { $value_username = $request->getStr('username'); if (!strlen($value_username)) { $e_username = pht('Required'); $errors[] = pht('Username is required.'); } else { if (!PhabricatorUser::validateUsername($value_username)) { $e_username = pht('Invalid'); $errors[] = PhabricatorUser::describeValidUsername(); } else { $e_username = null; } } } if ($must_set_password) { $value_password = $request->getStr('password'); $value_confirm = $request->getStr('confirm'); if (!strlen($value_password)) { $e_password = pht('Required'); $errors[] = pht('You must choose a password.'); } else { if ($value_password !== $value_confirm) { $e_password = pht('No Match'); $errors[] = pht('Password and confirmation must match.'); } else { if (strlen($value_password) < $min_len) { $e_password = pht('Too Short'); $errors[] = pht('Password is too short (must be at least %d characters long).', $min_len); } else { if (PhabricatorCommonPasswords::isCommonPassword($value_password)) { $e_password = pht('Very Weak'); $errors[] = pht('Password is pathologically weak. This password is one of the ' . 'most common passwords in use, and is extremely easy for ' . 'attackers to guess. You must choose a stronger password.'); } else { $e_password = null; } } } } } if ($can_edit_email) { $value_email = $request->getStr('email'); if (!strlen($value_email)) { $e_email = pht('Required'); $errors[] = pht('Email is required.'); } else { if (!PhabricatorUserEmail::isValidAddress($value_email)) { $e_email = pht('Invalid'); $errors[] = PhabricatorUserEmail::describeValidAddresses(); } else { if (!PhabricatorUserEmail::isAllowedAddress($value_email)) { $e_email = pht('Disallowed'); $errors[] = PhabricatorUserEmail::describeAllowedAddresses(); } else { $e_email = null; } } } } if ($can_edit_realname) { $value_realname = $request->getStr('realName'); if (!strlen($value_realname) && $require_real_name) { $e_realname = pht('Required'); $errors[] = pht('Real name is required.'); } else { $e_realname = null; } } if (!$errors) { $image = $this->loadProfilePicture($account); if ($image) { $user->setProfileImagePHID($image->getPHID()); } try { $verify_email = false; if ($force_verify) { $verify_email = true; } if ($value_email === $default_email) { if ($account->getEmailVerified()) { $verify_email = true; } if ($provider->shouldTrustEmails()) { $verify_email = true; } if ($invite) { $verify_email = true; } } $email_obj = null; if ($invite) { // If we have a valid invite, this email may exist but be // nonprimary and unverified, so we'll reassign it. $email_obj = id(new PhabricatorUserEmail())->loadOneWhere('address = %s', $value_email); } if (!$email_obj) { $email_obj = id(new PhabricatorUserEmail())->setAddress($value_email); } $email_obj->setIsVerified((int) $verify_email); $user->setUsername($value_username); $user->setRealname($value_realname); if ($is_setup) { $must_approve = false; } else { if ($invite) { $must_approve = false; } else { $must_approve = PhabricatorEnv::getEnvConfig('auth.require-approval'); } } if ($must_approve) { $user->setIsApproved(0); } else { $user->setIsApproved(1); } if ($invite) { $allow_reassign_email = true; } else { $allow_reassign_email = false; } $user->openTransaction(); $editor = id(new PhabricatorUserEditor())->setActor($user); $editor->createNewUser($user, $email_obj, $allow_reassign_email); if ($must_set_password) { $envelope = new PhutilOpaqueEnvelope($value_password); $editor->changePassword($user, $envelope); } if ($is_setup) { $editor->makeAdminUser($user, true); } $account->setUserPHID($user->getPHID()); $provider->willRegisterAccount($account); $account->save(); $user->saveTransaction(); if (!$email_obj->getIsVerified()) { $email_obj->sendVerificationEmail($user); } if ($must_approve) { $this->sendWaitingForApprovalEmail($user); } if ($invite) { $invite->setAcceptedByPHID($user->getPHID())->save(); } return $this->loginUser($user); } catch (AphrontDuplicateKeyQueryException $exception) { $same_username = id(new PhabricatorUser())->loadOneWhere('userName = %s', $user->getUserName()); $same_email = id(new PhabricatorUserEmail())->loadOneWhere('address = %s', $value_email); if ($same_username) { $e_username = pht('Duplicate'); $errors[] = pht('Another user already has that username.'); } if ($same_email) { // TODO: See T3340. $e_email = pht('Duplicate'); $errors[] = pht('Another user already has that email.'); } if (!$same_username && !$same_email) { throw $exception; } } } unset($unguarded); } $form = id(new AphrontFormView())->setUser($request->getUser()); if (!$is_default) { $form->appendChild(id(new AphrontFormMarkupControl())->setLabel(pht('External Account'))->setValue(id(new PhabricatorAuthAccountView())->setUser($request->getUser())->setExternalAccount($account)->setAuthProvider($provider))); } if ($can_edit_username) { $form->appendChild(id(new AphrontFormTextControl())->setLabel(pht('Phabricator Username'))->setName('username')->setValue($value_username)->setError($e_username)); } else { $form->appendChild(id(new AphrontFormMarkupControl())->setLabel(pht('Phabricator Username'))->setValue($value_username)->setError($e_username)); } if ($can_edit_realname) { $form->appendChild(id(new AphrontFormTextControl())->setLabel(pht('Real Name'))->setName('realName')->setValue($value_realname)->setError($e_realname)); } if ($must_set_password) { $form->appendChild(id(new AphrontFormPasswordControl())->setLabel(pht('Password'))->setName('password')->setError($e_password)); $form->appendChild(id(new AphrontFormPasswordControl())->setLabel(pht('Confirm Password'))->setName('confirm')->setError($e_password)->setCaption($min_len ? pht('Minimum length of %d characters.', $min_len) : null)); } if ($can_edit_email) { $form->appendChild(id(new AphrontFormTextControl())->setLabel(pht('Email'))->setName('email')->setValue($value_email)->setCaption(PhabricatorUserEmail::describeAllowedAddresses())->setError($e_email)); } if ($must_set_password && !$skip_captcha) { $form->appendChild(id(new AphrontFormRecaptchaControl())->setLabel(pht('Captcha'))->setError($e_captcha)); } $submit = id(new AphrontFormSubmitControl()); if ($is_setup) { $submit->setValue(pht('Create Admin Account')); } else { $submit->addCancelButton($this->getApplicationURI('start/'))->setValue(pht('Register Phabricator Account')); } $form->appendChild($submit); $crumbs = $this->buildApplicationCrumbs(); if ($is_setup) { $crumbs->addTextCrumb(pht('Setup Admin Account')); $title = pht('Welcome to Phabricator'); } else { $crumbs->addTextCrumb(pht('Register')); $crumbs->addTextCrumb($provider->getProviderName()); $title = pht('Phabricator Registration'); } $welcome_view = null; if ($is_setup) { $welcome_view = id(new PHUIInfoView())->setSeverity(PHUIInfoView::SEVERITY_NOTICE)->setTitle(pht('Welcome to Phabricator'))->appendChild(pht('Installation is complete. Register your administrator account ' . 'below to log in. You will be able to configure options and add ' . 'other authentication mechanisms (like LDAP or OAuth) later on.')); } $object_box = id(new PHUIObjectBoxView())->setHeaderText($title)->setForm($form)->setFormErrors($errors); $invite_header = null; if ($invite) { $invite_header = $this->renderInviteHeader($invite); } return $this->buildApplicationPage(array($crumbs, $welcome_view, $invite_header, $object_box), array('title' => $title)); }
public function processRequest(AphrontRequest $request) { $viewer = $request->getUser(); $user = $this->getUser(); $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession($viewer, $request, '/settings/'); $vcspassword = id(new PhabricatorRepositoryVCSPassword())->loadOneWhere('userPHID = %s', $user->getPHID()); if (!$vcspassword) { $vcspassword = id(new PhabricatorRepositoryVCSPassword()); $vcspassword->setUserPHID($user->getPHID()); } $panel_uri = $this->getPanelURI('?saved=true'); $errors = array(); $e_password = true; $e_confirm = true; if ($request->isFormPost()) { if ($request->getBool('remove')) { if ($vcspassword->getID()) { $vcspassword->delete(); return id(new AphrontRedirectResponse())->setURI($panel_uri); } } $new_password = $request->getStr('password'); $confirm = $request->getStr('confirm'); if (!strlen($new_password)) { $e_password = pht('Required'); $errors[] = pht('Password is required.'); } else { $e_password = null; } if (!strlen($confirm)) { $e_confirm = pht('Required'); $errors[] = pht('You must confirm the new password.'); } else { $e_confirm = null; } if (!$errors) { $envelope = new PhutilOpaqueEnvelope($new_password); try { // NOTE: This test is against $viewer (not $user), so that the error // message below makes sense in the case that the two are different, // and because an admin reusing their own password is bad, while // system agents generally do not have passwords anyway. $same_password = $viewer->comparePassword($envelope); } catch (PhabricatorPasswordHasherUnavailableException $ex) { // If we're missing the hasher, just let the user continue. $same_password = false; } if ($new_password !== $confirm) { $e_password = pht('Does Not Match'); $e_confirm = pht('Does Not Match'); $errors[] = pht('Password and confirmation do not match.'); } else { if ($same_password) { $e_password = pht('Not Unique'); $e_confirm = pht('Not Unique'); $errors[] = pht('This password is the same as another password associated ' . 'with your account. You must use a unique password for ' . 'VCS access.'); } else { if (PhabricatorCommonPasswords::isCommonPassword($new_password)) { $e_password = pht('Very Weak'); $e_confirm = pht('Very Weak'); $errors[] = pht('This password is extremely weak: it is one of the most common ' . 'passwords in use. Choose a stronger password.'); } } } if (!$errors) { $vcspassword->setPassword($envelope, $user); $vcspassword->save(); return id(new AphrontRedirectResponse())->setURI($panel_uri); } } } $title = pht('Set VCS Password'); $form = id(new AphrontFormView())->setUser($viewer)->appendRemarkupInstructions(pht('To access repositories hosted by Phabricator over HTTP, you must ' . 'set a version control password. This password should be unique.' . "\n\n" . "This password applies to all repositories available over " . "HTTP.")); if ($vcspassword->getID()) { $form->appendChild(id(new AphrontFormPasswordControl())->setDisableAutocomplete(true)->setLabel(pht('Current Password'))->setDisabled(true)->setValue('********************')); } else { $form->appendChild(id(new AphrontFormMarkupControl())->setLabel(pht('Current Password'))->setValue(phutil_tag('em', array(), pht('No Password Set')))); } $form->appendChild(id(new AphrontFormPasswordControl())->setDisableAutocomplete(true)->setName('password')->setLabel(pht('New VCS Password'))->setError($e_password))->appendChild(id(new AphrontFormPasswordControl())->setDisableAutocomplete(true)->setName('confirm')->setLabel(pht('Confirm VCS Password'))->setError($e_confirm))->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Change Password'))); if (!$vcspassword->getID()) { $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $suggest = Filesystem::readRandomBytes(128); $suggest = preg_replace('([^A-Za-z0-9/!().,;{}^&*%~])', '', $suggest); $suggest = substr($suggest, 0, 20); if ($is_serious) { $form->appendRemarkupInstructions(pht('Having trouble coming up with a good password? Try this randomly ' . 'generated one, made by a computer:' . "\n\n" . "`%s`", $suggest)); } else { $form->appendRemarkupInstructions(pht('Having trouble coming up with a good password? Try this ' . 'artisinal password, hand made in small batches by our expert ' . 'craftspeople: ' . "\n\n" . "`%s`", $suggest)); } } $hash_envelope = new PhutilOpaqueEnvelope($vcspassword->getPasswordHash()); $form->appendChild(id(new AphrontFormStaticControl())->setLabel(pht('Current Algorithm'))->setValue(PhabricatorPasswordHasher::getCurrentAlgorithmName($hash_envelope))); $form->appendChild(id(new AphrontFormStaticControl())->setLabel(pht('Best Available Algorithm'))->setValue(PhabricatorPasswordHasher::getBestAlgorithmName())); if (strlen($hash_envelope->openEnvelope())) { try { $can_upgrade = PhabricatorPasswordHasher::canUpgradeHash($hash_envelope); } catch (PhabricatorPasswordHasherUnavailableException $ex) { $can_upgrade = false; $errors[] = pht('Your VCS password is currently hashed using an algorithm which is ' . 'no longer available on this install.'); $errors[] = pht('Because the algorithm implementation is missing, your password ' . 'can not be used.'); $errors[] = pht('You can set a new password to replace the old password.'); } if ($can_upgrade) { $errors[] = pht('The strength of your stored VCS password hash can be upgraded. ' . 'To upgrade, either: use the password to authenticate with a ' . 'repository; or change your password.'); } } $object_box = id(new PHUIObjectBoxView())->setHeaderText($title)->setForm($form)->setFormErrors($errors); $remove_form = id(new AphrontFormView())->setUser($viewer); if ($vcspassword->getID()) { $remove_form->addHiddenInput('remove', true)->appendRemarkupInstructions(pht('You can remove your VCS password, which will prevent your ' . 'account from accessing repositories.'))->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Remove Password'))); } else { $remove_form->appendRemarkupInstructions(pht('You do not currently have a VCS password set. If you set one, you ' . 'can remove it here later.')); } $remove_box = id(new PHUIObjectBoxView())->setHeaderText(pht('Remove VCS Password'))->setForm($remove_form); $saved = null; if ($request->getBool('saved')) { $saved = id(new PHUIInfoView())->setSeverity(PHUIInfoView::SEVERITY_NOTICE)->setTitle(pht('Password Updated'))->appendChild(pht('Your VCS password has been updated.')); } return array($saved, $object_box, $remove_box); }