Path: blob/master/src/applications/auth/adapter/PhutilJIRAAuthAdapter.php
12256 views
<?php12/**3* Authentication adapter for JIRA OAuth1.4*/5final class PhutilJIRAAuthAdapter extends PhutilOAuth1AuthAdapter {67// TODO: JIRA tokens expire (after 5 years) and we could surface and store8// that.910private $jiraBaseURI;11private $adapterDomain;12private $userInfo;1314public function setJIRABaseURI($jira_base_uri) {15$this->jiraBaseURI = $jira_base_uri;16return $this;17}1819public function getJIRABaseURI() {20return $this->jiraBaseURI;21}2223protected function newAccountIdentifiers() {24// Make sure the handshake is finished; this method is used for its25// side effect by Auth providers.26$this->getHandshakeData();2728$info = $this->getUserInfo();2930// See T13493. Older versions of JIRA provide a "key" with a username or31// email address. Newer versions of JIRA provide a GUID "accountId".32// Intermediate versions of JIRA provide both.3334$identifiers = array();3536$account_key = idx($info, 'key');37if ($account_key !== null) {38$identifiers[] = $this->newAccountIdentifier($account_key);39}4041$account_id = idx($info, 'accountId');42if ($account_id !== null) {43$identifiers[] = $this->newAccountIdentifier(44sprintf(45'accountId(%s)',46$account_id));47}4849return $identifiers;50}5152public function getAccountName() {53return idx($this->getUserInfo(), 'name');54}5556public function getAccountImageURI() {57$avatars = idx($this->getUserInfo(), 'avatarUrls');58if ($avatars) {59return idx($avatars, '48x48');60}61return null;62}6364public function getAccountRealName() {65return idx($this->getUserInfo(), 'displayName');66}6768public function getAccountEmail() {69return idx($this->getUserInfo(), 'emailAddress');70}7172public function getAdapterType() {73return 'jira';74}7576public function getAdapterDomain() {77return $this->adapterDomain;78}7980public function setAdapterDomain($domain) {81$this->adapterDomain = $domain;82return $this;83}8485protected function getSignatureMethod() {86return 'RSA-SHA1';87}8889protected function getRequestTokenURI() {90return $this->getJIRAURI('plugins/servlet/oauth/request-token');91}9293protected function getAuthorizeTokenURI() {94return $this->getJIRAURI('plugins/servlet/oauth/authorize');95}9697protected function getValidateTokenURI() {98return $this->getJIRAURI('plugins/servlet/oauth/access-token');99}100101private function getJIRAURI($path) {102return rtrim($this->jiraBaseURI, '/').'/'.ltrim($path, '/');103}104105private function getUserInfo() {106if ($this->userInfo === null) {107$this->userInfo = $this->newUserInfo();108}109110return $this->userInfo;111}112113private function newUserInfo() {114// See T13493. Try a relatively modern (circa early 2020) API call first.115try {116return $this->newJIRAFuture('rest/api/3/myself', 'GET')117->resolveJSON();118} catch (Exception $ex) {119// If we failed the v3 call, assume the server version is too old120// to support this API and fall back to trying the older method.121}122123$session = $this->newJIRAFuture('rest/auth/1/session', 'GET')124->resolveJSON();125126// The session call gives us the username, but not the user key or other127// information. Make a second call to get additional information.128129$params = array(130'username' => $session['name'],131);132133return $this->newJIRAFuture('rest/api/2/user', 'GET', $params)134->resolveJSON();135}136137public static function newJIRAKeypair() {138$config = array(139'digest_alg' => 'sha512',140'private_key_bits' => 4096,141'private_key_type' => OPENSSL_KEYTYPE_RSA,142);143144$res = openssl_pkey_new($config);145if (!$res) {146throw new Exception(pht('%s failed!', 'openssl_pkey_new()'));147}148149$private_key = null;150$ok = openssl_pkey_export($res, $private_key);151if (!$ok) {152throw new Exception(pht('%s failed!', 'openssl_pkey_export()'));153}154155$public_key = openssl_pkey_get_details($res);156if (!$ok || empty($public_key['key'])) {157throw new Exception(pht('%s failed!', 'openssl_pkey_get_details()'));158}159$public_key = $public_key['key'];160161return array($public_key, $private_key);162}163164165/**166* JIRA indicates that the user has clicked the "Deny" button by passing a167* well known `oauth_verifier` value ("denied"), which we check for here.168*/169protected function willFinishOAuthHandshake() {170$jira_magic_word = 'denied';171if ($this->getVerifier() == $jira_magic_word) {172throw new PhutilAuthUserAbortedException();173}174}175176public function newJIRAFuture($path, $method, $params = array()) {177if ($method == 'GET') {178$uri_params = $params;179$body_params = array();180} else {181// For other types of requests, JIRA expects the request body to be182// JSON encoded.183$uri_params = array();184$body_params = phutil_json_encode($params);185}186187$uri = new PhutilURI($this->getJIRAURI($path), $uri_params);188189// JIRA returns a 415 error if we don't provide a Content-Type header.190191return $this->newOAuth1Future($uri, $body_params)192->setMethod($method)193->addHeader('Content-Type', 'application/json');194}195196}197198199