@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
at recaptime-dev/main 405 lines 13 kB view raw
1<?php 2 3final class PassphraseCredentialEditController extends PassphraseController { 4 5 public function handleRequest(AphrontRequest $request) { 6 $viewer = $request->getViewer(); 7 $id = $request->getURIData('id'); 8 9 if ($id) { 10 $credential = id(new PassphraseCredentialQuery()) 11 ->setViewer($viewer) 12 ->withIDs(array($id)) 13 ->requireCapabilities( 14 array( 15 PhabricatorPolicyCapability::CAN_VIEW, 16 PhabricatorPolicyCapability::CAN_EDIT, 17 )) 18 ->executeOne(); 19 if (!$credential) { 20 return new Aphront404Response(); 21 } 22 23 $type = $this->getCredentialType($credential->getCredentialType()); 24 $type_const = $type->getCredentialType(); 25 26 $is_new = false; 27 } else { 28 $type_const = $request->getStr('type'); 29 $type = $this->getCredentialType($type_const); 30 31 if (!$type->isCreateable()) { 32 throw new Exception( 33 pht( 34 'Credential has noncreateable type "%s"!', 35 $type_const)); 36 } 37 38 $credential = PassphraseCredential::initializeNewCredential($viewer) 39 ->setCredentialType($type->getCredentialType()) 40 ->setProvidesType($type->getProvidesType()) 41 ->attachImplementation($type); 42 43 $is_new = true; 44 45 // Prefill username if provided. 46 $credential->setUsername((string)$request->getStr('username')); 47 48 // Prefill name if provided. 49 $credential->setName((string)$request->getStr('name')); 50 51 // Prefill description if provided. 52 $credential->setDescription((string)$request->getStr('description')); 53 54 if (!$request->getStr('isInitialized')) { 55 $type->didInitializeNewCredential($viewer, $credential); 56 } 57 } 58 59 $errors = array(); 60 61 $v_name = $credential->getName(); 62 $e_name = true; 63 64 $v_desc = $credential->getDescription(); 65 $v_space = $credential->getSpacePHID(); 66 67 $v_username = $credential->getUsername(); 68 $e_username = true; 69 70 $v_is_locked = false; 71 72 $bullet = "\xE2\x80\xA2"; 73 74 $v_secret = $credential->getSecretID() ? str_repeat($bullet, 32) : null; 75 if ($is_new && ($v_secret === null)) { 76 // If we're creating a new credential, the credential type may have 77 // populated the secret for us (for example, generated an SSH key). In 78 // this case, 79 try { 80 $v_secret = $credential->getSecret()->openEnvelope(); 81 } catch (Exception $ex) { 82 // Ignore this. 83 } 84 } 85 86 $validation_exception = null; 87 $errors = array(); 88 $e_password = null; 89 $e_secret = null; 90 if ($request->isFormPost()) { 91 92 $v_name = $request->getStr('name'); 93 $v_desc = $request->getStr('description'); 94 $v_username = $request->getStr('username'); 95 $v_view_policy = $request->getStr('viewPolicy'); 96 $v_edit_policy = $request->getStr('editPolicy'); 97 $v_is_locked = $request->getStr('lock'); 98 99 $v_secret = $request->getStr('secret'); 100 $v_space = $request->getStr('spacePHID'); 101 $v_password = $request->getStr('password'); 102 $v_decrypt = $v_secret; 103 104 $env_secret = new PhutilOpaqueEnvelope($v_secret); 105 $env_password = new PhutilOpaqueEnvelope($v_password); 106 107 $has_secret = !preg_match('/^('.$bullet.')+$/', trim($v_decrypt)); 108 109 // Validate and repair SSH private keys, and apply passwords if they 110 // are provided. See T13454 for discussion. 111 112 // This should eventually be refactored to be modular rather than a 113 // hard-coded set of behaviors here in the Controller, but this is 114 // likely a fairly extensive change. 115 116 $is_ssh = ($type instanceof PassphraseSSHPrivateKeyTextCredentialType); 117 118 if ($is_ssh && $has_secret) { 119 $old_object = PhabricatorAuthSSHPrivateKey::newFromRawKey($env_secret); 120 121 if (strlen($v_password)) { 122 $old_object->setPassphrase($env_password); 123 } 124 125 try { 126 $new_object = $old_object->newBarePrivateKey(); 127 $v_decrypt = $new_object->getKeyBody()->openEnvelope(); 128 } catch (PhabricatorAuthSSHPrivateKeyException $ex) { 129 $errors[] = $ex->getMessage(); 130 131 if ($ex->isFormatException()) { 132 $e_secret = pht('Invalid'); 133 } 134 if ($ex->isPassphraseException()) { 135 $e_password = pht('Invalid'); 136 } 137 } 138 } 139 140 if (!$errors) { 141 $type_name = 142 PassphraseCredentialNameTransaction::TRANSACTIONTYPE; 143 $type_desc = 144 PassphraseCredentialDescriptionTransaction::TRANSACTIONTYPE; 145 $type_username = 146 PassphraseCredentialUsernameTransaction::TRANSACTIONTYPE; 147 $type_destroy = 148 PassphraseCredentialDestroyTransaction::TRANSACTIONTYPE; 149 $type_secret_id = 150 PassphraseCredentialSecretIDTransaction::TRANSACTIONTYPE; 151 $type_is_locked = 152 PassphraseCredentialLockTransaction::TRANSACTIONTYPE; 153 154 $type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY; 155 $type_edit_policy = PhabricatorTransactions::TYPE_EDIT_POLICY; 156 $type_space = PhabricatorTransactions::TYPE_SPACE; 157 158 $xactions = array(); 159 160 $xactions[] = id(new PassphraseCredentialTransaction()) 161 ->setTransactionType($type_name) 162 ->setNewValue($v_name); 163 164 $xactions[] = id(new PassphraseCredentialTransaction()) 165 ->setTransactionType($type_desc) 166 ->setNewValue($v_desc); 167 168 $xactions[] = id(new PassphraseCredentialTransaction()) 169 ->setTransactionType($type_view_policy) 170 ->setNewValue($v_view_policy); 171 172 $xactions[] = id(new PassphraseCredentialTransaction()) 173 ->setTransactionType($type_edit_policy) 174 ->setNewValue($v_edit_policy); 175 176 $xactions[] = id(new PassphraseCredentialTransaction()) 177 ->setTransactionType($type_space) 178 ->setNewValue($v_space); 179 180 // Open a transaction in case we're writing a new secret; this limits 181 // the amount of code which handles secret plaintexts. 182 $credential->openTransaction(); 183 184 if (!$credential->getIsLocked()) { 185 if ($type->shouldRequireUsername()) { 186 $xactions[] = id(new PassphraseCredentialTransaction()) 187 ->setTransactionType($type_username) 188 ->setNewValue($v_username); 189 } 190 191 // If some value other than a sequence of bullets was provided for 192 // the credential, update it. In particular, note that we are 193 // explicitly allowing empty secrets: one use case is HTTP auth where 194 // the username is a secret token which covers both identity and 195 // authentication. 196 197 if ($has_secret) { 198 // If the credential was previously destroyed, restore it when it is 199 // edited if a secret is provided. 200 $xactions[] = id(new PassphraseCredentialTransaction()) 201 ->setTransactionType($type_destroy) 202 ->setNewValue(0); 203 204 $new_secret = id(new PassphraseSecret()) 205 ->setSecretData($v_decrypt) 206 ->save(); 207 208 $xactions[] = id(new PassphraseCredentialTransaction()) 209 ->setTransactionType($type_secret_id) 210 ->setNewValue($new_secret->getID()); 211 } 212 213 $xactions[] = id(new PassphraseCredentialTransaction()) 214 ->setTransactionType($type_is_locked) 215 ->setNewValue($v_is_locked); 216 } 217 218 try { 219 $editor = id(new PassphraseCredentialTransactionEditor()) 220 ->setActor($viewer) 221 ->setContinueOnNoEffect(true) 222 ->setContentSourceFromRequest($request) 223 ->applyTransactions($credential, $xactions); 224 225 $credential->saveTransaction(); 226 227 if ($request->isAjax()) { 228 return id(new AphrontAjaxResponse())->setContent( 229 array( 230 'phid' => $credential->getPHID(), 231 'name' => 'K'.$credential->getID().' '.$credential->getName(), 232 )); 233 } else { 234 return id(new AphrontRedirectResponse()) 235 ->setURI('/K'.$credential->getID()); 236 } 237 } catch (PhabricatorApplicationTransactionValidationException $ex) { 238 $credential->killTransaction(); 239 240 $validation_exception = $ex; 241 242 $e_name = $ex->getShortMessage($type_name); 243 $e_username = $ex->getShortMessage($type_username); 244 245 $credential->setViewPolicy($v_view_policy); 246 $credential->setEditPolicy($v_edit_policy); 247 } 248 } 249 } 250 251 $policies = id(new PhabricatorPolicyQuery()) 252 ->setViewer($viewer) 253 ->setObject($credential) 254 ->execute(); 255 256 $secret_control = $type->newSecretControl(); 257 $credential_is_locked = $credential->getIsLocked(); 258 259 $form = id(new AphrontFormView()) 260 ->setViewer($viewer) 261 ->addHiddenInput('isInitialized', true) 262 ->addHiddenInput('type', $type_const) 263 ->appendChild( 264 id(new AphrontFormTextControl()) 265 ->setName('name') 266 ->setLabel(pht('Name')) 267 ->setValue($v_name) 268 ->setError($e_name)) 269 ->appendChild( 270 id(new PhabricatorRemarkupControl()) 271 ->setViewer($viewer) 272 ->setName('description') 273 ->setLabel(pht('Description')) 274 ->setValue($v_desc)) 275 ->appendChild( 276 id(new AphrontFormDividerControl())) 277 ->appendControl( 278 id(new AphrontFormPolicyControl()) 279 ->setName('viewPolicy') 280 ->setPolicyObject($credential) 281 ->setSpacePHID($v_space) 282 ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) 283 ->setPolicies($policies)) 284 ->appendControl( 285 id(new AphrontFormPolicyControl()) 286 ->setName('editPolicy') 287 ->setPolicyObject($credential) 288 ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) 289 ->setPolicies($policies)) 290 ->appendChild( 291 id(new AphrontFormDividerControl())); 292 293 if ($credential_is_locked) { 294 $form->appendRemarkupInstructions( 295 pht('This credential is permanently locked and can not be edited.')); 296 } 297 298 if ($type->shouldRequireUsername()) { 299 $form->appendChild( 300 id(new AphrontFormTextControl()) 301 ->setName('username') 302 ->setLabel(pht('Login/Username')) 303 ->setValue($v_username) 304 ->setDisabled($credential_is_locked) 305 ->setError($e_username)); 306 } 307 308 $form->appendChild( 309 $secret_control 310 ->setName('secret') 311 ->setLabel($type->getSecretLabel()) 312 ->setDisabled($credential_is_locked) 313 ->setValue($v_secret) 314 ->setError($e_secret)); 315 316 if ($type->shouldShowPasswordField()) { 317 $form->appendChild( 318 id(new AphrontFormPasswordControl()) 319 ->setDisableAutocomplete(true) 320 ->setName('password') 321 ->setLabel($type->getPasswordLabel()) 322 ->setDisabled($credential_is_locked) 323 ->setError($e_password)); 324 } 325 326 if ($is_new) { 327 $form->appendChild( 328 id(new AphrontFormCheckboxControl()) 329 ->addCheckbox( 330 'lock', 331 1, 332 array( 333 phutil_tag('strong', array(), pht('Lock Permanently:')), 334 ' ', 335 pht('Prevent the secret from being revealed or changed.'), 336 ), 337 $v_is_locked) 338 ->setDisabled($credential_is_locked)); 339 } 340 341 $crumbs = $this->buildApplicationCrumbs(); 342 $crumbs->setBorder(true); 343 344 if ($is_new) { 345 $title = pht('New Credential: %s', $type->getCredentialTypeName()); 346 $crumbs->addTextCrumb(pht('Create')); 347 $cancel_uri = $this->getApplicationURI(); 348 } else { 349 $title = pht('Edit Credential: %s', $credential->getName()); 350 $crumbs->addTextCrumb( 351 'K'.$credential->getID(), 352 '/K'.$credential->getID()); 353 $crumbs->addTextCrumb(pht('Edit')); 354 $cancel_uri = '/K'.$credential->getID(); 355 } 356 357 if ($request->isAjax()) { 358 if ($errors) { 359 $errors = id(new PHUIInfoView())->setErrors($errors); 360 } 361 362 return $this->newDialog() 363 ->setWidth(AphrontDialogView::WIDTH_FORM) 364 ->setTitle($title) 365 ->appendChild($errors) 366 ->appendChild($form->buildLayoutView()) 367 ->addSubmitButton(pht('Create Credential')) 368 ->addCancelButton($cancel_uri); 369 } 370 371 $form->appendChild( 372 id(new AphrontFormSubmitControl()) 373 ->setValue(pht('Save')) 374 ->addCancelButton($cancel_uri)); 375 376 $box = id(new PHUIObjectBoxView()) 377 ->setHeaderText($title) 378 ->setFormErrors($errors) 379 ->setValidationException($validation_exception) 380 ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) 381 ->setForm($form); 382 383 $view = id(new PHUITwoColumnView()) 384 ->setFooter(array( 385 $box, 386 )); 387 388 return $this->newPage() 389 ->setTitle($title) 390 ->setCrumbs($crumbs) 391 ->appendChild($view); 392 } 393 394 private function getCredentialType($type_const) { 395 $type = PassphraseCredentialType::getTypeByConstant($type_const); 396 397 if (!$type) { 398 throw new Exception( 399 pht('Credential has invalid type "%s"!', $type_const)); 400 } 401 402 return $type; 403 } 404 405}