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

at upstream/main 197 lines 5.3 kB view raw
1<?php 2 3/** 4 * Authentication adapter for JIRA OAuth1. 5 */ 6final class PhutilJIRAAuthAdapter extends PhutilOAuth1AuthAdapter { 7 8 // TODO: JIRA tokens expire (after 5 years) and we could surface and store 9 // that. 10 11 private $jiraBaseURI; 12 private $adapterDomain; 13 private $userInfo; 14 15 public function setJIRABaseURI($jira_base_uri) { 16 $this->jiraBaseURI = $jira_base_uri; 17 return $this; 18 } 19 20 public function getJIRABaseURI() { 21 return $this->jiraBaseURI; 22 } 23 24 protected function newAccountIdentifiers() { 25 // Make sure the handshake is finished; this method is used for its 26 // side effect by Auth providers. 27 $this->getHandshakeData(); 28 29 $info = $this->getUserInfo(); 30 31 // See T13493. Older versions of JIRA provide a "key" with a username or 32 // email address. Newer versions of JIRA provide a GUID "accountId". 33 // Intermediate versions of JIRA provide both. 34 35 $identifiers = array(); 36 37 $account_key = idx($info, 'key'); 38 if ($account_key !== null) { 39 $identifiers[] = $this->newAccountIdentifier($account_key); 40 } 41 42 $account_id = idx($info, 'accountId'); 43 if ($account_id !== null) { 44 $identifiers[] = $this->newAccountIdentifier( 45 sprintf( 46 'accountId(%s)', 47 $account_id)); 48 } 49 50 return $identifiers; 51 } 52 53 public function getAccountName() { 54 return idx($this->getUserInfo(), 'name'); 55 } 56 57 public function getAccountImageURI() { 58 $avatars = idx($this->getUserInfo(), 'avatarUrls'); 59 if ($avatars) { 60 return idx($avatars, '48x48'); 61 } 62 return null; 63 } 64 65 public function getAccountRealName() { 66 return idx($this->getUserInfo(), 'displayName'); 67 } 68 69 public function getAccountEmail() { 70 return idx($this->getUserInfo(), 'emailAddress'); 71 } 72 73 public function getAdapterType() { 74 return 'jira'; 75 } 76 77 public function getAdapterDomain() { 78 return $this->adapterDomain; 79 } 80 81 public function setAdapterDomain($domain) { 82 $this->adapterDomain = $domain; 83 return $this; 84 } 85 86 protected function getSignatureMethod() { 87 return 'RSA-SHA1'; 88 } 89 90 protected function getRequestTokenURI() { 91 return $this->getJIRAURI('plugins/servlet/oauth/request-token'); 92 } 93 94 protected function getAuthorizeTokenURI() { 95 return $this->getJIRAURI('plugins/servlet/oauth/authorize'); 96 } 97 98 protected function getValidateTokenURI() { 99 return $this->getJIRAURI('plugins/servlet/oauth/access-token'); 100 } 101 102 private function getJIRAURI($path) { 103 return rtrim($this->jiraBaseURI, '/').'/'.ltrim($path, '/'); 104 } 105 106 private function getUserInfo() { 107 if ($this->userInfo === null) { 108 $this->userInfo = $this->newUserInfo(); 109 } 110 111 return $this->userInfo; 112 } 113 114 private function newUserInfo() { 115 // See T13493. Try a relatively modern (circa early 2020) API call first. 116 try { 117 return $this->newJIRAFuture('rest/api/3/myself', 'GET') 118 ->resolveJSON(); 119 } catch (Exception $ex) { 120 // If we failed the v3 call, assume the server version is too old 121 // to support this API and fall back to trying the older method. 122 } 123 124 $session = $this->newJIRAFuture('rest/auth/1/session', 'GET') 125 ->resolveJSON(); 126 127 // The session call gives us the username, but not the user key or other 128 // information. Make a second call to get additional information. 129 130 $params = array( 131 'username' => $session['name'], 132 ); 133 134 return $this->newJIRAFuture('rest/api/2/user', 'GET', $params) 135 ->resolveJSON(); 136 } 137 138 public static function newJIRAKeypair() { 139 $config = array( 140 'digest_alg' => 'sha512', 141 'private_key_bits' => 4096, 142 'private_key_type' => OPENSSL_KEYTYPE_RSA, 143 ); 144 145 $res = openssl_pkey_new($config); 146 if (!$res) { 147 throw new Exception(pht('%s failed!', 'openssl_pkey_new()')); 148 } 149 150 $private_key = null; 151 $ok = openssl_pkey_export($res, $private_key); 152 if (!$ok) { 153 throw new Exception(pht('%s failed!', 'openssl_pkey_export()')); 154 } 155 156 $public_key = openssl_pkey_get_details($res); 157 if (!$ok || empty($public_key['key'])) { 158 throw new Exception(pht('%s failed!', 'openssl_pkey_get_details()')); 159 } 160 $public_key = $public_key['key']; 161 162 return array($public_key, $private_key); 163 } 164 165 166 /** 167 * JIRA indicates that the user has clicked the "Deny" button by passing a 168 * well known `oauth_verifier` value ("denied"), which we check for here. 169 */ 170 protected function willFinishOAuthHandshake() { 171 $jira_magic_word = 'denied'; 172 if ($this->getVerifier() == $jira_magic_word) { 173 throw new PhutilAuthUserAbortedException(); 174 } 175 } 176 177 public function newJIRAFuture($path, $method, $params = array()) { 178 if ($method == 'GET') { 179 $uri_params = $params; 180 $body_params = array(); 181 } else { 182 // For other types of requests, JIRA expects the request body to be 183 // JSON encoded. 184 $uri_params = array(); 185 $body_params = phutil_json_encode($params); 186 } 187 188 $uri = new PhutilURI($this->getJIRAURI($path), $uri_params); 189 190 // JIRA returns a 415 error if we don't provide a Content-Type header. 191 192 return $this->newOAuth1Future($uri, $body_params) 193 ->setMethod($method) 194 ->addHeader('Content-Type', 'application/json'); 195 } 196 197}