Admin and disabled flags for users

Summary:
Provide an "isAdmin" flag for users, to designate administrative users.

Restore the account editing interface and allow it to set role flags and reset
passwords.

Provide an "isDisabled" flag for users and shut down all system access for them.

Test Plan:
Created "admin" and "disabled" users. Did administrative things with the admin
user. Tried to do stuff with the disabled user and was rebuffed. Tried to access
administrative interfaces with a normal non-admin user and was denied.

Reviewed By: aran
Reviewers: tuomaspelkonen, jungejason, aran
CC: ccheever, aran
Differential Revision: 278
This commit is contained in:
epriestley
2011-05-12 10:06:54 -07:00
parent 03b56c1035
commit f9f8ef0e6e
17 changed files with 468 additions and 84 deletions

View File

@@ -301,6 +301,7 @@ phutil_register_library_map(array(
'PhabricatorDirectoryItemEditController' => 'applications/directory/controller/itemedit',
'PhabricatorDirectoryItemListController' => 'applications/directory/controller/itemlist',
'PhabricatorDirectoryMainController' => 'applications/directory/controller/main',
'PhabricatorDisabledUserController' => 'applications/auth/controller/disabled',
'PhabricatorDraft' => 'applications/draft/storage/draft',
'PhabricatorDraftDAO' => 'applications/draft/storage/base',
'PhabricatorEditPreferencesController' => 'applications/preferences/controller/edit',
@@ -727,6 +728,7 @@ phutil_register_library_map(array(
'PhabricatorDirectoryItemEditController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryItemListController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryMainController' => 'PhabricatorDirectoryController',
'PhabricatorDisabledUserController' => 'PhabricatorAuthController',
'PhabricatorDraft' => 'PhabricatorDraftDAO',
'PhabricatorDraftDAO' => 'PhabricatorLiskDAO',
'PhabricatorEditPreferencesController' => 'PhabricatorPreferencesController',

View File

@@ -69,7 +69,8 @@ class AphrontDefaultApplicationConfiguration
),
'/people/' => array(
'$' => 'PhabricatorPeopleListController',
'edit/(?:(?P<username>\w+)/)?$' => 'PhabricatorPeopleEditController',
'edit/(?:(?P<id>\d+)/(?:(?P<view>\w+)/)?)?$'
=> 'PhabricatorPeopleEditController',
),
'/p/(?P<username>\w+)/$' => 'PhabricatorPeopleProfileController',
'/profile/' => array(

View File

@@ -0,0 +1,43 @@
<?php
/*
* Copyright 2011 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.
*/
class PhabricatorDisabledUserController extends PhabricatorAuthController {
public function shouldRequireEnabledUser() {
return false;
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if (!$user->getIsDisabled()) {
return new Aphront404Response();
}
$failure_view = new AphrontRequestFailureView();
$failure_view->setHeader('Account Disabled');
$failure_view->appendChild('<p>Your account has been disabled.</p>');
return $this->buildStandardPageResponse(
$failure_view,
array(
'title' => 'Account Disabled',
));
}
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/404');
phutil_require_module('phabricator', 'applications/auth/controller/base');
phutil_require_module('phabricator', 'view/page/failure');
phutil_require_source('PhabricatorDisabledUserController.php');

View File

@@ -22,6 +22,11 @@ class PhabricatorLogoutController extends PhabricatorAuthController {
return true;
}
public function shouldRequireEnabledUser() {
// Allow disabled users to logout.
return false;
}
public function processRequest() {
$request = $this->getRequest();

View File

@@ -22,6 +22,14 @@ abstract class PhabricatorController extends AphrontController {
return true;
}
public function shouldRequireAdmin() {
return false;
}
public function shouldRequireEnabledUser() {
return true;
}
final public function willBeginExecution() {
$request = $this->getRequest();
@@ -47,6 +55,13 @@ abstract class PhabricatorController extends AphrontController {
$request->setUser($user);
if ($user->getIsDisabled() && $this->shouldRequireEnabledUser()) {
$disabled_user_controller = newv(
'PhabricatorDisabledUserController',
array($request));
return $this->delegateToController($disabled_user_controller);
}
if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) {
if ($user->getConsoleEnabled() ||
PhabricatorEnv::getEnvConfig('darkconsole.always-on')) {
@@ -59,11 +74,21 @@ abstract class PhabricatorController extends AphrontController {
$login_controller = newv('PhabricatorLoginController', array($request));
return $this->delegateToController($login_controller);
}
if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) {
return new Aphront404Response();
}
}
public function buildStandardPageView() {
$view = new PhabricatorStandardPageView();
$view->setRequest($this->getRequest());
if ($this->shouldRequireAdmin()) {
$view->setIsAdminInterface(true);
}
return $view;
}

View File

@@ -8,6 +8,7 @@
phutil_require_module('phabricator', 'aphront/console/core');
phutil_require_module('phabricator', 'aphront/controller');
phutil_require_module('phabricator', 'aphront/response/404');
phutil_require_module('phabricator', 'aphront/response/webpage');
phutil_require_module('phabricator', 'applications/people/storage/user');
phutil_require_module('phabricator', 'infrastructure/env');

View File

@@ -58,6 +58,10 @@ class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
return $this->setMessage("Invalid user '{$user_id}'")->save();
}
if ($user->getIsDisabled()) {
return $this->setMessage("User '{$user_id}' is disabled")->save();
}
$this->setAuthorPHID($user->getPHID());
$receiver = self::loadReceiverObject($receiver_name);

View File

@@ -18,20 +18,25 @@
class PhabricatorPeopleEditController extends PhabricatorPeopleController {
private $username;
public function shouldRequireAdmin() {
return true;
}
private $id;
private $view;
public function willProcessRequest(array $data) {
$this->username = idx($data, 'username');
$this->id = idx($data, 'id');
$this->view = idx($data, 'view');
}
public function processRequest() {
return new Aphront404Response();
$request = $this->getRequest();
$admin = $request->getUser();
if ($this->username) {
$user = id(new PhabricatorUser())->loadOneWhere(
'userName = %s',
$this->username);
if ($this->id) {
$user = id(new PhabricatorUser())->load($this->id);
if (!$user) {
return new Aphront404Response();
}
@@ -39,6 +44,77 @@ class PhabricatorPeopleEditController extends PhabricatorPeopleController {
$user = new PhabricatorUser();
}
$views = array(
'basic' => 'Basic Information',
'password' => 'Reset Password',
'role' => 'Edit Role',
);
if (!$user->getID()) {
$view = 'basic';
} else if (isset($views[$this->view])) {
$view = $this->view;
} else {
$view = 'basic';
}
$content = array();
if ($request->getStr('saved')) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle('Changed Saved');
$notice->appendChild('<p>Your changes were saved.</p>');
$content[] = $notice;
}
switch ($view) {
case 'basic':
$response = $this->processBasicRequest($user);
break;
case 'password':
$response = $this->processPasswordRequest($user);
break;
case 'role':
$response = $this->processRoleRequest($user);
break;
}
if ($response instanceof AphrontResponse) {
return $response;
}
$content[] = $response;
if ($user->getID()) {
$side_nav = new AphrontSideNavView();
$side_nav->appendChild($content);
foreach ($views as $key => $name) {
$side_nav->addNavItem(
phutil_render_tag(
'a',
array(
'href' => '/people/edit/'.$user->getID().'/'.$key.'/',
'class' => ($key == $view)
? 'aphront-side-nav-selected'
: null,
),
phutil_escape_html($name)));
}
$content = $side_nav;
}
return $this->buildStandardPageResponse(
$content,
array(
'title' => 'Edit User',
));
}
private function processBasicRequest(PhabricatorUser $user) {
$request = $this->getRequest();
$admin = $request->getUser();
$e_username = true;
$e_realname = true;
$e_email = true;
@@ -58,23 +134,46 @@ class PhabricatorPeopleEditController extends PhabricatorPeopleController {
} else if (!preg_match('/^[a-z0-9]+$/', $user->getUsername())) {
$errors[] = "Username must consist of only numbers and letters.";
$e_username = 'Invalid';
} else {
$e_username = null;
}
if (!strlen($user->getRealName())) {
$errors[] = 'Real name is required.';
$e_realname = 'Required';
} else {
$e_realname = null;
}
if (!strlen($user->getEmail())) {
$errors[] = 'Email is required.';
$e_email = 'Required';
} else {
$e_email = null;
}
if (!$errors) {
$user->save();
$response = id(new AphrontRedirectResponse())
->setURI('/p/'.$user->getUsername().'/');
return $response;
try {
$user->save();
$response = id(new AphrontRedirectResponse())
->setURI('/people/edit/'.$user->getID().'/?saved=true');
return $response;
} catch (AphrontQueryDuplicateKeyException $ex) {
$errors[] = 'Username and email must be unique.';
$same_username = id(new PhabricatorUser())
->loadOneWhere('username = %s', $user->getUsername());
$same_email = id(new PhabricatorUser())
->loadOneWhere('email = %s', $user->getEmail());
if ($same_username) {
$e_username = 'Duplicate';
}
if ($same_email) {
$e_email = 'Duplicate';
}
}
}
}
@@ -86,9 +185,9 @@ class PhabricatorPeopleEditController extends PhabricatorPeopleController {
}
$form = new AphrontFormView();
$form->setUser($request->getUser());
if ($user->getUsername()) {
$form->setAction('/people/edit/'.$user->getUsername().'/');
$form->setUser($admin);
if ($user->getID()) {
$form->setAction('/people/edit/'.$user->getID().'/');
} else {
$form->setAction('/people/edit/');
}
@@ -135,11 +234,146 @@ class PhabricatorPeopleEditController extends PhabricatorPeopleController {
$panel->appendChild($form);
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
return $this->buildStandardPageResponse(
array($error_view, $panel),
array(
'title' => 'Edit User',
));
return array($error_view, $panel);
}
private function processPasswordRequest(PhabricatorUser $user) {
$request = $this->getRequest();
$admin = $request->getUser();
$e_password = true;
$errors = array();
if ($request->isFormPost()) {
if (strlen($request->getStr('password'))) {
$user->setPassword($request->getStr('password'));
$e_password = null;
} else {
$errors[] = 'Password is required.';
$e_password = 'Required';
}
if (!$errors) {
$user->save();
return id(new AphrontRedirectResponse())
->setURI($request->getRequestURI()->alter('saved', 'true'));
}
}
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle('Form Errors')
->setErrors($errors);
}
$form = id(new AphrontFormView())
->setUser($admin)
->setAction($request->getRequestURI()->alter('saved', null))
->appendChild(
'<p class="aphront-form-instructions">Submitting this form will '.
'change this user\'s password. They will no longer be able to login '.
'with their old password.</p>')
->appendChild(
id(new AphrontFormTextControl())
->setLabel('New Password')
->setName('password')
->setError($e_password))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Reset Password'));
$panel = new AphrontPanelView();
$panel->setHeader('Reset Password');
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->appendChild($form);
return array($error_view, $panel);
}
private function processRoleRequest(PhabricatorUser $user) {
$request = $this->getRequest();
$admin = $request->getUser();
$is_self = ($user->getID() == $admin->getID());
$errors = array();
if ($request->isFormPost()) {
if ($is_self) {
$errors[] = "You can not edit your own role.";
} else {
$user->setIsAdmin($request->getInt('is_admin'));
$user->setIsDisabled($request->getInt('is_disabled'));
$user->setIsSystemAgent($request->getInt('is_agent'));
}
if (!$errors) {
$user->save();
return id(new AphrontRedirectResponse())
->setURI($request->getRequestURI()->alter('saved', 'true'));
}
}
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle('Form Errors')
->setErrors($errors);
}
$form = id(new AphrontFormView())
->setUser($admin)
->setAction($request->getRequestURI()->alter('saved', null));
if ($is_self) {
$form->appendChild(
'<p class="aphront-form-instructions">NOTE: You can not edit your own '.
'role.</p>');
}
$form
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'is_admin',
1,
'Admin: wields absolute power.',
$user->getIsAdmin())
->setDisabled($is_self))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'is_disabled',
1,
'Disabled: can not login.',
$user->getIsDisabled())
->setDisabled($is_self))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'is_agent',
1,
'Agent: system agent (robot).',
$user->getIsSystemAgent())
->setDisabled($is_self));
if (!$is_self) {
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Edit Role'));
}
$panel = new AphrontPanelView();
$panel->setHeader('Edit Role');
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->appendChild($form);
return array($error_view, $panel);
}
}

View File

@@ -11,11 +11,14 @@ phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/people/controller/base');
phutil_require_module('phabricator', 'applications/people/storage/user');
phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/checkbox');
phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/form/control/text');
phutil_require_module('phabricator', 'view/form/error');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phabricator', 'view/layout/sidenav');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');

View File

@@ -20,6 +20,7 @@ class PhabricatorPeopleListController extends PhabricatorPeopleController {
public function processRequest() {
$request = $this->getRequest();
$is_admin = $request->getUser()->getIsAdmin();
$user = new PhabricatorUser();
@@ -41,41 +42,92 @@ class PhabricatorPeopleListController extends PhabricatorPeopleController {
$rows = array();
foreach ($users as $user) {
$rows[] = array(
$user->getPHID(),
$user->getUserName(),
$user->getRealName(),
phutil_render_tag(
$cols = array();
$cols[] = date('M jS, Y', $user->getDateCreated());
$cols[] = date('g:i:s A', $user->getDateCreated());
$cols[] = phutil_render_tag(
'a',
array(
'href' => '/p/'.$user->getUsername().'/',
),
phutil_escape_html($user->getUserName()));
$cols[] = phutil_escape_html($user->getRealName());
if ($is_admin) {
$status = '';
if ($user->getIsDisabled()) {
$status = 'Disabled';
} else if ($user->getIsAdmin()) {
$status = 'Admin';
} else {
$status = '-';
}
$cols[] = $status;
$cols[] = phutil_render_tag(
'a',
array(
'class' => 'button grey small',
'href' => '/p/'.$user->getUsername().'/',
'href' => '/people/edit/'.$user->getID().'/',
),
'View Profile'),
);
'Administrate User');
}
$rows[] = $cols;
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'PHID',
'Username',
'Real Name',
'',
));
$table->setColumnClasses(
array(
null,
null,
'wide',
'action',
));
if ($is_admin) {
$table->setHeaders(
array(
'Join Date',
'Time',
'Username',
'Real Name',
'Status',
'',
));
$table->setColumnClasses(
array(
null,
'right',
'pri',
'wide',
null,
'action',
));
} else {
$table->setHeaders(
array(
'Join Date',
'Time',
'Username',
'Real Name',
));
$table->setColumnClasses(
array(
null,
'right',
'pri',
'wide',
));
}
$panel = new AphrontPanelView();
$panel->setHeader('People');
$panel->setHeader('People ('.number_format($count).')');
$panel->appendChild($table);
$panel->appendChild($pager);
if ($is_admin) {
$panel->addButton(
phutil_render_tag(
'a',
array(
'href' => '/people/edit/',
'class' => 'button green',
),
'Create New Account'));
}
return $this->buildStandardPageResponse($panel, array(
'title' => 'People',
'tab' => 'people',

View File

@@ -35,6 +35,8 @@ class PhabricatorUser extends PhabricatorUserDAO {
protected $conduitCertificate;
protected $isSystemAgent = 0;
protected $isAdmin = 0;
protected $isDisabled = 0;
private $preferences = null;
@@ -56,9 +58,13 @@ class PhabricatorUser extends PhabricatorUserDAO {
}
public function setPassword($password) {
$this->setPasswordSalt(md5(mt_rand()));
$hash = $this->hashPassword($password);
$this->setPasswordHash($hash);
if (!strlen($password)) {
$this->setPasswordHash('');
} else {
$this->setPasswordSalt(md5(mt_rand()));
$hash = $this->hashPassword($password);
$this->setPasswordHash($hash);
}
return $this;
}
@@ -77,6 +83,12 @@ class PhabricatorUser extends PhabricatorUserDAO {
}
public function comparePassword($password) {
if (!strlen($password)) {
return false;
}
if (!strlen($this->getPasswordHash())) {
return false;
}
$password = $this->hashPassword($password);
return ($password === $this->getPasswordHash());
}

View File

@@ -46,6 +46,7 @@ class AphrontFormCheckboxControl extends AphrontFormControl {
'name' => $box['name'],
'value' => $box['value'],
'checked' => $box['checked'] ? 'checked' : null,
'disabled' => $this->getDisabled() ? 'disabled' : null,
));
$label = phutil_render_tag(
'label',

View File

@@ -25,6 +25,16 @@ class PhabricatorStandardPageView extends AphrontPageView {
private $glyph;
private $bodyContent;
private $request;
private $isAdminInterface;
public function setIsAdminInterface($is_admin_interface) {
$this->isAdminInterface = $is_admin_interface;
return $this;
}
public function getIsAdminInterface() {
return $this->isAdminInterface;
}
public function setRequest($request) {
$this->request = $request;
@@ -254,9 +264,14 @@ class PhabricatorStandardPageView extends AphrontPageView {
$foot_links = implode(' &middot; ', $foot_links);
$admin_class = null;
if ($this->getIsAdminInterface()) {
$admin_class = 'phabricator-admin-page-view';
}
return
($console ? '<darkconsole />' : null).
'<div class="phabricator-standard-page">'.
'<div class="phabricator-standard-page '.$admin_class.'">'.
'<div class="phabricator-standard-header">'.
'<div class="phabricator-login-details">'.
$login_stuff.