3 abstract class PhabricatorOAuth2AuthProvider
4 extends PhabricatorOAuthAuthProvider
{
6 const PROPERTY_APP_ID
= 'oauth:app:id';
7 const PROPERTY_APP_SECRET
= 'oauth:app:secret';
9 protected function getIDKey() {
10 return self
::PROPERTY_APP_ID
;
13 protected function getSecretKey() {
14 return self
::PROPERTY_APP_SECRET
;
18 protected function configureAdapter(PhutilOAuthAuthAdapter
$adapter) {
19 $config = $this->getProviderConfig();
20 $adapter->setClientID($config->getProperty(self
::PROPERTY_APP_ID
));
21 $adapter->setClientSecret(
22 new PhutilOpaqueEnvelope(
23 $config->getProperty(self
::PROPERTY_APP_SECRET
)));
24 $adapter->setRedirectURI(PhabricatorEnv
::getURI($this->getLoginURI()));
28 protected function renderLoginForm(AphrontRequest
$request, $mode) {
29 $adapter = $this->getAdapter();
30 $adapter->setState($this->getAuthCSRFCode($request));
32 $scope = $request->getStr('scope');
34 $adapter->setScope($scope);
39 'uri' => $adapter->getAuthenticateURI(),
42 return $this->renderStandardLoginButton($request, $mode, $attributes);
45 public function processLoginRequest(
46 PhabricatorAuthLoginController
$controller) {
48 $request = $controller->getRequest();
49 $adapter = $this->getAdapter();
53 $error = $request->getStr('error');
55 $response = $controller->buildProviderErrorResponse(
58 'The OAuth provider returned an error: %s',
61 return array($account, $response);
64 $this->verifyAuthCSRFCode($request, $request->getStr('state'));
66 $code = $request->getStr('code');
68 $response = $controller->buildProviderErrorResponse(
71 'The OAuth provider did not return a "code" parameter in its '.
74 return array($account, $response);
77 $adapter->setCode($code);
79 // NOTE: As a side effect, this will cause the OAuth adapter to request
83 $identifiers = $adapter->getAccountIdentifiers();
84 } catch (Exception
$ex) {
85 // TODO: Handle this in a more user-friendly way.
90 $response = $controller->buildProviderErrorResponse(
93 'The OAuth provider failed to retrieve an account ID.'));
95 return array($account, $response);
98 $account = $this->newExternalAccountForIdentifiers($identifiers);
100 return array($account, $response);
103 public function processEditForm(
104 AphrontRequest
$request,
107 return $this->processOAuthEditForm(
110 pht('Application ID is required.'),
111 pht('Application secret is required.'));
114 public function extendEditForm(
115 AphrontRequest
$request,
116 AphrontFormView
$form,
120 return $this->extendOAuthEditForm(
126 pht('OAuth App Secret'));
129 public function renderConfigPropertyTransactionTitle(
130 PhabricatorAuthProviderConfigTransaction
$xaction) {
132 $author_phid = $xaction->getAuthorPHID();
133 $old = $xaction->getOldValue();
134 $new = $xaction->getNewValue();
135 $key = $xaction->getMetadataValue(
136 PhabricatorAuthProviderConfigTransaction
::PROPERTY_KEY
);
139 case self
::PROPERTY_APP_ID
:
142 '%s updated the OAuth application ID for this provider from '.
144 $xaction->renderHandleLink($author_phid),
149 '%s set the OAuth application ID for this provider to '.
151 $xaction->renderHandleLink($author_phid),
154 case self
::PROPERTY_APP_SECRET
:
157 '%s updated the OAuth application secret for this provider.',
158 $xaction->renderHandleLink($author_phid));
161 '%s set the OAuth application secret for this provider.',
162 $xaction->renderHandleLink($author_phid));
164 case self
::PROPERTY_NOTE
:
167 '%s updated the OAuth application notes for this provider.',
168 $xaction->renderHandleLink($author_phid));
171 '%s set the OAuth application notes for this provider.',
172 $xaction->renderHandleLink($author_phid));
177 return parent
::renderConfigPropertyTransactionTitle($xaction);
180 protected function synchronizeOAuthAccount(
181 PhabricatorExternalAccount
$account) {
182 $adapter = $this->getAdapter();
184 $oauth_token = $adapter->getAccessToken();
185 $account->setProperty('oauth.token.access', $oauth_token);
187 if ($adapter->supportsTokenRefresh()) {
188 $refresh_token = $adapter->getRefreshToken();
189 $account->setProperty('oauth.token.refresh', $refresh_token);
191 $account->setProperty('oauth.token.refresh', null);
194 $expires = $adapter->getAccessTokenExpires();
195 $account->setProperty('oauth.token.access.expires', $expires);
198 public function getOAuthAccessToken(
199 PhabricatorExternalAccount
$account,
200 $force_refresh = false) {
202 if ($account->getProviderConfigPHID() !== $this->getProviderConfigPHID()) {
203 throw new Exception(pht('Account does not match provider!'));
206 if (!$force_refresh) {
207 $access_expires = $account->getProperty('oauth.token.access.expires');
208 $access_token = $account->getProperty('oauth.token.access');
210 // Don't return a token with fewer than this many seconds remaining until
212 $shortest_token = 60;
214 if ($access_expires === null ||
215 $access_expires > (time() +
$shortest_token)) {
216 return $access_token;
221 $refresh_token = $account->getProperty('oauth.token.refresh');
222 if ($refresh_token) {
223 $adapter = $this->getAdapter();
224 if ($adapter->supportsTokenRefresh()) {
225 $adapter->refreshAccessToken($refresh_token);
227 $this->synchronizeOAuthAccount($account);
228 $unguarded = AphrontWriteGuard
::beginScopedUnguardedWrites();
232 return $account->getProperty('oauth.token.access');
239 public function willRenderLinkedAccount(
240 PhabricatorUser
$viewer,
241 PHUIObjectItemView
$item,
242 PhabricatorExternalAccount
$account) {
244 // Get a valid token, possibly refreshing it. If we're unable to refresh
245 // it, render a message to that effect. The user may be able to repair the
246 // link by manually reconnecting.
250 $oauth_token = $this->getOAuthAccessToken($account);
251 } catch (Exception
$ex) {
256 $item->addAttribute(pht('OAuth2 Account'));
259 $oauth_expires = $account->getProperty('oauth.token.access.expires');
260 if ($oauth_expires) {
263 'Active OAuth Token (Expires: %s)',
264 phabricator_datetime($oauth_expires, $viewer)));
267 pht('Active OAuth Token'));
269 } else if ($is_invalid) {
270 $item->addAttribute(pht('Invalid OAuth Access Token'));
272 $item->addAttribute(pht('No OAuth Access Token'));
275 parent
::willRenderLinkedAccount($viewer, $item, $account);
278 public function supportsAutoLogin() {
282 public function getAutoLoginURI(AphrontRequest
$request) {
283 $csrf_code = $this->getAuthCSRFCode($request);
285 $adapter = $this->getAdapter();
286 $adapter->setState($csrf_code);
288 return $adapter->getAuthenticateURI();