diff --git a/bin/people b/bin/people deleted file mode 120000 --- a/bin/people +++ /dev/null @@ -1 +0,0 @@ -../scripts/people/manage_people.php \ No newline at end of file diff --git a/bin/user b/bin/user new file mode 120000 --- /dev/null +++ b/bin/user @@ -0,0 +1 @@ +../scripts/setup/manage_user.php \ No newline at end of file diff --git a/scripts/people/manage_people.php b/scripts/setup/manage_user.php rename from scripts/people/manage_people.php rename to scripts/setup/manage_user.php --- a/scripts/people/manage_people.php +++ b/scripts/setup/manage_user.php @@ -6,8 +6,8 @@ $args = new PhutilArgumentParser($argv); $args->setSynopsis(<< 'applications/people/mail/PhabricatorPeopleMailEngine.php', 'PhabricatorPeopleMailEngineException' => 'applications/people/mail/PhabricatorPeopleMailEngineException.php', 'PhabricatorPeopleManageProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleManageProfileMenuItem.php', + 'PhabricatorPeopleManagementEmpowerWorkflow' => 'applications/people/management/PhabricatorPeopleManagementEmpowerWorkflow.php', + 'PhabricatorPeopleManagementEnableWorkflow' => 'applications/people/management/PhabricatorPeopleManagementEnableWorkflow.php', 'PhabricatorPeopleManagementWorkflow' => 'applications/people/management/PhabricatorPeopleManagementWorkflow.php', 'PhabricatorPeopleNewController' => 'applications/people/controller/PhabricatorPeopleNewController.php', 'PhabricatorPeopleNoOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleNoOwnerDatasource.php', @@ -4060,7 +4062,6 @@ 'PhabricatorPeopleProfileCommitsController' => 'applications/people/controller/PhabricatorPeopleProfileCommitsController.php', 'PhabricatorPeopleProfileController' => 'applications/people/controller/PhabricatorPeopleProfileController.php', 'PhabricatorPeopleProfileEditController' => 'applications/people/controller/PhabricatorPeopleProfileEditController.php', - 'PhabricatorPeopleProfileImageWorkflow' => 'applications/people/management/PhabricatorPeopleProfileImageWorkflow.php', 'PhabricatorPeopleProfileManageController' => 'applications/people/controller/PhabricatorPeopleProfileManageController.php', 'PhabricatorPeopleProfileMenuEngine' => 'applications/people/engine/PhabricatorPeopleProfileMenuEngine.php', 'PhabricatorPeopleProfilePictureController' => 'applications/people/controller/PhabricatorPeopleProfilePictureController.php', @@ -10332,6 +10333,8 @@ 'PhabricatorPeopleMailEngine' => 'Phobject', 'PhabricatorPeopleMailEngineException' => 'Exception', 'PhabricatorPeopleManageProfileMenuItem' => 'PhabricatorProfileMenuItem', + 'PhabricatorPeopleManagementEmpowerWorkflow' => 'PhabricatorPeopleManagementWorkflow', + 'PhabricatorPeopleManagementEnableWorkflow' => 'PhabricatorPeopleManagementWorkflow', 'PhabricatorPeopleManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorPeopleNewController' => 'PhabricatorPeopleController', 'PhabricatorPeopleNoOwnerDatasource' => 'PhabricatorTypeaheadDatasource', @@ -10341,7 +10344,6 @@ 'PhabricatorPeopleProfileCommitsController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController', 'PhabricatorPeopleProfileEditController' => 'PhabricatorPeopleProfileController', - 'PhabricatorPeopleProfileImageWorkflow' => 'PhabricatorPeopleManagementWorkflow', 'PhabricatorPeopleProfileManageController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleProfileMenuEngine' => 'PhabricatorProfileMenuEngine', 'PhabricatorPeopleProfilePictureController' => 'PhabricatorPeopleProfileController', diff --git a/src/applications/people/management/PhabricatorPeopleManagementEmpowerWorkflow.php b/src/applications/people/management/PhabricatorPeopleManagementEmpowerWorkflow.php new file mode 100644 --- /dev/null +++ b/src/applications/people/management/PhabricatorPeopleManagementEmpowerWorkflow.php @@ -0,0 +1,44 @@ +getUserSelectionArguments(), + array()); + + $this + ->setName('empower') + ->setExamples('**empower** --user __username__') + ->setSynopsis(pht('Turn a user account into an administrator account.')) + ->setArguments($arguments); + } + + public function execute(PhutilArgumentParser $args) { + $user = $this->selectUser($args); + $display_name = $user->getUsername(); + + if ($user->getIsAdmin()) { + throw new PhutilArgumentUsageException( + pht( + 'User account "%s" is already an administrator. You can only '. + 'empower accounts that are not yet administrators.', + $display_name)); + } + + $xactions = array(); + $xactions[] = $user->getApplicationTransactionTemplate() + ->setTransactionType(PhabricatorUserEmpowerTransaction::TRANSACTIONTYPE) + ->setNewValue(true); + + $this->applyTransactions($user, $xactions); + + $this->logOkay( + pht('DONE'), + pht('Empowered user account "%s".', $display_name)); + + return 0; + } + +} diff --git a/src/applications/people/management/PhabricatorPeopleManagementEnableWorkflow.php b/src/applications/people/management/PhabricatorPeopleManagementEnableWorkflow.php new file mode 100644 --- /dev/null +++ b/src/applications/people/management/PhabricatorPeopleManagementEnableWorkflow.php @@ -0,0 +1,44 @@ +getUserSelectionArguments(), + array()); + + $this + ->setName('enable') + ->setExamples('**enable** --user __username__') + ->setSynopsis(pht('Enable a disabled user account.')) + ->setArguments($arguments); + } + + public function execute(PhutilArgumentParser $args) { + $user = $this->selectUser($args); + $display_name = $user->getUsername(); + + if (!$user->getIsDisabled()) { + throw new PhutilArgumentUsageException( + pht( + 'User account "%s" is not disabled. You can only enable accounts '. + 'that are disabled.', + $display_name)); + } + + $xactions = array(); + $xactions[] = $user->getApplicationTransactionTemplate() + ->setTransactionType(PhabricatorUserDisableTransaction::TRANSACTIONTYPE) + ->setNewValue(false); + + $this->applyTransactions($user, $xactions); + + $this->logOkay( + pht('DONE'), + pht('Enabled user account "%s".', $display_name)); + + return 0; + } + +} diff --git a/src/applications/people/management/PhabricatorPeopleManagementWorkflow.php b/src/applications/people/management/PhabricatorPeopleManagementWorkflow.php --- a/src/applications/people/management/PhabricatorPeopleManagementWorkflow.php +++ b/src/applications/people/management/PhabricatorPeopleManagementWorkflow.php @@ -3,45 +3,55 @@ abstract class PhabricatorPeopleManagementWorkflow extends PhabricatorManagementWorkflow { - protected function buildIterator(PhutilArgumentParser $args) { - $usernames = $args->getArg('users'); - - if ($args->getArg('all')) { - if ($usernames) { - throw new PhutilArgumentUsageException( - pht( - 'Specify either a list of users or `%s`, but not both.', - '--all')); - } - return new LiskMigrationIterator(new PhabricatorUser()); + final protected function getUserSelectionArguments() { + return array( + array( + 'name' => 'user', + 'param' => 'username', + 'help' => pht('User account to act on.'), + ), + ); + } + + final protected function selectUser(PhutilArgumentParser $argv) { + $username = $argv->getArg('user'); + + if (!strlen($username)) { + throw new PhutilArgumentUsageException( + pht( + 'Select a user account to act on with "--user ".')); } - if ($usernames) { - return $this->loadUsersWithUsernames($usernames); + $user = id(new PhabricatorPeopleQuery()) + ->setViewer($this->getViewer()) + ->withUsernames(array($username)) + ->executeOne(); + if (!$user) { + throw new PhutilArgumentUsageException( + pht( + 'No user with username "%s" exists.', + $username)); } - return null; + return $user; } - protected function loadUsersWithUsernames(array $usernames) { - $users = array(); - foreach($usernames as $username) { - $query = id(new PhabricatorPeopleQuery()) - ->setViewer($this->getViewer()) - ->withUsernames(array($username)) - ->executeOne(); - - if (!$query) { - throw new PhutilArgumentUsageException( - pht( - '"%s" is not a valid username.', - $username)); - } - $users[] = $query; - } + final protected function applyTransactions( + PhabricatorUser $user, + array $xactions) { + assert_instances_of($xactions, 'PhabricatorUserTransaction'); - return $users; - } + $viewer = $this->getViewer(); + $application = id(new PhabricatorPeopleApplication())->getPHID(); + $content_source = $this->newContentSource(); + + $editor = $user->getApplicationTransactionEditor() + ->setActor($viewer) + ->setActingAsPHID($application) + ->setContentSource($content_source) + ->setContinueOnMissingFields(true); + return $editor->applyTransactions($user, $xactions); + } } diff --git a/src/applications/people/management/PhabricatorPeopleProfileImageWorkflow.php b/src/applications/people/management/PhabricatorPeopleProfileImageWorkflow.php deleted file mode 100644 --- a/src/applications/people/management/PhabricatorPeopleProfileImageWorkflow.php +++ /dev/null @@ -1,85 +0,0 @@ -setName('profileimage') - ->setExamples('**profileimage** --users __username__') - ->setSynopsis(pht('Generate default profile images.')) - ->setArguments( - array( - array( - 'name' => 'all', - 'help' => pht( - 'Generate default profile images for all users.'), - ), - array( - 'name' => 'force', - 'short' => 'f', - 'help' => pht( - 'Force a default profile image to be replaced.'), - ), - array( - 'name' => 'users', - 'wildcard' => true, - ), - )); - } - - public function execute(PhutilArgumentParser $args) { - $console = PhutilConsole::getConsole(); - - $is_force = $args->getArg('force'); - $is_all = $args->getArg('all'); - - $gd = function_exists('imagecreatefromstring'); - if (!$gd) { - throw new PhutilArgumentUsageException( - pht( - 'GD is not installed for php-cli. Aborting.')); - } - - $iterator = $this->buildIterator($args); - if (!$iterator) { - throw new PhutilArgumentUsageException( - pht( - 'Either specify a list of users to update, or use `%s` '. - 'to update all users.', - '--all')); - } - - $version = PhabricatorFilesComposeAvatarBuiltinFile::VERSION; - $generator = new PhabricatorFilesComposeAvatarBuiltinFile(); - - foreach ($iterator as $user) { - $username = $user->getUsername(); - $default_phid = $user->getDefaultProfileImagePHID(); - $gen_version = $user->getDefaultProfileImageVersion(); - - $generate = false; - if ($gen_version != $version) { - $generate = true; - } - - if ($default_phid == null || $is_force || $generate) { - $console->writeOut( - "%s\n", - pht( - 'Generating profile image for "%s".', - $username)); - - $generator->updateUser($user); - } else { - $console->writeOut( - "%s\n", - pht( - 'Default profile image "%s" already set for "%s".', - $version, - $username)); - } - } - } - -} diff --git a/src/docs/user/userguide/unlocking.diviner b/src/docs/user/userguide/unlocking.diviner new file mode 100644 --- /dev/null +++ b/src/docs/user/userguide/unlocking.diviner @@ -0,0 +1,116 @@ +@title User Guide: Unlocking Objects +@group userguide + +Explains how to access locked or invisible objects and accounts. + +Overview +======== + +Phabricator tries to make it difficult for users to lock themselves out of +things, but you can occasionally end up in situations where no one has access +to an object that you need access to. + +For example, sometimes the only user who had edit permission for something has +left the organization, or you configured a "Phase of the Moon" policy rule and +the stars aren't currently aligned. + +You can use various CLI tools to unlock objects and accounts if you need to +regain access. + + +Unlocking Accounts +================== + +If you need to regain access to an object, the easiest approach is usually to +recover access to the account which owns it, then change the object policies +to be more open using the web UI. + +For example, if an important task was accidentally locked so that only a user +who is currently on vacation can edit it, you can log in as that user and +change the edit policy to something more permissive. + +To regain access to an account: + +``` +$ ./bin/auth recover +``` + +If the account you're recovering access to has MFA or other session prompts, +use the `--force-full-session` to bypass them: + +``` +$ ./bin/auth recover --force-full-session +``` + +In either case, the command will give you a link you a one-time link you can +use to access the account from the web UI. From there, you can open up objects +or change settings. + + +Unlocking MFA +============= + +You can completely strip MFA from a user account with: + +``` +$ ./bin/auth strip --user ... +``` + +For detailed help on managing and stripping MFA, see the instructions in +@{article:User Guide: Multi-Factor Authentication} + + +Unlocking Objects +================= + +If you aren't sure who owns an object, or no user account has access to an +object, you can directly change object policies from the CLI: + +``` +$ ./bin/policy unlock [--view ...] [--edit ...] [--owner ...] +``` + +To identify the object you want to unlock, you can specify an object name (like +`T123`) or a PHID as the `` parameter. + +Use the `--view` and `--edit` flags (and, for some objects, the `--owner` +flag) to specify new policies for the object. + +For example, to make task `T123` editable by user `@alice`, run: + +``` +$ ./bin/policy unlock T123 --edit alice +``` + +Not every object has mutable view and edit policies, and not every object has +an owner, so each flag only works on some types of objects. + +From here, you can log in to the web UI and change the relevant policies to +whatever you want to set them to. + + +No Enabled Users +================ + +If you accidentally disabled all administrator accounts, you can enable a +disabled account from the CLI like this: + +``` +$ ./bin/user enable --user +``` + +From here, recover the account or log in normally. + + +No Administrators +================= + +If you accidentally deleted all the administrator accounts, you can empower +a user as an administrator from the CLI like this: + +``` +$ ./bin/user empower --user +``` + +This will upgrade the user account from a regular account to an administrator +account.