1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-13 18:32:41 +01:00
phorge-phorge/src/applications/auth/controller/PhabricatorOAuthLoginController.php

348 lines
12 KiB
PHP
Raw Normal View History

<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorOAuthLoginController
extends PhabricatorAuthController {
private $provider;
private $userID;
2011-02-28 04:47:22 +01:00
private $accessToken;
private $tokenExpires;
private $oauthState;
public function shouldRequireLogin() {
return false;
}
public function willProcessRequest(array $data) {
$this->provider = PhabricatorOAuthProvider::newProvider($data['provider']);
}
public function processRequest() {
$current_user = $this->getRequest()->getUser();
$provider = $this->provider;
if (!$provider->isProviderEnabled()) {
return new Aphront400Response();
}
$provider_name = $provider->getProviderName();
$provider_key = $provider->getProviderKey();
$request = $this->getRequest();
if ($request->getStr('error')) {
$error_view = id(new PhabricatorOAuthFailureView())
->setRequest($request);
return $this->buildErrorResponse($error_view);
}
2011-02-28 04:47:22 +01:00
$error_response = $this->retrieveAccessToken($provider);
if ($error_response) {
return $error_response;
}
$userinfo_uri = new PhutilURI($provider->getUserInfoURI());
$userinfo_uri->setQueryParam('access_token', $this->accessToken);
try {
$user_data = @file_get_contents($userinfo_uri);
$provider->setUserData($user_data);
} catch (PhabricatorOAuthProviderException $e) {
return $this->buildErrorResponse(new PhabricatorOAuthFailureView());
}
2011-02-28 04:47:22 +01:00
$provider->setAccessToken($this->accessToken);
2011-02-28 04:47:22 +01:00
$user_id = $provider->retrieveUserID();
$provider_key = $provider->getProviderKey();
2011-02-28 04:47:22 +01:00
$oauth_info = $this->retrieveOAuthInfo($provider);
if ($current_user->getPHID()) {
2011-02-28 04:47:22 +01:00
if ($oauth_info->getID()) {
if ($oauth_info->getUserID() != $current_user->getID()) {
$dialog = new AphrontDialogView();
$dialog->setUser($current_user);
$dialog->setTitle('Already Linked to Another Account');
$dialog->appendChild(
hsprintf(
'<p>The %s account you just authorized is already linked to '.
'another Phabricator account. Before you can associate your %s '.
'account with this Phabriactor account, you must unlink it from '.
'the Phabricator account it is currently linked to.</p>',
$provider_name,
$provider_name));
$dialog->addCancelButton('/settings/page/'.$provider_key.'/');
return id(new AphrontDialogResponse())->setDialog($dialog);
} else {
$this->saveOAuthInfo($oauth_info); // Refresh token.
return id(new AphrontRedirectResponse())
->setURI('/settings/page/'.$provider_key.'/');
}
}
2011-02-28 04:47:22 +01:00
$existing_oauth = id(new PhabricatorUserOAuthInfo())->loadOneWhere(
'userID = %d AND oauthProvider = %s',
$current_user->getID(),
$provider_key);
if ($existing_oauth) {
$dialog = new AphrontDialogView();
$dialog->setUser($current_user);
$dialog->setTitle('Already Linked to an Account From This Provider');
$dialog->appendChild(
hsprintf(
'<p>The account you are logged in with is already linked to a %s '.
'account. Before you can link it to a different %s account, you '.
'must unlink the old account.</p>',
$provider_name,
$provider_name));
2011-02-28 04:47:22 +01:00
$dialog->addCancelButton('/settings/page/'.$provider_key.'/');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
if (!$request->isDialogFormPost()) {
$dialog = new AphrontDialogView();
$dialog->setUser($current_user);
$dialog->setTitle('Link '.$provider_name.' Account');
$dialog->appendChild(
hsprintf(
'<p>Link your %s account to your Phabricator account?</p>',
$provider_name));
$dialog->addHiddenInput('confirm_token', $provider->getAccessToken());
2011-02-28 04:47:22 +01:00
$dialog->addHiddenInput('expires', $oauth_info->getTokenExpires());
$dialog->addHiddenInput('state', $this->oauthState);
$dialog->addHiddenInput('scope', $oauth_info->getTokenScope());
$dialog->addSubmitButton('Link Accounts');
$dialog->addCancelButton('/settings/page/'.$provider_key.'/');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$oauth_info->setUserID($current_user->getID());
$this->saveOAuthInfo($oauth_info);
return id(new AphrontRedirectResponse())
->setURI('/settings/page/'.$provider_key.'/');
}
// Login with known auth.
2011-02-28 04:47:22 +01:00
if ($oauth_info->getID()) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
2011-02-28 04:47:22 +01:00
$known_user = id(new PhabricatorUser())->load($oauth_info->getUserID());
2011-02-28 19:15:42 +01:00
$request->getApplicationConfiguration()->willAuthenticateUserWithOAuth(
$known_user,
$oauth_info,
$provider);
$session_key = $known_user->establishSession('web');
$this->saveOAuthInfo($oauth_info);
$request->setCookie('phusr', $known_user->getUsername());
$request->setCookie('phsid', $session_key);
$uri = new PhutilURI('/login/validate/');
$uri->setQueryParams(
array(
'phusr' => $known_user->getUsername(),
));
return id(new AphrontRedirectResponse())->setURI((string)$uri);
}
2011-02-28 04:47:22 +01:00
$oauth_email = $provider->retrieveUserEmail();
if ($oauth_email) {
Allow users to have multiple email addresses, and verify emails Summary: - Move email to a separate table. - Migrate existing email to new storage. - Allow users to add and remove email addresses. - Allow users to verify email addresses. - Allow users to change their primary email address. - Convert all the registration/reset/login code to understand these changes. - There are a few security considerations here but I think I've addressed them. Principally, it is important to never let a user acquire a verified email address they don't actually own. We ensure this by tightening the scoping of token generation rules to be (user, email) specific. - This should have essentially zero impact on Facebook, but may require some minor changes in the registration code -- I don't exactly remember how it is set up. Not included here (next steps): - Allow configuration to restrict email to certain domains. - Allow configuration to require validated email. Test Plan: This is a fairly extensive, difficult-to-test change. - From "Email Addresses" interface: - Added new email (verified email verifications sent). - Changed primary email (verified old/new notificactions sent). - Resent verification emails (verified they sent). - Removed email. - Tried to add already-owned email. - Created new users with "accountadmin". Edited existing users with "accountadmin". - Created new users with "add_user.php". - Created new users with web interface. - Clicked welcome email link, verified it verified email. - Reset password. - Linked/unlinked oauth accounts. - Logged in with oauth account. - Logged in with email. - Registered with Oauth account. - Tried to register with OAuth account with duplicate email. - Verified errors for email verification with bad tokens, etc. Reviewers: btrahan, vrana, jungejason Reviewed By: btrahan CC: aran Maniphest Tasks: T1184 Differential Revision: https://secure.phabricator.com/D2393
2012-05-07 19:29:33 +02:00
$known_email = id(new PhabricatorUserEmail())
->loadOneWhere('address = %s', $oauth_email);
if ($known_email) {
$dialog = new AphrontDialogView();
$dialog->setUser($current_user);
$dialog->setTitle('Already Linked to Another Account');
$dialog->appendChild(
hsprintf(
'<p>The %s account you just authorized has an email address which '.
'is already in use by another Phabricator account. To link the '.
'accounts, log in to your Phabricator account and then go to '.
'Settings.</p>',
$provider_name));
$dialog->addCancelButton('/login/');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
2011-02-28 04:47:22 +01:00
if (!$provider->isProviderRegistrationEnabled()) {
$dialog = new AphrontDialogView();
$dialog->setUser($current_user);
$dialog->setTitle('No Account Registration With '.$provider_name);
2011-02-28 04:47:22 +01:00
$dialog->appendChild(
hsprintf(
'<p>You can not register a new account using %s; you can only use '.
'your %s account to log into an existing Phabricator account which '.
'you have registered through other means.</p>',
$provider_name,
$provider_name));
2011-02-28 04:47:22 +01:00
$dialog->addCancelButton('/login/');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$controller = PhabricatorEnv::newObjectFromConfig(
'controller.oauth-registration',
array($this->getRequest()));
2011-02-28 04:47:22 +01:00
$controller->setOAuthProvider($provider);
$controller->setOAuthInfo($oauth_info);
$controller->setOAuthState($this->oauthState);
2011-02-28 04:47:22 +01:00
return $this->delegateToController($controller);
}
private function buildErrorResponse(PhabricatorOAuthFailureView $view) {
$provider = $this->provider;
$provider_name = $provider->getProviderName();
$view->setOAuthProvider($provider);
return $this->buildStandardPageResponse(
$view,
array(
'title' => $provider_name.' Auth Failed',
));
}
2011-02-28 04:47:22 +01:00
private function retrieveAccessToken(PhabricatorOAuthProvider $provider) {
$request = $this->getRequest();
$token = $request->getStr('confirm_token');
2011-02-28 04:47:22 +01:00
if ($token) {
$this->tokenExpires = $request->getInt('expires');
$this->accessToken = $token;
$this->oauthState = $request->getStr('state');
2011-02-28 04:47:22 +01:00
return null;
}
$client_id = $provider->getClientID();
$client_secret = $provider->getClientSecret();
$redirect_uri = $provider->getRedirectURI();
$auth_uri = $provider->getTokenURI();
2011-02-28 04:47:22 +01:00
$code = $request->getStr('code');
$query_data = array(
'client_id' => $client_id,
'client_secret' => $client_secret,
'redirect_uri' => $redirect_uri,
'code' => $code,
) + $provider->getExtraTokenParameters();
2011-02-28 04:47:22 +01:00
$post_data = http_build_query($query_data, '', '&');
2011-02-28 04:47:22 +01:00
$post_length = strlen($post_data);
$stream_context = stream_context_create(
array(
'http' => array(
'method' => 'POST',
'header' =>
"Content-Type: application/x-www-form-urlencoded\r\n".
"Content-Length: {$post_length}\r\n",
'content' => $post_data,
),
));
$stream = fopen($auth_uri, 'r', false, $stream_context);
$response = false;
$meta = null;
if ($stream) {
$meta = stream_get_meta_data($stream);
$response = stream_get_contents($stream);
fclose($stream);
}
2011-02-28 04:47:22 +01:00
if ($response === false) {
return $this->buildErrorResponse(new PhabricatorOAuthFailureView());
}
$data = $provider->decodeTokenResponse($response);
2011-02-28 04:47:22 +01:00
$token = idx($data, 'access_token');
if (!$token) {
return $this->buildErrorResponse(new PhabricatorOAuthFailureView());
}
2011-02-28 04:47:22 +01:00
$this->tokenExpires = $provider->getTokenExpiryFromArray($data);
$this->accessToken = $token;
$this->oauthState = $request->getStr('state');
2011-02-28 04:47:22 +01:00
return null;
}
2011-02-28 04:47:22 +01:00
private function retrieveOAuthInfo(PhabricatorOAuthProvider $provider) {
2011-02-28 04:47:22 +01:00
$oauth_info = id(new PhabricatorUserOAuthInfo())->loadOneWhere(
'oauthProvider = %s and oauthUID = %s',
$provider->getProviderKey(),
$provider->retrieveUserID());
$scope = $this->getRequest()->getStr('scope');
2011-02-28 04:47:22 +01:00
if (!$oauth_info) {
$oauth_info = new PhabricatorUserOAuthInfo();
$oauth_info->setOAuthProvider($provider->getProviderKey());
$oauth_info->setOAuthUID($provider->retrieveUserID());
// some providers don't tell you what scope you got, so default
// to the minimum Phabricator requires rather than assuming no scope
if (!$scope) {
$scope = $provider->getMinimumScope();
}
2011-02-28 04:47:22 +01:00
}
2011-02-28 04:47:22 +01:00
$oauth_info->setAccountURI($provider->retrieveUserAccountURI());
$oauth_info->setAccountName($provider->retrieveUserAccountName());
$oauth_info->setToken($provider->getAccessToken());
$oauth_info->setTokenStatus(PhabricatorUserOAuthInfo::TOKEN_STATUS_GOOD);
$oauth_info->setTokenScope($scope);
// If we have out-of-date expiration info, just clear it out. Then replace
// it with good info if the provider gave it to us.
$expires = $oauth_info->getTokenExpires();
if ($expires <= time()) {
$expires = null;
}
if ($this->tokenExpires) {
$expires = $this->tokenExpires;
}
$oauth_info->setTokenExpires($expires);
2011-02-28 04:47:22 +01:00
return $oauth_info;
}
private function saveOAuthInfo(PhabricatorUserOAuthInfo $info) {
// UNGUARDED WRITES: Logging-in users don't have their CSRF set up yet.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$info->save();
}
}