Consolidate user editing code
Summary:
  - We currently have some bugs in account creation due to nontransactional user/email editing.
    - We save $user, then try to save $email. This may fail for various reasons, commonly because the email isn't unique.
    - This leaves us with a $user with no email.
  - Also, logging of edits is somewhat inconsistent across various edit mechanisms.
  - Move all editing to a `PhabricatorUserEditor` class.
  - Handle some broken-data cases more gracefully.
Test Plan:
  - Created and edited a user with `accountadmin`.
  - Created a user with `add_user.php`
  - Created and edited a user with People editor.
  - Created a user with OAuth.
  - Edited user information via Settings.
  - Tried to create an OAuth user with a duplicate email address, got a proper error.
  - Tried to create a user via People with a duplicate email address, got a proper error.
Reviewers: btrahan, vrana, jungejason
Reviewed By: btrahan
CC: tberman, aran
Maniphest Tasks: T1184
Differential Revision: https://secure.phabricator.com/D2569
			
			
This commit is contained in:
		| @@ -121,7 +121,6 @@ $is_admin = $user->getIsAdmin(); | |||||||
| $set_admin = phutil_console_confirm( | $set_admin = phutil_console_confirm( | ||||||
|   'Should this user be an administrator?', |   'Should this user be an administrator?', | ||||||
|   $default_no = !$is_admin); |   $default_no = !$is_admin); | ||||||
| $user->setIsAdmin($set_admin); |  | ||||||
|  |  | ||||||
| echo "\n\nACCOUNT SUMMARY\n\n"; | echo "\n\nACCOUNT SUMMARY\n\n"; | ||||||
| $tpl = "%12s   %-30s   %-30s\n"; | $tpl = "%12s   %-30s   %-30s\n"; | ||||||
| @@ -149,21 +148,30 @@ if (!phutil_console_confirm("Save these changes?", $default_no = false)) { | |||||||
|   exit(1); |   exit(1); | ||||||
| } | } | ||||||
|  |  | ||||||
| $user->save(); | $user->openTransaction(); | ||||||
| if ($changed_pass !== false) { |  | ||||||
|   // This must happen after saving the user because we use their PHID as a |  | ||||||
|   // component of the password hash. |  | ||||||
|   $user->setPassword($changed_pass); |  | ||||||
|   $user->save(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| if ($new_email) { |   $editor = new PhabricatorUserEditor(); | ||||||
|   id(new PhabricatorUserEmail()) |  | ||||||
|     ->setUserPHID($user->getPHID()) |   // TODO: This is wrong, but we have a chicken-and-egg problem when you use | ||||||
|     ->setAddress($new_email) |   // this script to create the first user. | ||||||
|     ->setIsVerified(1) |   $editor->setActor($user); | ||||||
|     ->setIsPrimary(1) |  | ||||||
|     ->save(); |   if ($new_email) { | ||||||
| } |     $email = id(new PhabricatorUserEmail()) | ||||||
|  |       ->setAddress($new_email) | ||||||
|  |       ->setIsVerified(1); | ||||||
|  |  | ||||||
|  |     $editor->createNewUser($user, $email); | ||||||
|  |   } else { | ||||||
|  |     $editor->updateUser($user); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   $editor->makeAdminUser($user, $set_admin); | ||||||
|  |  | ||||||
|  |   if ($changed_pass !== false) { | ||||||
|  |     $editor->changePassword($user, $changed_pass); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | $user->saveTransaction(); | ||||||
|  |  | ||||||
| echo "Saved changes.\n"; | echo "Saved changes.\n"; | ||||||
|   | |||||||
| @@ -61,14 +61,14 @@ if ($existing_email) { | |||||||
| $user = new PhabricatorUser(); | $user = new PhabricatorUser(); | ||||||
| $user->setUsername($username); | $user->setUsername($username); | ||||||
| $user->setRealname($realname); | $user->setRealname($realname); | ||||||
| $user->save(); |  | ||||||
|  |  | ||||||
| $email_object = id(new PhabricatorUserEmail()) | $email_object = id(new PhabricatorUserEmail()) | ||||||
|   ->setUserPHID($user->getPHID()) |  | ||||||
|   ->setAddress($email) |   ->setAddress($email) | ||||||
|   ->setIsVerified(1) |   ->setIsVerified(1); | ||||||
|   ->setIsPrimary(1) |  | ||||||
|   ->save(); | id(new PhabricatorUserEditor()) | ||||||
|  |   ->setActor($admin) | ||||||
|  |   ->createNewUser($user, $email_object); | ||||||
|  |  | ||||||
| $user->sendWelcomeEmail($admin); | $user->sendWelcomeEmail($admin); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -956,6 +956,7 @@ phutil_register_library_map(array( | |||||||
|     'PhabricatorUserAccountSettingsPanelController' => 'applications/people/controller/settings/panels/account', |     'PhabricatorUserAccountSettingsPanelController' => 'applications/people/controller/settings/panels/account', | ||||||
|     'PhabricatorUserConduitSettingsPanelController' => 'applications/people/controller/settings/panels/conduit', |     'PhabricatorUserConduitSettingsPanelController' => 'applications/people/controller/settings/panels/conduit', | ||||||
|     'PhabricatorUserDAO' => 'applications/people/storage/base', |     'PhabricatorUserDAO' => 'applications/people/storage/base', | ||||||
|  |     'PhabricatorUserEditor' => 'applications/people/editor', | ||||||
|     'PhabricatorUserEmail' => 'applications/people/storage/email', |     'PhabricatorUserEmail' => 'applications/people/storage/email', | ||||||
|     'PhabricatorUserEmailPreferenceSettingsPanelController' => 'applications/people/controller/settings/panels/emailpref', |     'PhabricatorUserEmailPreferenceSettingsPanelController' => 'applications/people/controller/settings/panels/emailpref', | ||||||
|     'PhabricatorUserEmailSettingsPanelController' => 'applications/people/controller/settings/panels/email', |     'PhabricatorUserEmailSettingsPanelController' => 'applications/people/controller/settings/panels/email', | ||||||
|   | |||||||
| @@ -83,7 +83,6 @@ final class PhabricatorOAuthDefaultRegistrationController | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|           $user->save(); |  | ||||||
|  |  | ||||||
|           // NOTE: We don't verify OAuth email addresses by default because |           // NOTE: We don't verify OAuth email addresses by default because | ||||||
|           // OAuth providers might associate email addresses with accounts that |           // OAuth providers might associate email addresses with accounts that | ||||||
| @@ -92,12 +91,14 @@ final class PhabricatorOAuthDefaultRegistrationController | |||||||
|           // verifying an email address are high because having a corporate |           // verifying an email address are high because having a corporate | ||||||
|           // address at a company is sometimes the key to the castle. |           // address at a company is sometimes the key to the castle. | ||||||
|  |  | ||||||
|           $new_email = id(new PhabricatorUserEmail()) |  | ||||||
|             ->setUserPHID($user->getPHID()) |           $email_obj = id(new PhabricatorUserEmail()) | ||||||
|             ->setAddress($new_email) |             ->setAddress($new_email) | ||||||
|             ->setIsPrimary(1) |             ->setIsVerified(0); | ||||||
|             ->setIsVerified(0) |  | ||||||
|             ->save(); |           id(new PhabricatorUserEditor()) | ||||||
|  |             ->setActor($user) | ||||||
|  |             ->createNewUser($user, $email_obj); | ||||||
|  |  | ||||||
|           $oauth_info->setUserID($user->getID()); |           $oauth_info->setUserID($user->getID()); | ||||||
|           $oauth_info->save(); |           $oauth_info->save(); | ||||||
| @@ -106,7 +107,7 @@ final class PhabricatorOAuthDefaultRegistrationController | |||||||
|           $request->setCookie('phusr', $user->getUsername()); |           $request->setCookie('phusr', $user->getUsername()); | ||||||
|           $request->setCookie('phsid', $session_key); |           $request->setCookie('phsid', $session_key); | ||||||
|  |  | ||||||
|           $new_email->sendVerificationEmail($user); |           $email_obj->sendVerificationEmail($user); | ||||||
|  |  | ||||||
|           return id(new AphrontRedirectResponse())->setURI('/'); |           return id(new AphrontRedirectResponse())->setURI('/'); | ||||||
|         } catch (AphrontQueryDuplicateKeyException $exception) { |         } catch (AphrontQueryDuplicateKeyException $exception) { | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ | |||||||
| phutil_require_module('phabricator', 'aphront/response/redirect'); | phutil_require_module('phabricator', 'aphront/response/redirect'); | ||||||
| phutil_require_module('phabricator', 'applications/auth/controller/oauthregistration/base'); | phutil_require_module('phabricator', 'applications/auth/controller/oauthregistration/base'); | ||||||
| phutil_require_module('phabricator', 'applications/files/storage/file'); | phutil_require_module('phabricator', 'applications/files/storage/file'); | ||||||
|  | phutil_require_module('phabricator', 'applications/people/editor'); | ||||||
| phutil_require_module('phabricator', 'applications/people/storage/email'); | phutil_require_module('phabricator', 'applications/people/storage/email'); | ||||||
| phutil_require_module('phabricator', 'applications/people/storage/user'); | phutil_require_module('phabricator', 'applications/people/storage/user'); | ||||||
| phutil_require_module('phabricator', 'view/form/base'); | phutil_require_module('phabricator', 'view/form/base'); | ||||||
|   | |||||||
| @@ -44,7 +44,8 @@ final class ConduitAPI_user_disable_Method | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   protected function execute(ConduitAPIRequest $request) { |   protected function execute(ConduitAPIRequest $request) { | ||||||
|     if (!$request->getUser()->getIsAdmin()) { |     $actor = $request->getUser(); | ||||||
|  |     if (!$actor->getIsAdmin()) { | ||||||
|       throw new ConduitException('ERR-PERMISSIONS'); |       throw new ConduitException('ERR-PERMISSIONS'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -59,8 +60,9 @@ final class ConduitAPI_user_disable_Method | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     foreach ($users as $user) { |     foreach ($users as $user) { | ||||||
|       $user->setIsDisabled(true); |       id(new PhabricatorUserEditor()) | ||||||
|       $user->save(); |         ->setActor($actor) | ||||||
|  |         ->disableUser($user, true); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ | |||||||
|  |  | ||||||
| phutil_require_module('phabricator', 'applications/conduit/method/user/base'); | phutil_require_module('phabricator', 'applications/conduit/method/user/base'); | ||||||
| phutil_require_module('phabricator', 'applications/conduit/protocol/exception'); | phutil_require_module('phabricator', 'applications/conduit/protocol/exception'); | ||||||
|  | phutil_require_module('phabricator', 'applications/people/editor'); | ||||||
| phutil_require_module('phabricator', 'applications/people/storage/user'); | phutil_require_module('phabricator', 'applications/people/storage/user'); | ||||||
|  |  | ||||||
| phutil_require_module('phutil', 'utils'); | phutil_require_module('phutil', 'utils'); | ||||||
|   | |||||||
| @@ -165,22 +165,18 @@ final class PhabricatorPeopleEditController | |||||||
|         try { |         try { | ||||||
|           $is_new = !$user->getID(); |           $is_new = !$user->getID(); | ||||||
|  |  | ||||||
|           $user->save(); |           if (!$is_new) { | ||||||
|  |             id(new PhabricatorUserEditor()) | ||||||
|           if ($is_new) { |               ->setActor($admin) | ||||||
|  |               ->updateUser($user); | ||||||
|  |           } else { | ||||||
|             $email = id(new PhabricatorUserEmail()) |             $email = id(new PhabricatorUserEmail()) | ||||||
|               ->setUserPHID($user->getPHID()) |  | ||||||
|               ->setAddress($new_email) |               ->setAddress($new_email) | ||||||
|               ->setIsPrimary(1) |               ->setIsVerified(0); | ||||||
|               ->setIsVerified(0) |  | ||||||
|               ->save(); |  | ||||||
|  |  | ||||||
|             $log = PhabricatorUserLog::newLog( |             id(new PhabricatorUserEditor()) | ||||||
|               $admin, |               ->setActor($admin) | ||||||
|               $user, |               ->createNewUser($user, $email); | ||||||
|               PhabricatorUserLog::ACTION_CREATE); |  | ||||||
|             $log->save(); |  | ||||||
|  |  | ||||||
|             if ($welcome_checked) { |             if ($welcome_checked) { | ||||||
|               $user->sendWelcomeEmail($admin); |               $user->sendWelcomeEmail($admin); | ||||||
| @@ -255,13 +251,17 @@ final class PhabricatorPeopleEditController | |||||||
|           ->setValue($new_email) |           ->setValue($new_email) | ||||||
|           ->setError($e_email)); |           ->setError($e_email)); | ||||||
|     } else { |     } else { | ||||||
|  |       $email = $user->loadPrimaryEmail(); | ||||||
|  |       if ($email) { | ||||||
|  |         $status = $email->getIsVerified() ? 'Verified' : 'Unverified'; | ||||||
|  |       } else { | ||||||
|  |         $status = 'No Email Address'; | ||||||
|  |       } | ||||||
|  |  | ||||||
|       $form->appendChild( |       $form->appendChild( | ||||||
|         id(new AphrontFormStaticControl()) |         id(new AphrontFormStaticControl()) | ||||||
|           ->setLabel('Email') |           ->setLabel('Email') | ||||||
|           ->setValue( |           ->setValue($status)); | ||||||
|               $user->loadPrimaryEmail()->getIsVerified() |  | ||||||
|                 ? 'Verified' |  | ||||||
|                 : 'Unverified')); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     $form->appendChild($this->getRoleInstructions()); |     $form->appendChild($this->getRoleInstructions()); | ||||||
| @@ -354,31 +354,21 @@ final class PhabricatorPeopleEditController | |||||||
|         $new_admin = (bool)$request->getBool('is_admin'); |         $new_admin = (bool)$request->getBool('is_admin'); | ||||||
|         $old_admin = (bool)$user->getIsAdmin(); |         $old_admin = (bool)$user->getIsAdmin(); | ||||||
|         if ($new_admin != $old_admin) { |         if ($new_admin != $old_admin) { | ||||||
|           $log = clone $log_template; |           id(new PhabricatorUserEditor()) | ||||||
|           $log->setAction(PhabricatorUserLog::ACTION_ADMIN); |             ->setActor($admin) | ||||||
|           $log->setOldValue($old_admin); |             ->makeAdminUser($user, $new_admin); | ||||||
|           $log->setNewValue($new_admin); |  | ||||||
|           $user->setIsAdmin($new_admin); |  | ||||||
|           $logs[] = $log; |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         $new_disabled = (bool)$request->getBool('is_disabled'); |         $new_disabled = (bool)$request->getBool('is_disabled'); | ||||||
|         $old_disabled = (bool)$user->getIsDisabled(); |         $old_disabled = (bool)$user->getIsDisabled(); | ||||||
|         if ($new_disabled != $old_disabled) { |         if ($new_disabled != $old_disabled) { | ||||||
|           $log = clone $log_template; |           id(new PhabricatorUserEditor()) | ||||||
|           $log->setAction(PhabricatorUserLog::ACTION_DISABLE); |             ->setActor($admin) | ||||||
|           $log->setOldValue($old_disabled); |             ->disableUser($user, $new_disabled); | ||||||
|           $log->setNewValue($new_disabled); |  | ||||||
|           $user->setIsDisabled($new_disabled); |  | ||||||
|           $logs[] = $log; |  | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       if (!$errors) { |       if (!$errors) { | ||||||
|         $user->save(); |  | ||||||
|         foreach ($logs as $log) { |  | ||||||
|           $log->save(); |  | ||||||
|         } |  | ||||||
|         return id(new AphrontRedirectResponse()) |         return id(new AphrontRedirectResponse()) | ||||||
|           ->setURI($request->getRequestURI()->alter('saved', 'true')); |           ->setURI($request->getRequestURI()->alter('saved', 'true')); | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ | |||||||
| phutil_require_module('phabricator', 'aphront/response/404'); | phutil_require_module('phabricator', 'aphront/response/404'); | ||||||
| phutil_require_module('phabricator', 'aphront/response/redirect'); | phutil_require_module('phabricator', 'aphront/response/redirect'); | ||||||
| phutil_require_module('phabricator', 'applications/people/controller/base'); | phutil_require_module('phabricator', 'applications/people/controller/base'); | ||||||
|  | phutil_require_module('phabricator', 'applications/people/editor'); | ||||||
| phutil_require_module('phabricator', 'applications/people/storage/email'); | phutil_require_module('phabricator', 'applications/people/storage/email'); | ||||||
| phutil_require_module('phabricator', 'applications/people/storage/log'); | phutil_require_module('phabricator', 'applications/people/storage/log'); | ||||||
| phutil_require_module('phabricator', 'applications/people/storage/user'); | phutil_require_module('phabricator', 'applications/people/storage/user'); | ||||||
|   | |||||||
| @@ -175,13 +175,14 @@ final class PhabricatorUserEmailSettingsPanelController | |||||||
|  |  | ||||||
|       if (!$errors) { |       if (!$errors) { | ||||||
|         $object = id(new PhabricatorUserEmail()) |         $object = id(new PhabricatorUserEmail()) | ||||||
|           ->setUserPHID($user->getPHID()) |  | ||||||
|           ->setAddress($email) |           ->setAddress($email) | ||||||
|           ->setIsVerified(0) |           ->setIsVerified(0); | ||||||
|           ->setIsPrimary(0); |  | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|           $object->save(); |  | ||||||
|  |           id(new PhabricatorUserEditor()) | ||||||
|  |             ->setActor($user) | ||||||
|  |             ->addEmail($user, $object); | ||||||
|  |  | ||||||
|           $object->sendVerificationEmail($user); |           $object->sendVerificationEmail($user); | ||||||
|  |  | ||||||
| @@ -245,7 +246,11 @@ final class PhabricatorUserEmailSettingsPanelController | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     if ($request->isFormPost()) { |     if ($request->isFormPost()) { | ||||||
|       $email->delete(); |  | ||||||
|  |       id(new PhabricatorUserEditor()) | ||||||
|  |         ->setActor($user) | ||||||
|  |         ->removeEmail($user, $email); | ||||||
|  |  | ||||||
|       return id(new AphrontRedirectResponse())->setURI($uri); |       return id(new AphrontRedirectResponse())->setURI($uri); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -314,21 +319,9 @@ final class PhabricatorUserEmailSettingsPanelController | |||||||
|  |  | ||||||
|     if ($request->isFormPost()) { |     if ($request->isFormPost()) { | ||||||
|  |  | ||||||
|       // TODO: Transactions! |       id(new PhabricatorUserEditor()) | ||||||
|  |         ->setActor($user) | ||||||
|       $email->setIsPrimary(1); |         ->changePrimaryEmail($user, $email); | ||||||
|  |  | ||||||
|       $old_primary = $user->loadPrimaryEmail(); |  | ||||||
|       if ($old_primary) { |  | ||||||
|         $old_primary->setIsPrimary(0); |  | ||||||
|         $old_primary->save(); |  | ||||||
|       } |  | ||||||
|       $email->save(); |  | ||||||
|  |  | ||||||
|       if ($old_primary) { |  | ||||||
|         $old_primary->sendOldPrimaryEmail($user, $email); |  | ||||||
|       } |  | ||||||
|       $email->sendNewPrimaryEmail($user); |  | ||||||
|  |  | ||||||
|       return id(new AphrontRedirectResponse())->setURI($uri); |       return id(new AphrontRedirectResponse())->setURI($uri); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ phutil_require_module('phabricator', 'aphront/response/dialog'); | |||||||
| phutil_require_module('phabricator', 'aphront/response/redirect'); | phutil_require_module('phabricator', 'aphront/response/redirect'); | ||||||
| phutil_require_module('phabricator', 'aphront/response/reload'); | phutil_require_module('phabricator', 'aphront/response/reload'); | ||||||
| phutil_require_module('phabricator', 'applications/people/controller/settings/panels/base'); | phutil_require_module('phabricator', 'applications/people/controller/settings/panels/base'); | ||||||
|  | phutil_require_module('phabricator', 'applications/people/editor'); | ||||||
| phutil_require_module('phabricator', 'applications/people/storage/email'); | phutil_require_module('phabricator', 'applications/people/storage/email'); | ||||||
| phutil_require_module('phabricator', 'infrastructure/javelin/markup'); | phutil_require_module('phabricator', 'infrastructure/javelin/markup'); | ||||||
| phutil_require_module('phabricator', 'view/control/table'); | phutil_require_module('phabricator', 'view/control/table'); | ||||||
|   | |||||||
| @@ -79,13 +79,16 @@ final class PhabricatorUserPasswordSettingsPanelController | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       if (!$errors) { |       if (!$errors) { | ||||||
|         $user->setPassword($pass); |  | ||||||
|         // This write is unguarded because the CSRF token has already |         // This write is unguarded because the CSRF token has already | ||||||
|         // been checked in the call to $request->isFormPost() and |         // been checked in the call to $request->isFormPost() and | ||||||
|         // the CSRF token depends on the password hash, so when it |         // the CSRF token depends on the password hash, so when it | ||||||
|         // is changed here the CSRF token check will fail. |         // is changed here the CSRF token check will fail. | ||||||
|         $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); |         $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); | ||||||
|         $user->save(); |  | ||||||
|  |           id(new PhabricatorUserEditor()) | ||||||
|  |             ->setActor($user) | ||||||
|  |             ->changePassword($user, $pass); | ||||||
|  |  | ||||||
|         unset($unguarded); |         unset($unguarded); | ||||||
|  |  | ||||||
|         if ($valid_token) { |         if ($valid_token) { | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ phutil_require_module('phabricator', 'aphront/response/400'); | |||||||
| phutil_require_module('phabricator', 'aphront/response/redirect'); | phutil_require_module('phabricator', 'aphront/response/redirect'); | ||||||
| phutil_require_module('phabricator', 'aphront/writeguard'); | phutil_require_module('phabricator', 'aphront/writeguard'); | ||||||
| phutil_require_module('phabricator', 'applications/people/controller/settings/panels/base'); | phutil_require_module('phabricator', 'applications/people/controller/settings/panels/base'); | ||||||
|  | phutil_require_module('phabricator', 'applications/people/editor'); | ||||||
| phutil_require_module('phabricator', 'applications/people/storage/email'); | phutil_require_module('phabricator', 'applications/people/storage/email'); | ||||||
| phutil_require_module('phabricator', 'infrastructure/env'); | phutil_require_module('phabricator', 'infrastructure/env'); | ||||||
| phutil_require_module('phabricator', 'view/form/base'); | phutil_require_module('phabricator', 'view/form/base'); | ||||||
|   | |||||||
							
								
								
									
										393
									
								
								src/applications/people/editor/PhabricatorUserEditor.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										393
									
								
								src/applications/people/editor/PhabricatorUserEditor.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,393 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Copyright 2012 Facebook, Inc. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Editor class for creating and adjusting users. This class guarantees data | ||||||
|  |  * integrity and writes logs when user information changes. | ||||||
|  |  * | ||||||
|  |  * @task config     Configuration | ||||||
|  |  * @task edit       Creating and Editing Users | ||||||
|  |  * @task role       Editing Roles | ||||||
|  |  * @task email      Adding, Removing and Changing Email | ||||||
|  |  * @task internal   Internals | ||||||
|  |  */ | ||||||
|  | final class PhabricatorUserEditor { | ||||||
|  |  | ||||||
|  |   private $actor; | ||||||
|  |   private $logs = array(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /* -(  Configuration  )------------------------------------------------------ */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * @task config | ||||||
|  |    */ | ||||||
|  |   public function setActor(PhabricatorUser $actor) { | ||||||
|  |     $this->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); | ||||||
|  |  | ||||||
|  |     $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()); | ||||||
|  |  | ||||||
|  |     $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; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								src/applications/people/editor/__init__.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/applications/people/editor/__init__.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | <?php | ||||||
|  | /** | ||||||
|  |  * This file is automatically generated. Lint this module to rebuild it. | ||||||
|  |  * @generated | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | phutil_require_module('phabricator', 'applications/people/storage/log'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | phutil_require_source('PhabricatorUserEditor.php'); | ||||||
| @@ -24,6 +24,7 @@ final class PhabricatorUserLog extends PhabricatorUserDAO { | |||||||
|   const ACTION_RESET_PASSWORD = 'reset-pass'; |   const ACTION_RESET_PASSWORD = 'reset-pass'; | ||||||
|  |  | ||||||
|   const ACTION_CREATE         = 'create'; |   const ACTION_CREATE         = 'create'; | ||||||
|  |   const ACTION_EDIT           = 'edit'; | ||||||
|  |  | ||||||
|   const ACTION_ADMIN          = 'admin'; |   const ACTION_ADMIN          = 'admin'; | ||||||
|   const ACTION_DISABLE        = 'disable'; |   const ACTION_DISABLE        = 'disable'; | ||||||
| @@ -31,6 +32,12 @@ final class PhabricatorUserLog extends PhabricatorUserDAO { | |||||||
|   const ACTION_CONDUIT_CERTIFICATE = 'conduit-cert'; |   const ACTION_CONDUIT_CERTIFICATE = 'conduit-cert'; | ||||||
|   const ACTION_CONDUIT_CERTIFICATE_FAILURE = 'conduit-cert-fail'; |   const ACTION_CONDUIT_CERTIFICATE_FAILURE = 'conduit-cert-fail'; | ||||||
|  |  | ||||||
|  |   const ACTION_EMAIL_PRIMARY    = 'email-primary'; | ||||||
|  |   const ACTION_EMAIL_REMOVE     = 'email-remove'; | ||||||
|  |   const ACTION_EMAIL_ADD        = 'email-add'; | ||||||
|  |  | ||||||
|  |   const ACTION_CHANGE_PASSWORD  = 'change-password'; | ||||||
|  |  | ||||||
|   protected $actorPHID; |   protected $actorPHID; | ||||||
|   protected $userPHID; |   protected $userPHID; | ||||||
|   protected $action; |   protected $action; | ||||||
| @@ -75,7 +82,7 @@ final class PhabricatorUserLog extends PhabricatorUserDAO { | |||||||
|  |  | ||||||
|   public function save() { |   public function save() { | ||||||
|     if (!$this->remoteAddr) { |     if (!$this->remoteAddr) { | ||||||
|       $this->remoteAddr = idx($_SERVER, 'REMOTE_ADDR'); |       $this->remoteAddr = idx($_SERVER, 'REMOTE_ADDR', ''); | ||||||
|     } |     } | ||||||
|     if (!$this->session) { |     if (!$this->session) { | ||||||
|       $this->setSession(idx($_COOKIE, 'phsid')); |       $this->setSession(idx($_COOKIE, 'phsid')); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 epriestley
					epriestley