1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 23:02:42 +01:00

Modularize oauth.

This commit is contained in:
epriestley 2011-02-27 19:47:22 -08:00
parent eccc76dae6
commit d3efdcff03
21 changed files with 649 additions and 374 deletions

View file

@ -176,11 +176,25 @@ return array(
'amazon-ses.secret-key' => null, 'amazon-ses.secret-key' => null,
// -- Auth ------------------------------------------------------------------ //
// Can users login with a username/password, or by following the link from
// a password reset email? You can disable this and configure one or more
// OAuth providers instead.
'auth.password-auth-enabled' => true,
// -- Facebook ------------------------------------------------------------ // // -- Facebook ------------------------------------------------------------ //
// Can users use Facebook credentials to login to Phabricator? // Can users use Facebook credentials to login to Phabricator?
'facebook.auth-enabled' => false, 'facebook.auth-enabled' => false,
// Can users use Facebook credentials to create new Phabricator accounts?
'facebook.registration-enabled' => true,
// Are Facebook accounts permanently linked to Phabricator accounts, or can
// the user unlink them?
'facebook.auth-permanent' => false,
// The Facebook "Application ID" to use for Facebook API access. // The Facebook "Application ID" to use for Facebook API access.
'facebook.application-id' => null, 'facebook.application-id' => null,
@ -193,6 +207,13 @@ return array(
// Can users use Github credentials to login to Phabricator? // Can users use Github credentials to login to Phabricator?
'github.auth-enabled' => false, 'github.auth-enabled' => false,
// Can users use Github credentials to create new Phabricator accounts?
'github.registration-enabled' => true,
// Are Github accounts permanently linked to Phabricator accounts, or can
// the user unlink them?
'github.auth-permanent' => false,
// The Github "Client ID" to use for Github API access. // The Github "Client ID" to use for Github API access.
'github.application-id' => null, 'github.application-id' => null,
@ -252,4 +273,7 @@ return array(
'aphront.default-application-configuration-class' => 'aphront.default-application-configuration-class' =>
'AphrontDefaultApplicationConfiguration', 'AphrontDefaultApplicationConfiguration',
'controller.oauth-registration' =>
'PhabricatorOAuthDefaultRegistrationController',
); );

View file

@ -212,12 +212,14 @@ phutil_register_library_map(array(
'PhabricatorMetaMTAMailingListsController' => 'applications/metamta/controller/mailinglists', 'PhabricatorMetaMTAMailingListsController' => 'applications/metamta/controller/mailinglists',
'PhabricatorMetaMTASendController' => 'applications/metamta/controller/send', 'PhabricatorMetaMTASendController' => 'applications/metamta/controller/send',
'PhabricatorMetaMTAViewController' => 'applications/metamta/controller/view', 'PhabricatorMetaMTAViewController' => 'applications/metamta/controller/view',
'PhabricatorOAuthDefaultRegistrationController' => 'applications/auth/controller/oauthregistration/default',
'PhabricatorOAuthDiagnosticsController' => 'applications/auth/controller/oauthdiagnostics', 'PhabricatorOAuthDiagnosticsController' => 'applications/auth/controller/oauthdiagnostics',
'PhabricatorOAuthFailureView' => 'applications/auth/view/oauthfailure', 'PhabricatorOAuthFailureView' => 'applications/auth/view/oauthfailure',
'PhabricatorOAuthLoginController' => 'applications/auth/controller/oauth', 'PhabricatorOAuthLoginController' => 'applications/auth/controller/oauth',
'PhabricatorOAuthProvider' => 'applications/auth/oauth/provider/base', 'PhabricatorOAuthProvider' => 'applications/auth/oauth/provider/base',
'PhabricatorOAuthProviderFacebook' => 'applications/auth/oauth/provider/facebook', 'PhabricatorOAuthProviderFacebook' => 'applications/auth/oauth/provider/facebook',
'PhabricatorOAuthProviderGithub' => 'applications/auth/oauth/provider/github', 'PhabricatorOAuthProviderGithub' => 'applications/auth/oauth/provider/github',
'PhabricatorOAuthRegistrationController' => 'applications/auth/controller/oauthregistration/base',
'PhabricatorOAuthUnlinkController' => 'applications/auth/controller/unlink', 'PhabricatorOAuthUnlinkController' => 'applications/auth/controller/unlink',
'PhabricatorObjectHandle' => 'applications/phid/handle', 'PhabricatorObjectHandle' => 'applications/phid/handle',
'PhabricatorObjectHandleData' => 'applications/phid/handle/data', 'PhabricatorObjectHandleData' => 'applications/phid/handle/data',
@ -463,11 +465,13 @@ phutil_register_library_map(array(
'PhabricatorMetaMTAMailingListsController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMailingListsController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTASendController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTASendController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAViewController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAViewController' => 'PhabricatorMetaMTAController',
'PhabricatorOAuthDefaultRegistrationController' => 'PhabricatorOAuthRegistrationController',
'PhabricatorOAuthDiagnosticsController' => 'PhabricatorAuthController', 'PhabricatorOAuthDiagnosticsController' => 'PhabricatorAuthController',
'PhabricatorOAuthFailureView' => 'AphrontView', 'PhabricatorOAuthFailureView' => 'AphrontView',
'PhabricatorOAuthLoginController' => 'PhabricatorAuthController', 'PhabricatorOAuthLoginController' => 'PhabricatorAuthController',
'PhabricatorOAuthProviderFacebook' => 'PhabricatorOAuthProvider', 'PhabricatorOAuthProviderFacebook' => 'PhabricatorOAuthProvider',
'PhabricatorOAuthProviderGithub' => 'PhabricatorOAuthProvider', 'PhabricatorOAuthProviderGithub' => 'PhabricatorOAuthProvider',
'PhabricatorOAuthRegistrationController' => 'PhabricatorAuthController',
'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController', 'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController',
'PhabricatorPHID' => 'PhabricatorPHIDDAO', 'PhabricatorPHID' => 'PhabricatorPHIDDAO',
'PhabricatorPHIDAllocateController' => 'PhabricatorPHIDController', 'PhabricatorPHIDAllocateController' => 'PhabricatorPHIDController',

View file

@ -88,6 +88,7 @@ final class DarkConsoleCore {
$visible = $user->getConsoleVisible(); $visible = $user->getConsoleVisible();
if (!isset($plugins[$selected])) { if (!isset($plugins[$selected])) {
reset($plugins);
$selected = key($plugins); $selected = key($plugins);
} }

View file

@ -41,4 +41,8 @@ abstract class AphrontController {
return $this->request; return $this->request;
} }
final public function delegateToController(AphrontController $controller) {
return $controller->processRequest();
}
} }

View file

@ -25,6 +25,10 @@ class PhabricatorEmailLoginController extends PhabricatorAuthController {
public function processRequest() { public function processRequest() {
$request = $this->getRequest(); $request = $this->getRequest();
if (!PhabricatorEnv::getEnvConfig('auth.password-auth-enabled')) {
return new Aphront400Response();
}
$e_email = true; $e_email = true;
$e_captcha = true; $e_captcha = true;
$errors = array(); $errors = array();

View file

@ -6,6 +6,7 @@
phutil_require_module('phabricator', 'aphront/response/400');
phutil_require_module('phabricator', 'applications/auth/controller/base'); phutil_require_module('phabricator', 'applications/auth/controller/base');
phutil_require_module('phabricator', 'applications/metamta/storage/mail'); phutil_require_module('phabricator', 'applications/metamta/storage/mail');
phutil_require_module('phabricator', 'applications/people/storage/user'); phutil_require_module('phabricator', 'applications/people/storage/user');

View file

@ -31,6 +31,10 @@ class PhabricatorEmailTokenController extends PhabricatorAuthController {
public function processRequest() { public function processRequest() {
$request = $this->getRequest(); $request = $this->getRequest();
if (!PhabricatorEnv::getEnvConfig('auth.password-auth-enabled')) {
return new Aphront400Response();
}
$token = $this->token; $token = $this->token;
$email = $request->getStr('email'); $email = $request->getStr('email');

View file

@ -6,9 +6,11 @@
phutil_require_module('phabricator', 'aphront/response/400');
phutil_require_module('phabricator', 'aphront/response/redirect'); phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/auth/controller/base'); phutil_require_module('phabricator', 'applications/auth/controller/base');
phutil_require_module('phabricator', 'applications/people/storage/user'); phutil_require_module('phabricator', 'applications/people/storage/user');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'view/form/base'); phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/submit'); phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/form/error'); phutil_require_module('phabricator', 'view/form/error');

View file

@ -30,69 +30,73 @@ class PhabricatorLoginController extends PhabricatorAuthController {
return id(new AphrontRedirectResponse())->setURI('/'); return id(new AphrontRedirectResponse())->setURI('/');
} }
$error = false; $password_auth = PhabricatorEnv::getEnvConfig('auth.password-auth-enabled');
$username = $request->getCookie('phusr');
if ($request->isFormPost()) {
$username = $request->getStr('username');
$user = id(new PhabricatorUser())->loadOneWhere( $forms = array();
'username = %s',
$username);
$okay = false;
if ($user) {
if ($user->comparePassword($request->getStr('password'))) {
$session_key = $user->establishSession('web');
$request->setCookie('phusr', $user->getUsername());
$request->setCookie('phsid', $session_key);
return id(new AphrontRedirectResponse())
->setURI('/');
}
}
if (!$okay) {
$request->clearCookie('phusr');
$request->clearCookie('phsid');
}
$error = true;
}
$error_view = null; $error_view = null;
if ($error) { if ($password_auth) {
$error_view = new AphrontErrorView(); $error = false;
$error_view->setTitle('Bad username/password.'); $username = $request->getCookie('phusr');
if ($request->isFormPost()) {
$username = $request->getStr('username');
$user = id(new PhabricatorUser())->loadOneWhere(
'username = %s',
$username);
$okay = false;
if ($user) {
if ($user->comparePassword($request->getStr('password'))) {
$session_key = $user->establishSession('web');
$request->setCookie('phusr', $user->getUsername());
$request->setCookie('phsid', $session_key);
return id(new AphrontRedirectResponse())
->setURI('/');
}
}
if (!$okay) {
$request->clearCookie('phusr');
$request->clearCookie('phsid');
}
$error = true;
}
if ($error) {
$error_view = new AphrontErrorView();
$error_view->setTitle('Bad username/password.');
}
$form = new AphrontFormView();
$form
->setUser($request->getUser())
->setAction('/login/')
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Username/Email')
->setName('username')
->setValue($username))
->appendChild(
id(new AphrontFormPasswordControl())
->setLabel('Password')
->setName('password')
->setCaption(
'<a href="/login/email/">'.
'Forgot your password? / Email Login</a>'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Login'));
// $panel->setCreateButton('Register New Account', '/login/register/');
$forms['Phabricator Login'] = $form;
} }
$form = new AphrontFormView();
$form
->setUser($request->getUser())
->setAction('/login/')
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Username/Email')
->setName('username')
->setValue($username))
->appendChild(
id(new AphrontFormPasswordControl())
->setLabel('Password')
->setName('password')
->setCaption(
'<a href="/login/email/">Forgot your password? / Email Login</a>'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Login'));
$panel = new AphrontPanelView();
$panel->setHeader('Phabricator Login');
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
// $panel->setCreateButton('Register New Account', '/login/register/');
$panel->appendChild($form);
$providers = array( $providers = array(
PhabricatorOAuthProvider::PROVIDER_FACEBOOK, PhabricatorOAuthProvider::PROVIDER_FACEBOOK,
PhabricatorOAuthProvider::PROVIDER_GITHUB, PhabricatorOAuthProvider::PROVIDER_GITHUB,
@ -117,6 +121,19 @@ class PhabricatorLoginController extends PhabricatorAuthController {
// does not seem like the most severe threat in the world, and generating // does not seem like the most severe threat in the world, and generating
// CSRF for logged-out users is vaugely tricky. // CSRF for logged-out users is vaugely tricky.
if ($provider->isProviderRegistrationEnabled()) {
$title = "Login or Register with {$provider_name}";
$body = "Login or register for Phabricator using your ".
"{$provider_name} account.";
$button = "Login or Register with {$provider_name}";
} else {
$title = "Login with {$provider_name}";
$body = "Login to your existing Phabricator account using your ".
"{$provider_name} account.<br /><br /><strong>You can not use ".
"{$provider_name} to register a new account.</strong>";
$button = "Login with {$provider_name}";
}
$auth_form = new AphrontFormView(); $auth_form = new AphrontFormView();
$auth_form $auth_form
->setAction($auth_uri) ->setAction($auth_uri)
@ -126,16 +143,20 @@ class PhabricatorLoginController extends PhabricatorAuthController {
->setUser($request->getUser()) ->setUser($request->getUser())
->setMethod('GET') ->setMethod('GET')
->appendChild( ->appendChild(
'<p class="aphront-form-instructions">Login or register for '. '<p class="aphront-form-instructions">'.$body.'</p>')
'Phabricator using your '.$provider_name.' account.</p>')
->appendChild( ->appendChild(
id(new AphrontFormSubmitControl()) id(new AphrontFormSubmitControl())
->setValue("Login with {$provider_name} \xC2\xBB")); ->setValue("{$button} \xC2\xBB"));
$panel->appendChild( $forms[$title] = $auth_form;
'<br /><h1>Login or Register with '.$provider_name.'</h1>'); }
$panel->appendChild($auth_form); $panel = new AphrontPanelView();
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
foreach ($forms as $name => $form) {
$panel->appendChild('<h1>'.$name.'</h1>');
$panel->appendChild($form);
$panel->appendChild('<br />');
} }
return $this->buildStandardPageResponse( return $this->buildStandardPageResponse(

View file

@ -10,6 +10,7 @@ phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/auth/controller/base'); phutil_require_module('phabricator', 'applications/auth/controller/base');
phutil_require_module('phabricator', 'applications/auth/oauth/provider/base'); phutil_require_module('phabricator', 'applications/auth/oauth/provider/base');
phutil_require_module('phabricator', 'applications/people/storage/user'); phutil_require_module('phabricator', 'applications/people/storage/user');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'view/form/base'); phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/submit'); phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/form/error'); phutil_require_module('phabricator', 'view/form/error');

View file

@ -20,6 +20,7 @@ class PhabricatorOAuthLoginController extends PhabricatorAuthController {
private $provider; private $provider;
private $userID; private $userID;
private $accessToken; private $accessToken;
private $tokenExpires; private $tokenExpires;
@ -50,94 +51,31 @@ class PhabricatorOAuthLoginController extends PhabricatorAuthController {
return $this->buildErrorResponse($error_view); return $this->buildErrorResponse($error_view);
} }
$token = $request->getStr('token'); $error_response = $this->retrieveAccessToken($provider);
if (!$token) { if ($error_response) {
$client_id = $provider->getClientID(); return $error_response;
$client_secret = $provider->getClientSecret();
$redirect_uri = $provider->getRedirectURI();
$auth_uri = $provider->getTokenURI();
$code = $request->getStr('code');
$query_data = array(
'client_id' => $client_id,
'client_secret' => $client_secret,
'redirect_uri' => $redirect_uri,
'code' => $code,
);
$post_data = http_build_query($query_data);
$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);
}
if ($response === false) {
return $this->buildErrorResponse(new PhabricatorOAuthFailureView());
}
$data = array();
parse_str($response, $data);
$token = idx($data, 'access_token');
if (!$token) {
return $this->buildErrorResponse(new PhabricatorOAuthFailureView());
}
if (idx($data, 'expires')) {
$this->tokenExpires = time() + $data['expires'];
}
} else {
$this->tokenExpires = $request->getInt('expires');
} }
$userinfo_uri = new PhutilURI($provider->getUserInfoURI()); $userinfo_uri = new PhutilURI($provider->getUserInfoURI());
$userinfo_uri->setQueryParams( $userinfo_uri->setQueryParams(
array( array(
'access_token' => $token, 'access_token' => $this->accessToken,
)); ));
$user_json = @file_get_contents($userinfo_uri); $user_json = @file_get_contents($userinfo_uri);
$user_data = json_decode($user_json, true); $user_data = json_decode($user_json, true);
$this->accessToken = $token; $provider->setUserData($user_data);
$provider->setAccessToken($this->accessToken);
switch ($provider->getProviderKey()) { $user_id = $provider->retrieveUserID();
case PhabricatorOAuthProvider::PROVIDER_GITHUB: $provider_key = $provider->getProviderKey();
$user_data = $user_data['user'];
break;
}
$this->userData = $user_data;
$user_id = $this->retrieveUserID(); $oauth_info = $this->retrieveOAuthInfo($provider);
$known_oauth = id(new PhabricatorUserOAuthInfo())->loadOneWhere(
'oauthProvider = %s and oauthUID = %s',
$provider->getProviderKey(),
$user_id);
if ($current_user->getPHID()) { if ($current_user->getPHID()) {
if ($known_oauth) { if ($oauth_info->getID()) {
if ($known_oauth->getUserID() != $current_user->getID()) { if ($oauth_info->getUserID() != $current_user->getID()) {
$dialog = new AphrontDialogView(); $dialog = new AphrontDialogView();
$dialog->setUser($current_user); $dialog->setUser($current_user);
$dialog->setTitle('Already Linked to Another Account'); $dialog->setTitle('Already Linked to Another Account');
@ -156,6 +94,23 @@ class PhabricatorOAuthLoginController extends PhabricatorAuthController {
} }
} }
$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(
'<p>The account you are logged in with is already linked to a '.
$provider_name.' account. Before you can link it to a different '.
$provider_name.' account, you must unlink the old account.</p>');
$dialog->addCancelButton('/settings/page/'.$provider_key.'/');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
if (!$request->isDialogFormPost()) { if (!$request->isDialogFormPost()) {
$dialog = new AphrontDialogView(); $dialog = new AphrontDialogView();
$dialog->setUser($current_user); $dialog->setUser($current_user);
@ -163,17 +118,15 @@ class PhabricatorOAuthLoginController extends PhabricatorAuthController {
$dialog->appendChild( $dialog->appendChild(
'<p>Link your '.$provider_name.' account to your Phabricator '. '<p>Link your '.$provider_name.' account to your Phabricator '.
'account?</p>'); 'account?</p>');
$dialog->addHiddenInput('token', $token); $dialog->addHiddenInput('token', $provider->getAccessToken());
$dialog->addHiddenInput('expires', $this->tokenExpires); $dialog->addHiddenInput('expires', $oauth_info->getTokenExpires());
$dialog->addSubmitButton('Link Accounts'); $dialog->addSubmitButton('Link Accounts');
$dialog->addCancelButton('/settings/page/'.$provider_key.'/'); $dialog->addCancelButton('/settings/page/'.$provider_key.'/');
return id(new AphrontDialogResponse())->setDialog($dialog); return id(new AphrontDialogResponse())->setDialog($dialog);
} }
$oauth_info = new PhabricatorUserOAuthInfo();
$oauth_info->setUserID($current_user->getID()); $oauth_info->setUserID($current_user->getID());
$this->configureOAuthInfo($oauth_info);
$oauth_info->save(); $oauth_info->save();
return id(new AphrontRedirectResponse()) return id(new AphrontRedirectResponse())
@ -183,12 +136,11 @@ class PhabricatorOAuthLoginController extends PhabricatorAuthController {
// Login with known auth. // Login with known auth.
if ($known_oauth) { if ($oauth_info->getID()) {
$known_user = id(new PhabricatorUser())->load($known_oauth->getUserID()); $known_user = id(new PhabricatorUser())->load($oauth_info->getUserID());
$session_key = $known_user->establishSession('web'); $session_key = $known_user->establishSession('web');
$this->configureOAuthInfo($known_oauth); $oauth_info->save();
$known_oauth->save();
$request->setCookie('phusr', $known_user->getUsername()); $request->setCookie('phusr', $known_user->getUsername());
$request->setCookie('phsid', $session_key); $request->setCookie('phsid', $session_key);
@ -196,10 +148,7 @@ class PhabricatorOAuthLoginController extends PhabricatorAuthController {
->setURI('/'); ->setURI('/');
} }
// Merge accounts based on shared email. TODO: should probably get rid of $oauth_email = $provider->retrieveUserEmail();
// this.
$oauth_email = $this->retrieveUserEmail();
if ($oauth_email) { if ($oauth_email) {
$known_email = id(new PhabricatorUser()) $known_email = id(new PhabricatorUser())
->loadOneWhere('email = %s', $oauth_email); ->loadOneWhere('email = %s', $oauth_email);
@ -218,159 +167,28 @@ class PhabricatorOAuthLoginController extends PhabricatorAuthController {
} }
} }
$errors = array(); if (!$provider->isProviderRegistrationEnabled()) {
$e_username = true; $dialog = new AphrontDialogView();
$e_email = true; $dialog->setUser($current_user);
$e_realname = true; $dialog->setTitle('No Account Registration With '.$provider_name);
$dialog->appendChild(
'<p>You can not register a new account using '.$provider_name.'; '.
'you can only use your '.$provider_name.' account to log into an '.
'existing Phabricator account which you have registered through '.
'other means.</p>');
$dialog->addCancelButton('/login/');
$user = new PhabricatorUser(); return id(new AphrontDialogResponse())->setDialog($dialog);
$suggestion = $this->retrieveUsernameSuggestion();
$user->setUsername($suggestion);
$oauth_realname = $this->retreiveRealNameSuggestion();
if ($request->isFormPost()) {
$user->setUsername($request->getStr('username'));
$username = $user->getUsername();
$matches = null;
if (!strlen($user->getUsername())) {
$e_username = 'Required';
$errors[] = 'Username is required.';
} else if (!preg_match('/^[a-zA-Z0-9]+$/', $username, $matches)) {
$e_username = 'Invalid';
$errors[] = 'Username may only contain letters and numbers.';
} else {
$e_username = null;
}
if ($oauth_email) {
$user->setEmail($oauth_email);
} else {
$user->setEmail($request->getStr('email'));
if (!strlen($user->getEmail())) {
$e_email = 'Required';
$errors[] = 'Email is required.';
} else {
$e_email = null;
}
}
if ($oauth_realname) {
$user->setRealName($oauth_realname);
} else {
$user->setRealName($request->getStr('realname'));
if (!strlen($user->getStr('realname'))) {
$e_realname = 'Required';
$errors[] = 'Real name is required.';
} else {
$e_realname = null;
}
}
if (!$errors) {
$image = $this->retreiveProfileImageSuggestion();
if ($image) {
$file = PhabricatorFile::newFromFileData(
$image,
array(
'name' => $provider->getProviderKey().'-profile.jpg'
));
$user->setProfileImagePHID($file->getPHID());
}
try {
$user->save();
$oauth_info = new PhabricatorUserOAuthInfo();
$oauth_info->setUserID($user->getID());
$this->configureOAuthInfo($oauth_info);
$oauth_info->save();
$session_key = $user->establishSession('web');
$request->setCookie('phusr', $user->getUsername());
$request->setCookie('phsid', $session_key);
return id(new AphrontRedirectResponse())->setURI('/');
} catch (AphrontQueryDuplicateKeyException $exception) {
$same_username = id(new PhabricatorUser())->loadOneWhere(
'userName = %s',
$user->getUserName());
$same_email = id(new PhabricatorUser())->loadOneWhere(
'email = %s',
$user->getEmail());
if ($same_username) {
$e_username = 'Duplicate';
$errors[] = 'That username or email is not unique.';
} else if ($same_email) {
$e_email = 'Duplicate';
$errors[] = 'That email is not unique.';
} else {
throw $exception;
}
}
}
} }
$error_view = null; $class = PhabricatorEnv::getEnvConfig('controller.oauth-registration');
if ($errors) { PhutilSymbolLoader::loadClass($class);
$error_view = new AphrontErrorView(); $controller = newv($class, array($this->getRequest()));
$error_view->setTitle('Registration Failed');
$error_view->setErrors($errors);
}
$form = new AphrontFormView(); $controller->setOAuthProvider($provider);
$form $controller->setOAuthInfo($oauth_info);
->addHiddenInput('token', $token)
->addHiddenInput('expires', $this->tokenExpires)
->setUser($request->getUser())
->setAction($provider->getRedirectURI())
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Username')
->setName('username')
->setValue($user->getUsername())
->setError($e_username));
if (!$oauth_email) { return $this->delegateToController($controller);
$form->appendChild(
id(new AphrontFormTextControl())
->setLabel('Email')
->setName('email')
->setValue($request->getStr('email'))
->setError($e_email));
}
if (!$oauth_realname) {
$form->appendChild(
id(new AphrontFormTextControl())
->setLabel('Real Name')
->setName('realname')
->setValue($request->getStr('realname'))
->setError($e_realname));
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Create Account'));
$panel = new AphrontPanelView();
$panel->setHeader('Create New Account');
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->appendChild($form);
return $this->buildStandardPageResponse(
array(
$error_view,
$panel,
),
array(
'title' => 'Create New Account',
));
} }
private function buildErrorResponse(PhabricatorOAuthFailureView $view) { private function buildErrorResponse(PhabricatorOAuthFailureView $view) {
@ -386,71 +204,90 @@ class PhabricatorOAuthLoginController extends PhabricatorAuthController {
)); ));
} }
private function retrieveUserID() { private function retrieveAccessToken(PhabricatorOAuthProvider $provider) {
return $this->userData['id']; $request = $this->getRequest();
}
private function retrieveUserEmail() { $token = $request->getStr('token');
return $this->userData['email']; if ($token) {
} $this->tokenExpires = $request->getInt('expires');
$this->accessToken = $token;
private function retrieveUsernameSuggestion() { return null;
switch ($this->provider->getProviderKey()) {
case PhabricatorOAuthProvider::PROVIDER_FACEBOOK:
$matches = null;
$link = $this->userData['link'];
if (preg_match('@/([a-zA-Z0-9]+)$@', $link, $matches)) {
return $matches[1];
}
break;
case PhabricatorOAuthProvider::PROVIDER_GITHUB:
return $this->userData['login'];
} }
$client_id = $provider->getClientID();
$client_secret = $provider->getClientSecret();
$redirect_uri = $provider->getRedirectURI();
$auth_uri = $provider->getTokenURI();
$code = $request->getStr('code');
$query_data = array(
'client_id' => $client_id,
'client_secret' => $client_secret,
'redirect_uri' => $redirect_uri,
'code' => $code,
);
$post_data = http_build_query($query_data);
$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);
}
if ($response === false) {
return $this->buildErrorResponse(new PhabricatorOAuthFailureView());
}
$data = array();
parse_str($response, $data);
$token = idx($data, 'access_token');
if (!$token) {
return $this->buildErrorResponse(new PhabricatorOAuthFailureView());
}
if (idx($data, 'expires')) {
$this->tokenExpires = time() + $data['expires'];
}
$this->accessToken = $token;
return null; return null;
} }
private function retreiveProfileImageSuggestion() { private function retrieveOAuthInfo(PhabricatorOAuthProvider $provider) {
switch ($this->provider->getProviderKey()) {
case PhabricatorOAuthProvider::PROVIDER_FACEBOOK: $oauth_info = id(new PhabricatorUserOAuthInfo())->loadOneWhere(
$uri = 'https://graph.facebook.com/me/picture?access_token='; 'oauthProvider = %s and oauthUID = %s',
return @file_get_contents($uri.$this->accessToken); $provider->getProviderKey(),
case PhabricatorOAuthProvider::PROVIDER_GITHUB: $provider->retrieveUserID());
$id = $this->userData['gravatar_id'];
if ($id) { if (!$oauth_info) {
$uri = 'http://www.gravatar.com/avatar/'.$id.'?s=50'; $oauth_info = new PhabricatorUserOAuthInfo();
return @file_get_contents($uri); $oauth_info->setOAuthProvider($provider->getProviderKey());
} $oauth_info->setOAuthUID($provider->retrieveUserID());
} }
return null;
}
private function retrieveAccountURI() { $oauth_info->setAccountURI($provider->retrieveUserAccountURI());
switch ($this->provider->getProviderKey()) { $oauth_info->setAccountName($provider->retrieveUserAccountName());
case PhabricatorOAuthProvider::PROVIDER_FACEBOOK: $oauth_info->setToken($provider->getAccessToken());
return $this->userData['link'];
case PhabricatorOAuthProvider::PROVIDER_GITHUB:
$username = $this->retrieveUsernameSuggestion();
if ($username) {
return 'https://github.com/'.$username;
}
return null;
}
return null;
}
private function retreiveRealNameSuggestion() {
return $this->userData['name'];
}
private function configureOAuthInfo(PhabricatorUserOAuthInfo $oauth_info) {
$provider = $this->provider;
$oauth_info->setOAuthProvider($provider->getProviderKey());
$oauth_info->setOAuthUID($this->retrieveUserID());
$oauth_info->setAccountURI($this->retrieveAccountURI());
$oauth_info->setAccountName($this->retrieveUserNameSuggestion());
$oauth_info->setToken($this->accessToken);
$oauth_info->setTokenStatus(PhabricatorUserOAuthInfo::TOKEN_STATUS_GOOD); $oauth_info->setTokenStatus(PhabricatorUserOAuthInfo::TOKEN_STATUS_GOOD);
// If we have out-of-date expiration info, just clear it out. Then replace // If we have out-of-date expiration info, just clear it out. Then replace
@ -463,6 +300,8 @@ class PhabricatorOAuthLoginController extends PhabricatorAuthController {
$expires = $this->tokenExpires; $expires = $this->tokenExpires;
} }
$oauth_info->setTokenExpires($expires); $oauth_info->setTokenExpires($expires);
return $oauth_info;
} }
} }

View file

@ -12,17 +12,13 @@ phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/auth/controller/base'); phutil_require_module('phabricator', 'applications/auth/controller/base');
phutil_require_module('phabricator', 'applications/auth/oauth/provider/base'); phutil_require_module('phabricator', 'applications/auth/oauth/provider/base');
phutil_require_module('phabricator', 'applications/auth/view/oauthfailure'); phutil_require_module('phabricator', 'applications/auth/view/oauthfailure');
phutil_require_module('phabricator', 'applications/files/storage/file');
phutil_require_module('phabricator', 'applications/people/storage/user'); phutil_require_module('phabricator', 'applications/people/storage/user');
phutil_require_module('phabricator', 'applications/people/storage/useroauthinfo'); phutil_require_module('phabricator', 'applications/people/storage/useroauthinfo');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'view/dialog'); phutil_require_module('phabricator', 'view/dialog');
phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/form/control/text');
phutil_require_module('phabricator', 'view/form/error');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phutil', 'parser/uri'); phutil_require_module('phutil', 'parser/uri');
phutil_require_module('phutil', 'symbols');
phutil_require_module('phutil', 'utils'); phutil_require_module('phutil', 'utils');

View file

@ -0,0 +1,43 @@
<?php
/*
* Copyright 2011 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.
*/
abstract class PhabricatorOAuthRegistrationController
extends PhabricatorAuthController {
private $oauthProvider;
private $oauthInfo;
final public function setOAuthInfo($info) {
$this->oauthInfo = $info;
return $this;
}
final public function getOAuthInfo() {
return $this->oauthInfo;
}
final public function setOAuthProvider($provider) {
$this->oauthProvider = $provider;
return $this;
}
final public function getOAuthProvider() {
return $this->oauthProvider;
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/auth/controller/base');
phutil_require_source('PhabricatorOAuthRegistrationController.php');

View file

@ -0,0 +1,175 @@
<?php
/*
* Copyright 2011 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.
*/
class PhabricatorOAuthDefaultRegistrationController
extends PhabricatorOAuthRegistrationController {
public function processRequest() {
$provider = $this->getOAuthProvider();
$oauth_info = $this->getOAuthInfo();
$request = $this->getRequest();
$errors = array();
$e_username = true;
$e_email = true;
$e_realname = true;
$user = new PhabricatorUser();
$user->setUsername($provider->retrieveUserAccountName());
$user->setRealName($provider->retrieveUserRealName());
$user->setEmail($provider->retrieveUserEmail());
if ($request->isFormPost()) {
$user->setUsername($request->getStr('username'));
$username = $user->getUsername();
$matches = null;
if (!strlen($user->getUsername())) {
$e_username = 'Required';
$errors[] = 'Username is required.';
} else if (!preg_match('/^[a-zA-Z0-9]+$/', $username, $matches)) {
$e_username = 'Invalid';
$errors[] = 'Username may only contain letters and numbers.';
} else {
$e_username = null;
}
if ($user->getEmail() === null) {
$user->setEmail($request->getStr('email'));
if (!strlen($user->getEmail())) {
$e_email = 'Required';
$errors[] = 'Email is required.';
} else {
$e_email = null;
}
}
if ($user->getRealName() === null) {
$user->setRealName($request->getStr('realname'));
if (!strlen($user->getStr('realname'))) {
$e_realname = 'Required';
$errors[] = 'Real name is required.';
} else {
$e_realname = null;
}
}
if (!$errors) {
$image = $provider->retreiveUserProfileImage();
if ($image) {
$file = PhabricatorFile::newFromFileData(
$image,
array(
'name' => $provider->getProviderKey().'-profile.jpg'
));
$user->setProfileImagePHID($file->getPHID());
}
try {
$user->save();
$oauth_info->setUserID($user->getID());
$oauth_info->save();
$session_key = $user->establishSession('web');
$request->setCookie('phusr', $user->getUsername());
$request->setCookie('phsid', $session_key);
return id(new AphrontRedirectResponse())->setURI('/');
} catch (AphrontQueryDuplicateKeyException $exception) {
$same_username = id(new PhabricatorUser())->loadOneWhere(
'userName = %s',
$user->getUserName());
$same_email = id(new PhabricatorUser())->loadOneWhere(
'email = %s',
$user->getEmail());
if ($same_username) {
$e_username = 'Duplicate';
$errors[] = 'That username or email is not unique.';
} else if ($same_email) {
$e_email = 'Duplicate';
$errors[] = 'That email is not unique.';
} else {
throw $exception;
}
}
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle('Registration Failed');
$error_view->setErrors($errors);
}
$form = new AphrontFormView();
$form
->addHiddenInput('token', $provider->getAccessToken())
->addHiddenInput('expires', $oauth_info->getTokenExpires())
->setUser($request->getUser())
->setAction($provider->getRedirectURI())
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Username')
->setName('username')
->setValue($user->getUsername())
->setError($e_username));
if ($provider->retrieveUserEmail() === null) {
$form->appendChild(
id(new AphrontFormTextControl())
->setLabel('Email')
->setName('email')
->setValue($request->getStr('email'))
->setError($e_email));
}
if ($provider->retrieveUserRealName () === null) {
$form->appendChild(
id(new AphrontFormTextControl())
->setLabel('Real Name')
->setName('realname')
->setValue($request->getStr('realname'))
->setError($e_realname));
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Create Account'));
$panel = new AphrontPanelView();
$panel->setHeader('Create New Account');
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->appendChild($form);
return $this->buildStandardPageResponse(
array(
$error_view,
$panel,
),
array(
'title' => 'Create New Account',
));
}
}

View file

@ -0,0 +1,22 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/auth/controller/oauthregistration/base');
phutil_require_module('phabricator', 'applications/files/storage/file');
phutil_require_module('phabricator', 'applications/people/storage/user');
phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/form/control/text');
phutil_require_module('phabricator', 'view/form/error');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorOAuthDefaultRegistrationController.php');

View file

@ -29,6 +29,12 @@ class PhabricatorOAuthUnlinkController extends PhabricatorAuthController {
$user = $request->getUser(); $user = $request->getUser();
$provider = $this->provider; $provider = $this->provider;
if ($provider->isProviderLinkPermanent()) {
throw new Exception(
"You may not unlink accounts from this OAuth provider.");
}
$provider_name = $provider->getProviderName(); $provider_name = $provider->getProviderName();
$provider_key = $provider->getProviderKey(); $provider_key = $provider->getProviderKey();

View file

@ -21,9 +21,13 @@ abstract class PhabricatorOAuthProvider {
const PROVIDER_FACEBOOK = 'facebook'; const PROVIDER_FACEBOOK = 'facebook';
const PROVIDER_GITHUB = 'github'; const PROVIDER_GITHUB = 'github';
private $accessToken;
abstract public function getProviderKey(); abstract public function getProviderKey();
abstract public function getProviderName(); abstract public function getProviderName();
abstract public function isProviderEnabled(); abstract public function isProviderEnabled();
abstract public function isProviderLinkPermanent();
abstract public function isProviderRegistrationEnabled();
abstract public function getRedirectURI(); abstract public function getRedirectURI();
abstract public function getClientID(); abstract public function getClientID();
abstract public function getClientSecret(); abstract public function getClientSecret();
@ -32,10 +36,27 @@ abstract class PhabricatorOAuthProvider {
abstract public function getUserInfoURI(); abstract public function getUserInfoURI();
abstract public function getMinimumScope(); abstract public function getMinimumScope();
abstract public function setUserData($data);
abstract public function retrieveUserID();
abstract public function retrieveUserEmail();
abstract public function retrieveUserAccountName();
abstract public function retrieveUserProfileImage();
abstract public function retrieveUserAccountURI();
abstract public function retrieveUserRealName();
public function __construct() { public function __construct() {
} }
final public function setAccessToken($access_token) {
$this->accessToken = $access_token;
return $this;
}
final public function getAccessToken() {
return $this->accessToken;
}
public static function newProvider($which) { public static function newProvider($which) {
switch ($which) { switch ($which) {
case self::PROVIDER_FACEBOOK: case self::PROVIDER_FACEBOOK:

View file

@ -18,6 +18,8 @@
class PhabricatorOAuthProviderFacebook extends PhabricatorOAuthProvider { class PhabricatorOAuthProviderFacebook extends PhabricatorOAuthProvider {
private $userData;
public function getProviderKey() { public function getProviderKey() {
return self::PROVIDER_FACEBOOK; return self::PROVIDER_FACEBOOK;
} }
@ -30,6 +32,14 @@ class PhabricatorOAuthProviderFacebook extends PhabricatorOAuthProvider {
return PhabricatorEnv::getEnvConfig('facebook.auth-enabled'); return PhabricatorEnv::getEnvConfig('facebook.auth-enabled');
} }
public function isProviderLinkPermanent() {
return PhabricatorEnv::getEnvConfig('facebook.auth-permanent');
}
public function isProviderRegistrationEnabled() {
return PhabricatorEnv::getEnvConfig('facebook.registration-enabled');
}
public function getRedirectURI() { public function getRedirectURI() {
return PhabricatorEnv::getURI('/oauth/facebook/login/'); return PhabricatorEnv::getURI('/oauth/facebook/login/');
} }
@ -58,4 +68,39 @@ class PhabricatorOAuthProviderFacebook extends PhabricatorOAuthProvider {
return 'email'; return 'email';
} }
public function setUserData($data) {
$this->userData = $data;
return $this;
}
public function retrieveUserID() {
return $this->userData['id'];
}
public function retrieveUserEmail() {
return $this->userData['email'];
}
public function retrieveUserAccountName() {
$matches = null;
$link = $this->userData['link'];
if (preg_match('@/([a-zA-Z0-9]+)$@', $link, $matches)) {
return $matches[1];
}
return null;
}
public function retrieveUserProfileImage() {
$uri = 'https://graph.facebook.com/me/picture?access_token=';
return @file_get_contents($uri.$this->getAccessToken());
}
public function retrieveUserAccountURI() {
return $this->userData['link'];
}
public function retrieveUserRealName() {
return $this->userData['name'];
}
} }

View file

@ -18,6 +18,8 @@
class PhabricatorOAuthProviderGithub extends PhabricatorOAuthProvider { class PhabricatorOAuthProviderGithub extends PhabricatorOAuthProvider {
private $userData;
public function getProviderKey() { public function getProviderKey() {
return self::PROVIDER_GITHUB; return self::PROVIDER_GITHUB;
} }
@ -30,6 +32,14 @@ class PhabricatorOAuthProviderGithub extends PhabricatorOAuthProvider {
return PhabricatorEnv::getEnvConfig('github.auth-enabled'); return PhabricatorEnv::getEnvConfig('github.auth-enabled');
} }
public function isProviderLinkPermanent() {
return PhabricatorEnv::getEnvConfig('github.auth-permanent');
}
public function isProviderRegistrationEnabled() {
return PhabricatorEnv::getEnvConfig('github.registration-enabled');
}
public function getRedirectURI() { public function getRedirectURI() {
return PhabricatorEnv::getURI('/oauth/github/login/'); return PhabricatorEnv::getURI('/oauth/github/login/');
} }
@ -58,4 +68,42 @@ class PhabricatorOAuthProviderGithub extends PhabricatorOAuthProvider {
return null; return null;
} }
public function setUserData($data) {
$this->userData = $data['user'];
return $this;
}
public function retrieveUserID() {
return $this->userData['id'];
}
public function retrieveUserEmail() {
return $this->userData['email'];
}
public function retrieveUserAccountName() {
return $this->userData['login'];
}
public function retrieveUserProfileImage() {
$id = $this->userData['gravatar_id'];
if ($id) {
$uri = 'http://www.gravatar.com/avatar/'.$id.'?s=50';
return @file_get_contents($uri);
}
return null;
}
public function retrieveUserAccountURI() {
$username = $this->retrieveUserAccountName();
if ($username) {
return 'https://github.com/'.$username;
}
return null;
}
public function retrieveUserRealName() {
return $this->userData['name'];
}
} }

View file

@ -358,18 +358,20 @@ class PhabricatorUserSettingsController extends PhabricatorPeopleController {
->setLabel($provider_name.' URI') ->setLabel($provider_name.' URI')
->setValue($oauth_info->getAccountURI())); ->setValue($oauth_info->getAccountURI()));
$unlink = 'Unlink '.$provider_name.' Account'; if (!$provider->isProviderLinkPermanent()) {
$unlink_form = new AphrontFormView(); $unlink = 'Unlink '.$provider_name.' Account';
$unlink_form $unlink_form = new AphrontFormView();
->setUser($user) $unlink_form
->appendChild( ->setUser($user)
'<p class="aphront-form-instructions">You may unlink this account '. ->appendChild(
'from your '.$provider_name.' account. This will prevent you from '. '<p class="aphront-form-instructions">You may unlink this account '.
'logging in with your '.$provider_name.' credentials.</p>') 'from your '.$provider_name.' account. This will prevent you from '.
->appendChild( 'logging in with your '.$provider_name.' credentials.</p>')
id(new AphrontFormSubmitControl()) ->appendChild(
->addCancelButton('/oauth/'.$provider_key.'/unlink/', $unlink)); id(new AphrontFormSubmitControl())
$forms['Unlink Account'] = $unlink_form; ->addCancelButton('/oauth/'.$provider_key.'/unlink/', $unlink));
$forms['Unlink Account'] = $unlink_form;
}
$expires = $oauth_info->getTokenExpires(); $expires = $oauth_info->getTokenExpires();
if ($expires) { if ($expires) {