@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.

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

+190 -79
+14 -4
src/applications/diffusion/panel/DiffusionSetPasswordPanel.php
··· 2 2 3 3 final class DiffusionSetPasswordPanel extends PhabricatorSettingsPanel { 4 4 5 + public function isEditableByAdministrators() { 6 + return true; 7 + } 8 + 5 9 public function getPanelKey() { 6 10 return 'vcspassword'; 7 11 } ··· 19 23 } 20 24 21 25 public function processRequest(AphrontRequest $request) { 22 - $user = $request->getUser(); 26 + $viewer = $request->getUser(); 27 + $user = $this->getUser(); 23 28 24 29 $vcspassword = id(new PhabricatorRepositoryVCSPassword()) 25 30 ->loadOneWhere( ··· 68 73 $e_password = pht('Does Not Match'); 69 74 $e_confirm = pht('Does Not Match'); 70 75 $errors[] = pht('Password and confirmation do not match.'); 71 - } else if ($user->comparePassword($envelope)) { 76 + } else if ($viewer->comparePassword($envelope)) { 77 + // NOTE: The above test is against $viewer (not $user), so that the 78 + // error message below makes sense in the case that the two are 79 + // different, and because an admin reusing their own password is bad, 80 + // while system agents generally do not have passwords anyway. 81 + 72 82 $e_password = pht('Not Unique'); 73 83 $e_confirm = pht('Not Unique'); 74 84 $errors[] = pht( ··· 97 107 $title = pht('Set VCS Password'); 98 108 99 109 $form = id(new AphrontFormView()) 100 - ->setUser($user) 110 + ->setUser($viewer) 101 111 ->appendRemarkupInstructions( 102 112 pht( 103 113 'To access repositories hosted by Phabricator over HTTP, you must '. ··· 193 203 ->setFormErrors($errors); 194 204 195 205 $remove_form = id(new AphrontFormView()) 196 - ->setUser($user); 206 + ->setUser($viewer); 197 207 198 208 if ($vcspassword->getID()) { 199 209 $remove_form
-45
src/applications/people/controller/PhabricatorPeopleEditController.php
··· 35 35 $nav->setBaseURI(new PhutilURI($base_uri)); 36 36 $nav->addLabel(pht('User Information')); 37 37 $nav->addFilter('basic', pht('Basic Information')); 38 - $nav->addFilter('cert', pht('Conduit Certificate')); 39 38 $nav->addFilter('profile', 40 39 pht('View Profile'), '/p/'.$user->getUsername().'/'); 41 40 ··· 59 58 switch ($view) { 60 59 case 'basic': 61 60 $response = $this->processBasicRequest($user); 62 - break; 63 - case 'cert': 64 - $response = $this->processCertificateRequest($user); 65 61 break; 66 62 default: 67 63 return new Aphront404Response(); ··· 322 318 $form_box = id(new PHUIObjectBoxView()) 323 319 ->setHeaderText($title) 324 320 ->setFormErrors($errors) 325 - ->setForm($form); 326 - 327 - return array($form_box); 328 - } 329 - 330 - private function processCertificateRequest($user) { 331 - $request = $this->getRequest(); 332 - $admin = $request->getUser(); 333 - 334 - $inst = pht('You can use this certificate '. 335 - 'to write scripts or bots which interface with Phabricator over '. 336 - 'Conduit.'); 337 - $form = new AphrontFormView(); 338 - $form 339 - ->setUser($admin) 340 - ->setAction($request->getRequestURI()) 341 - ->appendChild( 342 - phutil_tag('p', array('class' => 'aphront-form-instructions'), $inst)); 343 - 344 - if ($user->getIsSystemAgent()) { 345 - $form 346 - ->appendChild( 347 - id(new AphrontFormTextControl()) 348 - ->setLabel(pht('Username')) 349 - ->setValue($user->getUsername())) 350 - ->appendChild( 351 - id(new AphrontFormTextAreaControl()) 352 - ->setLabel(pht('Certificate')) 353 - ->setValue($user->getConduitCertificate())); 354 - } else { 355 - $form->appendChild( 356 - id(new AphrontFormStaticControl()) 357 - ->setLabel(pht('Certificate')) 358 - ->setValue( 359 - pht('You may only view the certificates of System Agents.'))); 360 - } 361 - 362 - $title = pht('Conduit Certificate'); 363 - 364 - $form_box = id(new PHUIObjectBoxView()) 365 - ->setHeaderText($title) 366 321 ->setForm($form); 367 322 368 323 return array($form_box);
+8
src/applications/people/controller/PhabricatorPeopleProfileController.php
··· 64 64 ->setWorkflow(!$can_edit)); 65 65 66 66 if ($viewer->getIsAdmin()) { 67 + $actions->addAction( 68 + id(new PhabricatorActionView()) 69 + ->setIcon('wrench') 70 + ->setName(pht('Edit Settings')) 71 + ->setDisabled(!$can_edit) 72 + ->setWorkflow(!$can_edit) 73 + ->setHref('/settings/'.$user->getID().'/')); 74 + 67 75 if ($user->getIsAdmin()) { 68 76 $empower_icon = 'lower-priority'; 69 77 $empower_name = pht('Remove Administrator');
+2 -1
src/applications/settings/application/PhabricatorApplicationSettings.php
··· 21 21 public function getRoutes() { 22 22 return array( 23 23 '/settings/' => array( 24 - '(?:panel/(?P<key>[^/]+)/)?' => 'PhabricatorSettingsMainController', 24 + '(?:(?P<id>\d+)/)?(?:panel/(?P<key>[^/]+)/)?' 25 + => 'PhabricatorSettingsMainController', 25 26 'adjust/' => 'PhabricatorSettingsAdjustController', 26 27 ), 27 28 );
+69 -2
src/applications/settings/controller/PhabricatorSettingsMainController.php
··· 3 3 final class PhabricatorSettingsMainController 4 4 extends PhabricatorController { 5 5 6 + private $id; 6 7 private $key; 8 + private $user; 9 + 10 + private function getUser() { 11 + return $this->user; 12 + } 13 + 14 + private function isSelf() { 15 + $viewer_phid = $this->getRequest()->getUser()->getPHID(); 16 + $user_phid = $this->getUser()->getPHID(); 17 + return ($viewer_phid == $user_phid); 18 + } 7 19 8 20 public function willProcessRequest(array $data) { 21 + $this->id = idx($data, 'id'); 9 22 $this->key = idx($data, 'key'); 10 23 } 11 24 12 25 public function processRequest() { 13 26 $request = $this->getRequest(); 27 + $viewer = $request->getUser(); 28 + 29 + if ($this->id) { 30 + $user = id(new PhabricatorPeopleQuery()) 31 + ->setViewer($viewer) 32 + ->withIDs(array($this->id)) 33 + ->requireCapabilities( 34 + array( 35 + PhabricatorPolicyCapability::CAN_VIEW, 36 + PhabricatorPolicyCapability::CAN_EDIT, 37 + )) 38 + ->executeOne(); 39 + 40 + if (!$user) { 41 + return new Aphront404Response(); 42 + } 43 + 44 + $this->user = $user; 45 + } else { 46 + $this->user = $viewer; 47 + } 14 48 15 49 $panels = $this->buildPanels(); 16 50 $nav = $this->renderSideNav($panels); ··· 19 53 20 54 21 55 $panel = $panels[$key]; 56 + $panel->setUser($this->getUser()); 57 + $panel->setViewer($viewer); 22 58 23 59 $response = $panel->processRequest($request); 24 60 if ($response instanceof AphrontResponse) { 25 61 return $response; 26 62 } 27 63 28 - $nav->appendChild($response); 64 + $crumbs = $this->buildApplicationCrumbs(); 65 + if (!$this->isSelf()) { 66 + $crumbs->addTextCrumb( 67 + $this->getUser()->getUsername(), 68 + '/p/'.$this->getUser()->getUsername().'/'); 69 + } 70 + $crumbs->addTextCrumb($panel->getPanelName()); 71 + $nav->appendChild( 72 + array( 73 + $crumbs, 74 + $response, 75 + )); 76 + 29 77 return $this->buildApplicationPage( 30 78 $nav, 31 79 array( ··· 54 102 if (!$panel->isEnabled()) { 55 103 continue; 56 104 } 105 + 106 + if (!$this->isSelf()) { 107 + if (!$panel->isEditableByAdministrators()) { 108 + continue; 109 + } 110 + } 111 + 57 112 if (!empty($result[$key])) { 58 113 throw new Exception(pht( 59 114 "Two settings panels share the same panel key ('%s'): %s, %s.", ··· 61 116 get_class($panel), 62 117 get_class($result[$key]))); 63 118 } 119 + 64 120 $result[$key] = $panel; 65 121 } 66 122 67 123 $result = msort($result, 'getPanelSortKey'); 68 124 125 + if (!$result) { 126 + throw new Exception(pht('No settings panels are available.')); 127 + } 128 + 69 129 return $result; 70 130 } 71 131 72 132 private function renderSideNav(array $panels) { 73 133 $nav = new AphrontSideNavFilterView(); 74 - $nav->setBaseURI(new PhutilURI($this->getApplicationURI('/panel/'))); 134 + 135 + if ($this->isSelf()) { 136 + $base_uri = 'panel/'; 137 + } else { 138 + $base_uri = $this->getUser()->getID().'/panel/'; 139 + } 140 + 141 + $nav->setBaseURI(new PhutilURI($this->getApplicationURI($base_uri))); 75 142 76 143 $group = null; 77 144 foreach ($panels as $panel) {
+42 -1
src/applications/settings/panel/PhabricatorSettingsPanel.php
··· 17 17 */ 18 18 abstract class PhabricatorSettingsPanel { 19 19 20 + private $user; 21 + private $viewer; 22 + 23 + public function setUser(PhabricatorUser $user) { 24 + $this->user = $user; 25 + return $this; 26 + } 27 + 28 + public function getUser() { 29 + return $this->user; 30 + } 31 + 32 + public function setViewer(PhabricatorUser $viewer) { 33 + $this->viewer = $viewer; 34 + return $this; 35 + } 36 + 37 + public function getViewer() { 38 + return $this->viewer; 39 + } 40 + 20 41 21 42 /* -( Panel Configuration )------------------------------------------------ */ 22 43 ··· 86 107 } 87 108 88 109 110 + /** 111 + * Return true if this panel is available to administrators while editing 112 + * system agent accounts. 113 + * 114 + * @return bool True to enable edit by administrators. 115 + * @task config 116 + */ 117 + public function isEditableByAdministrators() { 118 + return false; 119 + } 120 + 121 + 89 122 /* -( Panel Implementation )----------------------------------------------- */ 90 123 91 124 ··· 117 150 final public function getPanelURI($path = '') { 118 151 $key = $this->getPanelKey(); 119 152 $key = phutil_escape_uri($key); 120 - return '/settings/panel/'.$key.'/'.ltrim($path, '/'); 153 + 154 + $path = ltrim($path, '/'); 155 + 156 + if ($this->getUser()->getPHID() != $this->getViewer()->getPHID()) { 157 + $user_id = $this->getUser()->getID(); 158 + return "/settings/{$user_id}/panel/{$key}/{$path}"; 159 + } else { 160 + return "/settings/panel/{$key}/{$path}"; 161 + } 121 162 } 122 163 123 164
+10 -5
src/applications/settings/panel/PhabricatorSettingsPanelConduit.php
··· 3 3 final class PhabricatorSettingsPanelConduit 4 4 extends PhabricatorSettingsPanel { 5 5 6 + public function isEditableByAdministrators() { 7 + return true; 8 + } 9 + 6 10 public function getPanelKey() { 7 11 return 'conduit'; 8 12 } 9 13 10 14 public function getPanelName() { 11 - return pht('Conduit'); 15 + return pht('Conduit Certificate'); 12 16 } 13 17 14 18 public function getPanelGroup() { ··· 16 20 } 17 21 18 22 public function processRequest(AphrontRequest $request) { 19 - $user = $request->getUser(); 23 + $user = $this->getUser(); 24 + $viewer = $request->getUser(); 20 25 21 26 if ($request->isFormPost()) { 22 27 if (!$request->isDialogFormPost()) { 23 28 $dialog = new AphrontDialogView(); 24 - $dialog->setUser($user); 29 + $dialog->setUser($viewer); 25 30 $dialog->setTitle(pht('Really regenerate session?')); 26 31 $dialog->setSubmitURI($this->getPanelURI()); 27 32 $dialog->addSubmitButton(pht('Regenerate')); ··· 69 74 70 75 $cert_form = new AphrontFormView(); 71 76 $cert_form 72 - ->setUser($user) 77 + ->setUser($viewer) 73 78 ->appendChild(phutil_tag( 74 79 'p', 75 80 array('class' => 'aphront-form-instructions'), ··· 93 98 94 99 $regen_form = new AphrontFormView(); 95 100 $regen_form 96 - ->setUser($user) 101 + ->setUser($viewer) 97 102 ->setAction($this->getPanelURI()) 98 103 ->setWorkflow(true) 99 104 ->appendChild(phutil_tag(
+45 -21
src/applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php
··· 3 3 final class PhabricatorSettingsPanelSSHKeys 4 4 extends PhabricatorSettingsPanel { 5 5 6 + public function isEditableByAdministrators() { 7 + return true; 8 + } 9 + 6 10 public function getPanelKey() { 7 11 return 'ssh'; 8 12 } ··· 20 24 } 21 25 22 26 public function processRequest(AphrontRequest $request) { 23 - 24 - $user = $request->getUser(); 27 + $viewer = $request->getUser(); 28 + $user = $this->getUser(); 25 29 26 30 $generate = $request->getStr('generate'); 27 31 if ($generate) { ··· 37 41 $id = nonempty($edit, $delete); 38 42 39 43 if ($id && is_numeric($id)) { 40 - // NOTE: Prevent editing/deleting of keys you don't own. 44 + // NOTE: This prevents editing/deleting of keys not owned by the user. 41 45 $key = id(new PhabricatorUserSSHKey())->loadOneWhere( 42 46 'userPHID = %s AND id = %d', 43 47 $user->getPHID(), ··· 142 146 } 143 147 144 148 $form = id(new AphrontFormView()) 145 - ->setUser($user) 149 + ->setUser($viewer) 146 150 ->addHiddenInput('edit', $is_new ? 'true' : $key->getID()) 147 151 ->appendChild( 148 152 id(new AphrontFormTextControl()) ··· 170 174 } 171 175 172 176 private function renderKeyListView(AphrontRequest $request) { 173 - 174 - $user = $request->getUser(); 177 + $user = $this->getUser(); 178 + $viewer = $request->getUser(); 175 179 176 180 $keys = id(new PhabricatorUserSSHKey())->loadAllWhere( 177 181 'userPHID = %s', ··· 188 192 $key->getName()), 189 193 $key->getKeyComment(), 190 194 $key->getKeyType(), 191 - phabricator_date($key->getDateCreated(), $user), 192 - phabricator_time($key->getDateCreated(), $user), 195 + phabricator_date($key->getDateCreated(), $viewer), 196 + phabricator_time($key->getDateCreated(), $viewer), 193 197 javelin_tag( 194 198 'a', 195 199 array( ··· 266 270 AphrontRequest $request, 267 271 PhabricatorUserSSHKey $key) { 268 272 269 - $user = $request->getUser(); 273 + $viewer = $request->getUser(); 274 + $user = $this->getUser(); 270 275 271 276 $name = phutil_tag('strong', array(), $key->getName()); 272 277 ··· 277 282 } 278 283 279 284 $dialog = id(new AphrontDialogView()) 280 - ->setUser($user) 285 + ->setUser($viewer) 281 286 ->addHiddenInput('delete', $key->getID()) 282 287 ->setTitle(pht('Really delete SSH Public Key?')) 283 288 ->appendChild(phutil_tag('p', array(), pht( ··· 291 296 ->setDialog($dialog); 292 297 } 293 298 294 - private function processGenerate( 295 - AphrontRequest $request) { 299 + private function processGenerate(AphrontRequest $request) { 300 + $user = $this->getUser(); 296 301 $viewer = $request->getUser(); 302 + 303 + $is_self = ($user->getPHID() == $viewer->getPHID()); 297 304 298 305 if ($request->isFormPost()) { 299 306 $keys = PhabricatorSSHKeyGenerator::generateKeypair(); ··· 308 315 )); 309 316 310 317 $key = id(new PhabricatorUserSSHKey()) 311 - ->setUserPHID($viewer->getPHID()) 318 + ->setUserPHID($user->getPHID()) 312 319 ->setName('id_rsa_phabricator') 313 320 ->setKeyType('rsa') 314 321 ->setKeyBody($public_key) ··· 320 327 // disabling workflow on cancel so the page reloads, showing the new 321 328 // key. 322 329 330 + if ($is_self) { 331 + $what_happened = pht( 332 + 'The public key has been associated with your Phabricator '. 333 + 'account. Use the button below to download the private key.'); 334 + } else { 335 + $what_happened = pht( 336 + 'The public key has been associated with the %s account. '. 337 + 'Use the button below to download the private key.', 338 + phutil_tag('strong', array(), $user->getUsername())); 339 + } 340 + 323 341 $dialog = id(new AphrontDialogView()) 324 342 ->setTitle(pht('Download Private Key')) 325 343 ->setUser($viewer) ··· 329 347 ->appendParagraph( 330 348 pht( 331 349 'Successfully generated a new keypair.')) 332 - ->appendParagraph( 333 - pht( 334 - 'The public key has been associated with your Phabricator '. 335 - 'account. Use the button below to download the private key.')) 350 + ->appendParagraph($what_happened) 336 351 ->appendParagraph( 337 352 pht( 338 353 'After you download the private key, it will be destroyed. '. ··· 350 365 351 366 try { 352 367 PhabricatorSSHKeyGenerator::assertCanGenerateKeypair(); 368 + 369 + if ($is_self) { 370 + $explain = pht( 371 + 'This will generate an SSH keypair, associate the public key '. 372 + 'with your account, and let you download the private key.'); 373 + } else { 374 + $explain = pht( 375 + 'This will generate an SSH keypair, associate the public key with '. 376 + 'the %s account, and let you download the private key.', 377 + phutil_tag('strong', array(), $user->getUsername())); 378 + } 379 + 353 380 $dialog 354 381 ->addHiddenInput('generate', true) 355 382 ->setTitle(pht('Generate New Keypair')) 356 - ->appendParagraph( 357 - pht( 358 - "This will generate an SSH keypair, associate the public key ". 359 - "with your account, and let you download the private key.")) 383 + ->appendParagraph($explain) 360 384 ->appendParagraph( 361 385 pht( 362 386 "Phabricator will not retain a copy of the private key."))