@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<?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}