From b462349ec8a7bc8877b51fd452bbffdde605be9c Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 21 Feb 2011 22:51:34 -0800 Subject: [PATCH] OAuth linking/unlinking controls. Summary: Test Plan: Reviewers: CC: --- src/__phutil_library_map__.php | 2 + ...AphrontDefaultApplicationConfiguration.php | 1 + .../login/PhabricatorLoginController.php | 5 + .../oauth/PhabricatorOAuthLoginController.php | 60 ++++++++-- .../auth/controller/oauth/__init__.php | 2 + .../PhabricatorOAuthUnlinkController.php | 63 ++++++++++ .../auth/controller/unlink/__init__.php | 20 ++++ .../base/PhabricatorOAuthProvider.php | 2 +- .../PhabricatorUserSettingsController.php | 108 +++++++++++++++++- .../people/controller/settings/__init__.php | 4 + 10 files changed, 256 insertions(+), 11 deletions(-) create mode 100644 src/applications/auth/controller/unlink/PhabricatorOAuthUnlinkController.php create mode 100644 src/applications/auth/controller/unlink/__init__.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 61286449f3..86549d07f0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -218,6 +218,7 @@ phutil_register_library_map(array( 'PhabricatorOAuthProvider' => 'applications/auth/oauth/provider/base', 'PhabricatorOAuthProviderFacebook' => 'applications/auth/oauth/provider/facebook', 'PhabricatorOAuthProviderGithub' => 'applications/auth/oauth/provider/github', + 'PhabricatorOAuthUnlinkController' => 'applications/auth/controller/unlink', 'PhabricatorObjectHandle' => 'applications/phid/handle', 'PhabricatorObjectHandleData' => 'applications/phid/handle/data', 'PhabricatorObjectSelectorDialog' => 'view/control/objectselector', @@ -467,6 +468,7 @@ phutil_register_library_map(array( 'PhabricatorOAuthLoginController' => 'PhabricatorAuthController', 'PhabricatorOAuthProviderFacebook' => 'PhabricatorOAuthProvider', 'PhabricatorOAuthProviderGithub' => 'PhabricatorOAuthProvider', + 'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController', 'PhabricatorPHID' => 'PhabricatorPHIDDAO', 'PhabricatorPHIDAllocateController' => 'PhabricatorPHIDController', 'PhabricatorPHIDController' => 'PhabricatorController', diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index 3b3dee2f44..4047a6c7c0 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -134,6 +134,7 @@ class AphrontDefaultApplicationConfiguration '(?Pgithub|facebook)/' => array( 'login/$' => 'PhabricatorOAuthLoginController', 'diagnose/$' => 'PhabricatorOAuthDiagnosticsController', + 'unlink/$' => 'PhabricatorOAuthUnlinkController', ), ), diff --git a/src/applications/auth/controller/login/PhabricatorLoginController.php b/src/applications/auth/controller/login/PhabricatorLoginController.php index 57979f3fde..eca7c6c648 100644 --- a/src/applications/auth/controller/login/PhabricatorLoginController.php +++ b/src/applications/auth/controller/login/PhabricatorLoginController.php @@ -25,6 +25,11 @@ class PhabricatorLoginController extends PhabricatorAuthController { public function processRequest() { $request = $this->getRequest(); + if ($request->getUser()->getPHID()) { + // Kick the user out if they're already logged in. + return id(new AphrontRedirectResponse())->setURI('/'); + } + $error = false; $username = $request->getCookie('phusr'); if ($request->isFormPost()) { diff --git a/src/applications/auth/controller/oauth/PhabricatorOAuthLoginController.php b/src/applications/auth/controller/oauth/PhabricatorOAuthLoginController.php index d84e266de6..6238e30ad0 100644 --- a/src/applications/auth/controller/oauth/PhabricatorOAuthLoginController.php +++ b/src/applications/auth/controller/oauth/PhabricatorOAuthLoginController.php @@ -32,17 +32,15 @@ class PhabricatorOAuthLoginController extends PhabricatorAuthController { public function processRequest() { $current_user = $this->getRequest()->getUser(); - if ($current_user->getPHID()) { - // If we're already logged in, ignore everything going on here. TODO: - // restore account linking. - return id(new AphrontRedirectResponse())->setURI('/'); - } $provider = $this->provider; if (!$provider->isProviderEnabled()) { return new Aphront400Response(); } + $provider_name = $provider->getProviderName(); + $provider_key = $provider->getProviderKey(); + $request = $this->getRequest(); if ($request->getStr('error')) { @@ -115,12 +113,60 @@ class PhabricatorOAuthLoginController extends PhabricatorAuthController { $user_id = $this->retrieveUserID(); - // Login with known auth. - $known_oauth = id(new PhabricatorUserOAuthInfo())->loadOneWhere( 'oauthProvider = %s and oauthUID = %s', $provider->getProviderKey(), $user_id); + + 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( + '

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.

'); + $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( + '

Link your '.$provider_name.' account to your Phabricator '. + 'account?

'); + $dialog->addHiddenInput('token', $token); + $dialog->addSubmitButton('Link Accounts'); + $dialog->addCancelButton('/settings/page/'.$provider_key.'/'); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } + + $oauth_info = new PhabricatorUserOAuthInfo(); + $oauth_info->setUserID($current_user->getID()); + $oauth_info->setOAuthProvider($provider_key); + $oauth_info->setOAuthUID($user_id); + $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'); diff --git a/src/applications/auth/controller/oauth/__init__.php b/src/applications/auth/controller/oauth/__init__.php index d9e4cc1b35..19082b7aa2 100644 --- a/src/applications/auth/controller/oauth/__init__.php +++ b/src/applications/auth/controller/oauth/__init__.php @@ -7,6 +7,7 @@ phutil_require_module('phabricator', 'aphront/response/400'); +phutil_require_module('phabricator', 'aphront/response/dialog'); phutil_require_module('phabricator', 'aphront/response/redirect'); phutil_require_module('phabricator', 'applications/auth/controller/base'); phutil_require_module('phabricator', 'applications/auth/oauth/provider/base'); @@ -14,6 +15,7 @@ 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/useroauthinfo'); +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'); diff --git a/src/applications/auth/controller/unlink/PhabricatorOAuthUnlinkController.php b/src/applications/auth/controller/unlink/PhabricatorOAuthUnlinkController.php new file mode 100644 index 0000000000..e2e16562a8 --- /dev/null +++ b/src/applications/auth/controller/unlink/PhabricatorOAuthUnlinkController.php @@ -0,0 +1,63 @@ +provider = PhabricatorOAuthProvider::newProvider($data['provider']); + } + + public function processRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + + $provider = $this->provider; + $provider_name = $provider->getProviderName(); + $provider_key = $provider->getProviderKey(); + + $oauth_info = id(new PhabricatorUserOAuthInfo())->loadOneWhere( + 'userID = %d AND oauthProvider = %s', + $user->getID(), + $provider_key); + + if (!$oauth_info) { + return new Aphront400Response(); + } + + if (!$request->isDialogFormPost()) { + $dialog = new AphrontDialogView(); + $dialog->setUser($user); + $dialog->setTitle('Really unlink account?'); + $dialog->appendChild( + '

You will not be able to login using this account '. + 'once you unlink it. Continue?

'); + $dialog->addSubmitButton('Unlink Account'); + $dialog->addCancelButton('/settings/page/'.$provider_key.'/'); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } + + $oauth_info->delete(); + + return id(new AphrontRedirectResponse()) + ->setURI('/settings/page/'.$provider_key.'/'); + } + +} diff --git a/src/applications/auth/controller/unlink/__init__.php b/src/applications/auth/controller/unlink/__init__.php new file mode 100644 index 0000000000..53b32b66c2 --- /dev/null +++ b/src/applications/auth/controller/unlink/__init__.php @@ -0,0 +1,20 @@ + 'Account', 'email' => 'Email', // 'password' => 'Password', -// 'facebook' => 'Facebook Account', 'arcanist' => 'Arcanist Certificate', ); + $oauth_providers = PhabricatorOAuthProvider::getAllProviders(); + foreach ($oauth_providers as $provider) { + if (!$provider->isProviderEnabled()) { + continue; + } + $key = $provider->getProviderKey(); + $name = $provider->getProviderName(); + $pages[$key] = $name.' Account'; + } + if (empty($pages[$this->page])) { $this->page = key($pages); } @@ -103,7 +112,10 @@ class PhabricatorUserSettingsController extends PhabricatorPeopleController { $content = $this->renderEmailForm(); break; default: - $content = 'derp derp'; + if (empty($pages[$this->page])) { + return new Aphront404Response(); + } + $content = $this->renderOAuthForm($oauth_providers[$this->page]); break; } @@ -244,7 +256,6 @@ class PhabricatorUserSettingsController extends PhabricatorPeopleController { $request = $this->getRequest(); $user = $request->getUser(); - if ($request->getStr('saved')) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); @@ -278,4 +289,95 @@ class PhabricatorUserSettingsController extends PhabricatorPeopleController { return $notice.$panel->render(); } + private function renderOAuthForm(PhabricatorOAuthProvider $provider) { + + $request = $this->getRequest(); + $user = $request->getUser(); + + $notice = null; + + $provider_name = $provider->getProviderName(); + $provider_key = $provider->getProviderKey(); + + $oauth_info = id(new PhabricatorUserOAuthInfo())->loadOneWhere( + 'userID = %d AND oauthProvider = %s', + $user->getID(), + $provider->getProviderKey()); + + $form = new AphrontFormView(); + $form + ->setUser($user); + + $forms = array(); + $forms[] = $form; + if (!$oauth_info) { + $form + ->appendChild( + '

There is currently no '. + $provider_name.' account linked to your Phabricator account. You '. + 'can link an account, which will allow you to use it to log into '. + 'Phabricator.

'); + + switch ($provider_key) { + case PhabricatorOAuthProvider::PROVIDER_GITHUB: + $form->appendChild( + '

Additionally, you must '. + 'link your Github account before Phabricator can access any '. + 'information about hosted repositories.

'); + break; + } + + $auth_uri = $provider->getAuthURI(); + $client_id = $provider->getClientID(); + $redirect_uri = $provider->getRedirectURI(); + + $form + ->setAction($auth_uri) + ->setMethod('GET') + ->addHiddenInput('redirect_uri', $redirect_uri) + ->addHiddenInput('client_id', $client_id) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue('Link '.$provider_name." Account \xC2\xBB")); + } else { + $form + ->appendChild( + '

Your account is linked with '. + 'a '.$provider_name.' account. You may use your '.$provider_name.' '. + 'credentials to log into Phabricator.

') + ->appendChild( + id(new AphrontFormStaticControl()) + ->setLabel($provider_name.' ID') + ->setValue($oauth_info->getOAuthUID())); + + + $unlink = 'Unlink '.$provider_name.' Account'; + $unlink_form = new AphrontFormView(); + $unlink_form + ->setUser($user) + ->appendChild( + '

You may unlink this account '. + 'from your '.$provider_name.' account. This will prevent you from '. + 'logging in with your '.$provider_name.' credentials.

') + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton('/oauth/'.$provider_key.'/unlink/', $unlink)); + $forms['Unlink Account'] = $unlink_form; + } + + $panel = new AphrontPanelView(); + $panel->setHeader($provider_name.' Account Settings'); + $panel->setWidth(AphrontPanelView::WIDTH_FORM); + foreach ($forms as $name => $form) { + if ($name) { + $panel->appendChild('

'.$name.'

'); + } + $panel->appendChild($form); + } + + return $notice.$panel->render(); + + + } + } diff --git a/src/applications/people/controller/settings/__init__.php b/src/applications/people/controller/settings/__init__.php index 7a772e97cd..b57f21861e 100644 --- a/src/applications/people/controller/settings/__init__.php +++ b/src/applications/people/controller/settings/__init__.php @@ -6,16 +6,20 @@ +phutil_require_module('phabricator', 'aphront/response/404'); phutil_require_module('phabricator', 'aphront/response/dialog'); phutil_require_module('phabricator', 'aphront/response/redirect'); +phutil_require_module('phabricator', 'applications/auth/oauth/provider/base'); phutil_require_module('phabricator', 'applications/files/storage/file'); phutil_require_module('phabricator', 'applications/files/uri'); phutil_require_module('phabricator', 'applications/people/controller/base'); phutil_require_module('phabricator', 'applications/people/storage/user'); +phutil_require_module('phabricator', 'applications/people/storage/useroauthinfo'); phutil_require_module('phabricator', 'infrastructure/env'); phutil_require_module('phabricator', 'storage/queryfx'); phutil_require_module('phabricator', 'view/dialog'); phutil_require_module('phabricator', 'view/form/base'); +phutil_require_module('phabricator', 'view/form/control/static'); phutil_require_module('phabricator', 'view/form/control/submit'); phutil_require_module('phabricator', 'view/form/control/textarea'); phutil_require_module('phabricator', 'view/form/error');