diff --git a/src/applications/people/application/PhabricatorPeopleApplication.php b/src/applications/people/application/PhabricatorPeopleApplication.php index c14dd2c35..01844fdae 100644 --- a/src/applications/people/application/PhabricatorPeopleApplication.php +++ b/src/applications/people/application/PhabricatorPeopleApplication.php @@ -1,178 +1,189 @@ getIsAdmin(); } public function getFlavorText() { return pht('Sort of a social utility.'); } public function canUninstall() { return false; } public function getEventListeners() { return array( new PhabricatorPeopleHovercardEventListener(), ); } public function getRoutes() { return array( '/people/' => array( '(query/(?P[^/]+)/)?' => 'PhabricatorPeopleListController', 'logs/(?:query/(?P[^/]+)/)?' => 'PhabricatorPeopleLogsController', 'approve/(?P[1-9]\d*)/' => 'PhabricatorPeopleApproveController', '(?Pdisapprove)/(?P[1-9]\d*)/' => 'PhabricatorPeopleDisableController', '(?Pdisable)/(?P[1-9]\d*)/' => 'PhabricatorPeopleDisableController', 'empower/(?P[1-9]\d*)/' => 'PhabricatorPeopleEmpowerController', 'delete/(?P[1-9]\d*)/' => 'PhabricatorPeopleDeleteController', 'rename/(?P[1-9]\d*)/' => 'PhabricatorPeopleRenameController', 'welcome/(?P[1-9]\d*)/' => 'PhabricatorPeopleWelcomeController', 'create/' => 'PhabricatorPeopleCreateController', 'new/(?P[^/]+)/' => 'PhabricatorPeopleNewController', 'ldap/' => 'PhabricatorPeopleLdapController', 'editprofile/(?P[1-9]\d*)/' => 'PhabricatorPeopleProfileEditController', 'picture/(?P[1-9]\d*)/' => 'PhabricatorPeopleProfilePictureController', ), '/p/(?P[\w._-]+)/' => 'PhabricatorPeopleProfileController', '/p/(?P[\w._-]+)/calendar/' => 'PhabricatorPeopleCalendarController', '/p/(?P[\w._-]+)/feed/' => 'PhabricatorPeopleFeedController', ); } public function getRemarkupRules() { return array( new PhabricatorMentionRemarkupRule(), ); } protected function getCustomCapabilities() { return array( PeopleCreateUsersCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), PeopleBrowseUserDirectoryCapability::CAPABILITY => array(), ); } public function loadStatus(PhabricatorUser $user) { if (!$user->getIsAdmin()) { return array(); } $need_approval = id(new PhabricatorPeopleQuery()) ->setViewer($user) ->withIsApproved(false) ->withIsDisabled(false) ->setLimit(self::MAX_STATUS_ITEMS) ->execute(); if (!$need_approval) { return array(); } $status = array(); $count = count($need_approval); $count_str = self::formatStatusCount( $count, '%s Users Need Approval', '%d User(s) Need Approval'); $type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($count_str) ->setCount($count); return $status; } public function buildMainMenuItems( PhabricatorUser $user, PhabricatorController $controller = null) { $items = array(); if ($user->isLoggedIn() && $user->isUserActivated()) { $profile = id(new PhabricatorPeopleQuery()) ->setViewer($user) ->needProfileImage(true) ->withPHIDs(array($user->getPHID())) ->executeOne(); $image = $profile->getProfileImageURI(); $item = id(new PHUIListItemView()) ->setName($user->getUsername()) ->setHref('/p/'.$user->getUsername().'/') ->addClass('core-menu-item') ->setAural(pht('Profile')) ->setOrder(100); $classes = array( 'phabricator-core-menu-icon', 'phabricator-core-menu-profile-image', ); $item->appendChild( phutil_tag( 'span', array( 'class' => implode(' ', $classes), 'style' => 'background-image: url('.$image.')', ), '')); $items[] = $item; } return $items; } public function getQuickCreateItems(PhabricatorUser $viewer) { $items = array(); - if ($viewer->getIsAdmin()) { + $can_create = PhabricatorPolicyFilter::hasCapability( + $viewer, + $this, + PeopleCreateUsersCapability::CAPABILITY); + + if ($can_create) { $item = id(new PHUIListItemView()) ->setName(pht('User Account')) ->setIcon('fa-users') ->setHref($this->getBaseURI().'create/'); $items[] = $item; + } else if ($viewer->getIsAdmin()) { + $item = id(new PHUIListItemView()) + ->setName(pht('Bot Account')) + ->setIcon('fa-android') + ->setHref($this->getBaseURI().'new/bot/'); + $items[] = $item; } return $items; } } diff --git a/src/applications/people/capability/PeopleCreateUsersCapability.php b/src/applications/people/capability/PeopleCreateUsersCapability.php index 26d8b7ffe..0b5214195 100644 --- a/src/applications/people/capability/PeopleCreateUsersCapability.php +++ b/src/applications/people/capability/PeopleCreateUsersCapability.php @@ -1,16 +1,16 @@ key = idx($data, 'key'); } public function processRequest() { $this->requireApplicationCapability( PeopleBrowseUserDirectoryCapability::CAPABILITY); $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($this->key) ->setSearchEngine(new PhabricatorPeopleSearchEngine()) ->setNavigation($this->buildSideNavView()); return $this->delegateToController($controller); } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $viewer = $this->getRequest()->getUser(); $can_create = $this->hasApplicationCapability( PeopleCreateUsersCapability::CAPABILITY); - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create New User')) - ->setHref($this->getApplicationURI('create/')) - ->setDisabled(!$can_create) - ->setIcon('fa-plus-square')); + if ($can_create) { + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create New User')) + ->setHref($this->getApplicationURI('create/')) + ->setIcon('fa-plus-square')); + } else if ($viewer->getIsAdmin()) { + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create New Bot')) + ->setHref($this->getApplicationURI('new/bot/')) + ->setIcon('fa-plus-square')); + } return $crumbs; } } diff --git a/src/applications/people/controller/PhabricatorPeopleNewController.php b/src/applications/people/controller/PhabricatorPeopleNewController.php index a64d00f26..21dce0419 100644 --- a/src/applications/people/controller/PhabricatorPeopleNewController.php +++ b/src/applications/people/controller/PhabricatorPeopleNewController.php @@ -1,218 +1,218 @@ requireApplicationCapability( - PeopleCreateUsersCapability::CAPABILITY); $type = $request->getURIData('type'); $admin = $request->getUser(); switch ($type) { case 'standard': + $this->requireApplicationCapability( + PeopleCreateUsersCapability::CAPABILITY); $is_bot = false; break; case 'bot': $is_bot = true; break; default: return new Aphront404Response(); } $user = new PhabricatorUser(); $require_real_name = PhabricatorEnv::getEnvConfig('user.require-real-name'); $e_username = true; $e_realname = $require_real_name ? true : null; $e_email = true; $errors = array(); $welcome_checked = true; $new_email = null; if ($request->isFormPost()) { $welcome_checked = $request->getInt('welcome'); $user->setUsername($request->getStr('username')); $new_email = $request->getStr('email'); if (!strlen($new_email)) { $errors[] = pht('Email is required.'); $e_email = pht('Required'); } else if (!PhabricatorUserEmail::isAllowedAddress($new_email)) { $e_email = pht('Invalid'); $errors[] = PhabricatorUserEmail::describeAllowedAddresses(); } else { $e_email = null; } $user->setRealName($request->getStr('realname')); if (!strlen($user->getUsername())) { $errors[] = pht('Username is required.'); $e_username = pht('Required'); } else if (!PhabricatorUser::validateUsername($user->getUsername())) { $errors[] = PhabricatorUser::describeValidUsername(); $e_username = pht('Invalid'); } else { $e_username = null; } if (!strlen($user->getRealName()) && $require_real_name) { $errors[] = pht('Real name is required.'); $e_realname = pht('Required'); } else { $e_realname = null; } if (!$errors) { try { $email = id(new PhabricatorUserEmail()) ->setAddress($new_email) ->setIsVerified(0); // Automatically approve the user, since an admin is creating them. $user->setIsApproved(1); // If the user is a bot, approve their email too. if ($is_bot) { $email->setIsVerified(1); } id(new PhabricatorUserEditor()) ->setActor($admin) ->createNewUser($user, $email); if ($is_bot) { id(new PhabricatorUserEditor()) ->setActor($admin) ->makeSystemAgentUser($user, true); } if ($welcome_checked && !$is_bot) { $user->sendWelcomeEmail($admin); } $response = id(new AphrontRedirectResponse()) ->setURI('/p/'.$user->getUsername().'/'); return $response; } catch (AphrontDuplicateKeyQueryException $ex) { $errors[] = pht('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 = pht('Duplicate'); } if ($same_email) { $e_email = pht('Duplicate'); } } } } $form = id(new AphrontFormView()) ->setUser($admin); if ($is_bot) { $form->appendRemarkupInstructions( pht( 'You are creating a new **bot/script** user account.')); } else { $form->appendRemarkupInstructions( pht( 'You are creating a new **standard** user account.')); } $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Username')) ->setName('username') ->setValue($user->getUsername()) ->setError($e_username)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Real Name')) ->setName('realname') ->setValue($user->getRealName()) ->setError($e_realname)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Email')) ->setName('email') ->setValue($new_email) ->setCaption(PhabricatorUserEmail::describeAllowedAddresses()) ->setError($e_email)); if (!$is_bot) { $form->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'welcome', 1, pht('Send "Welcome to Phabricator" email with login instructions.'), $welcome_checked)); } $form ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($this->getApplicationURI()) ->setValue(pht('Create User'))); if ($is_bot) { $form ->appendChild(id(new AphrontFormDividerControl())) ->appendRemarkupInstructions( pht( '**Why do bot/script accounts need an email address?**'. "\n\n". 'Although bots do not normally receive email from Phabricator, '. 'they can interact with other systems which require an email '. 'address. Examples include:'. "\n\n". " - If the account takes actions which //send// email, we need ". " an address to use in the //From// header.\n". " - If the account creates commits, Git and Mercurial require ". " an email address for authorship.\n". " - If you send email //to// Phabricator on behalf of the ". " account, the address can identify the sender.\n". " - Some internal authentication functions depend on accounts ". " having an email address.\n". "\n\n". "The address will automatically be verified, so you do not need ". "to be able to receive mail at this address, and can enter some ". "invalid or nonexistent (but correctly formatted) address like ". "`bot@yourcompany.com` if you prefer.")); } $title = pht('Create New User'); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setFormErrors($errors) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); return $this->buildApplicationPage( array( $crumbs, $form_box, ), array( 'title' => $title, )); } }