Path: blob/master/src/applications/auth/controller/PhabricatorAuthRegisterController.php
13401 views
<?php12final class PhabricatorAuthRegisterController3extends PhabricatorAuthController {45public function shouldRequireLogin() {6return false;7}89private function emailToUsername($email) {10$mangled_email = str_ireplace('@freebsd.org', '', $email);11$mangled_email = str_replace('@', '_', $mangled_email);12$mangled_email = str_replace('+', '_', $mangled_email);13return $mangled_email;14}1516public function handleRequest(AphrontRequest $request) {17$viewer = $this->getViewer();18$account_key = $request->getURIData('akey');1920if ($viewer->isLoggedIn()) {21return id(new AphrontRedirectResponse())->setURI('/');22}2324$invite = $this->loadInvite();2526$is_setup = false;27if ($account_key !== null && strlen($account_key)) {28$result = $this->loadAccountForRegistrationOrLinking($account_key);29list($account, $provider, $response) = $result;30$is_default = false;31} else if ($this->isFirstTimeSetup()) {32$account = null;33$provider = null;34$response = null;35$is_default = true;36$is_setup = true;37} else {38list($account, $provider, $response) = $this->loadDefaultAccount($invite);39$is_default = true;40}4142if ($response) {43return $response;44}4546if (!$is_setup) {47if (!$provider->shouldAllowRegistration()) {48if ($invite) {49// If the user has an invite, we allow them to register with any50// provider, even a login-only provider.51} else {52// TODO: This is a routine error if you click "Login" on an external53// auth source which doesn't allow registration. The error should be54// more tailored.5556return $this->renderError(57pht(58'The account you are attempting to register with uses an '.59'authentication provider ("%s") which does not allow '.60'registration. An administrator may have recently disabled '.61'registration with this provider.',62$provider->getProviderName()));63}64}65}6667$errors = array();6869$user = new PhabricatorUser();7071if ($is_setup) {72$default_username = null;73$default_realname = null;74$default_email = null;75} else {76$default_realname = $account->getRealName();77$default_email = $account->getEmail();78$fbsd_username = $this->emailToUsername($default_email);79}8081$account_type = PhabricatorAuthPassword::PASSWORD_TYPE_ACCOUNT;82$content_source = PhabricatorContentSource::newFromRequest($request);8384if ($invite) {85$default_email = $invite->getEmailAddress();86}8788if ($default_email !== null) {89if (!PhabricatorUserEmail::isValidAddress($default_email)) {90$errors[] = pht(91'The email address associated with this external account ("%s") is '.92'not a valid email address and can not be used to register an '.93'account. Choose a different, valid address.',94phutil_tag('strong', array(), $default_email));95$default_email = null;96}97}9899if ($default_email !== null) {100// We should bypass policy here because e.g. limiting an application use101// to a subset of users should not allow the others to overwrite102// configured application emails.103$application_email = id(new PhabricatorMetaMTAApplicationEmailQuery())104->setViewer(PhabricatorUser::getOmnipotentUser())105->withAddresses(array($default_email))106->executeOne();107if ($application_email) {108$errors[] = pht(109'The email address associated with this account ("%s") is '.110'already in use by an application and can not be used to '.111'register a new account. Choose a different, valid address.',112phutil_tag('strong', array(), $default_email));113$default_email = null;114}115}116117$show_existing = null;118if ($default_email !== null) {119// If the account source provided an email, but it's not allowed by120// the configuration, roadblock the user. Previously, we let the user121// pick a valid email address instead, but this does not align well with122// user expectation and it's not clear the cases it enables are valuable.123// See discussion in T3472.124if (!PhabricatorUserEmail::isAllowedAddress($default_email)) {125$debug_email = new PHUIInvisibleCharacterView($default_email);126return $this->renderError(127array(128pht(129'The account you are attempting to register with has an invalid '.130'email address (%s). This server only allows registration with '.131'specific email addresses:',132$debug_email),133phutil_tag('br'),134phutil_tag('br'),135PhabricatorUserEmail::describeAllowedAddresses(),136));137}138139// If the account source provided an email, but another account already140// has that email, just pretend we didn't get an email.141if ($default_email !== null) {142$same_email = id(new PhabricatorUserEmail())->loadOneWhere(143'address = %s',144$default_email);145if ($same_email) {146if ($invite) {147// We're allowing this to continue. The fact that we loaded the148// invite means that the address is nonprimary and unverified and149// we're OK to steal it.150} else {151$show_existing = $default_email;152$default_email = null;153}154}155}156}157158if ($show_existing !== null) {159if (!$request->getInt('phase')) {160return $this->newDialog()161->setTitle(pht('Email Address Already in Use'))162->addHiddenInput('phase', 1)163->appendParagraph(164pht(165'You are creating a new account linked to an existing '.166'external account.'))167->appendParagraph(168pht(169'The email address ("%s") associated with the external account '.170'is already in use by an existing %s account. Multiple '.171'%s accounts may not have the same email address, so '.172'you can not use this email address to register a new account.',173phutil_tag('strong', array(), $show_existing),174PlatformSymbols::getPlatformServerName(),175PlatformSymbols::getPlatformServerName()))176->appendParagraph(177pht(178'If you want to register a new account, continue with this '.179'registration workflow and choose a new, unique email address '.180'for the new account.'))181->appendParagraph(182pht(183'If you want to link an existing %s account to this '.184'external account, do not continue. Instead: log in to your '.185'existing account, then go to "Settings" and link the account '.186'in the "External Accounts" panel.',187PlatformSymbols::getPlatformServerName()))188->appendParagraph(189pht(190'If you continue, you will create a new account. You will not '.191'be able to link this external account to an existing account.'))192->addCancelButton('/auth/login/', pht('Cancel'))193->addSubmitButton(pht('Create New Account'));194} else {195$errors[] = pht(196'The external account you are registering with has an email address '.197'that is already in use ("%s") by an existing %s account. '.198'Choose a new, valid email address to register a new account.',199phutil_tag('strong', array(), $show_existing),200PlatformSymbols::getPlatformServerName());201}202}203204$profile = id(new PhabricatorRegistrationProfile())205->setDefaultUsername($fbsd_username)206->setDefaultEmail($default_email)207->setDefaultRealName($default_realname)208->setCanEditUsername(true)209->setCanEditEmail(($default_email === null))210->setCanEditRealName(true)211->setShouldVerifyEmail(false);212213$event_type = PhabricatorEventType::TYPE_AUTH_WILLREGISTERUSER;214$event_data = array(215'account' => $account,216'profile' => $profile,217);218219$event = id(new PhabricatorEvent($event_type, $event_data))220->setUser($user);221PhutilEventEngine::dispatchEvent($event);222223$fbsd_username = $profile->getDefaultUsername();224$default_email = $profile->getDefaultEmail();225$default_realname = $profile->getDefaultRealName();226227$can_edit_username = false;228$can_edit_email = $profile->getCanEditEmail();229$can_edit_realname = $profile->getCanEditRealName();230231if ($is_setup) {232$must_set_password = false;233} else {234$must_set_password = $provider->shouldRequireRegistrationPassword();235}236237$can_edit_anything = $profile->getCanEditAnything() || $must_set_password;238$force_verify = $profile->getShouldVerifyEmail();239240// Automatically verify the administrator's email address during first-time241// setup.242if ($is_setup) {243$force_verify = true;244}245246$value_username = $fbsd_username;247$value_realname = $default_realname;248$value_email = $default_email;249$value_password = null;250251$require_real_name = PhabricatorEnv::getEnvConfig('user.require-real-name');252253$e_username = phutil_nonempty_string($value_username) ? null : true;254$e_realname = $require_real_name ? true : null;255$e_email = phutil_nonempty_string($value_email) ? null : true;256$e_password = true;257$e_captcha = true;258259$skip_captcha = false;260if ($invite) {261// If the user is accepting an invite, assume they're trustworthy enough262// that we don't need to CAPTCHA them.263$skip_captcha = true;264}265266$min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length');267$min_len = (int)$min_len;268269$from_invite = $request->getStr('invite');270if ($from_invite && $can_edit_username) {271$value_username = $request->getStr('username');272$e_username = null;273}274275$try_register =276($request->isFormPost() || !$can_edit_anything) &&277!$from_invite &&278($request->getInt('phase') != 1);279280if ($try_register) {281$errors = array();282283$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();284285if ($must_set_password && !$skip_captcha) {286$e_captcha = pht('Again');287288$captcha_ok = AphrontFormRecaptchaControl::processCaptcha($request);289if (!$captcha_ok) {290$errors[] = pht('Captcha response is incorrect, try again.');291$e_captcha = pht('Invalid');292}293}294295if ($can_edit_username) {296$value_username = $request->getStr('username');297if (!phutil_nonempty_string($value_username)) {298$e_username = pht('Required');299$errors[] = pht('Username is required.');300} else if (!PhabricatorUser::validateUsername($value_username)) {301$e_username = pht('Invalid');302$errors[] = PhabricatorUser::describeValidUsername();303} else {304$e_username = null;305}306}307308if ($must_set_password) {309$value_password = $request->getStr('password');310$value_confirm = $request->getStr('confirm');311312$password_envelope = new PhutilOpaqueEnvelope($value_password);313$confirm_envelope = new PhutilOpaqueEnvelope($value_confirm);314315$engine = id(new PhabricatorAuthPasswordEngine())316->setViewer($user)317->setContentSource($content_source)318->setPasswordType($account_type)319->setObject($user);320321try {322$engine->checkNewPassword($password_envelope, $confirm_envelope);323$e_password = null;324} catch (PhabricatorAuthPasswordException $ex) {325$errors[] = $ex->getMessage();326$e_password = $ex->getPasswordError();327}328}329330if ($can_edit_email) {331$value_email = $request->getStr('email');332if (!phutil_nonempty_string($value_email)) {333$e_email = pht('Required');334$errors[] = pht('Email is required.');335} else if (!PhabricatorUserEmail::isValidAddress($value_email)) {336$e_email = pht('Invalid');337$errors[] = PhabricatorUserEmail::describeValidAddresses();338} else if (!PhabricatorUserEmail::isAllowedAddress($value_email)) {339$e_email = pht('Disallowed');340$errors[] = PhabricatorUserEmail::describeAllowedAddresses();341} else {342$e_email = null;343}344}345346if ($account->getAccountType() != 'ldap') {347$value_username = $this->emailToUsername($value_email);348}349350if ($can_edit_realname) {351$value_realname = $request->getStr('realName');352if (!phutil_nonempty_string($value_realname) && $require_real_name) {353$e_realname = pht('Required');354$errors[] = pht('Real name is required.');355} else {356$e_realname = null;357}358}359360if (!$errors) {361if (!$is_setup) {362$image = $this->loadProfilePicture($account);363if ($image) {364$user->setProfileImagePHID($image->getPHID());365}366}367368try {369$verify_email = false;370371if ($force_verify) {372$verify_email = true;373}374375if (!$is_setup) {376if ($value_email === $default_email) {377if ($account->getEmailVerified()) {378$verify_email = true;379}380381if ($provider->shouldTrustEmails()) {382$verify_email = true;383}384385if ($invite) {386$verify_email = true;387}388}389}390391$email_obj = null;392if ($invite) {393// If we have a valid invite, this email may exist but be394// nonprimary and unverified, so we'll reassign it.395$email_obj = id(new PhabricatorUserEmail())->loadOneWhere(396'address = %s',397$value_email);398}399if (!$email_obj) {400$email_obj = id(new PhabricatorUserEmail())401->setAddress($value_email);402}403404$email_obj->setIsVerified((int)$verify_email);405406$user->setUsername($value_username);407$user->setRealname($value_realname);408409if ($is_setup) {410$must_approve = false;411} else if ($invite) {412$must_approve = false;413} else {414$must_approve = PhabricatorEnv::getEnvConfig(415'auth.require-approval');416}417418if ($must_approve) {419$user->setIsApproved(0);420} else {421$user->setIsApproved(1);422}423424if ($invite) {425$allow_reassign_email = true;426} else {427$allow_reassign_email = false;428}429430$user->openTransaction();431432$editor = id(new PhabricatorUserEditor())433->setActor($user);434435$editor->createNewUser($user, $email_obj, $allow_reassign_email);436if ($must_set_password) {437$password_object = PhabricatorAuthPassword::initializeNewPassword(438$user,439$account_type);440441$password_object442->setPassword($password_envelope, $user)443->save();444}445446if ($is_setup) {447$xactions = array();448$xactions[] = id(new PhabricatorUserTransaction())449->setTransactionType(450PhabricatorUserEmpowerTransaction::TRANSACTIONTYPE)451->setNewValue(true);452453$actor = PhabricatorUser::getOmnipotentUser();454$content_source = PhabricatorContentSource::newFromRequest(455$request);456457$people_application_phid = id(new PhabricatorPeopleApplication())458->getPHID();459460$transaction_editor = id(new PhabricatorUserTransactionEditor())461->setActor($actor)462->setActingAsPHID($people_application_phid)463->setContentSource($content_source)464->setContinueOnMissingFields(true);465466$transaction_editor->applyTransactions($user, $xactions);467}468469if (!$is_setup) {470$account->setUserPHID($user->getPHID());471$account->save();472}473474$user->saveTransaction();475476if (!$email_obj->getIsVerified()) {477$email_obj->sendVerificationEmail($user);478}479480if ($must_approve) {481$this->sendWaitingForApprovalEmail($user);482}483484if ($invite) {485$invite->setAcceptedByPHID($user->getPHID())->save();486}487488return $this->loginUser($user);489} catch (AphrontDuplicateKeyQueryException $exception) {490$same_username = id(new PhabricatorUser())->loadOneWhere(491'userName = %s',492$user->getUserName());493494$same_email = id(new PhabricatorUserEmail())->loadOneWhere(495'address = %s',496$value_email);497498if ($same_username) {499$e_username = pht('Duplicate');500$errors[] = pht('Another user already has that username.');501}502503if ($same_email) {504// TODO: See T3340.505$e_email = pht('Duplicate');506$errors[] = pht('Another user already has that email.');507}508509if (!$same_username && !$same_email) {510throw $exception;511}512}513}514515unset($unguarded);516}517518$form = id(new AphrontFormView())519->setUser($request->getUser())520->addHiddenInput('phase', 2);521522if (!$is_default) {523$form->appendChild(524id(new AphrontFormMarkupControl())525->setLabel(pht('External Account'))526->setValue(527id(new PhabricatorAuthAccountView())528->setUser($request->getUser())529->setExternalAccount($account)530->setAuthProvider($provider)));531}532533if ($can_edit_username) {534$form->appendChild(535id(new AphrontFormTextControl())536->setLabel(pht('Username'))537->setName('username')538->setValue($value_username));539} else {540$form->appendChild(541id(new AphrontFormMarkupControl())542->setLabel(pht('Username'))543->setValue('automatically set based on email address'));544}545546if ($can_edit_realname) {547$form->appendChild(548id(new AphrontFormTextControl())549->setLabel(pht('Real Name'))550->setName('realName')551->setValue($value_realname)552->setError($e_realname));553}554555if ($must_set_password) {556$form->appendChild(557id(new AphrontFormPasswordControl())558->setLabel(pht('Password'))559->setName('password')560->setError($e_password));561$form->appendChild(562id(new AphrontFormPasswordControl())563->setLabel(pht('Confirm Password'))564->setName('confirm')565->setError($e_password)566->setCaption(567$min_len568? pht('Minimum length of %d characters.', $min_len)569: null));570}571572if ($can_edit_email) {573$form->appendChild(574id(new AphrontFormTextControl())575->setLabel(pht('Email'))576->setName('email')577->setValue($value_email)578->setCaption(pht('public information'))579->setError($e_email));580}581582if ($must_set_password && !$skip_captcha) {583$form->appendChild(584id(new AphrontFormRecaptchaControl())585->setLabel(pht('Captcha'))586->setError($e_captcha));587}588589$submit = id(new AphrontFormSubmitControl());590591if ($is_setup) {592$submit593->setValue(pht('Create Admin Account'));594} else {595$submit596->addCancelButton($this->getApplicationURI('start/'))597->setValue(pht('Register Account'));598}599600601$form->appendChild($submit);602603$crumbs = $this->buildApplicationCrumbs();604605if ($is_setup) {606$crumbs->addTextCrumb(pht('Setup Admin Account'));607$title = pht(608'Welcome to %s',609PlatformSymbols::getPlatformServerName());610} else {611$crumbs->addTextCrumb(pht('Register'));612$crumbs->addTextCrumb($provider->getProviderName());613$title = pht('Create a New Account');614}615$crumbs->setBorder(true);616617$welcome_view = null;618if ($is_setup) {619$welcome_view = id(new PHUIInfoView())620->setSeverity(PHUIInfoView::SEVERITY_NOTICE)621->setTitle(622pht(623'Welcome to %s',624PlatformSymbols::getPlatformServerName()))625->appendChild(626pht(627'Installation is complete. Register your administrator account '.628'below to log in. You will be able to configure options and add '.629'authentication mechanisms later on.'));630}631632$freebsd_view = id(new PHUIInfoView())633->setSeverity(PHUIInfoView::SEVERITY_NOTICE)634->setTitle(pht('Note to new registration'))635->appendChild(636pht(637'Due to spam, after creating new account, please send an email to '.638'<phabric-admin AT FreeBSD.org> from the registered email address '.639'and briefly describe your plan for using your account, '.640'in order to have it activated.'));641642$object_box = id(new PHUIObjectBoxView())643->setForm($form)644->setFormErrors($errors);645646$invite_header = null;647if ($invite) {648$invite_header = $this->renderInviteHeader($invite);649}650651$header = id(new PHUIHeaderView())652->setHeader($title);653654$view = id(new PHUITwoColumnView())655->setHeader($header)656->setFooter(657array(658$welcome_view,659$freebsd_view,660$invite_header,661$object_box,662));663664return $this->newPage()665->setTitle($title)666->setCrumbs($crumbs)667->appendChild($view);668}669670private function loadDefaultAccount($invite) {671$providers = PhabricatorAuthProvider::getAllEnabledProviders();672$account = null;673$provider = null;674$response = null;675676foreach ($providers as $key => $candidate_provider) {677if (!$invite) {678if (!$candidate_provider->shouldAllowRegistration()) {679unset($providers[$key]);680continue;681}682}683684if (!$candidate_provider->isDefaultRegistrationProvider()) {685unset($providers[$key]);686}687}688689if (!$providers) {690$response = $this->renderError(691pht(692'There are no configured default registration providers.'));693return array($account, $provider, $response);694} else if (count($providers) > 1) {695$response = $this->renderError(696pht('There are too many configured default registration providers.'));697return array($account, $provider, $response);698}699700$provider = head($providers);701$account = $provider->newDefaultExternalAccount();702703return array($account, $provider, $response);704}705706private function loadProfilePicture(PhabricatorExternalAccount $account) {707$phid = $account->getProfileImagePHID();708if (!$phid) {709return null;710}711712// NOTE: Use of omnipotent user is okay here because the registering user713// can not control the field value, and we can't use their user object to714// do meaningful policy checks anyway since they have not registered yet.715// Reaching this means the user holds the account secret key and the716// registration secret key, and thus has permission to view the image.717718$file = id(new PhabricatorFileQuery())719->setViewer(PhabricatorUser::getOmnipotentUser())720->withPHIDs(array($phid))721->executeOne();722if (!$file) {723return null;724}725726$xform = PhabricatorFileTransform::getTransformByKey(727PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE);728return $xform->executeTransform($file);729}730731protected function renderError($message) {732return $this->renderErrorPage(733pht('Registration Failed'),734array($message));735}736737private function sendWaitingForApprovalEmail(PhabricatorUser $user) {738$title = pht(739'[%s] New User "%s" Awaiting Approval',740PlatformSymbols::getPlatformServerName(),741$user->getUsername());742743$body = new PhabricatorMetaMTAMailBody();744745$body->addRawSection(746pht(747'Newly registered user "%s" is awaiting account approval by an '.748'administrator.',749$user->getUsername()));750751$body->addLinkSection(752pht('APPROVAL QUEUE'),753PhabricatorEnv::getProductionURI(754'/people/query/approval/'));755756$body->addLinkSection(757pht('DISABLE APPROVAL QUEUE'),758PhabricatorEnv::getProductionURI(759'/config/edit/auth.require-approval/'));760761$admins = id(new PhabricatorPeopleQuery())762->setViewer(PhabricatorUser::getOmnipotentUser())763->withIsAdmin(true)764->execute();765766if (!$admins) {767return;768}769770$mail = id(new PhabricatorMetaMTAMail())771->addCCs(mpull($admins, 'getPHID'))772->setSubject($title)773->setBody($body->render())774->saveAndSend();775}776777}778779780