1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-30 01:10:58 +01:00

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
This commit is contained in:
epriestley 2013-11-13 11:24:18 -08:00
parent a3c811f281
commit c8320923c4
11 changed files with 269 additions and 10 deletions

View file

@ -1082,6 +1082,7 @@ phutil_register_library_map(array(
'PhabricatorAuthManagementRecoverWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php', 'PhabricatorAuthManagementRecoverWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php',
'PhabricatorAuthManagementRefreshWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRefreshWorkflow.php', 'PhabricatorAuthManagementRefreshWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRefreshWorkflow.php',
'PhabricatorAuthManagementWorkflow' => 'applications/auth/management/PhabricatorAuthManagementWorkflow.php', 'PhabricatorAuthManagementWorkflow' => 'applications/auth/management/PhabricatorAuthManagementWorkflow.php',
'PhabricatorAuthNeedsApprovalController' => 'applications/auth/controller/PhabricatorAuthNeedsApprovalController.php',
'PhabricatorAuthNewController' => 'applications/auth/controller/config/PhabricatorAuthNewController.php', 'PhabricatorAuthNewController' => 'applications/auth/controller/config/PhabricatorAuthNewController.php',
'PhabricatorAuthOldOAuthRedirectController' => 'applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php', 'PhabricatorAuthOldOAuthRedirectController' => 'applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php',
'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php', 'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php',
@ -1600,7 +1601,9 @@ phutil_register_library_map(array(
'PhabricatorPasteTransactionComment' => 'applications/paste/storage/PhabricatorPasteTransactionComment.php', 'PhabricatorPasteTransactionComment' => 'applications/paste/storage/PhabricatorPasteTransactionComment.php',
'PhabricatorPasteTransactionQuery' => 'applications/paste/query/PhabricatorPasteTransactionQuery.php', 'PhabricatorPasteTransactionQuery' => 'applications/paste/query/PhabricatorPasteTransactionQuery.php',
'PhabricatorPasteViewController' => 'applications/paste/controller/PhabricatorPasteViewController.php', 'PhabricatorPasteViewController' => 'applications/paste/controller/PhabricatorPasteViewController.php',
'PhabricatorPeopleApproveController' => 'applications/people/controller/PhabricatorPeopleApproveController.php',
'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php', 'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php',
'PhabricatorPeopleDisableController' => 'applications/people/controller/PhabricatorPeopleDisableController.php',
'PhabricatorPeopleEditController' => 'applications/people/controller/PhabricatorPeopleEditController.php', 'PhabricatorPeopleEditController' => 'applications/people/controller/PhabricatorPeopleEditController.php',
'PhabricatorPeopleHovercardEventListener' => 'applications/people/event/PhabricatorPeopleHovercardEventListener.php', 'PhabricatorPeopleHovercardEventListener' => 'applications/people/event/PhabricatorPeopleHovercardEventListener.php',
'PhabricatorPeopleLdapController' => 'applications/people/controller/PhabricatorPeopleLdapController.php', 'PhabricatorPeopleLdapController' => 'applications/people/controller/PhabricatorPeopleLdapController.php',
@ -3445,6 +3448,7 @@ phutil_register_library_map(array(
'PhabricatorAuthManagementRecoverWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementRecoverWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementRefreshWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementRefreshWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementWorkflow' => 'PhutilArgumentWorkflow', 'PhabricatorAuthManagementWorkflow' => 'PhutilArgumentWorkflow',
'PhabricatorAuthNeedsApprovalController' => 'PhabricatorAuthController',
'PhabricatorAuthNewController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthNewController' => 'PhabricatorAuthProviderConfigController',
'PhabricatorAuthOldOAuthRedirectController' => 'PhabricatorAuthController', 'PhabricatorAuthOldOAuthRedirectController' => 'PhabricatorAuthController',
'PhabricatorAuthProviderConfig' => 'PhabricatorAuthProviderConfig' =>
@ -4011,7 +4015,9 @@ phutil_register_library_map(array(
'PhabricatorPasteTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorPasteTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhabricatorPasteTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorPasteTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorPasteViewController' => 'PhabricatorPasteController', 'PhabricatorPasteViewController' => 'PhabricatorPasteController',
'PhabricatorPeopleApproveController' => 'PhabricatorPeopleController',
'PhabricatorPeopleController' => 'PhabricatorController', 'PhabricatorPeopleController' => 'PhabricatorController',
'PhabricatorPeopleDisableController' => 'PhabricatorPeopleController',
'PhabricatorPeopleEditController' => 'PhabricatorPeopleController', 'PhabricatorPeopleEditController' => 'PhabricatorPeopleController',
'PhabricatorPeopleHovercardEventListener' => 'PhabricatorEventListener', 'PhabricatorPeopleHovercardEventListener' => 'PhabricatorEventListener',
'PhabricatorPeopleLdapController' => 'PhabricatorPeopleController', 'PhabricatorPeopleLdapController' => 'PhabricatorPeopleController',

View file

@ -0,0 +1,36 @@
<?php
final class PhabricatorAuthNeedsApprovalController
extends PhabricatorAuthController {
public function shouldRequireLogin() {
return false;
}
public function shouldRequireEmailVerification() {
return false;
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$wait_for_approval = pht(
"Your account has been created, but needs to be approved by an ".
"administrator. You'll receive an email once your account is approved.");
$dialog = id(new AphrontDialogView())
->setUser($user)
->setTitle(pht('Wait for Approval'))
->appendChild($wait_for_approval)
->addCancelButton('/', pht('Wait Patiently'));
return $this->buildApplicationPage(
$dialog,
array(
'title' => pht('Wait For Approval'),
'device' => true,
));
}
}

View file

@ -120,6 +120,11 @@ abstract class PhabricatorController extends AphrontController {
return $this->delegateToController($controller); return $this->delegateToController($controller);
} }
} }
if (!$user->getIsApproved()) {
$controller = new PhabricatorAuthNeedsApprovalController($request);
$this->setCurrentApplication($auth_application);
return $this->delegateToController($controller);
}
} }
// If the user doesn't have access to the application, don't let them use // If the user doesn't have access to the application, don't let them use

View file

@ -41,6 +41,8 @@ final class PhabricatorApplicationPeople extends PhabricatorApplication {
'/people/' => array( '/people/' => array(
'(query/(?P<key>[^/]+)/)?' => 'PhabricatorPeopleListController', '(query/(?P<key>[^/]+)/)?' => 'PhabricatorPeopleListController',
'logs/' => 'PhabricatorPeopleLogsController', 'logs/' => 'PhabricatorPeopleLogsController',
'approve/(?P<id>[1-9]\d*)/' => 'PhabricatorPeopleApproveController',
'disable/(?P<id>[1-9]\d*)/' => 'PhabricatorPeopleDisableController',
'edit/(?:(?P<id>[1-9]\d*)/(?:(?P<view>\w+)/)?)?' 'edit/(?:(?P<id>[1-9]\d*)/(?:(?P<view>\w+)/)?)?'
=> 'PhabricatorPeopleEditController', => 'PhabricatorPeopleEditController',
'ldap/' => 'PhabricatorPeopleLdapController', 'ldap/' => 'PhabricatorPeopleLdapController',

View file

@ -0,0 +1,66 @@
<?php
final class PhabricatorPeopleApproveController
extends PhabricatorPeopleController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$admin = $request->getUser();
$user = id(new PhabricatorPeopleQuery())
->setViewer($admin)
->withIDs(array($this->id))
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$done_uri = $this->getApplicationURI('query/approval/');
if ($request->isFormPost()) {
id(new PhabricatorUserEditor())
->setActor($admin)
->approveUser($user, true);
$title = pht(
'Phabricator Account "%s" Approved',
$user->getUsername(),
$admin->getUsername());
$body = pht(
"Your Phabricator account (%s) has been approved by %s. You can ".
"login here:\n\n %s\n\n",
$user->getUsername(),
$admin->getUsername(),
PhabricatorEnv::getProductionURI('/'));
$mail = id(new PhabricatorMetaMTAMail())
->addTos(array($user->getPHID()))
->addCCs(array($admin->getPHID()))
->setSubject('[Phabricator] '.$title)
->setBody($body)
->saveAndSend();
return id(new AphrontRedirectResponse())->setURI($done_uri);
}
$dialog = id(new AphrontDialogView())
->setUser($admin)
->setTitle(pht('Confirm Approval'))
->appendChild(
pht(
'Allow %s to access this Phabricator install?',
phutil_tag('strong', array(), $user->getUsername())))
->addCancelButton($done_uri)
->addSubmitButton(pht('Approve Account'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}

View file

@ -0,0 +1,48 @@
<?php
final class PhabricatorPeopleDisableController
extends PhabricatorPeopleController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$admin = $request->getUser();
$user = id(new PhabricatorPeopleQuery())
->setViewer($admin)
->withIDs(array($this->id))
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$done_uri = $this->getApplicationURI('query/approval/');
if ($request->isFormPost()) {
id(new PhabricatorUserEditor())
->setActor($admin)
->disableUser($user, true);
return id(new AphrontRedirectResponse())->setURI($done_uri);
}
$dialog = id(new AphrontDialogView())
->setUser($admin)
->setTitle(pht('Confirm Disable'))
->appendChild(
pht(
'Disable %s? They will no longer be able to access Phabricator or '.
'receive email.',
phutil_tag('strong', array(), $user->getUsername())))
->addCancelButton($done_uri)
->addSubmitButton(pht('Disable Account'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}

View file

@ -38,6 +38,8 @@ final class PhabricatorPeopleListController extends PhabricatorPeopleController
$list = new PHUIObjectItemListView(); $list = new PHUIObjectItemListView();
$is_approval = ($query->getQueryKey() == 'approval');
foreach ($users as $user) { foreach ($users as $user) {
$primary_email = $user->loadPrimaryEmail(); $primary_email = $user->loadPrimaryEmail();
if ($primary_email && $primary_email->getIsVerified()) { if ($primary_email && $primary_email->getIsVerified()) {
@ -61,8 +63,10 @@ final class PhabricatorPeopleListController extends PhabricatorPeopleController
$item->addIcon('disable', pht('Disabled')); $item->addIcon('disable', pht('Disabled'));
} }
if (!$user->getIsApproved()) { if (!$is_approval) {
$item->addIcon('raise-priority', pht('Not Approved')); if (!$user->getIsApproved()) {
$item->addIcon('perflab-grey', pht('Needs Approval'));
}
} }
if ($user->getIsAdmin()) { if ($user->getIsAdmin()) {
@ -74,11 +78,26 @@ final class PhabricatorPeopleListController extends PhabricatorPeopleController
} }
if ($viewer->getIsAdmin()) { if ($viewer->getIsAdmin()) {
$uid = $user->getID(); $user_id = $user->getID();
$item->addAction( if ($is_approval) {
id(new PHUIListItemView()) $item->addAction(
->setIcon('edit') id(new PHUIListItemView())
->setHref($this->getApplicationURI('edit/'.$uid.'/'))); ->setIcon('disable')
->setName(pht('Disable'))
->setWorkflow(true)
->setHref($this->getApplicationURI('disable/'.$user_id.'/')));
$item->addAction(
id(new PHUIListItemView())
->setIcon('like')
->setName(pht('Approve'))
->setWorkflow(true)
->setHref($this->getApplicationURI('approve/'.$user_id.'/')));
} else {
$item->addAction(
id(new PHUIListItemView())
->setIcon('edit')
->setHref($this->getApplicationURI('edit/'.$user_id.'/')));
}
} }
$list->addItem($item); $list->addItem($item);

View file

@ -294,6 +294,44 @@ final class PhabricatorUserEditor extends PhabricatorEditor {
} }
/**
* @task role
*/
public function approveUser(PhabricatorUser $user, $approve) {
$actor = $this->requireActor();
if (!$user->getID()) {
throw new Exception("User has not been created yet!");
}
$user->openTransaction();
$user->beginWriteLocking();
$user->reload();
if ($user->getIsApproved() == $approve) {
$user->endWriteLocking();
$user->killTransaction();
return $this;
}
$log = PhabricatorUserLog::newLog(
$actor,
$user,
PhabricatorUserLog::ACTION_APPROVE);
$log->setOldValue($user->getIsApproved());
$log->setNewValue($approve);
$user->setIsApproved($approve);
$user->save();
$log->save();
$user->endWriteLocking();
$user->saveTransaction();
return $this;
}
/** /**
* @task role * @task role
*/ */

View file

@ -13,6 +13,7 @@ final class PhabricatorPeopleQuery
private $isAdmin; private $isAdmin;
private $isSystemAgent; private $isSystemAgent;
private $isDisabled; private $isDisabled;
private $isApproved;
private $nameLike; private $nameLike;
private $needPrimaryEmail; private $needPrimaryEmail;
@ -70,6 +71,11 @@ final class PhabricatorPeopleQuery
return $this; return $this;
} }
public function withIsApproved($approved) {
$this->isApproved = $approved;
return $this;
}
public function withNameLike($like) { public function withNameLike($like) {
$this->nameLike = $like; $this->nameLike = $like;
return $this; return $this;
@ -249,10 +255,18 @@ final class PhabricatorPeopleQuery
'user.isAdmin = 1'); 'user.isAdmin = 1');
} }
if ($this->isDisabled) { if ($this->isDisabled !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn_r,
'user.isDisabled = 1'); 'user.isDisabled = %d',
(int)$this->isDisabled);
}
if ($this->isApproved !== null) {
$where[] = qsprintf(
$conn_r,
'user.isApproved = %d',
(int)$this->isApproved);
} }
if ($this->isSystemAgent) { if ($this->isSystemAgent) {

View file

@ -15,6 +15,7 @@ final class PhabricatorPeopleSearchEngine
$saved->setParameter('isAdmin', $request->getStr('isAdmin')); $saved->setParameter('isAdmin', $request->getStr('isAdmin'));
$saved->setParameter('isDisabled', $request->getStr('isDisabled')); $saved->setParameter('isDisabled', $request->getStr('isDisabled'));
$saved->setParameter('isSystemAgent', $request->getStr('isSystemAgent')); $saved->setParameter('isSystemAgent', $request->getStr('isSystemAgent'));
$saved->setParameter('needsApproval', $request->getStr('needsApproval'));
$saved->setParameter('createdStart', $request->getStr('createdStart')); $saved->setParameter('createdStart', $request->getStr('createdStart'));
$saved->setParameter('createdEnd', $request->getStr('createdEnd')); $saved->setParameter('createdEnd', $request->getStr('createdEnd'));
@ -40,6 +41,8 @@ final class PhabricatorPeopleSearchEngine
$is_admin = $saved->getParameter('isAdmin'); $is_admin = $saved->getParameter('isAdmin');
$is_disabled = $saved->getParameter('isDisabled'); $is_disabled = $saved->getParameter('isDisabled');
$is_system_agent = $saved->getParameter('isSystemAgent'); $is_system_agent = $saved->getParameter('isSystemAgent');
$needs_approval = $saved->getParameter('needsApproval');
$no_disabled = $saved->getParameter('noDisabled');
if ($is_admin) { if ($is_admin) {
$query->withIsAdmin(true); $query->withIsAdmin(true);
@ -47,12 +50,18 @@ final class PhabricatorPeopleSearchEngine
if ($is_disabled) { if ($is_disabled) {
$query->withIsDisabled(true); $query->withIsDisabled(true);
} else if ($no_disabled) {
$query->withIsDisabled(false);
} }
if ($is_system_agent) { if ($is_system_agent) {
$query->withIsSystemAgent(true); $query->withIsSystemAgent(true);
} }
if ($needs_approval) {
$query->withIsApproved(false);
}
$start = $this->parseDateTime($saved->getParameter('createdStart')); $start = $this->parseDateTime($saved->getParameter('createdStart'));
$end = $this->parseDateTime($saved->getParameter('createdEnd')); $end = $this->parseDateTime($saved->getParameter('createdEnd'));
@ -79,6 +88,7 @@ final class PhabricatorPeopleSearchEngine
$is_admin = $saved->getParameter('isAdmin'); $is_admin = $saved->getParameter('isAdmin');
$is_disabled = $saved->getParameter('isDisabled'); $is_disabled = $saved->getParameter('isDisabled');
$is_system_agent = $saved->getParameter('isSystemAgent'); $is_system_agent = $saved->getParameter('isSystemAgent');
$needs_approval = $saved->getParameter('needsApproval');
$form $form
->appendChild( ->appendChild(
@ -108,7 +118,12 @@ final class PhabricatorPeopleSearchEngine
'isSystemAgent', 'isSystemAgent',
1, 1,
pht('Show only System Agents.'), pht('Show only System Agents.'),
$is_system_agent)); $is_system_agent)
->addCheckbox(
'needsApproval',
1,
pht('Show only users who need approval.'),
$needs_approval));
$this->appendCustomFieldsToForm($form, $saved); $this->appendCustomFieldsToForm($form, $saved);
@ -130,6 +145,11 @@ final class PhabricatorPeopleSearchEngine
'all' => pht('All'), 'all' => pht('All'),
); );
$viewer = $this->requireViewer();
if ($viewer->getIsAdmin()) {
$names['approval'] = pht('Approval Queue');
}
return $names; return $names;
} }
@ -140,6 +160,10 @@ final class PhabricatorPeopleSearchEngine
switch ($query_key) { switch ($query_key) {
case 'all': case 'all':
return $query; return $query;
case 'approval':
return $query
->setParameter('needsApproval', true)
->setParameter('noDisabled', true);
} }
return parent::buildSavedQueryFromBuiltin($query_key); return parent::buildSavedQueryFromBuiltin($query_key);

View file

@ -13,6 +13,7 @@ final class PhabricatorUserLog extends PhabricatorUserDAO {
const ACTION_ADMIN = 'admin'; const ACTION_ADMIN = 'admin';
const ACTION_SYSTEM_AGENT = 'system-agent'; const ACTION_SYSTEM_AGENT = 'system-agent';
const ACTION_DISABLE = 'disable'; const ACTION_DISABLE = 'disable';
const ACTION_APPROVE = 'approve';
const ACTION_DELETE = 'delete'; const ACTION_DELETE = 'delete';
const ACTION_CONDUIT_CERTIFICATE = 'conduit-cert'; const ACTION_CONDUIT_CERTIFICATE = 'conduit-cert';