@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 211 lines 5.8 kB view raw
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}