Skip to content

Commit 7a3f33b

Browse files
committed
OAuth - Phabricator OAuth server and Phabricator client for new Phabricator OAuth Server
Summary: adds a Phabricator OAuth server, which has three big commands: - auth - allows $user to authorize a given client or application. if $user has already authorized, it hands an authoization code back to $redirect_uri - token - given a valid authorization code, this command returns an authorization token - whoami - Conduit.whoami, all nice and purdy relative to the oauth server. Also has a "test" handler, which I used to create some test data. T850 will delete this as it adds the ability to create this data in the Phabricator product. This diff also adds the corresponding client in Phabricator for the Phabricator OAuth Server. (Note that clients are known as "providers" in the Phabricator codebase but client makes more sense relative to the server nomenclature) Also, related to make this work well - clean up the diagnostics page by variabilizing the provider-specific information and extending the provider classes as appropriate. - augment Conduit.whoami for more full-featured OAuth support, at least where the Phabricator client is concerned What's missing here... See T844, T848, T849, T850, and T852. Test Plan: - created a dummy client via the test handler. setup development.conf to have have proper variables for this dummy client. went through authorization and de-authorization flows - viewed the diagnostics page for all known oauth providers and saw provider-specific debugging information Reviewers: epriestley CC: aran, epriestley Maniphest Tasks: T44, T797 Differential Revision: https://secure.phabricator.com/D1595
1 parent 9748520 commit 7a3f33b

34 files changed

+1161
-14
lines changed

conf/default.conf.php

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@
329329
'account.minimum-password-length' => 8,
330330

331331

332-
// -- Facebook ------------------------------------------------------------ //
332+
// -- Facebook OAuth -------------------------------------------------------- //
333333

334334
// Can users use Facebook credentials to login to Phabricator?
335335
'facebook.auth-enabled' => false,
@@ -348,7 +348,7 @@
348348
'facebook.application-secret' => null,
349349

350350

351-
// -- GitHub ---------------------------------------------------------------- //
351+
// -- GitHub OAuth ---------------------------------------------------------- //
352352

353353
// Can users use GitHub credentials to login to Phabricator?
354354
'github.auth-enabled' => false,
@@ -367,7 +367,7 @@
367367
'github.application-secret' => null,
368368

369369

370-
// -- Google ---------------------------------------------------------------- //
370+
// -- Google OAuth ---------------------------------------------------------- //
371371

372372
// Can users use Google credentials to login to Phabricator?
373373
'google.auth-enabled' => false,
@@ -385,6 +385,30 @@
385385
// The Google "Client Secret" to use for Google API access.
386386
'google.application-secret' => null,
387387

388+
// -- Phabricator OAuth ----------------------------------------------------- //
389+
390+
// Meta-town -- Phabricator is itself an OAuth Provider
391+
// TODO -- T887 -- make this support multiple Phabricator instances!
392+
393+
// The URI of the Phabricator instance to use as an OAuth server.
394+
'phabricator.oauth-uri' => null,
395+
396+
// Can users use Phabricator credentials to login to Phabricator?
397+
'phabricator.auth-enabled' => false,
398+
399+
// Can users use Phabricator credentials to create new Phabricator accounts?
400+
'phabricator.registration-enabled' => true,
401+
402+
// Are Phabricator accounts permanently linked to Phabricator accounts, or can
403+
// the user unlink them?
404+
'phabricator.auth-permanent' => false,
405+
406+
// The Phabricator "Client ID" to use for Phabricator API access.
407+
'phabricator.application-id' => null,
408+
409+
// The Phabricator "Client Secret" to use for Phabricator API access.
410+
'phabricator.application-secret' => null,
411+
388412
// -- Recaptcha ------------------------------------------------------------- //
389413

390414
// Is Recaptcha enabled? If disabled, captchas will not appear. You should

resources/sql/patches/106.chatlog.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ CREATE TABLE phabricator_chatlog.chatlog_event (
88
message LONGBLOB NOT NULL,
99
loggedByPHID VARCHAR(64) BINARY NOT NULL,
1010
KEY (channel, epoch)
11-
);
11+
);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
CREATE DATABASE IF NOT EXISTS `phabricator_oauth_server`;
2+
3+
CREATE TABLE `phabricator_oauth_server`.`oauth_server_oauthserverclient` (
4+
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
5+
`phid` varchar(64) BINARY NOT NULL,
6+
`name` varchar(255) NOT NULL,
7+
`secret` varchar(32) NOT NULL,
8+
`redirectURI` varchar(255) NOT NULL,
9+
`creatorPHID` varchar(64) BINARY NOT NULL,
10+
`dateCreated` int(10) unsigned NOT NULL,
11+
`dateModified` int(10) unsigned NOT NULL,
12+
PRIMARY KEY (`id`),
13+
UNIQUE KEY `phid` (`phid`)
14+
) ENGINE=InnoDB;
15+
16+
CREATE TABLE `phabricator_oauth_server`.`oauth_server_oauthclientauthorization` (
17+
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
18+
`phid` varchar(64) BINARY NOT NULL,
19+
`userPHID` varchar(64) BINARY NOT NULL,
20+
`clientPHID` varchar(64) BINARY NOT NULL,
21+
`dateCreated` int(10) unsigned NOT NULL,
22+
`dateModified` int(10) unsigned NOT NULL,
23+
PRIMARY KEY (`id`),
24+
UNIQUE KEY `phid` (`phid`),
25+
UNIQUE KEY `userPHID` (`userPHID`,`clientPHID`)
26+
) ENGINE=InnoDB;
27+
28+
CREATE TABLE `phabricator_oauth_server`.`oauth_server_oauthserverauthorizationcode` (
29+
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
30+
`code` varchar(32) NOT NULL,
31+
`clientPHID` varchar(64) BINARY NOT NULL,
32+
`clientSecret` varchar(32) NOT NULL,
33+
`userPHID` varchar(64) BINARY NOT NULL,
34+
`dateCreated` int(10) unsigned NOT NULL,
35+
`dateModified` int(10) unsigned NOT NULL,
36+
PRIMARY KEY (`id`),
37+
UNIQUE KEY `code` (`code`)
38+
) ENGINE=InnoDB;
39+
40+
CREATE TABLE `phabricator_oauth_server`.`oauth_server_oauthserveraccesstoken` (
41+
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
42+
`token` varchar(32) NOT NULL,
43+
`userPHID` varchar(64) BINARY NOT NULL,
44+
`clientPHID` varchar(64) BINARY NOT NULL,
45+
`dateExpires` int(10) unsigned NOT NULL,
46+
`dateCreated` int(10) unsigned NOT NULL,
47+
`dateModified` int(10) unsigned NOT NULL,
48+
PRIMARY KEY (`id`),
49+
UNIQUE KEY `token` (`token`)
50+
) ENGINE=InnoDB;
51+

src/__phutil_library_map__.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@
597597
'PhabricatorMetaMTASendGridReceiveController' => 'applications/metamta/controller/sendgridreceive',
598598
'PhabricatorMetaMTAViewController' => 'applications/metamta/controller/view',
599599
'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/mysql',
600+
'PhabricatorOAuthClientAuthorization' => 'applications/oauthserver/storage/clientauthorization',
600601
'PhabricatorOAuthDefaultRegistrationController' => 'applications/auth/controller/oauthregistration/default',
601602
'PhabricatorOAuthDiagnosticsController' => 'applications/auth/controller/oauthdiagnostics',
602603
'PhabricatorOAuthFailureView' => 'applications/auth/view/oauthfailure',
@@ -605,7 +606,17 @@
605606
'PhabricatorOAuthProviderFacebook' => 'applications/auth/oauth/provider/facebook',
606607
'PhabricatorOAuthProviderGitHub' => 'applications/auth/oauth/provider/github',
607608
'PhabricatorOAuthProviderGoogle' => 'applications/auth/oauth/provider/google',
609+
'PhabricatorOAuthProviderPhabricator' => 'applications/auth/oauth/provider/phabricator',
608610
'PhabricatorOAuthRegistrationController' => 'applications/auth/controller/oauthregistration/base',
611+
'PhabricatorOAuthResponse' => 'applications/oauthserver/response',
612+
'PhabricatorOAuthServer' => 'applications/oauthserver/server',
613+
'PhabricatorOAuthServerAccessToken' => 'applications/oauthserver/storage/accesstoken',
614+
'PhabricatorOAuthServerAuthController' => 'applications/oauthserver/controller/auth',
615+
'PhabricatorOAuthServerAuthorizationCode' => 'applications/oauthserver/storage/authorizationcode',
616+
'PhabricatorOAuthServerClient' => 'applications/oauthserver/storage/client',
617+
'PhabricatorOAuthServerDAO' => 'applications/oauthserver/storage/base',
618+
'PhabricatorOAuthServerTestController' => 'applications/oauthserver/controller/test',
619+
'PhabricatorOAuthServerTokenController' => 'applications/oauthserver/controller/token',
609620
'PhabricatorOAuthUnlinkController' => 'applications/auth/controller/unlink',
610621
'PhabricatorObjectAttachmentEditor' => 'applications/search/editor/attach',
611622
'PhabricatorObjectGraph' => 'applications/phid/graph',
@@ -1324,14 +1335,24 @@
13241335
'PhabricatorMetaMTASendGridReceiveController' => 'PhabricatorMetaMTAController',
13251336
'PhabricatorMetaMTAViewController' => 'PhabricatorMetaMTAController',
13261337
'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine',
1338+
'PhabricatorOAuthClientAuthorization' => 'PhabricatorOAuthServerDAO',
13271339
'PhabricatorOAuthDefaultRegistrationController' => 'PhabricatorOAuthRegistrationController',
13281340
'PhabricatorOAuthDiagnosticsController' => 'PhabricatorAuthController',
13291341
'PhabricatorOAuthFailureView' => 'AphrontView',
13301342
'PhabricatorOAuthLoginController' => 'PhabricatorAuthController',
13311343
'PhabricatorOAuthProviderFacebook' => 'PhabricatorOAuthProvider',
13321344
'PhabricatorOAuthProviderGitHub' => 'PhabricatorOAuthProvider',
13331345
'PhabricatorOAuthProviderGoogle' => 'PhabricatorOAuthProvider',
1346+
'PhabricatorOAuthProviderPhabricator' => 'PhabricatorOAuthProvider',
13341347
'PhabricatorOAuthRegistrationController' => 'PhabricatorAuthController',
1348+
'PhabricatorOAuthResponse' => 'AphrontResponse',
1349+
'PhabricatorOAuthServerAccessToken' => 'PhabricatorOAuthServerDAO',
1350+
'PhabricatorOAuthServerAuthController' => 'PhabricatorAuthController',
1351+
'PhabricatorOAuthServerAuthorizationCode' => 'PhabricatorOAuthServerDAO',
1352+
'PhabricatorOAuthServerClient' => 'PhabricatorOAuthServerDAO',
1353+
'PhabricatorOAuthServerDAO' => 'PhabricatorLiskDAO',
1354+
'PhabricatorOAuthServerTestController' => 'PhabricatorAuthController',
1355+
'PhabricatorOAuthServerTokenController' => 'PhabricatorAuthController',
13351356
'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController',
13361357
'PhabricatorObjectGraph' => 'AbstractDirectedGraph',
13371358
'PhabricatorObjectHandleStatus' => 'PhabricatorObjectHandleConstants',

src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@ public function getURIMap() {
151151
),
152152
),
153153

154+
'/oauthserver/' => array(
155+
'auth/' => 'PhabricatorOAuthServerAuthController',
156+
'token/' => 'PhabricatorOAuthServerTokenController',
157+
'test/' => 'PhabricatorOAuthServerTestController',
158+
),
159+
154160
'/xhprof/' => array(
155161
'profile/(?P<phid>[^/]+)/$' => 'PhabricatorXHProfProfileController',
156162
),

src/aphront/response/ajax/AphrontAjaxResponse.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ public function buildResponseString() {
3535
$this->content,
3636
$this->error);
3737

38-
return $this->encodeJSONForHTTPResponse(
39-
$object,
40-
$use_javelin_shield = true);
38+
$response_json = $this->encodeJSONForHTTPResponse($object);
39+
return $this->addJSONShield($response_json, $use_javelin_shield = true);
4140
}
4241

4342
public function getHeaders() {

src/aphront/response/base/AphrontResponse.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ protected function encodeJSONForHTTPResponse(
8484
array('\u003c', '\u003e'),
8585
$response);
8686

87+
return $response;
88+
}
89+
90+
protected function addJSONShield($json_response, $use_javelin_shield) {
91+
8792
// Add a shield to prevent "JSON Hijacking" attacks where an attacker
8893
// requests a JSON response using a normal <script /> tag and then uses
8994
// Object.prototype.__defineSetter__() or similar to read response data.
@@ -96,7 +101,7 @@ protected function encodeJSONForHTTPResponse(
96101
? 'for (;;);'
97102
: 'for(;;);';
98103

99-
$response = $shield.$response;
104+
$response = $shield.$json_response;
100105

101106
return $response;
102107
}

src/aphront/response/json/AphrontJSONResponse.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,8 @@ public function setContent($content) {
2929
}
3030

3131
public function buildResponseString() {
32-
$response = $this->encodeJSONForHTTPResponse(
33-
$this->content,
34-
$use_javelin_shield = false);
35-
return $response;
32+
$response = $this->encodeJSONForHTTPResponse($this->content);
33+
return $this->addJSONShield($response, $use_javelin_shield = false);
3634
}
3735

3836
public function getHeaders() {

src/applications/auth/oauth/provider/base/PhabricatorOAuthProvider.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ abstract class PhabricatorOAuthProvider {
2121
const PROVIDER_FACEBOOK = 'facebook';
2222
const PROVIDER_GITHUB = 'github';
2323
const PROVIDER_GOOGLE = 'google';
24+
const PROVIDER_PHABRICATOR = 'phabricator';
2425

2526
private $accessToken;
2627

@@ -108,6 +109,9 @@ public static function newProvider($which) {
108109
case self::PROVIDER_GOOGLE:
109110
$class = 'PhabricatorOAuthProviderGoogle';
110111
break;
112+
case self::PROVIDER_PHABRICATOR:
113+
$class = 'PhabricatorOAuthProviderPhabricator';
114+
break;
111115
default:
112116
throw new Exception('Unknown OAuth provider.');
113117
}
@@ -120,6 +124,7 @@ public static function getAllProviders() {
120124
self::PROVIDER_FACEBOOK,
121125
self::PROVIDER_GITHUB,
122126
self::PROVIDER_GOOGLE,
127+
self::PROVIDER_PHABRICATOR,
123128
);
124129
$providers = array();
125130
foreach ($all as $provider) {
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
3+
/*
4+
* Copyright 2012 Facebook, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
final class PhabricatorOAuthProviderPhabricator
20+
extends PhabricatorOAuthProvider {
21+
22+
private $userData;
23+
24+
public function decodeTokenResponse($response) {
25+
$decoded = json_decode($response, true);
26+
if (!is_array($decoded)) {
27+
throw new Exception('Invalid token response.');
28+
}
29+
return $decoded;
30+
}
31+
32+
public function getProviderKey() {
33+
return self::PROVIDER_PHABRICATOR;
34+
}
35+
36+
public function getProviderName() {
37+
return 'Phabricator';
38+
}
39+
40+
public function isProviderEnabled() {
41+
return PhabricatorEnv::getEnvConfig('phabricator.auth-enabled');
42+
}
43+
44+
public function isProviderLinkPermanent() {
45+
return PhabricatorEnv::getEnvConfig('phabricator.auth-permanent');
46+
}
47+
48+
public function isProviderRegistrationEnabled() {
49+
return PhabricatorEnv::getEnvConfig('phabricator.registration-enabled');
50+
}
51+
52+
public function getClientID() {
53+
return PhabricatorEnv::getEnvConfig('phabricator.application-id');
54+
}
55+
56+
public function renderGetClientIDHelp() {
57+
return null;
58+
}
59+
60+
public function getClientSecret() {
61+
return PhabricatorEnv::getEnvConfig('phabricator.application-secret');
62+
}
63+
64+
public function renderGetClientSecretHelp() {
65+
return null;
66+
}
67+
68+
public function getAuthURI() {
69+
return $this->getURI('/oauthserver/auth/');
70+
}
71+
72+
public function getTestURIs() {
73+
return array(
74+
$this->getURI('/'),
75+
$this->getURI('/api/user.whoami/')
76+
);
77+
}
78+
79+
public function getTokenURI() {
80+
return $this->getURI('/oauthserver/token/');
81+
}
82+
83+
public function getUserInfoURI() {
84+
return $this->getURI('/api/user.whoami/');
85+
}
86+
87+
public function getMinimumScope() {
88+
return 'email';
89+
}
90+
91+
public function setUserData($data) {
92+
$data = json_decode($data, true);
93+
$this->userData = $data['result'];
94+
return $this;
95+
}
96+
97+
public function retrieveUserID() {
98+
return $this->userData['phid'];
99+
}
100+
101+
public function retrieveUserEmail() {
102+
return $this->userData['email'];
103+
}
104+
105+
public function retrieveUserAccountName() {
106+
return $this->userData['userName'];
107+
}
108+
109+
public function retrieveUserProfileImage() {
110+
$uri = $this->userData['image'];
111+
return @file_get_contents($uri);
112+
}
113+
114+
public function retrieveUserAccountURI() {
115+
return $this->userData['uri'];
116+
}
117+
118+
public function retrieveUserRealName() {
119+
return $this->userData['realName'];
120+
}
121+
122+
private function getURI($path) {
123+
return
124+
rtrim(PhabricatorEnv::getEnvConfig('phabricator.oauth-uri'), '/') .
125+
$path;
126+
}
127+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
/**
3+
* This file is automatically generated. Lint this module to rebuild it.
4+
* @generated
5+
*/
6+
7+
8+
9+
phutil_require_module('phabricator', 'applications/auth/oauth/provider/base');
10+
phutil_require_module('phabricator', 'infrastructure/env');
11+
12+
13+
phutil_require_source('PhabricatorOAuthProviderPhabricator.php');

0 commit comments

Comments
 (0)