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,6 +30,12 @@ class PhabricatorLoginController extends PhabricatorAuthController {
return id(new AphrontRedirectResponse())->setURI('/'); return id(new AphrontRedirectResponse())->setURI('/');
} }
$password_auth = PhabricatorEnv::getEnvConfig('auth.password-auth-enabled');
$forms = array();
$error_view = null;
if ($password_auth) {
$error = false; $error = false;
$username = $request->getCookie('phusr'); $username = $request->getCookie('phusr');
if ($request->isFormPost()) { if ($request->isFormPost()) {
@ -61,7 +67,6 @@ class PhabricatorLoginController extends PhabricatorAuthController {
$error = true; $error = true;
} }
$error_view = null;
if ($error) { if ($error) {
$error_view = new AphrontErrorView(); $error_view = new AphrontErrorView();
$error_view->setTitle('Bad username/password.'); $error_view->setTitle('Bad username/password.');
@ -81,17 +86,16 @@ class PhabricatorLoginController extends PhabricatorAuthController {
->setLabel('Password') ->setLabel('Password')
->setName('password') ->setName('password')
->setCaption( ->setCaption(
'<a href="/login/email/">Forgot your password? / Email Login</a>')) '<a href="/login/email/">'.
'Forgot your password? / Email Login</a>'))
->appendChild( ->appendChild(
id(new AphrontFormSubmitControl()) id(new AphrontFormSubmitControl())
->setValue('Login')); ->setValue('Login'));
$panel = new AphrontPanelView();
$panel->setHeader('Phabricator Login');
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
// $panel->setCreateButton('Register New Account', '/login/register/'); // $panel->setCreateButton('Register New Account', '/login/register/');
$panel->appendChild($form); $forms['Phabricator Login'] = $form;
}
$providers = array( $providers = array(
PhabricatorOAuthProvider::PROVIDER_FACEBOOK, PhabricatorOAuthProvider::PROVIDER_FACEBOOK,
@ -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,8 +51,169 @@ class PhabricatorOAuthLoginController extends PhabricatorAuthController {
return $this->buildErrorResponse($error_view); return $this->buildErrorResponse($error_view);
} }
$error_response = $this->retrieveAccessToken($provider);
if ($error_response) {
return $error_response;
}
$userinfo_uri = new PhutilURI($provider->getUserInfoURI());
$userinfo_uri->setQueryParams(
array(
'access_token' => $this->accessToken,
));
$user_json = @file_get_contents($userinfo_uri);
$user_data = json_decode($user_json, true);
$provider->setUserData($user_data);
$provider->setAccessToken($this->accessToken);
$user_id = $provider->retrieveUserID();
$provider_key = $provider->getProviderKey();
$oauth_info = $this->retrieveOAuthInfo($provider);
if ($current_user->getPHID()) {
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(
'<p>The '.$provider_name.' account you just authorized '.
'is already linked to another Phabricator account. Before you can '.
'associate your '.$provider_name.' account with this Phabriactor '.
'account, you must unlink it from the Phabricator account it is '.
'currently linked to.</p>');
$dialog->addCancelButton('/settings/page/'.$provider_key.'/');
return id(new AphrontDialogResponse())->setDialog($dialog);
} else {
return id(new AphrontRedirectResponse())
->setURI('/settings/page/'.$provider_key.'/');
}
}
$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()) {
$dialog = new AphrontDialogView();
$dialog->setUser($current_user);
$dialog->setTitle('Link '.$provider_name.' Account');
$dialog->appendChild(
'<p>Link your '.$provider_name.' account to your Phabricator '.
'account?</p>');
$dialog->addHiddenInput('token', $provider->getAccessToken());
$dialog->addHiddenInput('expires', $oauth_info->getTokenExpires());
$dialog->addSubmitButton('Link Accounts');
$dialog->addCancelButton('/settings/page/'.$provider_key.'/');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$oauth_info->setUserID($current_user->getID());
$oauth_info->save();
return id(new AphrontRedirectResponse())
->setURI('/settings/page/'.$provider_key.'/');
}
// Login with known auth.
if ($oauth_info->getID()) {
$known_user = id(new PhabricatorUser())->load($oauth_info->getUserID());
$session_key = $known_user->establishSession('web');
$oauth_info->save();
$request->setCookie('phusr', $known_user->getUsername());
$request->setCookie('phsid', $session_key);
return id(new AphrontRedirectResponse())
->setURI('/');
}
$oauth_email = $provider->retrieveUserEmail();
if ($oauth_email) {
$known_email = id(new PhabricatorUser())
->loadOneWhere('email = %s', $oauth_email);
if ($known_email) {
$dialog = new AphrontDialogView();
$dialog->setUser($current_user);
$dialog->setTitle('Already Linked to Another Account');
$dialog->appendChild(
'<p>The '.$provider_name.' 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>');
$dialog->addCancelButton('/login/');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
if (!$provider->isProviderRegistrationEnabled()) {
$dialog = new AphrontDialogView();
$dialog->setUser($current_user);
$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/');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$class = PhabricatorEnv::getEnvConfig('controller.oauth-registration');
PhutilSymbolLoader::loadClass($class);
$controller = newv($class, array($this->getRequest()));
$controller->setOAuthProvider($provider);
$controller->setOAuthInfo($oauth_info);
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',
));
}
private function retrieveAccessToken(PhabricatorOAuthProvider $provider) {
$request = $this->getRequest();
$token = $request->getStr('token'); $token = $request->getStr('token');
if (!$token) { if ($token) {
$this->tokenExpires = $request->getInt('expires');
$this->accessToken = $token;
return null;
}
$client_id = $provider->getClientID(); $client_id = $provider->getClientID();
$client_secret = $provider->getClientSecret(); $client_secret = $provider->getClientSecret();
$redirect_uri = $provider->getRedirectURI(); $redirect_uri = $provider->getRedirectURI();
@ -89,7 +251,6 @@ class PhabricatorOAuthLoginController extends PhabricatorAuthController {
fclose($stream); fclose($stream);
} }
if ($response === false) { if ($response === false) {
return $this->buildErrorResponse(new PhabricatorOAuthFailureView()); return $this->buildErrorResponse(new PhabricatorOAuthFailureView());
} }
@ -106,351 +267,27 @@ class PhabricatorOAuthLoginController extends PhabricatorAuthController {
$this->tokenExpires = time() + $data['expires']; $this->tokenExpires = time() + $data['expires'];
} }
} else {
$this->tokenExpires = $request->getInt('expires');
}
$userinfo_uri = new PhutilURI($provider->getUserInfoURI());
$userinfo_uri->setQueryParams(
array(
'access_token' => $token,
));
$user_json = @file_get_contents($userinfo_uri);
$user_data = json_decode($user_json, true);
$this->accessToken = $token; $this->accessToken = $token;
switch ($provider->getProviderKey()) { return null;
case PhabricatorOAuthProvider::PROVIDER_GITHUB:
$user_data = $user_data['user'];
break;
} }
$this->userData = $user_data;
$user_id = $this->retrieveUserID(); private function retrieveOAuthInfo(PhabricatorOAuthProvider $provider) {
$known_oauth = id(new PhabricatorUserOAuthInfo())->loadOneWhere( $oauth_info = id(new PhabricatorUserOAuthInfo())->loadOneWhere(
'oauthProvider = %s and oauthUID = %s', 'oauthProvider = %s and oauthUID = %s',
$provider->getProviderKey(), $provider->getProviderKey(),
$user_id); $provider->retrieveUserID());
if ($current_user->getPHID()) {
if ($known_oauth) {
if ($known_oauth->getUserID() != $current_user->getID()) {
$dialog = new AphrontDialogView();
$dialog->setUser($current_user);
$dialog->setTitle('Already Linked to Another Account');
$dialog->appendChild(
'<p>The '.$provider_name.' account you just authorized '.
'is already linked to another Phabricator account. Before you can '.
'associate your '.$provider_name.' account with this Phabriactor '.
'account, you must unlink it from the Phabricator account it is '.
'currently linked to.</p>');
$dialog->addCancelButton('/settings/page/'.$provider_key.'/');
return id(new AphrontDialogResponse())->setDialog($dialog);
} else {
return id(new AphrontRedirectResponse())
->setURI('/settings/page/'.$provider_key.'/');
}
}
if (!$request->isDialogFormPost()) {
$dialog = new AphrontDialogView();
$dialog->setUser($current_user);
$dialog->setTitle('Link '.$provider_name.' Account');
$dialog->appendChild(
'<p>Link your '.$provider_name.' account to your Phabricator '.
'account?</p>');
$dialog->addHiddenInput('token', $token);
$dialog->addHiddenInput('expires', $this->tokenExpires);
$dialog->addSubmitButton('Link Accounts');
$dialog->addCancelButton('/settings/page/'.$provider_key.'/');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
if (!$oauth_info) {
$oauth_info = new PhabricatorUserOAuthInfo(); $oauth_info = new PhabricatorUserOAuthInfo();
$oauth_info->setUserID($current_user->getID());
$this->configureOAuthInfo($oauth_info);
$oauth_info->save();
return id(new AphrontRedirectResponse())
->setURI('/settings/page/'.$provider_key.'/');
}
// Login with known auth.
if ($known_oauth) {
$known_user = id(new PhabricatorUser())->load($known_oauth->getUserID());
$session_key = $known_user->establishSession('web');
$this->configureOAuthInfo($known_oauth);
$known_oauth->save();
$request->setCookie('phusr', $known_user->getUsername());
$request->setCookie('phsid', $session_key);
return id(new AphrontRedirectResponse())
->setURI('/');
}
// Merge accounts based on shared email. TODO: should probably get rid of
// this.
$oauth_email = $this->retrieveUserEmail();
if ($oauth_email) {
$known_email = id(new PhabricatorUser())
->loadOneWhere('email = %s', $oauth_email);
if ($known_email) {
$dialog = new AphrontDialogView();
$dialog->setUser($current_user);
$dialog->setTitle('Already Linked to Another Account');
$dialog->appendChild(
'<p>The '.$provider_name.' 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>');
$dialog->addCancelButton('/login/');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
$errors = array();
$e_username = true;
$e_email = true;
$e_realname = true;
$user = new PhabricatorUser();
$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;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle('Registration Failed');
$error_view->setErrors($errors);
}
$form = new AphrontFormView();
$form
->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) {
$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) {
$provider = $this->provider;
$provider_name = $provider->getProviderName();
$view->setOAuthProvider($provider);
return $this->buildStandardPageResponse(
$view,
array(
'title' => $provider_name.' Auth Failed',
));
}
private function retrieveUserID() {
return $this->userData['id'];
}
private function retrieveUserEmail() {
return $this->userData['email'];
}
private function retrieveUsernameSuggestion() {
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'];
}
return null;
}
private function retreiveProfileImageSuggestion() {
switch ($this->provider->getProviderKey()) {
case PhabricatorOAuthProvider::PROVIDER_FACEBOOK:
$uri = 'https://graph.facebook.com/me/picture?access_token=';
return @file_get_contents($uri.$this->accessToken);
case PhabricatorOAuthProvider::PROVIDER_GITHUB:
$id = $this->userData['gravatar_id'];
if ($id) {
$uri = 'http://www.gravatar.com/avatar/'.$id.'?s=50';
return @file_get_contents($uri);
}
}
return null;
}
private function retrieveAccountURI() {
switch ($this->provider->getProviderKey()) {
case PhabricatorOAuthProvider::PROVIDER_FACEBOOK:
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->setOAuthProvider($provider->getProviderKey());
$oauth_info->setOAuthUID($this->retrieveUserID()); $oauth_info->setOAuthUID($provider->retrieveUserID());
$oauth_info->setAccountURI($this->retrieveAccountURI()); }
$oauth_info->setAccountName($this->retrieveUserNameSuggestion());
$oauth_info->setToken($this->accessToken); $oauth_info->setAccountURI($provider->retrieveUserAccountURI());
$oauth_info->setAccountName($provider->retrieveUserAccountName());
$oauth_info->setToken($provider->getAccessToken());
$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,6 +358,7 @@ class PhabricatorUserSettingsController extends PhabricatorPeopleController {
->setLabel($provider_name.' URI') ->setLabel($provider_name.' URI')
->setValue($oauth_info->getAccountURI())); ->setValue($oauth_info->getAccountURI()));
if (!$provider->isProviderLinkPermanent()) {
$unlink = 'Unlink '.$provider_name.' Account'; $unlink = 'Unlink '.$provider_name.' Account';
$unlink_form = new AphrontFormView(); $unlink_form = new AphrontFormView();
$unlink_form $unlink_form
@ -370,6 +371,7 @@ class PhabricatorUserSettingsController extends PhabricatorPeopleController {
id(new AphrontFormSubmitControl()) id(new AphrontFormSubmitControl())
->addCancelButton('/oauth/'.$provider_key.'/unlink/', $unlink)); ->addCancelButton('/oauth/'.$provider_key.'/unlink/', $unlink));
$forms['Unlink Account'] = $unlink_form; $forms['Unlink Account'] = $unlink_form;
}
$expires = $oauth_info->getTokenExpires(); $expires = $oauth_info->getTokenExpires();
if ($expires) { if ($expires) {