From 726041584f98dc709ddf8a770cc3e16e5272aae1 Mon Sep 17 00:00:00 2001 From: Espen Volden Date: Wed, 13 Jun 2012 08:52:05 -0700 Subject: [PATCH] Made it possible to login using LDAP Summary: Made it possible to link and unlink LDAP accounts with Phabricator accounts. Test Plan: I've tested this code locally and in production where I work. I've tried creating an account from scratch by logging in with LDAP and linking and unlinking an LDAP account with an existing account. I've tried to associate the same LDAP account with different Phabricator accounts and it failed as expected. Reviewers: epriestley Reviewed By: epriestley CC: aran, Korvin, auduny, svemir Maniphest Tasks: T742 Differential Revision: https://secure.phabricator.com/D2722 --- conf/default.conf.php | 21 ++ resources/sql/patches/ldapinfo.sql | 9 + src/__phutil_library_map__.php | 11 + ...AphrontDefaultApplicationConfiguration.php | 5 + .../PhabricatorLDAPLoginController.php | 187 ++++++++++++++ .../PhabricatorLDAPRegistrationController.php | 236 ++++++++++++++++++ .../PhabricatorLDAPUnlinkController.php | 52 ++++ .../controller/PhabricatorLoginController.php | 24 ++ .../auth/ldap/PhabricatorLDAPProvider.php | 149 +++++++++++ .../PhabricatorUserSettingsController.php | 8 + ...ricatorUserLDAPSettingsPanelController.php | 87 +++++++ .../storage/PhabricatorUserLDAPInfo.php | 22 ++ .../setup/sql/PhabricatorBuiltinPatchList.php | 4 + 13 files changed, 815 insertions(+) create mode 100644 resources/sql/patches/ldapinfo.sql create mode 100644 src/applications/auth/controller/PhabricatorLDAPLoginController.php create mode 100644 src/applications/auth/controller/PhabricatorLDAPRegistrationController.php create mode 100644 src/applications/auth/controller/PhabricatorLDAPUnlinkController.php create mode 100644 src/applications/auth/ldap/PhabricatorLDAPProvider.php create mode 100644 src/applications/people/controller/settings/panels/PhabricatorUserLDAPSettingsPanelController.php create mode 100644 src/applications/people/storage/PhabricatorUserLDAPInfo.php diff --git a/conf/default.conf.php b/conf/default.conf.php index 15a0f3742f..889e80591f 100644 --- a/conf/default.conf.php +++ b/conf/default.conf.php @@ -548,6 +548,27 @@ return array( // The Google "Client Secret" to use for Google API access. 'google.application-secret' => null, +// -- LDAP Auth ----------------------------------------------------- // + // Enable ldap auth + 'ldap.auth-enabled' => false, + + // The LDAP server hostname + 'ldap.hostname' => '', + + // The LDAP base domain name + 'ldap.base_dn' => '', + + // The attribute to be regarded as 'username'. Has to be unique + 'ldap.search_attribute' => '', + + // The attribute(s) to be regarded as 'real name'. + // If more then one attribute is supplied the values of the attributes in + // the array will be joined + 'ldap.real_name_attributes' => array(), + + // The LDAP version + 'ldap.version' => 3, + // -- Disqus OAuth ---------------------------------------------------------- // // Can users use Disqus credentials to login to Phabricator? diff --git a/resources/sql/patches/ldapinfo.sql b/resources/sql/patches/ldapinfo.sql new file mode 100644 index 0000000000..16ff1fc496 --- /dev/null +++ b/resources/sql/patches/ldapinfo.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_user.user_ldapinfo ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `userID` int(10) unsigned NOT NULL, + `ldapUsername` varchar(255) NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY (`userID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 278fb7e335..b5dedd5606 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -699,6 +699,10 @@ phutil_register_library_map(array( 'PhabricatorInlineSummaryView' => 'infrastructure/diff/view/PhabricatorInlineSummaryView.php', 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php', 'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php', + 'PhabricatorLDAPLoginController' => 'applications/auth/controller/PhabricatorLDAPLoginController.php', + 'PhabricatorLDAPProvider' => 'applications/auth/ldap/PhabricatorLDAPProvider.php', + 'PhabricatorLDAPRegistrationController' => 'applications/auth/controller/PhabricatorLDAPRegistrationController.php', + 'PhabricatorLDAPUnlinkController' => 'applications/auth/controller/PhabricatorLDAPUnlinkController.php', 'PhabricatorLintEngine' => 'infrastructure/lint/PhabricatorLintEngine.php', 'PhabricatorLiskDAO' => 'applications/base/storage/PhabricatorLiskDAO.php', 'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php', @@ -973,6 +977,8 @@ phutil_register_library_map(array( 'PhabricatorUserEmail' => 'applications/people/storage/PhabricatorUserEmail.php', 'PhabricatorUserEmailPreferenceSettingsPanelController' => 'applications/people/controller/settings/panels/PhabricatorUserEmailPreferenceSettingsPanelController.php', 'PhabricatorUserEmailSettingsPanelController' => 'applications/people/controller/settings/panels/PhabricatorUserEmailSettingsPanelController.php', + 'PhabricatorUserLDAPInfo' => 'applications/people/storage/PhabricatorUserLDAPInfo.php', + 'PhabricatorUserLDAPSettingsPanelController' => 'applications/people/controller/settings/panels/PhabricatorUserLDAPSettingsPanelController.php', 'PhabricatorUserLog' => 'applications/people/storage/PhabricatorUserLog.php', 'PhabricatorUserOAuthInfo' => 'applications/people/storage/PhabricatorUserOAuthInfo.php', 'PhabricatorUserOAuthSettingsPanelController' => 'applications/people/controller/settings/panels/PhabricatorUserOAuthSettingsPanelController.php', @@ -1669,6 +1675,9 @@ phutil_register_library_map(array( 'PhabricatorInlineCommentController' => 'PhabricatorController', 'PhabricatorInlineSummaryView' => 'AphrontView', 'PhabricatorJavelinLinter' => 'ArcanistLinter', + 'PhabricatorLDAPLoginController' => 'PhabricatorAuthController', + 'PhabricatorLDAPRegistrationController' => 'PhabricatorAuthController', + 'PhabricatorLDAPUnlinkController' => 'PhabricatorAuthController', 'PhabricatorLintEngine' => 'PhutilLintEngine', 'PhabricatorLiskDAO' => 'LiskDAO', 'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine', @@ -1901,6 +1910,8 @@ phutil_register_library_map(array( 'PhabricatorUserEmail' => 'PhabricatorUserDAO', 'PhabricatorUserEmailPreferenceSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserEmailSettingsPanelController' => 'PhabricatorUserSettingsPanelController', + 'PhabricatorUserLDAPInfo' => 'PhabricatorUserDAO', + 'PhabricatorUserLDAPSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserLog' => 'PhabricatorUserDAO', 'PhabricatorUserOAuthInfo' => 'PhabricatorUserDAO', 'PhabricatorUserOAuthSettingsPanelController' => 'PhabricatorUserSettingsPanelController', diff --git a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php index 71532f4e92..3e047ff694 100644 --- a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php @@ -146,6 +146,11 @@ class AphrontDefaultApplicationConfiguration ), ), + '/ldap/' => array( + 'login/' => 'PhabricatorLDAPLoginController', + 'unlink/' => 'PhabricatorLDAPUnlinkController', + ), + '/oauthserver/' => array( 'auth/' => 'PhabricatorOAuthServerAuthController', 'test/' => 'PhabricatorOAuthServerTestController', diff --git a/src/applications/auth/controller/PhabricatorLDAPLoginController.php b/src/applications/auth/controller/PhabricatorLDAPLoginController.php new file mode 100644 index 0000000000..0525625373 --- /dev/null +++ b/src/applications/auth/controller/PhabricatorLDAPLoginController.php @@ -0,0 +1,187 @@ +provider = new PhabricatorLDAPProvider(); + } + + public function processRequest() { + if (!$this->provider->isProviderEnabled()) { + return new Aphront400Response(); + } + + $current_user = $this->getRequest()->getUser(); + $request = $this->getRequest(); + + if ($request->isFormPost()) { + try { + $this->provider->auth($request->getStr('username'), + $request->getStr('password')); + + } catch (Exception $e) { + $errors[] = $e->getMessage(); + } + + if (empty($errors)) { + $ldap_info = $this->retrieveLDAPInfo($this->provider); + + if ($current_user->getPHID()) { + if ($ldap_info->getID()) { + $existing_ldap = id(new PhabricatorUserLDAPInfo())->loadOneWhere( + 'userID = %d', + $current_user->getID()); + + if ($ldap_info->getUserID() != $current_user->getID() || + $existing_ldap) { + $dialog = new AphrontDialogView(); + $dialog->setUser($current_user); + $dialog->setTitle('Already Linked to Another Account'); + $dialog->appendChild( + '

The LDAP account you just authorized is already linked to '. + 'another Phabricator account. Before you can link it to a '. + 'different LDAP account, you must unlink the old account.

' + ); + $dialog->addCancelButton('/settings/page/ldap/'); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } else { + return id(new AphrontRedirectResponse()) + ->setURI('/settings/page/ldap/'); + } + } + + if (!$request->isDialogFormPost()) { + $dialog = new AphrontDialogView(); + $dialog->setUser($current_user); + $dialog->setTitle('Link LDAP Account'); + $dialog->appendChild( + '

Link your LDAP account to your Phabricator account?

'); + $dialog->addHiddenInput('username', $request->getStr('username')); + $dialog->addHiddenInput('password', $request->getStr('password')); + $dialog->addSubmitButton('Link Accounts'); + $dialog->addCancelButton('/settings/page/ldap/'); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } + + $ldap_info->setUserID($current_user->getID()); + + $this->saveLDAPInfo($ldap_info); + + return id(new AphrontRedirectResponse()) + ->setURI('/settings/page/ldap/'); + } + + if ($ldap_info->getID()) { + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + + $known_user = id(new PhabricatorUser())->load( + $ldap_info->getUserID()); + + $session_key = $known_user->establishSession('web'); + + $this->saveLDAPInfo($ldap_info); + + $request->setCookie('phusr', $known_user->getUsername()); + $request->setCookie('phsid', $session_key); + + $uri = new PhutilURI('/login/validate/'); + $uri->setQueryParams( + array( + 'phusr' => $known_user->getUsername(), + )); + + return id(new AphrontRedirectResponse())->setURI((string)$uri); + } + + $controller = newv('PhabricatorLDAPRegistrationController', + array($this->getRequest())); + $controller->setLDAPProvider($this->provider); + $controller->setLDAPInfo($ldap_info); + + return $this->delegateToController($controller); + } + } + + $ldap_username = $request->getCookie('phusr'); + $ldap_form = new AphrontFormView(); + $ldap_form + ->setUser($request->getUser()) + ->setAction('/ldap/login/') + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('LDAP username') + ->setName('username') + ->setValue($ldap_username)) + ->appendChild( + id(new AphrontFormPasswordControl()) + ->setLabel('Password') + ->setName('password')); + + $ldap_form + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue('Login')); + + $panel = new AphrontPanelView(); + $panel->setWidth(AphrontPanelView::WIDTH_FORM); + $panel->appendChild('

LDAP login

'); + $panel->appendChild($ldap_form); + + if (isset($errors) && count($errors) > 0) { + $error_view = new AphrontErrorView(); + $error_view->setTitle('Login Failed'); + $error_view->setErrors($errors); + } + + return $this->buildStandardPageResponse( + array( + isset($error_view) ? $error_view : null, + $panel, + ), + array( + 'title' => 'Login', + )); + } + + private function retrieveLDAPInfo(PhabricatorLDAPProvider $provider) { + $ldap_info = id(new PhabricatorUserLDAPInfo())->loadOneWhere( + 'ldapUsername = %s', + $provider->retrieveUsername()); + + if (!$ldap_info) { + $ldap_info = new PhabricatorUserLDAPInfo(); + $ldap_info->setLDAPUsername($provider->retrieveUsername()); + } + + return $ldap_info; + } + + private function saveLDAPInfo(PhabricatorUserLDAPInfo $info) { + // UNGUARDED WRITES: Logging-in users don't have their CSRF set up yet. + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $info->save(); + } +} diff --git a/src/applications/auth/controller/PhabricatorLDAPRegistrationController.php b/src/applications/auth/controller/PhabricatorLDAPRegistrationController.php new file mode 100644 index 0000000000..111c21a9e3 --- /dev/null +++ b/src/applications/auth/controller/PhabricatorLDAPRegistrationController.php @@ -0,0 +1,236 @@ +ldapProvider = $provider; + return $this; + } + + public function getLDAProvider() { + return $this->ldapProvider; + } + + public function setLDAPInfo($info) { + $this->ldapInfo = $info; + return $this; + } + + public function getLDAPInfo() { + return $this->ldapInfo; + } + + public function processRequest() { + $provider = $this->getLDAProvider(); + $ldap_info = $this->getLDAPInfo(); + $request = $this->getRequest(); + + $errors = array(); + $e_username = true; + $e_email = true; + $e_realname = true; + + $user = new PhabricatorUser(); + $user->setUsername(); + $user->setRealname($provider->retrieveUserRealName()); + + $new_email = $provider->retrieveUserEmail(); + + if ($new_email) { + // If the user's LDAP provider account has an email address but the + // email address domain is not allowed by the Phabricator configuration, + // we just pretend the provider did not supply an address. + // + // For instance, if the user uses LDAP Auth and their email address + // is "joe@personal.com" but Phabricator is configured to require users + // use "@company.com" addresses, we show a prompt below and tell the user + // to provide their "@company.com" address. They can still use the LDAP + // account to login, they just need to associate their account with an + // allowed address. + // + // If the email address is fine, we just use it and don't prompt the user. + if (!PhabricatorUserEmail::isAllowedAddress($new_email)) { + $new_email = null; + } + } + + $show_email_input = ($new_email === null); + + if ($request->isFormPost()) { + $user->setUsername($request->getStr('username')); + $username = $user->getUsername(); + if (!strlen($user->getUsername())) { + $e_username = 'Required'; + $errors[] = 'Username is required.'; + } else if (!PhabricatorUser::validateUsername($username)) { + $e_username = 'Invalid'; + $errors[] = PhabricatorUser::describeValidUsername(); + } else { + $e_username = null; + } + + if (!$new_email) { + $new_email = trim($request->getStr('email')); + if (!$new_email) { + $e_email = 'Required'; + $errors[] = 'Email is required.'; + } else { + $e_email = null; + } + } + + if ($new_email) { + if (!PhabricatorUserEmail::isAllowedAddress($new_email)) { + $e_email = 'Invalid'; + $errors[] = PhabricatorUserEmail::describeAllowedAddresses(); + } + } + + if (!strlen($user->getRealName())) { + $user->setRealName($request->getStr('realname')); + if (!strlen($user->getRealName())) { + $e_realname = 'Required'; + $errors[] = 'Real name is required.'; + } else { + $e_realname = null; + } + } + + if (!$errors) { + try { + // NOTE: We don't verify LDAP email addresses by default because + // LDAP providers might associate email addresses with accounts that + // haven't actually verified they own them. We could selectively + // auto-verify some providers that we trust here, but the stakes for + // verifying an email address are high because having a corporate + // address at a company is sometimes the key to the castle. + + $email_obj = id(new PhabricatorUserEmail()) + ->setAddress($new_email) + ->setIsVerified(0); + + id(new PhabricatorUserEditor()) + ->setActor($user) + ->createNewUser($user, $email_obj); + + $ldap_info->setUserID($user->getID()); + $ldap_info->save(); + + $session_key = $user->establishSession('web'); + $request->setCookie('phusr', $user->getUsername()); + $request->setCookie('phsid', $session_key); + + $email_obj->sendVerificationEmail($user); + + return id(new AphrontRedirectResponse())->setURI('/'); + } catch (AphrontQueryDuplicateKeyException $exception) { + + $same_username = id(new PhabricatorUser())->loadOneWhere( + 'userName = %s', + $user->getUserName()); + + $same_email = id(new PhabricatorUserEmail())->loadOneWhere( + 'address = %s', + $new_email); + + 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); + } + + // Strip the URI down to the path, because otherwise we'll trigger + // external CSRF protection (by having a protocol in the form "action") + // and generate a form with no CSRF token. + $action_uri = new PhutilURI('/ldap/login/'); + $action_path = $action_uri->getPath(); + + $form = new AphrontFormView(); + $form + ->setUser($request->getUser()) + ->setAction($action_path) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Username') + ->setName('username') + ->setValue($user->getUsername()) + ->setError($e_username)); + + $form->appendChild( + id(new AphrontFormPasswordControl()) + ->setLabel('Password') + ->setName('password')); + + if ($show_email_input) { + $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', + )); + } + +} diff --git a/src/applications/auth/controller/PhabricatorLDAPUnlinkController.php b/src/applications/auth/controller/PhabricatorLDAPUnlinkController.php new file mode 100644 index 0000000000..c793716d5d --- /dev/null +++ b/src/applications/auth/controller/PhabricatorLDAPUnlinkController.php @@ -0,0 +1,52 @@ +getRequest(); + $user = $request->getUser(); + + $ldap_info = id(new PhabricatorUserLDAPInfo())->loadOneWhere( + 'userID = %d', + $user->getID()); + + if (!$ldap_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/ldap/'); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } + + $ldap_info->delete(); + + return id(new AphrontRedirectResponse()) + ->setURI('/settings/page/ldap/'); + } + +} diff --git a/src/applications/auth/controller/PhabricatorLoginController.php b/src/applications/auth/controller/PhabricatorLoginController.php index 5e7df9ee59..bedd20965a 100644 --- a/src/applications/auth/controller/PhabricatorLoginController.php +++ b/src/applications/auth/controller/PhabricatorLoginController.php @@ -187,6 +187,30 @@ final class PhabricatorLoginController // $panel->setCreateButton('Register New Account', '/login/register/'); $forms['Phabricator Login'] = $form; + + $ldap_provider = new PhabricatorLDAPProvider(); + if ($ldap_provider->isProviderEnabled()) { + $ldap_form = new AphrontFormView(); + $ldap_form + ->setUser($request->getUser()) + ->setAction('/ldap/login/') + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('LDAP username') + ->setName('username') + ->setValue($username_or_email)) + ->appendChild( + id(new AphrontFormPasswordControl()) + ->setLabel('Password') + ->setName('password')); + + $ldap_form + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue('Login')); + + $forms['LDAP Login'] = $ldap_form; + } } $providers = PhabricatorOAuthProvider::getAllProviders(); diff --git a/src/applications/auth/ldap/PhabricatorLDAPProvider.php b/src/applications/auth/ldap/PhabricatorLDAPProvider.php new file mode 100644 index 0000000000..d1d0b29c31 --- /dev/null +++ b/src/applications/auth/ldap/PhabricatorLDAPProvider.php @@ -0,0 +1,149 @@ +connection)) { + ldap_unbind($this->connection); + } + } + + public function isProviderEnabled() { + return PhabricatorEnv::getEnvConfig('ldap.auth-enabled'); + } + + public function getHostname() { + return PhabricatorEnv::getEnvConfig('ldap.hostname'); + } + + public function getBaseDN() { + return PhabricatorEnv::getEnvConfig('ldap.base_dn'); + } + + public function getSearchAttribute() { + return PhabricatorEnv::getEnvConfig('ldap.search_attribute'); + } + + public function getLDAPVersion() { + return PhabricatorEnv::getEnvConfig('ldap.version'); + } + + public function retrieveUserEmail() { + return $this->userData['mail'][0]; + } + + public function retrieveUserRealName() { + $name_attributes = PhabricatorEnv::getEnvConfig( + 'ldap.real_name_attributes'); + + $real_name = ''; + if (is_array($name_attributes)) { + foreach ($name_attributes AS $attribute) { + if (isset($this->userData[$attribute][0])) { + $real_name .= $this->userData[$attribute][0] . ' '; + } + } + + trim($real_name); + } else if (isset($this->userData[$name_attributes][0])) { + $real_name = $this->userData[$name_attributes][0]; + } + + if ($real_name == '') { + return null; + } + + return $real_name; + } + + public function retrieveUsername() { + return $this->userData[$this->getSearchAttribute()][0]; + } + + public function getConnection() { + if (!isset($this->connection)) { + $this->connection = ldap_connect($this->getHostname()); + + if (!$this->connection) { + throw new Exception('Could not connect to LDAP host at ' . + $this->getHostname()); + } + + ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, + $this->getLDAPVersion()); + } + + return $this->connection; + } + + public function getUserData() { + return $this->userData; + } + + public function auth($username, $password) { + if (strlen(trim($username)) == 0 || strlen(trim($password)) == 0) { + throw new Exception('Username and/or password can not be empty'); + } + + $result = ldap_bind($this->getConnection(), + $this->getSearchAttribute() . '=' . $username . ',' . + $this->getBaseDN(), + $password); + + if (!$result) { + throw new Exception('Bad username/password.'); + } + + $this->userData = $this->getUser($username); + return $this->userData; + } + + private function getUser($username) { + $result = ldap_search($this->getConnection(), $this->getBaseDN(), + $this->getSearchAttribute() . '=' . $username); + + if (!$result) { + throw new Exception('Search failed. Please check your LDAP and HTTP '. + 'logs for more information.'); + } + + $entries = ldap_get_entries($this->getConnection(), $result); + + if ($entries === false) { + throw new Exception('Could not get entries'); + } + + if ($entries['count'] > 1) { + throw new Exception('Found more then one user with this ' . + $this->getSearchAttribute()); + } + + if ($entries['count'] == 0) { + throw new Exception('Could not find user'); + } + + return $entries[0]; + } +} diff --git a/src/applications/people/controller/PhabricatorUserSettingsController.php b/src/applications/people/controller/PhabricatorUserSettingsController.php index a5d3dc4b1f..199cb5cad9 100644 --- a/src/applications/people/controller/PhabricatorUserSettingsController.php +++ b/src/applications/people/controller/PhabricatorUserSettingsController.php @@ -65,6 +65,9 @@ final class PhabricatorUserSettingsController case 'search': $delegate = new PhabricatorUserSearchSettingsPanelController($request); break; + case 'ldap': + $delegate = new PhabricatorUserLDAPSettingsPanelController($request); + break; default: $delegate = new PhabricatorUserOAuthSettingsPanelController($request); $delegate->setOAuthProvider($oauth_providers[$this->page]); @@ -125,6 +128,11 @@ final class PhabricatorUserSettingsController $items[$key] = $name.' Account'; } + $ldap_provider = new PhabricatorLDAPProvider(); + if ($ldap_provider->isProviderEnabled()) { + $items['ldap'] = 'LDAP Account'; + } + if ($items) { $sidenav->addSpacer(); $sidenav->addLabel('Linked Accounts'); diff --git a/src/applications/people/controller/settings/panels/PhabricatorUserLDAPSettingsPanelController.php b/src/applications/people/controller/settings/panels/PhabricatorUserLDAPSettingsPanelController.php new file mode 100644 index 0000000000..c422c1db73 --- /dev/null +++ b/src/applications/people/controller/settings/panels/PhabricatorUserLDAPSettingsPanelController.php @@ -0,0 +1,87 @@ +getRequest(); + $user = $request->getUser(); + + $ldap_info = id(new PhabricatorUserLDAPInfo())->loadOneWhere( + 'userID = %d', + $user->getID()); + + $forms = array(); + + if (!$ldap_info) { + $unlink = 'Link LDAP Account'; + $unlink_form = new AphrontFormView(); + $unlink_form + ->setUser($user) + ->setAction('/ldap/login/') + ->appendChild( + '

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

') + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('LDAP username') + ->setName('username')) + ->appendChild( + id(new AphrontFormPasswordControl()) + ->setLabel('Password') + ->setName('password')) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue("Link LDAP Account \xC2\xBB")); + + $forms['Link Account'] = $unlink_form; + } else { + $unlink = 'Unlink LDAP Account'; + $unlink_form = new AphrontFormView(); + $unlink_form + ->setUser($user) + ->appendChild( + '

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

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

'.$name.'


'); + } + $panel->appendChild($form); + } + + return id(new AphrontNullView()) + ->appendChild( + array( + $panel, + )); + } +} diff --git a/src/applications/people/storage/PhabricatorUserLDAPInfo.php b/src/applications/people/storage/PhabricatorUserLDAPInfo.php new file mode 100644 index 0000000000..a7c901fa23 --- /dev/null +++ b/src/applications/people/storage/PhabricatorUserLDAPInfo.php @@ -0,0 +1,22 @@ + 'sql', 'name' => $this->getPatchPath('testdatabase.sql'), ), + 'ldapinfo.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('ldapinfo.sql'), + ), ); }