Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/auth/adapter/PhutilJIRAAuthAdapter.php
12256 views
1
<?php
2
3
/**
4
* Authentication adapter for JIRA OAuth1.
5
*/
6
final 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
}
198
199