1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 14:52:41 +01:00

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

@ -35,43 +35,6 @@ return array(
// be 50x50px.
'user.default-profile-image-phid' => 'PHID-FILE-4d61229816cfe6f2b2a3',
// -- Access Control -------------------------------------------------------- //
// Phabricator users have one of three access levels: "anyone", "verified",
// or "admin". "anyone" means every user, including users who do not have
// accounts or are not logged into the system. "verified" is users who have
// accounts, are logged in, and have satisfied whatever verification steps
// the configuration requires (e.g., email verification and/or manual
// approval). "admin" is verified users with the "administrator" flag set.
// These configuration options control which access level is required to read
// data from Phabricator (e.g., view revisions and comments in Differential)
// and write data to Phabricator (e.g., upload files and create diffs). By
// default they are both set to "verified", meaning only verified user
// accounts can interact with the system in any meaningful way.
// If you are configuring an install for an open source project, you may
// want to reduce the "phabricator.read-access" requirement to "anyone". This
// will allow anyone to browse Phabricator content, even without logging in.
// Alternatively, you could raise the "phabricator.write-access" requirement
// to "admin", effectively creating a read-only install.
// Controls the minimum access level required to read data from Phabricator
// (e.g., view revisions in Differential). Allowed values are "anyone",
// "verified", or "admin". Note that "anyone" includes users who are not
// logged in! You should leave this at 'verified' unless you want your data
// to be publicly readable (e.g., you are developing open source software).
'phabricator.read-access' => 'verified',
// Controls the minimum access level required to write data to Phabricator
// (e.g., create new revisions in Differential). Allowed values are
// "verified" or "admin". Setting this to "admin" will effectively create a
// read-only install.
'phabricator.write-access' => 'verified',
// -- DarkConsole ----------------------------------------------------------- //
// DarkConsole is a administrative debugging/profiling tool built into

View file

@ -0,0 +1,5 @@
ALTER TABLE phabricator_user.user
ADD isDisabled bool NOT NULL;
ALTER TABLE phabricator_user.user
ADD isAdmin bool NOT NULL;

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.

View file

@ -19,6 +19,10 @@
position: relative;
}
.phabricator-admin-page-view .phabricator-standard-header {
background: #aa0000;
}
.phabricator-primary-navigation {
padding-top: 12px;
padding-bottom: 8px;