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

Give administrators selective access to System Agent settings panels

Summary: Ref T4065. Give administrators an "Edit Settings" link from profiles, which allows selective edit of settings panels. Enable Conduit, SSH Keys, and VCS Password.

Test Plan:
  - Used these panels for a bot.
  - Used these panels on my own account.
  - Tried to use these panels for a non-bot account, was denied.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T4065

Differential Revision: https://secure.phabricator.com/D8668
This commit is contained in:
epriestley 2014-04-02 12:06:05 -07:00
parent c9268c4858
commit 04b9f94602
8 changed files with 190 additions and 79 deletions

View file

@ -2,6 +2,10 @@
final class DiffusionSetPasswordPanel extends PhabricatorSettingsPanel { final class DiffusionSetPasswordPanel extends PhabricatorSettingsPanel {
public function isEditableByAdministrators() {
return true;
}
public function getPanelKey() { public function getPanelKey() {
return 'vcspassword'; return 'vcspassword';
} }
@ -19,7 +23,8 @@ final class DiffusionSetPasswordPanel extends PhabricatorSettingsPanel {
} }
public function processRequest(AphrontRequest $request) { public function processRequest(AphrontRequest $request) {
$user = $request->getUser(); $viewer = $request->getUser();
$user = $this->getUser();
$vcspassword = id(new PhabricatorRepositoryVCSPassword()) $vcspassword = id(new PhabricatorRepositoryVCSPassword())
->loadOneWhere( ->loadOneWhere(
@ -68,7 +73,12 @@ final class DiffusionSetPasswordPanel extends PhabricatorSettingsPanel {
$e_password = pht('Does Not Match'); $e_password = pht('Does Not Match');
$e_confirm = pht('Does Not Match'); $e_confirm = pht('Does Not Match');
$errors[] = pht('Password and confirmation do not match.'); $errors[] = pht('Password and confirmation do not match.');
} else if ($user->comparePassword($envelope)) { } else if ($viewer->comparePassword($envelope)) {
// NOTE: The above test is against $viewer (not $user), so that the
// error message below makes sense in the case that the two are
// different, and because an admin reusing their own password is bad,
// while system agents generally do not have passwords anyway.
$e_password = pht('Not Unique'); $e_password = pht('Not Unique');
$e_confirm = pht('Not Unique'); $e_confirm = pht('Not Unique');
$errors[] = pht( $errors[] = pht(
@ -97,7 +107,7 @@ final class DiffusionSetPasswordPanel extends PhabricatorSettingsPanel {
$title = pht('Set VCS Password'); $title = pht('Set VCS Password');
$form = id(new AphrontFormView()) $form = id(new AphrontFormView())
->setUser($user) ->setUser($viewer)
->appendRemarkupInstructions( ->appendRemarkupInstructions(
pht( pht(
'To access repositories hosted by Phabricator over HTTP, you must '. 'To access repositories hosted by Phabricator over HTTP, you must '.
@ -193,7 +203,7 @@ final class DiffusionSetPasswordPanel extends PhabricatorSettingsPanel {
->setFormErrors($errors); ->setFormErrors($errors);
$remove_form = id(new AphrontFormView()) $remove_form = id(new AphrontFormView())
->setUser($user); ->setUser($viewer);
if ($vcspassword->getID()) { if ($vcspassword->getID()) {
$remove_form $remove_form

View file

@ -35,7 +35,6 @@ final class PhabricatorPeopleEditController
$nav->setBaseURI(new PhutilURI($base_uri)); $nav->setBaseURI(new PhutilURI($base_uri));
$nav->addLabel(pht('User Information')); $nav->addLabel(pht('User Information'));
$nav->addFilter('basic', pht('Basic Information')); $nav->addFilter('basic', pht('Basic Information'));
$nav->addFilter('cert', pht('Conduit Certificate'));
$nav->addFilter('profile', $nav->addFilter('profile',
pht('View Profile'), '/p/'.$user->getUsername().'/'); pht('View Profile'), '/p/'.$user->getUsername().'/');
@ -60,9 +59,6 @@ final class PhabricatorPeopleEditController
case 'basic': case 'basic':
$response = $this->processBasicRequest($user); $response = $this->processBasicRequest($user);
break; break;
case 'cert':
$response = $this->processCertificateRequest($user);
break;
default: default:
return new Aphront404Response(); return new Aphront404Response();
} }
@ -327,47 +323,6 @@ final class PhabricatorPeopleEditController
return array($form_box); return array($form_box);
} }
private function processCertificateRequest($user) {
$request = $this->getRequest();
$admin = $request->getUser();
$inst = pht('You can use this certificate '.
'to write scripts or bots which interface with Phabricator over '.
'Conduit.');
$form = new AphrontFormView();
$form
->setUser($admin)
->setAction($request->getRequestURI())
->appendChild(
phutil_tag('p', array('class' => 'aphront-form-instructions'), $inst));
if ($user->getIsSystemAgent()) {
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Username'))
->setValue($user->getUsername()))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Certificate'))
->setValue($user->getConduitCertificate()));
} else {
$form->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Certificate'))
->setValue(
pht('You may only view the certificates of System Agents.')));
}
$title = pht('Conduit Certificate');
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setForm($form);
return array($form_box);
}
private function getRoleInstructions() { private function getRoleInstructions() {
$roles_link = phutil_tag( $roles_link = phutil_tag(
'a', 'a',

View file

@ -64,6 +64,14 @@ final class PhabricatorPeopleProfileController
->setWorkflow(!$can_edit)); ->setWorkflow(!$can_edit));
if ($viewer->getIsAdmin()) { if ($viewer->getIsAdmin()) {
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('wrench')
->setName(pht('Edit Settings'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit)
->setHref('/settings/'.$user->getID().'/'));
if ($user->getIsAdmin()) { if ($user->getIsAdmin()) {
$empower_icon = 'lower-priority'; $empower_icon = 'lower-priority';
$empower_name = pht('Remove Administrator'); $empower_name = pht('Remove Administrator');

View file

@ -21,7 +21,8 @@ final class PhabricatorApplicationSettings extends PhabricatorApplication {
public function getRoutes() { public function getRoutes() {
return array( return array(
'/settings/' => array( '/settings/' => array(
'(?:panel/(?P<key>[^/]+)/)?' => 'PhabricatorSettingsMainController', '(?:(?P<id>\d+)/)?(?:panel/(?P<key>[^/]+)/)?'
=> 'PhabricatorSettingsMainController',
'adjust/' => 'PhabricatorSettingsAdjustController', 'adjust/' => 'PhabricatorSettingsAdjustController',
), ),
); );

View file

@ -3,14 +3,48 @@
final class PhabricatorSettingsMainController final class PhabricatorSettingsMainController
extends PhabricatorController { extends PhabricatorController {
private $id;
private $key; private $key;
private $user;
private function getUser() {
return $this->user;
}
private function isSelf() {
$viewer_phid = $this->getRequest()->getUser()->getPHID();
$user_phid = $this->getUser()->getPHID();
return ($viewer_phid == $user_phid);
}
public function willProcessRequest(array $data) { public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
$this->key = idx($data, 'key'); $this->key = idx($data, 'key');
} }
public function processRequest() { public function processRequest() {
$request = $this->getRequest(); $request = $this->getRequest();
$viewer = $request->getUser();
if ($this->id) {
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$this->user = $user;
} else {
$this->user = $viewer;
}
$panels = $this->buildPanels(); $panels = $this->buildPanels();
$nav = $this->renderSideNav($panels); $nav = $this->renderSideNav($panels);
@ -19,13 +53,27 @@ final class PhabricatorSettingsMainController
$panel = $panels[$key]; $panel = $panels[$key];
$panel->setUser($this->getUser());
$panel->setViewer($viewer);
$response = $panel->processRequest($request); $response = $panel->processRequest($request);
if ($response instanceof AphrontResponse) { if ($response instanceof AphrontResponse) {
return $response; return $response;
} }
$nav->appendChild($response); $crumbs = $this->buildApplicationCrumbs();
if (!$this->isSelf()) {
$crumbs->addTextCrumb(
$this->getUser()->getUsername(),
'/p/'.$this->getUser()->getUsername().'/');
}
$crumbs->addTextCrumb($panel->getPanelName());
$nav->appendChild(
array(
$crumbs,
$response,
));
return $this->buildApplicationPage( return $this->buildApplicationPage(
$nav, $nav,
array( array(
@ -54,6 +102,13 @@ final class PhabricatorSettingsMainController
if (!$panel->isEnabled()) { if (!$panel->isEnabled()) {
continue; continue;
} }
if (!$this->isSelf()) {
if (!$panel->isEditableByAdministrators()) {
continue;
}
}
if (!empty($result[$key])) { if (!empty($result[$key])) {
throw new Exception(pht( throw new Exception(pht(
"Two settings panels share the same panel key ('%s'): %s, %s.", "Two settings panels share the same panel key ('%s'): %s, %s.",
@ -61,17 +116,29 @@ final class PhabricatorSettingsMainController
get_class($panel), get_class($panel),
get_class($result[$key]))); get_class($result[$key])));
} }
$result[$key] = $panel; $result[$key] = $panel;
} }
$result = msort($result, 'getPanelSortKey'); $result = msort($result, 'getPanelSortKey');
if (!$result) {
throw new Exception(pht('No settings panels are available.'));
}
return $result; return $result;
} }
private function renderSideNav(array $panels) { private function renderSideNav(array $panels) {
$nav = new AphrontSideNavFilterView(); $nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI('/panel/')));
if ($this->isSelf()) {
$base_uri = 'panel/';
} else {
$base_uri = $this->getUser()->getID().'/panel/';
}
$nav->setBaseURI(new PhutilURI($this->getApplicationURI($base_uri)));
$group = null; $group = null;
foreach ($panels as $panel) { foreach ($panels as $panel) {

View file

@ -17,6 +17,27 @@
*/ */
abstract class PhabricatorSettingsPanel { abstract class PhabricatorSettingsPanel {
private $user;
private $viewer;
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function getUser() {
return $this->user;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
/* -( Panel Configuration )------------------------------------------------ */ /* -( Panel Configuration )------------------------------------------------ */
@ -86,6 +107,18 @@ abstract class PhabricatorSettingsPanel {
} }
/**
* Return true if this panel is available to administrators while editing
* system agent accounts.
*
* @return bool True to enable edit by administrators.
* @task config
*/
public function isEditableByAdministrators() {
return false;
}
/* -( Panel Implementation )----------------------------------------------- */ /* -( Panel Implementation )----------------------------------------------- */
@ -117,7 +150,15 @@ abstract class PhabricatorSettingsPanel {
final public function getPanelURI($path = '') { final public function getPanelURI($path = '') {
$key = $this->getPanelKey(); $key = $this->getPanelKey();
$key = phutil_escape_uri($key); $key = phutil_escape_uri($key);
return '/settings/panel/'.$key.'/'.ltrim($path, '/');
$path = ltrim($path, '/');
if ($this->getUser()->getPHID() != $this->getViewer()->getPHID()) {
$user_id = $this->getUser()->getID();
return "/settings/{$user_id}/panel/{$key}/{$path}";
} else {
return "/settings/panel/{$key}/{$path}";
}
} }

View file

@ -3,12 +3,16 @@
final class PhabricatorSettingsPanelConduit final class PhabricatorSettingsPanelConduit
extends PhabricatorSettingsPanel { extends PhabricatorSettingsPanel {
public function isEditableByAdministrators() {
return true;
}
public function getPanelKey() { public function getPanelKey() {
return 'conduit'; return 'conduit';
} }
public function getPanelName() { public function getPanelName() {
return pht('Conduit'); return pht('Conduit Certificate');
} }
public function getPanelGroup() { public function getPanelGroup() {
@ -16,12 +20,13 @@ final class PhabricatorSettingsPanelConduit
} }
public function processRequest(AphrontRequest $request) { public function processRequest(AphrontRequest $request) {
$user = $request->getUser(); $user = $this->getUser();
$viewer = $request->getUser();
if ($request->isFormPost()) { if ($request->isFormPost()) {
if (!$request->isDialogFormPost()) { if (!$request->isDialogFormPost()) {
$dialog = new AphrontDialogView(); $dialog = new AphrontDialogView();
$dialog->setUser($user); $dialog->setUser($viewer);
$dialog->setTitle(pht('Really regenerate session?')); $dialog->setTitle(pht('Really regenerate session?'));
$dialog->setSubmitURI($this->getPanelURI()); $dialog->setSubmitURI($this->getPanelURI());
$dialog->addSubmitButton(pht('Regenerate')); $dialog->addSubmitButton(pht('Regenerate'));
@ -69,7 +74,7 @@ final class PhabricatorSettingsPanelConduit
$cert_form = new AphrontFormView(); $cert_form = new AphrontFormView();
$cert_form $cert_form
->setUser($user) ->setUser($viewer)
->appendChild(phutil_tag( ->appendChild(phutil_tag(
'p', 'p',
array('class' => 'aphront-form-instructions'), array('class' => 'aphront-form-instructions'),
@ -93,7 +98,7 @@ final class PhabricatorSettingsPanelConduit
$regen_form = new AphrontFormView(); $regen_form = new AphrontFormView();
$regen_form $regen_form
->setUser($user) ->setUser($viewer)
->setAction($this->getPanelURI()) ->setAction($this->getPanelURI())
->setWorkflow(true) ->setWorkflow(true)
->appendChild(phutil_tag( ->appendChild(phutil_tag(

View file

@ -3,6 +3,10 @@
final class PhabricatorSettingsPanelSSHKeys final class PhabricatorSettingsPanelSSHKeys
extends PhabricatorSettingsPanel { extends PhabricatorSettingsPanel {
public function isEditableByAdministrators() {
return true;
}
public function getPanelKey() { public function getPanelKey() {
return 'ssh'; return 'ssh';
} }
@ -20,8 +24,8 @@ final class PhabricatorSettingsPanelSSHKeys
} }
public function processRequest(AphrontRequest $request) { public function processRequest(AphrontRequest $request) {
$viewer = $request->getUser();
$user = $request->getUser(); $user = $this->getUser();
$generate = $request->getStr('generate'); $generate = $request->getStr('generate');
if ($generate) { if ($generate) {
@ -37,7 +41,7 @@ final class PhabricatorSettingsPanelSSHKeys
$id = nonempty($edit, $delete); $id = nonempty($edit, $delete);
if ($id && is_numeric($id)) { if ($id && is_numeric($id)) {
// NOTE: Prevent editing/deleting of keys you don't own. // NOTE: This prevents editing/deleting of keys not owned by the user.
$key = id(new PhabricatorUserSSHKey())->loadOneWhere( $key = id(new PhabricatorUserSSHKey())->loadOneWhere(
'userPHID = %s AND id = %d', 'userPHID = %s AND id = %d',
$user->getPHID(), $user->getPHID(),
@ -142,7 +146,7 @@ final class PhabricatorSettingsPanelSSHKeys
} }
$form = id(new AphrontFormView()) $form = id(new AphrontFormView())
->setUser($user) ->setUser($viewer)
->addHiddenInput('edit', $is_new ? 'true' : $key->getID()) ->addHiddenInput('edit', $is_new ? 'true' : $key->getID())
->appendChild( ->appendChild(
id(new AphrontFormTextControl()) id(new AphrontFormTextControl())
@ -170,8 +174,8 @@ final class PhabricatorSettingsPanelSSHKeys
} }
private function renderKeyListView(AphrontRequest $request) { private function renderKeyListView(AphrontRequest $request) {
$user = $this->getUser();
$user = $request->getUser(); $viewer = $request->getUser();
$keys = id(new PhabricatorUserSSHKey())->loadAllWhere( $keys = id(new PhabricatorUserSSHKey())->loadAllWhere(
'userPHID = %s', 'userPHID = %s',
@ -188,8 +192,8 @@ final class PhabricatorSettingsPanelSSHKeys
$key->getName()), $key->getName()),
$key->getKeyComment(), $key->getKeyComment(),
$key->getKeyType(), $key->getKeyType(),
phabricator_date($key->getDateCreated(), $user), phabricator_date($key->getDateCreated(), $viewer),
phabricator_time($key->getDateCreated(), $user), phabricator_time($key->getDateCreated(), $viewer),
javelin_tag( javelin_tag(
'a', 'a',
array( array(
@ -266,7 +270,8 @@ final class PhabricatorSettingsPanelSSHKeys
AphrontRequest $request, AphrontRequest $request,
PhabricatorUserSSHKey $key) { PhabricatorUserSSHKey $key) {
$user = $request->getUser(); $viewer = $request->getUser();
$user = $this->getUser();
$name = phutil_tag('strong', array(), $key->getName()); $name = phutil_tag('strong', array(), $key->getName());
@ -277,7 +282,7 @@ final class PhabricatorSettingsPanelSSHKeys
} }
$dialog = id(new AphrontDialogView()) $dialog = id(new AphrontDialogView())
->setUser($user) ->setUser($viewer)
->addHiddenInput('delete', $key->getID()) ->addHiddenInput('delete', $key->getID())
->setTitle(pht('Really delete SSH Public Key?')) ->setTitle(pht('Really delete SSH Public Key?'))
->appendChild(phutil_tag('p', array(), pht( ->appendChild(phutil_tag('p', array(), pht(
@ -291,10 +296,12 @@ final class PhabricatorSettingsPanelSSHKeys
->setDialog($dialog); ->setDialog($dialog);
} }
private function processGenerate( private function processGenerate(AphrontRequest $request) {
AphrontRequest $request) { $user = $this->getUser();
$viewer = $request->getUser(); $viewer = $request->getUser();
$is_self = ($user->getPHID() == $viewer->getPHID());
if ($request->isFormPost()) { if ($request->isFormPost()) {
$keys = PhabricatorSSHKeyGenerator::generateKeypair(); $keys = PhabricatorSSHKeyGenerator::generateKeypair();
list($public_key, $private_key) = $keys; list($public_key, $private_key) = $keys;
@ -308,7 +315,7 @@ final class PhabricatorSettingsPanelSSHKeys
)); ));
$key = id(new PhabricatorUserSSHKey()) $key = id(new PhabricatorUserSSHKey())
->setUserPHID($viewer->getPHID()) ->setUserPHID($user->getPHID())
->setName('id_rsa_phabricator') ->setName('id_rsa_phabricator')
->setKeyType('rsa') ->setKeyType('rsa')
->setKeyBody($public_key) ->setKeyBody($public_key)
@ -320,6 +327,17 @@ final class PhabricatorSettingsPanelSSHKeys
// disabling workflow on cancel so the page reloads, showing the new // disabling workflow on cancel so the page reloads, showing the new
// key. // key.
if ($is_self) {
$what_happened = pht(
'The public key has been associated with your Phabricator '.
'account. Use the button below to download the private key.');
} else {
$what_happened = pht(
'The public key has been associated with the %s account. '.
'Use the button below to download the private key.',
phutil_tag('strong', array(), $user->getUsername()));
}
$dialog = id(new AphrontDialogView()) $dialog = id(new AphrontDialogView())
->setTitle(pht('Download Private Key')) ->setTitle(pht('Download Private Key'))
->setUser($viewer) ->setUser($viewer)
@ -329,10 +347,7 @@ final class PhabricatorSettingsPanelSSHKeys
->appendParagraph( ->appendParagraph(
pht( pht(
'Successfully generated a new keypair.')) 'Successfully generated a new keypair.'))
->appendParagraph( ->appendParagraph($what_happened)
pht(
'The public key has been associated with your Phabricator '.
'account. Use the button below to download the private key.'))
->appendParagraph( ->appendParagraph(
pht( pht(
'After you download the private key, it will be destroyed. '. 'After you download the private key, it will be destroyed. '.
@ -350,13 +365,22 @@ final class PhabricatorSettingsPanelSSHKeys
try { try {
PhabricatorSSHKeyGenerator::assertCanGenerateKeypair(); PhabricatorSSHKeyGenerator::assertCanGenerateKeypair();
if ($is_self) {
$explain = pht(
'This will generate an SSH keypair, associate the public key '.
'with your account, and let you download the private key.');
} else {
$explain = pht(
'This will generate an SSH keypair, associate the public key with '.
'the %s account, and let you download the private key.',
phutil_tag('strong', array(), $user->getUsername()));
}
$dialog $dialog
->addHiddenInput('generate', true) ->addHiddenInput('generate', true)
->setTitle(pht('Generate New Keypair')) ->setTitle(pht('Generate New Keypair'))
->appendParagraph( ->appendParagraph($explain)
pht(
"This will generate an SSH keypair, associate the public key ".
"with your account, and let you download the private key."))
->appendParagraph( ->appendParagraph(
pht( pht(
"Phabricator will not retain a copy of the private key.")) "Phabricator will not retain a copy of the private key."))