'.$value.''; return $value; } } diff --git a/src/applications/conduit/controller/api/__init__.php b/src/applications/conduit/controller/api/__init__.php index b2bd93c152..c48c2fad65 100644 --- a/src/applications/conduit/controller/api/__init__.php +++ b/src/applications/conduit/controller/api/__init__.php @@ -1,32 +1,32 @@ id = idx($data, 'id'); $this->view = idx($data, 'view'); } public function processRequest() { $request = $this->getRequest(); $admin = $request->getUser(); if ($this->id) { $user = id(new PhabricatorUser())->load($this->id); if (!$user) { return new Aphront404Response(); } } else { $user = new PhabricatorUser(); } $views = array( 'basic' => 'Basic Information', 'role' => 'Edit Roles', 'cert' => 'Conduit Certificate', ); if (!$user->getID()) { $view = 'basic'; } else if (isset($views[$this->view])) { $view = $this->view; } else { $view = 'basic'; } $content = array(); if ($request->getStr('saved')) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $notice->setTitle('Changes Saved'); $notice->appendChild('
Your changes were saved.
'); $content[] = $notice; } switch ($view) { case 'basic': $response = $this->processBasicRequest($user); break; case 'role': $response = $this->processRoleRequest($user); break; case 'cert': $response = $this->processCertificateRequest($user); break; } if ($response instanceof AphrontResponse) { return $response; } $content[] = $response; if ($user->getID()) { $side_nav = new AphrontSideNavView(); $side_nav->appendChild($content); foreach ($views as $key => $name) { $side_nav->addNavItem( phutil_render_tag( 'a', array( 'href' => '/people/edit/'.$user->getID().'/'.$key.'/', 'class' => ($key == $view) ? 'aphront-side-nav-selected' : null, ), phutil_escape_html($name))); } $content = $side_nav; } return $this->buildStandardPageResponse( $content, array( 'title' => 'Edit User', )); } private function processBasicRequest(PhabricatorUser $user) { $request = $this->getRequest(); $admin = $request->getUser(); $e_username = true; $e_realname = true; $e_email = true; $errors = array(); $welcome_checked = true; $new_email = null; $request = $this->getRequest(); if ($request->isFormPost()) { $welcome_checked = $request->getInt('welcome'); if (!$user->getID()) { $user->setUsername($request->getStr('username')); $new_email = $request->getStr('email'); if (!strlen($new_email)) { $errors[] = 'Email is required.'; $e_email = 'Required'; + } else if (!PhabricatorUserEmail::isAllowedAddress($new_email)) { + $e_email = 'Invalid'; + $errors[] = PhabricatorUserEmail::describeAllowedAddresses(); } if ($request->getStr('role') == 'agent') { $user->setIsSystemAgent(true); } } $user->setRealName($request->getStr('realname')); if (!strlen($user->getUsername())) { $errors[] = "Username is required."; $e_username = 'Required'; } else if (!PhabricatorUser::validateUsername($user->getUsername())) { $errors[] = "Username must consist of only numbers and letters."; $e_username = 'Invalid'; } else { $e_username = null; } if (!strlen($user->getRealName())) { $errors[] = 'Real name is required.'; $e_realname = 'Required'; } else { $e_realname = null; } if (!$errors) { try { $is_new = !$user->getID(); if (!$is_new) { id(new PhabricatorUserEditor()) ->setActor($admin) ->updateUser($user); } else { $email = id(new PhabricatorUserEmail()) ->setAddress($new_email) ->setIsVerified(0); id(new PhabricatorUserEditor()) ->setActor($admin) ->createNewUser($user, $email); if ($welcome_checked) { $user->sendWelcomeEmail($admin); } } $response = id(new AphrontRedirectResponse()) ->setURI('/people/edit/'.$user->getID().'/?saved=true'); return $response; } catch (AphrontQueryDuplicateKeyException $ex) { $errors[] = 'Username and email must be unique.'; $same_username = id(new PhabricatorUser()) ->loadOneWhere('username = %s', $user->getUsername()); $same_email = id(new PhabricatorUserEmail()) ->loadOneWhere('address = %s', $new_email); if ($same_username) { $e_username = 'Duplicate'; } if ($same_email) { $e_email = 'Duplicate'; } } } } $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle('Form Errors') ->setErrors($errors); } $form = new AphrontFormView(); $form->setUser($admin); if ($user->getID()) { $form->setAction('/people/edit/'.$user->getID().'/'); } else { $form->setAction('/people/edit/'); } if ($user->getID()) { $is_immutable = true; } else { $is_immutable = false; } $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Username') ->setName('username') ->setValue($user->getUsername()) ->setError($e_username) ->setDisabled($is_immutable) ->setCaption('Usernames are permanent and can not be changed later!')) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Real Name') ->setName('realname') ->setValue($user->getRealName()) ->setError($e_realname)); if (!$user->getID()) { $form->appendChild( id(new AphrontFormTextControl()) ->setLabel('Email') ->setName('email') ->setDisabled($is_immutable) ->setValue($new_email) + ->setCaption(PhabricatorUserEmail::describeAllowedAddresses()) ->setError($e_email)); } else { $email = $user->loadPrimaryEmail(); if ($email) { $status = $email->getIsVerified() ? 'Verified' : 'Unverified'; } else { $status = 'No Email Address'; } $form->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Email') ->setValue($status)); } $form->appendChild($this->getRoleInstructions()); if (!$user->getID()) { $form ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Role') ->setName('role') ->setValue('user') ->setOptions( array( 'user' => 'Normal User', 'agent' => 'System Agent', )) ->setCaption( 'You can create a "system agent" account for bots, scripts, '. 'etc.')) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'welcome', 1, 'Send "Welcome to Phabricator" email.', $welcome_checked)); } else { $roles = array(); if ($user->getIsSystemAgent()) { $roles[] = 'System Agent'; } if ($user->getIsAdmin()) { $roles[] = 'Admin'; } if ($user->getIsDisabled()) { $roles[] = 'Disabled'; } if (!$roles) { $roles[] = 'Normal User'; } $roles = implode(', ', $roles); $form->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Roles') ->setValue($roles)); } $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save')); $panel = new AphrontPanelView(); if ($user->getID()) { $panel->setHeader('Edit User'); } else { $panel->setHeader('Create New User'); } $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_FORM); return array($error_view, $panel); } private function processRoleRequest(PhabricatorUser $user) { $request = $this->getRequest(); $admin = $request->getUser(); $is_self = ($user->getID() == $admin->getID()); $errors = array(); if ($request->isFormPost()) { $log_template = PhabricatorUserLog::newLog( $admin, $user, null); $logs = array(); if ($is_self) { $errors[] = "You can not edit your own role."; } else { $new_admin = (bool)$request->getBool('is_admin'); $old_admin = (bool)$user->getIsAdmin(); if ($new_admin != $old_admin) { id(new PhabricatorUserEditor()) ->setActor($admin) ->makeAdminUser($user, $new_admin); } $new_disabled = (bool)$request->getBool('is_disabled'); $old_disabled = (bool)$user->getIsDisabled(); if ($new_disabled != $old_disabled) { id(new PhabricatorUserEditor()) ->setActor($admin) ->disableUser($user, $new_disabled); } } if (!$errors) { return id(new AphrontRedirectResponse()) ->setURI($request->getRequestURI()->alter('saved', 'true')); } } $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle('Form Errors') ->setErrors($errors); } $form = id(new AphrontFormView()) ->setUser($admin) ->setAction($request->getRequestURI()->alter('saved', null)); if ($is_self) { $form->appendChild( 'NOTE: You can not edit your own '. 'role.
'); } $form ->appendChild($this->getRoleInstructions()) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'is_admin', 1, 'Administrator', $user->getIsAdmin()) ->setDisabled($is_self)) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'is_disabled', 1, 'Disabled', $user->getIsDisabled()) ->setDisabled($is_self)) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'is_agent', 1, 'System Agent (Bot/Script User)', $user->getIsSystemAgent()) ->setDisabled(true)); if (!$is_self) { $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Edit Role')); } $panel = new AphrontPanelView(); $panel->setHeader('Edit Role'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); return array($error_view, $panel); } private function processCertificateRequest($user) { $request = $this->getRequest(); $admin = $request->getUser(); $form = new AphrontFormView(); $form ->setUser($admin) ->setAction($request->getRequestURI()) ->appendChild( 'You can use this certificate '. 'to write scripts or bots which interface with Phabricator over '. 'Conduit.
'); if ($user->getIsSystemAgent()) { $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Username') ->setValue($user->getUsername())) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Certificate') ->setValue($user->getConduitCertificate())); } else { $form->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Certificate') ->setValue( 'You may only view the certificates of System Agents.')); } $panel = new AphrontPanelView(); $panel->setHeader('Conduit Certificate'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); return array($panel); } private function getRoleInstructions() { $roles_link = phutil_render_tag( 'a', array( 'href' => PhabricatorEnv::getDoclink( 'article/User_Guide_Account_Roles.html'), 'target' => '_blank', ), 'User Guide: Account Roles'); return ''. 'For a detailed explanation of account roles, see '. $roles_link.'.'. '
'; } } diff --git a/src/applications/people/controller/settings/panels/email/PhabricatorUserEmailSettingsPanelController.php b/src/applications/people/controller/settings/panels/email/PhabricatorUserEmailSettingsPanelController.php index 362363b1d6..e3af109929 100644 --- a/src/applications/people/controller/settings/panels/email/PhabricatorUserEmailSettingsPanelController.php +++ b/src/applications/people/controller/settings/panels/email/PhabricatorUserEmailSettingsPanelController.php @@ -1,344 +1,348 @@ getRequest(); $user = $request->getUser(); $editable = $this->getAccountEditable(); $uri = $request->getRequestURI(); $uri->setQueryParams(array()); if ($editable) { $new = $request->getStr('new'); if ($new) { return $this->returnNewAddressResponse($uri, $new); } $delete = $request->getInt('delete'); if ($delete) { return $this->returnDeleteAddressResponse($uri, $delete); } } $verify = $request->getInt('verify'); if ($verify) { return $this->returnVerifyAddressResponse($uri, $verify); } $primary = $request->getInt('primary'); if ($primary) { return $this->returnPrimaryAddressResponse($uri, $primary); } $emails = id(new PhabricatorUserEmail())->loadAllWhere( 'userPHID = %s ORDER BY address', $user->getPHID()); $rowc = array(); $rows = array(); foreach ($emails as $email) { $button_verify = javelin_render_tag( 'a', array( 'class' => 'button small grey', 'href' => $uri->alter('verify', $email->getID()), 'sigil' => 'workflow', ), 'Verify'); $button_make_primary = javelin_render_tag( 'a', array( 'class' => 'button small grey', 'href' => $uri->alter('primary', $email->getID()), 'sigil' => 'workflow', ), 'Make Primary'); $button_remove = javelin_render_tag( 'a', array( 'class' => 'button small grey', 'href' => $uri->alter('delete', $email->getID()), 'sigil' => 'workflow' ), 'Remove'); $button_primary = phutil_render_tag( 'a', array( 'class' => 'button small disabled', ), 'Primary'); if (!$email->getIsVerified()) { $action = $button_verify; } else if ($email->getIsPrimary()) { $action = $button_primary; } else { $action = $button_make_primary; } if ($email->getIsPrimary()) { $remove = $button_primary; $rowc[] = 'highlighted'; } else { $remove = $button_remove; $rowc[] = null; } $rows[] = array( phutil_escape_html($email->getAddress()), $action, $remove, ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Email', 'Status', 'Remove', )); $table->setColumnClasses( array( 'wide', 'action', 'action', )); $table->setRowClasses($rowc); $table->setColumnVisibility( array( true, true, $editable, )); $view = new AphrontPanelView(); if ($editable) { $view->addButton( javelin_render_tag( 'a', array( 'href' => $uri->alter('new', 'true'), 'class' => 'green button', 'sigil' => 'workflow', ), 'Add New Address')); } $view->setHeader('Email Addresses'); $view->appendChild($table); return $view; } private function returnNewAddressResponse(PhutilURI $uri, $new) { $request = $this->getRequest(); $user = $request->getUser(); $e_email = true; $email = trim($request->getStr('email')); $errors = array(); if ($request->isDialogFormPost()) { if ($new == 'verify') { // The user clicked "Done" from the "an email has been sent" dialog. return id(new AphrontReloadResponse())->setURI($uri); } if (!strlen($email)) { $e_email = 'Required'; $errors[] = 'Email is required.'; + } else if (!PhabricatorUserEmail::isAllowedAddress($email)) { + $e_email = 'Invalid'; + $errors[] = PhabricatorUserEmail::describeAllowedAddresses(); } if (!$errors) { $object = id(new PhabricatorUserEmail()) ->setAddress($email) ->setIsVerified(0); try { id(new PhabricatorUserEditor()) ->setActor($user) ->addEmail($user, $object); $object->sendVerificationEmail($user); $dialog = id(new AphrontDialogView()) ->setUser($user) ->addHiddenInput('new', 'verify') ->setTitle('Verification Email Sent') ->appendChild( 'A verification email has been sent. Click the link in the '. 'email to verify your address.
') ->setSubmitURI($uri) ->addSubmitButton('Done'); return id(new AphrontDialogResponse())->setDialog($dialog); } catch (AphrontQueryDuplicateKeyException $ex) { $email = 'Duplicate'; $errors[] = 'Another user already has this email.'; } } } if ($errors) { $errors = id(new AphrontErrorView()) ->setWidth(AphrontErrorView::WIDTH_DIALOG) ->setErrors($errors); } $form = id(new AphrontFormLayoutView()) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Email') ->setName('email') ->setValue($request->getStr('email')) + ->setCaption(PhabricatorUserEmail::describeAllowedAddresses()) ->setError($e_email)); $dialog = id(new AphrontDialogView()) ->setUser($user) ->addHiddenInput('new', 'true') ->setTitle('New Address') ->appendChild($errors) ->appendChild($form) ->addSubmitButton('Save') ->addCancelButton($uri); return id(new AphrontDialogResponse())->setDialog($dialog); } private function returnDeleteAddressResponse(PhutilURI $uri, $email_id) { $request = $this->getRequest(); $user = $request->getUser(); // NOTE: You can only delete your own email addresses, and you can not // delete your primary address. $email = id(new PhabricatorUserEmail())->loadOneWhere( 'id = %d AND userPHID = %s AND isPrimary = 0', $email_id, $user->getPHID()); if (!$email) { return new Aphront404Response(); } if ($request->isFormPost()) { id(new PhabricatorUserEditor()) ->setActor($user) ->removeEmail($user, $email); return id(new AphrontRedirectResponse())->setURI($uri); } $address = $email->getAddress(); $dialog = id(new AphrontDialogView()) ->setUser($user) ->addHiddenInput('delete', $email_id) ->setTitle("Really delete address '{$address}'?") ->appendChild( 'Are you sure you want to delete this address? You will no '. 'longer be able to use it to login.
') ->addSubmitButton('Delete') ->addCancelButton($uri); return id(new AphrontDialogResponse())->setDialog($dialog); } private function returnVerifyAddressResponse(PhutilURI $uri, $email_id) { $request = $this->getRequest(); $user = $request->getUser(); // NOTE: You can only send more email for your unverified addresses. $email = id(new PhabricatorUserEmail())->loadOneWhere( 'id = %d AND userPHID = %s AND isVerified = 0', $email_id, $user->getPHID()); if (!$email) { return new Aphront404Response(); } if ($request->isFormPost()) { $email->sendVerificationEmail($user); return id(new AphrontRedirectResponse())->setURI($uri); } $address = $email->getAddress(); $dialog = id(new AphrontDialogView()) ->setUser($user) ->addHiddenInput('verify', $email_id) ->setTitle("Send Another Verification Email?") ->appendChild( 'Send another copy of the verification email to '. phutil_escape_html($address).'?
') ->addSubmitButton('Send Email') ->addCancelButton($uri); return id(new AphrontDialogResponse())->setDialog($dialog); } private function returnPrimaryAddressResponse(PhutilURI $uri, $email_id) { $request = $this->getRequest(); $user = $request->getUser(); // NOTE: You can only make your own verified addresses primary. $email = id(new PhabricatorUserEmail())->loadOneWhere( 'id = %d AND userPHID = %s AND isVerified = 1 AND isPrimary = 0', $email_id, $user->getPHID()); if (!$email) { return new Aphront404Response(); } if ($request->isFormPost()) { id(new PhabricatorUserEditor()) ->setActor($user) ->changePrimaryEmail($user, $email); return id(new AphrontRedirectResponse())->setURI($uri); } $address = $email->getAddress(); $dialog = id(new AphrontDialogView()) ->setUser($user) ->addHiddenInput('primary', $email_id) ->setTitle("Change primary email address?") ->appendChild( 'If you change your primary address, Phabricator will send all '. 'email to '.phutil_escape_html($address).'.
') ->addSubmitButton('Change Primary Address') ->addCancelButton($uri); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/people/editor/PhabricatorUserEditor.php b/src/applications/people/editor/PhabricatorUserEditor.php index 67db0eaa18..6fa19424d3 100644 --- a/src/applications/people/editor/PhabricatorUserEditor.php +++ b/src/applications/people/editor/PhabricatorUserEditor.php @@ -1,393 +1,413 @@ actor = $actor; return $this; } /* -( Creating and Editing Users )----------------------------------------- */ /** * @task edit */ public function createNewUser( PhabricatorUser $user, PhabricatorUserEmail $email) { if ($user->getID()) { throw new Exception("User has already been created!"); } if ($email->getID()) { throw new Exception("Email has already been created!"); } // Always set a new user's email address to primary. $email->setIsPrimary(1); + $this->willAddEmail($email); + $user->openTransaction(); $user->save(); $email->setUserPHID($user->getPHID()); try { $email->save(); } catch (AphrontQueryDuplicateKeyException $ex) { $user->killTransaction(); throw $ex; } $log = PhabricatorUserLog::newLog( $this->actor, $user, PhabricatorUserLog::ACTION_CREATE); $log->setNewValue($email->getAddress()); $log->save(); $user->saveTransaction(); return $this; } /** * @task edit */ public function updateUser(PhabricatorUser $user) { if (!$user->getID()) { throw new Exception("User has not been created yet!"); } $actor = $this->requireActor(); $user->openTransaction(); $user->save(); $log = PhabricatorUserLog::newLog( $actor, $user, PhabricatorUserLog::ACTION_EDIT); $log->save(); $user->saveTransaction(); return $this; } /** * @task edit */ public function changePassword(PhabricatorUser $user, $password) { if (!$user->getID()) { throw new Exception("User has not been created yet!"); } $user->openTransaction(); $user->reload(); $user->setPassword($password); $user->save(); $log = PhabricatorUserLog::newLog( $this->actor, $user, PhabricatorUserLog::ACTION_CHANGE_PASSWORD); $log->save(); $user->saveTransaction(); } /* -( Editing Roles )------------------------------------------------------ */ /** * @task role */ public function makeAdminUser(PhabricatorUser $user, $admin) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception("User has not been created yet!"); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); if ($user->getIsAdmin() == $admin) { $user->endWriteLocking(); $user->killTransaction(); return $this; } $log = PhabricatorUserLog::newLog( $actor, $user, PhabricatorUserLog::ACTION_ADMIN); $log->setOldValue($user->getIsAdmin()); $log->setNewValue($admin); $user->setIsAdmin($admin); $user->save(); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); return $this; } /** * @task role */ public function disableUser(PhabricatorUser $user, $disable) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception("User has not been created yet!"); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); if ($user->getIsDisabled() == $disable) { $user->endWriteLocking(); $user->killTransaction(); return $this; } $log = PhabricatorUserLog::newLog( $actor, $user, PhabricatorUserLog::ACTION_DISABLE); $log->setOldValue($user->getIsDisabled()); $log->setNewValue($disable); $user->setIsDisabled($disable); $user->save(); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); return $this; } /* -( Adding, Removing and Changing Email )-------------------------------- */ /** * @task email */ public function addEmail( PhabricatorUser $user, PhabricatorUserEmail $email) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception("User has not been created yet!"); } if ($email->getID()) { throw new Exception("Email has already been created!"); } // Use changePrimaryEmail() to change primary email. $email->setIsPrimary(0); $email->setUserPHID($user->getPHID()); + $this->willAddEmail($email); + $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); try { $email->save(); } catch (AphrontQueryDuplicateKeyException $ex) { $user->endWriteLocking(); $user->killTransaction(); throw $ex; } $log = PhabricatorUserLog::newLog( $this->actor, $user, PhabricatorUserLog::ACTION_EMAIL_ADD); $log->setNewValue($email->getAddress()); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); return $this; } /** * @task email */ public function removeEmail( PhabricatorUser $user, PhabricatorUserEmail $email) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception("User has not been created yet!"); } if (!$email->getID()) { throw new Exception("Email has not been created yet!"); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); $email->reload(); if ($email->getIsPrimary()) { throw new Exception("Can't remove primary email!"); } if ($email->getUserPHID() != $user->getPHID()) { throw new Exception("Email not owned by user!"); } $email->delete(); $log = PhabricatorUserLog::newLog( $this->actor, $user, PhabricatorUserLog::ACTION_EMAIL_REMOVE); $log->setOldValue($email->getAddress()); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); return $this; } /** * @task email */ public function changePrimaryEmail( PhabricatorUser $user, PhabricatorUserEmail $email) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception("User has not been created yet!"); } if (!$email->getID()) { throw new Exception("Email has not been created yet!"); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); $email->reload(); if ($email->getUserPHID() != $user->getPHID()) { throw new Exception("User does not own email!"); } if ($email->getIsPrimary()) { throw new Exception("Email is already primary!"); } if (!$email->getIsVerified()) { throw new Exception("Email is not verified!"); } $old_primary = $user->loadPrimaryEmail(); if ($old_primary) { $old_primary->setIsPrimary(0); $old_primary->save(); } $email->setIsPrimary(1); $email->save(); $log = PhabricatorUserLog::newLog( $actor, $user, PhabricatorUserLog::ACTION_EMAIL_PRIMARY); $log->setOldValue($old_primary ? $old_primary->getAddress() : null); $log->setNewValue($email->getAddress()); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); if ($old_primary) { $old_primary->sendOldPrimaryEmail($user, $email); } $email->sendNewPrimaryEmail($user); return $this; } /* -( Internals )---------------------------------------------------------- */ /** * @task internal */ private function requireActor() { if (!$this->actor) { throw new Exception("User edit requires actor!"); } return $this->actor; } + + /** + * @task internal + */ + private function willAddEmail(PhabricatorUserEmail $email) { + + // Hard check before write to prevent creation of disallowed email + // addresses. Normally, the application does checks and raises more + // user friendly errors for us, but we omit the courtesy checks on some + // pathways like administrative scripts for simplicity. + + if (!PhabricatorUserEmail::isAllowedAddress($email->getAddress())) { + throw new Exception(PhabricatorUserEmail::describeAllowedAddresses()); + } + } + } diff --git a/src/applications/people/editor/__init__.php b/src/applications/people/editor/__init__.php index 3896d621ca..6b81022f2a 100644 --- a/src/applications/people/editor/__init__.php +++ b/src/applications/people/editor/__init__.php @@ -1,12 +1,13 @@ getVerificationCode().'/'; } public function save() { if (!$this->verificationCode) { $this->setVerificationCode(Filesystem::readRandomCharacters(24)); } return parent::save(); } +/* -( Domain Restrictions )------------------------------------------------ */ + + + /** + * @task restrictions + */ + public static function isAllowedAddress($address) { + $allowed_domains = PhabricatorEnv::getEnvConfig('auth.email-domains'); + if (!$allowed_domains) { + return true; + } + + $addr_obj = new PhutilEmailAddress($address); + + $domain = $addr_obj->getDomainName(); + if (!$domain) { + return false; + } + + return in_array($domain, $allowed_domains); + } + + + /** + * @task restrictions + */ + public static function describeAllowedAddresses() { + $domains = PhabricatorEnv::getEnvConfig('auth.email-domains'); + if (!$domains) { + return null; + } + + if (count($domains) == 1) { + return 'Email address must be @'.head($domains); + } else { + return 'Email address must be at one of: '. + implode(', ', $domains); + } + } + + + /** + * Check if this install requires email verification. + * + * @return bool True if email addresses must be verified. + * + * @task restrictions + */ + public static function isEmailVerificationRequired() { + // NOTE: Configuring required email domains implies required verification. + return PhabricatorEnv::getEnvConfig('auth.require-email-verification') || + PhabricatorEnv::getEnvConfig('auth.email-domains'); + } + + /* -( Email About Email )-------------------------------------------------- */ /** * Send a verification email from $user to this address. * * @param PhabricatorUser The user sending the verification. * @return this * @task email */ public function sendVerificationEmail(PhabricatorUser $user) { $username = $user->getUsername(); $address = $this->getAddress(); $link = PhabricatorEnv::getProductionURI($this->getVerificationURI()); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $signature = null; if (!$is_serious) { $signature = <<