@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 PassphraseCredentialControl extends AphrontFormControl {
4
5 private $options = array();
6 private $credentialType;
7 private $defaultUsername;
8 private $allowNull;
9
10 public function setAllowNull($allow_null) {
11 $this->allowNull = $allow_null;
12 return $this;
13 }
14
15 public function setDefaultUsername($default_username) {
16 $this->defaultUsername = $default_username;
17 return $this;
18 }
19
20 public function setCredentialType($credential_type) {
21 $this->credentialType = $credential_type;
22 return $this;
23 }
24
25 public function getCredentialType() {
26 return $this->credentialType;
27 }
28
29 /**
30 * @param array<PassphraseCredential> $options
31 */
32 public function setOptions(array $options) {
33 assert_instances_of($options, PassphraseCredential::class);
34 $this->options = $options;
35 return $this;
36 }
37
38 protected function getCustomControlClass() {
39 return 'passphrase-credential-control';
40 }
41
42 protected function renderInput() {
43
44 $options_map = array();
45 foreach ($this->options as $option) {
46 $options_map[$option->getPHID()] = pht(
47 '%s %s',
48 $option->getMonogram(),
49 $option->getName());
50 }
51
52 // The user editing the form may not have permission to see the current
53 // credential. Populate it into the menu to allow them to save the form
54 // without making any changes.
55 $current_phid = $this->getValue();
56 if (phutil_nonempty_string($current_phid) &&
57 empty($options_map[$current_phid])) {
58 $viewer = $this->getViewer();
59
60 $current_name = null;
61 try {
62 $user_credential = id(new PassphraseCredentialQuery())
63 ->setViewer($viewer)
64 ->withPHIDs(array($current_phid))
65 ->executeOne();
66
67 if ($user_credential) {
68 $current_name = pht(
69 '%s %s',
70 $user_credential->getMonogram(),
71 $user_credential->getName());
72 }
73 } catch (PhabricatorPolicyException $policy_exception) {
74 // Pull the credential with the omnipotent viewer so we can look up
75 // the ID and provide the monogram.
76 $omnipotent_credential = id(new PassphraseCredentialQuery())
77 ->setViewer(PhabricatorUser::getOmnipotentUser())
78 ->withPHIDs(array($current_phid))
79 ->executeOne();
80 if ($omnipotent_credential) {
81 $current_name = pht(
82 '%s (Restricted Credential)',
83 $omnipotent_credential->getMonogram());
84 }
85 }
86
87 if ($current_name === null) {
88 $current_name = pht(
89 'Invalid Credential ("%s")',
90 $current_phid);
91 }
92
93 $options_map = array(
94 $current_phid => $current_name,
95 ) + $options_map;
96 }
97
98
99 $disabled = $this->getDisabled();
100 if ($this->allowNull) {
101 $options_map = array('' => pht('(No Credentials)')) + $options_map;
102 } else {
103 if (!$options_map) {
104 $options_map[''] = pht('(No Existing Credentials)');
105 $disabled = true;
106 }
107 }
108
109 Javelin::initBehavior('passphrase-credential-control');
110
111 $options = AphrontFormSelectControl::renderSelectTag(
112 $this->getValue(),
113 $options_map,
114 array(
115 'id' => $this->getControlID(),
116 'name' => $this->getName(),
117 'disabled' => $disabled ? 'disabled' : null,
118 'sigil' => 'passphrase-credential-select',
119 ));
120
121 if ($this->credentialType) {
122 $button = javelin_tag(
123 'a',
124 array(
125 'href' => '#',
126 'class' => 'button button-grey mll',
127 'sigil' => 'passphrase-credential-add',
128 'mustcapture' => true,
129 'style' => 'height: 20px;', // move aphront-form to tables
130 ),
131 pht('Add New Credential'));
132 } else {
133 $button = null;
134 }
135
136 return javelin_tag(
137 'div',
138 array(
139 'sigil' => 'passphrase-credential-control',
140 'meta' => array(
141 'type' => $this->getCredentialType(),
142 'username' => $this->defaultUsername,
143 'allowNull' => $this->allowNull,
144 ),
145 ),
146 array(
147 $options,
148 $button,
149 ));
150 }
151
152 /**
153 * Verify that a given actor has permission to use all of the credentials
154 * in a list of credential transactions.
155 *
156 * In general, the rule here is:
157 *
158 * - If you're editing an object and it uses a credential you can't use,
159 * that's fine as long as you don't change the credential.
160 * - If you do change the credential, the new credential must be one you
161 * can use.
162 *
163 * @param PhabricatorUser $actor The acting user.
164 * @param list<PhabricatorApplicationTransaction> $xactions List of
165 * credential altering transactions.
166 * @return bool True if the transactions are valid.
167 */
168 public static function validateTransactions(
169 PhabricatorUser $actor,
170 array $xactions) {
171
172 $new_phids = array();
173 foreach ($xactions as $xaction) {
174 $new = $xaction->getNewValue();
175 if (!$new) {
176 // Removing a credential, so this is OK.
177 continue;
178 }
179
180 $old = $xaction->getOldValue();
181 if ($old == $new) {
182 // This is a no-op transaction, so this is also OK.
183 continue;
184 }
185
186 // Otherwise, we need to check this credential.
187 $new_phids[] = $new;
188 }
189
190 if (!$new_phids) {
191 // No new credentials being set, so this is fine.
192 return true;
193 }
194
195 $usable_credentials = id(new PassphraseCredentialQuery())
196 ->setViewer($actor)
197 ->withPHIDs($new_phids)
198 ->execute();
199 $usable_credentials = mpull($usable_credentials, null, 'getPHID');
200
201 foreach ($new_phids as $phid) {
202 if (empty($usable_credentials[$phid])) {
203 return false;
204 }
205 }
206
207 return true;
208 }
209
210
211}