@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement most of the administrative UI for approval queues

Summary:
Nothing fancy here, just:

- UI to show users needing approval.
- "Approve" and "Disable" actions.
- Send "Approved" email on approve.
- "Approve" edit + log operations.
- "Wait for Approval" state for users who need approval.

There's still no natural way for users to end up not-approved -- you have to write directly to the database.

Test Plan: See screenshots.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Differential Revision: https://secure.phabricator.com/D7573

+269 -10
+6
src/__phutil_library_map__.php
··· 1082 1082 'PhabricatorAuthManagementRecoverWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php', 1083 1083 'PhabricatorAuthManagementRefreshWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRefreshWorkflow.php', 1084 1084 'PhabricatorAuthManagementWorkflow' => 'applications/auth/management/PhabricatorAuthManagementWorkflow.php', 1085 + 'PhabricatorAuthNeedsApprovalController' => 'applications/auth/controller/PhabricatorAuthNeedsApprovalController.php', 1085 1086 'PhabricatorAuthNewController' => 'applications/auth/controller/config/PhabricatorAuthNewController.php', 1086 1087 'PhabricatorAuthOldOAuthRedirectController' => 'applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php', 1087 1088 'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php', ··· 1600 1601 'PhabricatorPasteTransactionComment' => 'applications/paste/storage/PhabricatorPasteTransactionComment.php', 1601 1602 'PhabricatorPasteTransactionQuery' => 'applications/paste/query/PhabricatorPasteTransactionQuery.php', 1602 1603 'PhabricatorPasteViewController' => 'applications/paste/controller/PhabricatorPasteViewController.php', 1604 + 'PhabricatorPeopleApproveController' => 'applications/people/controller/PhabricatorPeopleApproveController.php', 1603 1605 'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php', 1606 + 'PhabricatorPeopleDisableController' => 'applications/people/controller/PhabricatorPeopleDisableController.php', 1604 1607 'PhabricatorPeopleEditController' => 'applications/people/controller/PhabricatorPeopleEditController.php', 1605 1608 'PhabricatorPeopleHovercardEventListener' => 'applications/people/event/PhabricatorPeopleHovercardEventListener.php', 1606 1609 'PhabricatorPeopleLdapController' => 'applications/people/controller/PhabricatorPeopleLdapController.php', ··· 3445 3448 'PhabricatorAuthManagementRecoverWorkflow' => 'PhabricatorAuthManagementWorkflow', 3446 3449 'PhabricatorAuthManagementRefreshWorkflow' => 'PhabricatorAuthManagementWorkflow', 3447 3450 'PhabricatorAuthManagementWorkflow' => 'PhutilArgumentWorkflow', 3451 + 'PhabricatorAuthNeedsApprovalController' => 'PhabricatorAuthController', 3448 3452 'PhabricatorAuthNewController' => 'PhabricatorAuthProviderConfigController', 3449 3453 'PhabricatorAuthOldOAuthRedirectController' => 'PhabricatorAuthController', 3450 3454 'PhabricatorAuthProviderConfig' => ··· 4011 4015 'PhabricatorPasteTransactionComment' => 'PhabricatorApplicationTransactionComment', 4012 4016 'PhabricatorPasteTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 4013 4017 'PhabricatorPasteViewController' => 'PhabricatorPasteController', 4018 + 'PhabricatorPeopleApproveController' => 'PhabricatorPeopleController', 4014 4019 'PhabricatorPeopleController' => 'PhabricatorController', 4020 + 'PhabricatorPeopleDisableController' => 'PhabricatorPeopleController', 4015 4021 'PhabricatorPeopleEditController' => 'PhabricatorPeopleController', 4016 4022 'PhabricatorPeopleHovercardEventListener' => 'PhabricatorEventListener', 4017 4023 'PhabricatorPeopleLdapController' => 'PhabricatorPeopleController',
+36
src/applications/auth/controller/PhabricatorAuthNeedsApprovalController.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthNeedsApprovalController 4 + extends PhabricatorAuthController { 5 + 6 + public function shouldRequireLogin() { 7 + return false; 8 + } 9 + 10 + public function shouldRequireEmailVerification() { 11 + return false; 12 + } 13 + 14 + public function processRequest() { 15 + $request = $this->getRequest(); 16 + $user = $request->getUser(); 17 + 18 + $wait_for_approval = pht( 19 + "Your account has been created, but needs to be approved by an ". 20 + "administrator. You'll receive an email once your account is approved."); 21 + 22 + $dialog = id(new AphrontDialogView()) 23 + ->setUser($user) 24 + ->setTitle(pht('Wait for Approval')) 25 + ->appendChild($wait_for_approval) 26 + ->addCancelButton('/', pht('Wait Patiently')); 27 + 28 + return $this->buildApplicationPage( 29 + $dialog, 30 + array( 31 + 'title' => pht('Wait For Approval'), 32 + 'device' => true, 33 + )); 34 + } 35 + 36 + }
+5
src/applications/base/controller/PhabricatorController.php
··· 120 120 return $this->delegateToController($controller); 121 121 } 122 122 } 123 + if (!$user->getIsApproved()) { 124 + $controller = new PhabricatorAuthNeedsApprovalController($request); 125 + $this->setCurrentApplication($auth_application); 126 + return $this->delegateToController($controller); 127 + } 123 128 } 124 129 125 130 // If the user doesn't have access to the application, don't let them use
+2
src/applications/people/application/PhabricatorApplicationPeople.php
··· 41 41 '/people/' => array( 42 42 '(query/(?P<key>[^/]+)/)?' => 'PhabricatorPeopleListController', 43 43 'logs/' => 'PhabricatorPeopleLogsController', 44 + 'approve/(?P<id>[1-9]\d*)/' => 'PhabricatorPeopleApproveController', 45 + 'disable/(?P<id>[1-9]\d*)/' => 'PhabricatorPeopleDisableController', 44 46 'edit/(?:(?P<id>[1-9]\d*)/(?:(?P<view>\w+)/)?)?' 45 47 => 'PhabricatorPeopleEditController', 46 48 'ldap/' => 'PhabricatorPeopleLdapController',
+66
src/applications/people/controller/PhabricatorPeopleApproveController.php
··· 1 + <?php 2 + 3 + final class PhabricatorPeopleApproveController 4 + extends PhabricatorPeopleController { 5 + 6 + private $id; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->id = idx($data, 'id'); 10 + } 11 + 12 + public function processRequest() { 13 + 14 + $request = $this->getRequest(); 15 + $admin = $request->getUser(); 16 + 17 + $user = id(new PhabricatorPeopleQuery()) 18 + ->setViewer($admin) 19 + ->withIDs(array($this->id)) 20 + ->executeOne(); 21 + if (!$user) { 22 + return new Aphront404Response(); 23 + } 24 + 25 + $done_uri = $this->getApplicationURI('query/approval/'); 26 + 27 + if ($request->isFormPost()) { 28 + id(new PhabricatorUserEditor()) 29 + ->setActor($admin) 30 + ->approveUser($user, true); 31 + 32 + $title = pht( 33 + 'Phabricator Account "%s" Approved', 34 + $user->getUsername(), 35 + $admin->getUsername()); 36 + 37 + $body = pht( 38 + "Your Phabricator account (%s) has been approved by %s. You can ". 39 + "login here:\n\n %s\n\n", 40 + $user->getUsername(), 41 + $admin->getUsername(), 42 + PhabricatorEnv::getProductionURI('/')); 43 + 44 + $mail = id(new PhabricatorMetaMTAMail()) 45 + ->addTos(array($user->getPHID())) 46 + ->addCCs(array($admin->getPHID())) 47 + ->setSubject('[Phabricator] '.$title) 48 + ->setBody($body) 49 + ->saveAndSend(); 50 + 51 + return id(new AphrontRedirectResponse())->setURI($done_uri); 52 + } 53 + 54 + $dialog = id(new AphrontDialogView()) 55 + ->setUser($admin) 56 + ->setTitle(pht('Confirm Approval')) 57 + ->appendChild( 58 + pht( 59 + 'Allow %s to access this Phabricator install?', 60 + phutil_tag('strong', array(), $user->getUsername()))) 61 + ->addCancelButton($done_uri) 62 + ->addSubmitButton(pht('Approve Account')); 63 + 64 + return id(new AphrontDialogResponse())->setDialog($dialog); 65 + } 66 + }
+48
src/applications/people/controller/PhabricatorPeopleDisableController.php
··· 1 + <?php 2 + 3 + final class PhabricatorPeopleDisableController 4 + extends PhabricatorPeopleController { 5 + 6 + private $id; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->id = idx($data, 'id'); 10 + } 11 + 12 + public function processRequest() { 13 + 14 + $request = $this->getRequest(); 15 + $admin = $request->getUser(); 16 + 17 + $user = id(new PhabricatorPeopleQuery()) 18 + ->setViewer($admin) 19 + ->withIDs(array($this->id)) 20 + ->executeOne(); 21 + if (!$user) { 22 + return new Aphront404Response(); 23 + } 24 + 25 + $done_uri = $this->getApplicationURI('query/approval/'); 26 + 27 + if ($request->isFormPost()) { 28 + id(new PhabricatorUserEditor()) 29 + ->setActor($admin) 30 + ->disableUser($user, true); 31 + 32 + return id(new AphrontRedirectResponse())->setURI($done_uri); 33 + } 34 + 35 + $dialog = id(new AphrontDialogView()) 36 + ->setUser($admin) 37 + ->setTitle(pht('Confirm Disable')) 38 + ->appendChild( 39 + pht( 40 + 'Disable %s? They will no longer be able to access Phabricator or '. 41 + 'receive email.', 42 + phutil_tag('strong', array(), $user->getUsername()))) 43 + ->addCancelButton($done_uri) 44 + ->addSubmitButton(pht('Disable Account')); 45 + 46 + return id(new AphrontDialogResponse())->setDialog($dialog); 47 + } 48 + }
+26 -7
src/applications/people/controller/PhabricatorPeopleListController.php
··· 38 38 39 39 $list = new PHUIObjectItemListView(); 40 40 41 + $is_approval = ($query->getQueryKey() == 'approval'); 42 + 41 43 foreach ($users as $user) { 42 44 $primary_email = $user->loadPrimaryEmail(); 43 45 if ($primary_email && $primary_email->getIsVerified()) { ··· 61 63 $item->addIcon('disable', pht('Disabled')); 62 64 } 63 65 64 - if (!$user->getIsApproved()) { 65 - $item->addIcon('raise-priority', pht('Not Approved')); 66 + if (!$is_approval) { 67 + if (!$user->getIsApproved()) { 68 + $item->addIcon('perflab-grey', pht('Needs Approval')); 69 + } 66 70 } 67 71 68 72 if ($user->getIsAdmin()) { ··· 74 78 } 75 79 76 80 if ($viewer->getIsAdmin()) { 77 - $uid = $user->getID(); 78 - $item->addAction( 79 - id(new PHUIListItemView()) 80 - ->setIcon('edit') 81 - ->setHref($this->getApplicationURI('edit/'.$uid.'/'))); 81 + $user_id = $user->getID(); 82 + if ($is_approval) { 83 + $item->addAction( 84 + id(new PHUIListItemView()) 85 + ->setIcon('disable') 86 + ->setName(pht('Disable')) 87 + ->setWorkflow(true) 88 + ->setHref($this->getApplicationURI('disable/'.$user_id.'/'))); 89 + $item->addAction( 90 + id(new PHUIListItemView()) 91 + ->setIcon('like') 92 + ->setName(pht('Approve')) 93 + ->setWorkflow(true) 94 + ->setHref($this->getApplicationURI('approve/'.$user_id.'/'))); 95 + } else { 96 + $item->addAction( 97 + id(new PHUIListItemView()) 98 + ->setIcon('edit') 99 + ->setHref($this->getApplicationURI('edit/'.$user_id.'/'))); 100 + } 82 101 } 83 102 84 103 $list->addItem($item);
+38
src/applications/people/editor/PhabricatorUserEditor.php
··· 297 297 /** 298 298 * @task role 299 299 */ 300 + public function approveUser(PhabricatorUser $user, $approve) { 301 + $actor = $this->requireActor(); 302 + 303 + if (!$user->getID()) { 304 + throw new Exception("User has not been created yet!"); 305 + } 306 + 307 + $user->openTransaction(); 308 + $user->beginWriteLocking(); 309 + 310 + $user->reload(); 311 + if ($user->getIsApproved() == $approve) { 312 + $user->endWriteLocking(); 313 + $user->killTransaction(); 314 + return $this; 315 + } 316 + 317 + $log = PhabricatorUserLog::newLog( 318 + $actor, 319 + $user, 320 + PhabricatorUserLog::ACTION_APPROVE); 321 + $log->setOldValue($user->getIsApproved()); 322 + $log->setNewValue($approve); 323 + 324 + $user->setIsApproved($approve); 325 + $user->save(); 326 + 327 + $log->save(); 328 + 329 + $user->endWriteLocking(); 330 + $user->saveTransaction(); 331 + 332 + return $this; 333 + } 334 + 335 + /** 336 + * @task role 337 + */ 300 338 public function deleteUser(PhabricatorUser $user, $disable) { 301 339 $actor = $this->requireActor(); 302 340
+16 -2
src/applications/people/query/PhabricatorPeopleQuery.php
··· 13 13 private $isAdmin; 14 14 private $isSystemAgent; 15 15 private $isDisabled; 16 + private $isApproved; 16 17 private $nameLike; 17 18 18 19 private $needPrimaryEmail; ··· 67 68 68 69 public function withIsDisabled($disabled) { 69 70 $this->isDisabled = $disabled; 71 + return $this; 72 + } 73 + 74 + public function withIsApproved($approved) { 75 + $this->isApproved = $approved; 70 76 return $this; 71 77 } 72 78 ··· 249 255 'user.isAdmin = 1'); 250 256 } 251 257 252 - if ($this->isDisabled) { 258 + if ($this->isDisabled !== null) { 253 259 $where[] = qsprintf( 254 260 $conn_r, 255 - 'user.isDisabled = 1'); 261 + 'user.isDisabled = %d', 262 + (int)$this->isDisabled); 263 + } 264 + 265 + if ($this->isApproved !== null) { 266 + $where[] = qsprintf( 267 + $conn_r, 268 + 'user.isApproved = %d', 269 + (int)$this->isApproved); 256 270 } 257 271 258 272 if ($this->isSystemAgent) {
+25 -1
src/applications/people/query/PhabricatorPeopleSearchEngine.php
··· 15 15 $saved->setParameter('isAdmin', $request->getStr('isAdmin')); 16 16 $saved->setParameter('isDisabled', $request->getStr('isDisabled')); 17 17 $saved->setParameter('isSystemAgent', $request->getStr('isSystemAgent')); 18 + $saved->setParameter('needsApproval', $request->getStr('needsApproval')); 18 19 $saved->setParameter('createdStart', $request->getStr('createdStart')); 19 20 $saved->setParameter('createdEnd', $request->getStr('createdEnd')); 20 21 ··· 40 41 $is_admin = $saved->getParameter('isAdmin'); 41 42 $is_disabled = $saved->getParameter('isDisabled'); 42 43 $is_system_agent = $saved->getParameter('isSystemAgent'); 44 + $needs_approval = $saved->getParameter('needsApproval'); 45 + $no_disabled = $saved->getParameter('noDisabled'); 43 46 44 47 if ($is_admin) { 45 48 $query->withIsAdmin(true); ··· 47 50 48 51 if ($is_disabled) { 49 52 $query->withIsDisabled(true); 53 + } else if ($no_disabled) { 54 + $query->withIsDisabled(false); 50 55 } 51 56 52 57 if ($is_system_agent) { 53 58 $query->withIsSystemAgent(true); 54 59 } 55 60 61 + if ($needs_approval) { 62 + $query->withIsApproved(false); 63 + } 64 + 56 65 $start = $this->parseDateTime($saved->getParameter('createdStart')); 57 66 $end = $this->parseDateTime($saved->getParameter('createdEnd')); 58 67 ··· 79 88 $is_admin = $saved->getParameter('isAdmin'); 80 89 $is_disabled = $saved->getParameter('isDisabled'); 81 90 $is_system_agent = $saved->getParameter('isSystemAgent'); 91 + $needs_approval = $saved->getParameter('needsApproval'); 82 92 83 93 $form 84 94 ->appendChild( ··· 108 118 'isSystemAgent', 109 119 1, 110 120 pht('Show only System Agents.'), 111 - $is_system_agent)); 121 + $is_system_agent) 122 + ->addCheckbox( 123 + 'needsApproval', 124 + 1, 125 + pht('Show only users who need approval.'), 126 + $needs_approval)); 112 127 113 128 $this->appendCustomFieldsToForm($form, $saved); 114 129 ··· 130 145 'all' => pht('All'), 131 146 ); 132 147 148 + $viewer = $this->requireViewer(); 149 + if ($viewer->getIsAdmin()) { 150 + $names['approval'] = pht('Approval Queue'); 151 + } 152 + 133 153 return $names; 134 154 } 135 155 ··· 140 160 switch ($query_key) { 141 161 case 'all': 142 162 return $query; 163 + case 'approval': 164 + return $query 165 + ->setParameter('needsApproval', true) 166 + ->setParameter('noDisabled', true); 143 167 } 144 168 145 169 return parent::buildSavedQueryFromBuiltin($query_key);
+1
src/applications/people/storage/PhabricatorUserLog.php
··· 13 13 const ACTION_ADMIN = 'admin'; 14 14 const ACTION_SYSTEM_AGENT = 'system-agent'; 15 15 const ACTION_DISABLE = 'disable'; 16 + const ACTION_APPROVE = 'approve'; 16 17 const ACTION_DELETE = 'delete'; 17 18 18 19 const ACTION_CONDUIT_CERTIFICATE = 'conduit-cert';