Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php
12256 views
1
<?php
2
3
final class PhabricatorJIRAAuthProvider
4
extends PhabricatorOAuth1AuthProvider
5
implements DoorkeeperRemarkupURIInterface {
6
7
public function getProviderName() {
8
return pht('JIRA');
9
}
10
11
public function getDescriptionForCreate() {
12
return pht('Configure JIRA OAuth. NOTE: Only supports JIRA 6.');
13
}
14
15
public function getConfigurationHelp() {
16
return $this->getProviderConfigurationHelp();
17
}
18
19
protected function getProviderConfigurationHelp() {
20
if ($this->isSetup()) {
21
return pht(
22
"**Step 1 of 2**: Provide the name and URI for your JIRA install.\n\n".
23
"In the next step, you will configure JIRA.");
24
} else {
25
$login_uri = PhabricatorEnv::getURI($this->getLoginURI());
26
return pht(
27
"**Step 2 of 2**: In this step, you will configure JIRA.\n\n".
28
"**Create a JIRA Application**: Log into JIRA and go to ".
29
"**Administration**, then **Add-ons**, then **Application Links**. ".
30
"Click the button labeled **Add Application Link**, and use these ".
31
"settings to create an application:\n\n".
32
" - **Server URL**: `%s`\n".
33
" - Then, click **Next**. On the second page:\n".
34
" - **Application Name**: `%s`\n".
35
" - **Application Type**: `Generic Application`\n".
36
" - Then, click **Create**.\n\n".
37
"**Configure Your Application**: Find the application you just ".
38
"created in the table, and click the **Configure** link under ".
39
"**Actions**. Select **Incoming Authentication** and click the ".
40
"**OAuth** tab (it may be selected by default). Then, use these ".
41
"settings:\n\n".
42
" - **Consumer Key**: Set this to the \"Consumer Key\" value in the ".
43
"form above.\n".
44
" - **Consumer Name**: `%s`\n".
45
" - **Public Key**: Set this to the \"Public Key\" value in the ".
46
"form above.\n".
47
" - **Consumer Callback URL**: `%s`\n".
48
"Click **Save** in JIRA. Authentication should now be configured, ".
49
"and this provider should work correctly.",
50
PhabricatorEnv::getProductionURI('/'),
51
PlatformSymbols::getPlatformServerName(),
52
PlatformSymbols::getPlatformServerName(),
53
$login_uri);
54
}
55
}
56
57
protected function newOAuthAdapter() {
58
$config = $this->getProviderConfig();
59
60
return id(new PhutilJIRAAuthAdapter())
61
->setAdapterDomain($config->getProviderDomain())
62
->setJIRABaseURI($config->getProperty(self::PROPERTY_JIRA_URI))
63
->setPrivateKey(
64
new PhutilOpaqueEnvelope(
65
$config->getProperty(self::PROPERTY_PRIVATE_KEY)));
66
}
67
68
protected function getLoginIcon() {
69
return 'Jira';
70
}
71
72
private function isSetup() {
73
return !$this->getProviderConfig()->getID();
74
}
75
76
const PROPERTY_JIRA_NAME = 'oauth1:jira:name';
77
const PROPERTY_JIRA_URI = 'oauth1:jira:uri';
78
const PROPERTY_PUBLIC_KEY = 'oauth1:jira:key:public';
79
const PROPERTY_PRIVATE_KEY = 'oauth1:jira:key:private';
80
const PROPERTY_REPORT_LINK = 'oauth1:jira:report:link';
81
const PROPERTY_REPORT_COMMENT = 'oauth1:jira:report:comment';
82
83
84
public function readFormValuesFromProvider() {
85
$config = $this->getProviderConfig();
86
$uri = $config->getProperty(self::PROPERTY_JIRA_URI);
87
88
return array(
89
self::PROPERTY_JIRA_NAME => $this->getProviderDomain(),
90
self::PROPERTY_JIRA_URI => $uri,
91
);
92
}
93
94
public function readFormValuesFromRequest(AphrontRequest $request) {
95
$is_setup = $this->isSetup();
96
if ($is_setup) {
97
$name = $request->getStr(self::PROPERTY_JIRA_NAME);
98
} else {
99
$name = $this->getProviderDomain();
100
}
101
102
return array(
103
self::PROPERTY_JIRA_NAME => $name,
104
self::PROPERTY_JIRA_URI => $request->getStr(self::PROPERTY_JIRA_URI),
105
self::PROPERTY_REPORT_LINK =>
106
$request->getInt(self::PROPERTY_REPORT_LINK, 0),
107
self::PROPERTY_REPORT_COMMENT =>
108
$request->getInt(self::PROPERTY_REPORT_COMMENT, 0),
109
);
110
}
111
112
public function processEditForm(
113
AphrontRequest $request,
114
array $values) {
115
$errors = array();
116
$issues = array();
117
118
$is_setup = $this->isSetup();
119
120
$key_name = self::PROPERTY_JIRA_NAME;
121
$key_uri = self::PROPERTY_JIRA_URI;
122
123
if (!strlen($values[$key_name])) {
124
$errors[] = pht('JIRA instance name is required.');
125
$issues[$key_name] = pht('Required');
126
} else if (!preg_match('/^[a-z0-9.]+\z/', $values[$key_name])) {
127
$errors[] = pht(
128
'JIRA instance name must contain only lowercase letters, digits, and '.
129
'period.');
130
$issues[$key_name] = pht('Invalid');
131
}
132
133
if (!strlen($values[$key_uri])) {
134
$errors[] = pht('JIRA base URI is required.');
135
$issues[$key_uri] = pht('Required');
136
} else {
137
$uri = new PhutilURI($values[$key_uri]);
138
if (!$uri->getProtocol()) {
139
$errors[] = pht(
140
'JIRA base URI should include protocol (like "https://").');
141
$issues[$key_uri] = pht('Invalid');
142
}
143
}
144
145
if (!$errors && $is_setup) {
146
$config = $this->getProviderConfig();
147
148
$config->setProviderDomain($values[$key_name]);
149
150
$consumer_key = 'phjira.'.Filesystem::readRandomCharacters(16);
151
list($public, $private) = PhutilJIRAAuthAdapter::newJIRAKeypair();
152
153
$config->setProperty(self::PROPERTY_PUBLIC_KEY, $public);
154
$config->setProperty(self::PROPERTY_PRIVATE_KEY, $private);
155
$config->setProperty(self::PROPERTY_CONSUMER_KEY, $consumer_key);
156
}
157
158
return array($errors, $issues, $values);
159
}
160
161
public function extendEditForm(
162
AphrontRequest $request,
163
AphrontFormView $form,
164
array $values,
165
array $issues) {
166
167
if (!function_exists('openssl_pkey_new')) {
168
// TODO: This could be a bit prettier.
169
throw new Exception(
170
pht(
171
"The PHP 'openssl' extension is not installed. You must install ".
172
"this extension in order to add a JIRA authentication provider, ".
173
"because JIRA OAuth requests use the RSA-SHA1 signing algorithm. ".
174
"Install the 'openssl' extension, restart everything, and try ".
175
"again."));
176
}
177
178
$form->appendRemarkupInstructions(
179
pht(
180
'NOTE: This provider **only supports JIRA 6**. It will not work with '.
181
'JIRA 5 or earlier.'));
182
183
$is_setup = $this->isSetup();
184
$viewer = $request->getViewer();
185
186
$e_required = $request->isFormPost() ? null : true;
187
188
$v_name = $values[self::PROPERTY_JIRA_NAME];
189
if ($is_setup) {
190
$e_name = idx($issues, self::PROPERTY_JIRA_NAME, $e_required);
191
} else {
192
$e_name = null;
193
}
194
195
$v_uri = $values[self::PROPERTY_JIRA_URI];
196
$e_uri = idx($issues, self::PROPERTY_JIRA_URI, $e_required);
197
198
if ($is_setup) {
199
$form
200
->appendRemarkupInstructions(
201
pht(
202
"**JIRA Instance Name**\n\n".
203
"Choose a permanent name for this instance of JIRA. This name is ".
204
"used internally to keep track of this particular instance of ".
205
"JIRA, in case the URL changes later.\n\n".
206
"Use lowercase letters, digits, and period. For example, ".
207
"`jira`, `jira.mycompany` or `jira.engineering` are reasonable ".
208
"names."))
209
->appendChild(
210
id(new AphrontFormTextControl())
211
->setLabel(pht('JIRA Instance Name'))
212
->setValue($v_name)
213
->setName(self::PROPERTY_JIRA_NAME)
214
->setError($e_name));
215
} else {
216
$form
217
->appendChild(
218
id(new AphrontFormStaticControl())
219
->setLabel(pht('JIRA Instance Name'))
220
->setValue($v_name));
221
}
222
223
$form
224
->appendChild(
225
id(new AphrontFormTextControl())
226
->setLabel(pht('JIRA Base URI'))
227
->setValue($v_uri)
228
->setName(self::PROPERTY_JIRA_URI)
229
->setCaption(
230
pht(
231
'The URI where JIRA is installed. For example: %s',
232
phutil_tag('tt', array(), 'https://jira.mycompany.com/')))
233
->setError($e_uri));
234
235
if (!$is_setup) {
236
$config = $this->getProviderConfig();
237
238
239
$ckey = $config->getProperty(self::PROPERTY_CONSUMER_KEY);
240
$ckey = phutil_tag('tt', array(), $ckey);
241
242
$pkey = $config->getProperty(self::PROPERTY_PUBLIC_KEY);
243
$pkey = phutil_escape_html_newlines($pkey);
244
$pkey = phutil_tag('tt', array(), $pkey);
245
246
$form
247
->appendRemarkupInstructions(
248
pht(
249
'NOTE: **To complete setup**, copy and paste these keys into JIRA '.
250
'according to the instructions below.'))
251
->appendChild(
252
id(new AphrontFormStaticControl())
253
->setLabel(pht('Consumer Key'))
254
->setValue($ckey))
255
->appendChild(
256
id(new AphrontFormStaticControl())
257
->setLabel(pht('Public Key'))
258
->setValue($pkey));
259
260
$form
261
->appendRemarkupInstructions(
262
pht(
263
'= Integration Options = '."\n".
264
'Configure how to record Revisions on JIRA tasks.'."\n\n".
265
'Note you\'ll have to restart the daemons for this to take '.
266
'effect.'))
267
->appendChild(
268
id(new AphrontFormCheckboxControl())
269
->addCheckbox(
270
self::PROPERTY_REPORT_LINK,
271
1,
272
new PHUIRemarkupView(
273
$viewer,
274
pht(
275
'Create **Issue Link** to the Revision, as an "implemented '.
276
'in" relationship.')),
277
$this->shouldCreateJIRALink()))
278
->appendChild(
279
id(new AphrontFormCheckboxControl())
280
->addCheckbox(
281
self::PROPERTY_REPORT_COMMENT,
282
1,
283
new PHUIRemarkupView(
284
$viewer,
285
pht(
286
'**Post a comment** in the JIRA task.')),
287
$this->shouldCreateJIRAComment()));
288
}
289
290
}
291
292
/**
293
* JIRA uses a setup step to generate public/private keys.
294
*/
295
public function hasSetupStep() {
296
return true;
297
}
298
299
public static function getJIRAProvider() {
300
$providers = self::getAllEnabledProviders();
301
302
foreach ($providers as $provider) {
303
if ($provider instanceof PhabricatorJIRAAuthProvider) {
304
return $provider;
305
}
306
}
307
308
return null;
309
}
310
311
public function newJIRAFuture(
312
PhabricatorExternalAccount $account,
313
$path,
314
$method,
315
$params = array()) {
316
317
$adapter = clone $this->getAdapter();
318
$adapter->setToken($account->getProperty('oauth1.token'));
319
$adapter->setTokenSecret($account->getProperty('oauth1.token.secret'));
320
321
return $adapter->newJIRAFuture($path, $method, $params);
322
}
323
324
public function shouldCreateJIRALink() {
325
$config = $this->getProviderConfig();
326
return $config->getProperty(self::PROPERTY_REPORT_LINK, true);
327
}
328
329
public function shouldCreateJIRAComment() {
330
$config = $this->getProviderConfig();
331
return $config->getProperty(self::PROPERTY_REPORT_COMMENT, true);
332
}
333
334
/* -( DoorkeeperRemarkupURIInterface )------------------------------------- */
335
336
public function getDoorkeeperURIRef(PhutilURI $uri) {
337
$uri_string = phutil_string_cast($uri);
338
339
$pattern = '((https?://\S+?)/browse/([A-Z][A-Z0-9]*-[1-9]\d*))';
340
$matches = null;
341
if (!preg_match($pattern, $uri_string, $matches)) {
342
return null;
343
}
344
345
if (strlen($uri->getFragment())) {
346
return null;
347
}
348
349
if ($uri->getQueryParamsAsPairList()) {
350
return null;
351
}
352
353
$domain = $matches[1];
354
$issue = $matches[2];
355
356
$config = $this->getProviderConfig();
357
$base_uri = $config->getProperty(self::PROPERTY_JIRA_URI);
358
359
if ($domain !== rtrim($base_uri, '/')) {
360
return null;
361
}
362
363
return id(new DoorkeeperURIRef())
364
->setURI($uri)
365
->setApplicationType(DoorkeeperBridgeJIRA::APPTYPE_JIRA)
366
->setApplicationDomain($this->getProviderDomain())
367
->setObjectType(DoorkeeperBridgeJIRA::OBJTYPE_ISSUE)
368
->setObjectID($issue);
369
}
370
371
}
372
373