@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

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

authored by

Espen Volden and committed by
epriestley
72604158 d73a6d3d

+815
+21
conf/default.conf.php
··· 548 548 // The Google "Client Secret" to use for Google API access. 549 549 'google.application-secret' => null, 550 550 551 + // -- LDAP Auth ----------------------------------------------------- // 552 + // Enable ldap auth 553 + 'ldap.auth-enabled' => false, 554 + 555 + // The LDAP server hostname 556 + 'ldap.hostname' => '', 557 + 558 + // The LDAP base domain name 559 + 'ldap.base_dn' => '', 560 + 561 + // The attribute to be regarded as 'username'. Has to be unique 562 + 'ldap.search_attribute' => '', 563 + 564 + // The attribute(s) to be regarded as 'real name'. 565 + // If more then one attribute is supplied the values of the attributes in 566 + // the array will be joined 567 + 'ldap.real_name_attributes' => array(), 568 + 569 + // The LDAP version 570 + 'ldap.version' => 3, 571 + 551 572 // -- Disqus OAuth ---------------------------------------------------------- // 552 573 553 574 // Can users use Disqus credentials to login to Phabricator?
+9
resources/sql/patches/ldapinfo.sql
··· 1 + CREATE TABLE {$NAMESPACE}_user.user_ldapinfo ( 2 + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 3 + `userID` int(10) unsigned NOT NULL, 4 + `ldapUsername` varchar(255) NOT NULL, 5 + `dateCreated` int(10) unsigned NOT NULL, 6 + `dateModified` int(10) unsigned NOT NULL, 7 + PRIMARY KEY (`id`), 8 + UNIQUE KEY (`userID`) 9 + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+11
src/__phutil_library_map__.php
··· 699 699 'PhabricatorInlineSummaryView' => 'infrastructure/diff/view/PhabricatorInlineSummaryView.php', 700 700 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php', 701 701 'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php', 702 + 'PhabricatorLDAPLoginController' => 'applications/auth/controller/PhabricatorLDAPLoginController.php', 703 + 'PhabricatorLDAPProvider' => 'applications/auth/ldap/PhabricatorLDAPProvider.php', 704 + 'PhabricatorLDAPRegistrationController' => 'applications/auth/controller/PhabricatorLDAPRegistrationController.php', 705 + 'PhabricatorLDAPUnlinkController' => 'applications/auth/controller/PhabricatorLDAPUnlinkController.php', 702 706 'PhabricatorLintEngine' => 'infrastructure/lint/PhabricatorLintEngine.php', 703 707 'PhabricatorLiskDAO' => 'applications/base/storage/PhabricatorLiskDAO.php', 704 708 'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php', ··· 973 977 'PhabricatorUserEmail' => 'applications/people/storage/PhabricatorUserEmail.php', 974 978 'PhabricatorUserEmailPreferenceSettingsPanelController' => 'applications/people/controller/settings/panels/PhabricatorUserEmailPreferenceSettingsPanelController.php', 975 979 'PhabricatorUserEmailSettingsPanelController' => 'applications/people/controller/settings/panels/PhabricatorUserEmailSettingsPanelController.php', 980 + 'PhabricatorUserLDAPInfo' => 'applications/people/storage/PhabricatorUserLDAPInfo.php', 981 + 'PhabricatorUserLDAPSettingsPanelController' => 'applications/people/controller/settings/panels/PhabricatorUserLDAPSettingsPanelController.php', 976 982 'PhabricatorUserLog' => 'applications/people/storage/PhabricatorUserLog.php', 977 983 'PhabricatorUserOAuthInfo' => 'applications/people/storage/PhabricatorUserOAuthInfo.php', 978 984 'PhabricatorUserOAuthSettingsPanelController' => 'applications/people/controller/settings/panels/PhabricatorUserOAuthSettingsPanelController.php', ··· 1669 1675 'PhabricatorInlineCommentController' => 'PhabricatorController', 1670 1676 'PhabricatorInlineSummaryView' => 'AphrontView', 1671 1677 'PhabricatorJavelinLinter' => 'ArcanistLinter', 1678 + 'PhabricatorLDAPLoginController' => 'PhabricatorAuthController', 1679 + 'PhabricatorLDAPRegistrationController' => 'PhabricatorAuthController', 1680 + 'PhabricatorLDAPUnlinkController' => 'PhabricatorAuthController', 1672 1681 'PhabricatorLintEngine' => 'PhutilLintEngine', 1673 1682 'PhabricatorLiskDAO' => 'LiskDAO', 1674 1683 'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine', ··· 1901 1910 'PhabricatorUserEmail' => 'PhabricatorUserDAO', 1902 1911 'PhabricatorUserEmailPreferenceSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 1903 1912 'PhabricatorUserEmailSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 1913 + 'PhabricatorUserLDAPInfo' => 'PhabricatorUserDAO', 1914 + 'PhabricatorUserLDAPSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 1904 1915 'PhabricatorUserLog' => 'PhabricatorUserDAO', 1905 1916 'PhabricatorUserOAuthInfo' => 'PhabricatorUserDAO', 1906 1917 'PhabricatorUserOAuthSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
+5
src/aphront/configuration/AphrontDefaultApplicationConfiguration.php
··· 146 146 ), 147 147 ), 148 148 149 + '/ldap/' => array( 150 + 'login/' => 'PhabricatorLDAPLoginController', 151 + 'unlink/' => 'PhabricatorLDAPUnlinkController', 152 + ), 153 + 149 154 '/oauthserver/' => array( 150 155 'auth/' => 'PhabricatorOAuthServerAuthController', 151 156 'test/' => 'PhabricatorOAuthServerTestController',
+187
src/applications/auth/controller/PhabricatorLDAPLoginController.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + final class PhabricatorLDAPLoginController extends PhabricatorAuthController { 20 + private $provider; 21 + 22 + public function shouldRequireLogin() { 23 + return false; 24 + } 25 + 26 + public function willProcessRequest(array $data) { 27 + $this->provider = new PhabricatorLDAPProvider(); 28 + } 29 + 30 + public function processRequest() { 31 + if (!$this->provider->isProviderEnabled()) { 32 + return new Aphront400Response(); 33 + } 34 + 35 + $current_user = $this->getRequest()->getUser(); 36 + $request = $this->getRequest(); 37 + 38 + if ($request->isFormPost()) { 39 + try { 40 + $this->provider->auth($request->getStr('username'), 41 + $request->getStr('password')); 42 + 43 + } catch (Exception $e) { 44 + $errors[] = $e->getMessage(); 45 + } 46 + 47 + if (empty($errors)) { 48 + $ldap_info = $this->retrieveLDAPInfo($this->provider); 49 + 50 + if ($current_user->getPHID()) { 51 + if ($ldap_info->getID()) { 52 + $existing_ldap = id(new PhabricatorUserLDAPInfo())->loadOneWhere( 53 + 'userID = %d', 54 + $current_user->getID()); 55 + 56 + if ($ldap_info->getUserID() != $current_user->getID() || 57 + $existing_ldap) { 58 + $dialog = new AphrontDialogView(); 59 + $dialog->setUser($current_user); 60 + $dialog->setTitle('Already Linked to Another Account'); 61 + $dialog->appendChild( 62 + '<p>The LDAP account you just authorized is already linked to '. 63 + 'another Phabricator account. Before you can link it to a '. 64 + 'different LDAP account, you must unlink the old account.</p>' 65 + ); 66 + $dialog->addCancelButton('/settings/page/ldap/'); 67 + 68 + return id(new AphrontDialogResponse())->setDialog($dialog); 69 + } else { 70 + return id(new AphrontRedirectResponse()) 71 + ->setURI('/settings/page/ldap/'); 72 + } 73 + } 74 + 75 + if (!$request->isDialogFormPost()) { 76 + $dialog = new AphrontDialogView(); 77 + $dialog->setUser($current_user); 78 + $dialog->setTitle('Link LDAP Account'); 79 + $dialog->appendChild( 80 + '<p>Link your LDAP account to your Phabricator account?</p>'); 81 + $dialog->addHiddenInput('username', $request->getStr('username')); 82 + $dialog->addHiddenInput('password', $request->getStr('password')); 83 + $dialog->addSubmitButton('Link Accounts'); 84 + $dialog->addCancelButton('/settings/page/ldap/'); 85 + 86 + return id(new AphrontDialogResponse())->setDialog($dialog); 87 + } 88 + 89 + $ldap_info->setUserID($current_user->getID()); 90 + 91 + $this->saveLDAPInfo($ldap_info); 92 + 93 + return id(new AphrontRedirectResponse()) 94 + ->setURI('/settings/page/ldap/'); 95 + } 96 + 97 + if ($ldap_info->getID()) { 98 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 99 + 100 + $known_user = id(new PhabricatorUser())->load( 101 + $ldap_info->getUserID()); 102 + 103 + $session_key = $known_user->establishSession('web'); 104 + 105 + $this->saveLDAPInfo($ldap_info); 106 + 107 + $request->setCookie('phusr', $known_user->getUsername()); 108 + $request->setCookie('phsid', $session_key); 109 + 110 + $uri = new PhutilURI('/login/validate/'); 111 + $uri->setQueryParams( 112 + array( 113 + 'phusr' => $known_user->getUsername(), 114 + )); 115 + 116 + return id(new AphrontRedirectResponse())->setURI((string)$uri); 117 + } 118 + 119 + $controller = newv('PhabricatorLDAPRegistrationController', 120 + array($this->getRequest())); 121 + $controller->setLDAPProvider($this->provider); 122 + $controller->setLDAPInfo($ldap_info); 123 + 124 + return $this->delegateToController($controller); 125 + } 126 + } 127 + 128 + $ldap_username = $request->getCookie('phusr'); 129 + $ldap_form = new AphrontFormView(); 130 + $ldap_form 131 + ->setUser($request->getUser()) 132 + ->setAction('/ldap/login/') 133 + ->appendChild( 134 + id(new AphrontFormTextControl()) 135 + ->setLabel('LDAP username') 136 + ->setName('username') 137 + ->setValue($ldap_username)) 138 + ->appendChild( 139 + id(new AphrontFormPasswordControl()) 140 + ->setLabel('Password') 141 + ->setName('password')); 142 + 143 + $ldap_form 144 + ->appendChild( 145 + id(new AphrontFormSubmitControl()) 146 + ->setValue('Login')); 147 + 148 + $panel = new AphrontPanelView(); 149 + $panel->setWidth(AphrontPanelView::WIDTH_FORM); 150 + $panel->appendChild('<h1>LDAP login</h1>'); 151 + $panel->appendChild($ldap_form); 152 + 153 + if (isset($errors) && count($errors) > 0) { 154 + $error_view = new AphrontErrorView(); 155 + $error_view->setTitle('Login Failed'); 156 + $error_view->setErrors($errors); 157 + } 158 + 159 + return $this->buildStandardPageResponse( 160 + array( 161 + isset($error_view) ? $error_view : null, 162 + $panel, 163 + ), 164 + array( 165 + 'title' => 'Login', 166 + )); 167 + } 168 + 169 + private function retrieveLDAPInfo(PhabricatorLDAPProvider $provider) { 170 + $ldap_info = id(new PhabricatorUserLDAPInfo())->loadOneWhere( 171 + 'ldapUsername = %s', 172 + $provider->retrieveUsername()); 173 + 174 + if (!$ldap_info) { 175 + $ldap_info = new PhabricatorUserLDAPInfo(); 176 + $ldap_info->setLDAPUsername($provider->retrieveUsername()); 177 + } 178 + 179 + return $ldap_info; 180 + } 181 + 182 + private function saveLDAPInfo(PhabricatorUserLDAPInfo $info) { 183 + // UNGUARDED WRITES: Logging-in users don't have their CSRF set up yet. 184 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 185 + $info->save(); 186 + } 187 + }
+236
src/applications/auth/controller/PhabricatorLDAPRegistrationController.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + final class PhabricatorLDAPRegistrationController 20 + extends PhabricatorAuthController { 21 + private $ldapProvider; 22 + private $ldapInfo; 23 + 24 + public function setLDAPProvider($provider) { 25 + $this->ldapProvider = $provider; 26 + return $this; 27 + } 28 + 29 + public function getLDAProvider() { 30 + return $this->ldapProvider; 31 + } 32 + 33 + public function setLDAPInfo($info) { 34 + $this->ldapInfo = $info; 35 + return $this; 36 + } 37 + 38 + public function getLDAPInfo() { 39 + return $this->ldapInfo; 40 + } 41 + 42 + public function processRequest() { 43 + $provider = $this->getLDAProvider(); 44 + $ldap_info = $this->getLDAPInfo(); 45 + $request = $this->getRequest(); 46 + 47 + $errors = array(); 48 + $e_username = true; 49 + $e_email = true; 50 + $e_realname = true; 51 + 52 + $user = new PhabricatorUser(); 53 + $user->setUsername(); 54 + $user->setRealname($provider->retrieveUserRealName()); 55 + 56 + $new_email = $provider->retrieveUserEmail(); 57 + 58 + if ($new_email) { 59 + // If the user's LDAP provider account has an email address but the 60 + // email address domain is not allowed by the Phabricator configuration, 61 + // we just pretend the provider did not supply an address. 62 + // 63 + // For instance, if the user uses LDAP Auth and their email address 64 + // is "joe@personal.com" but Phabricator is configured to require users 65 + // use "@company.com" addresses, we show a prompt below and tell the user 66 + // to provide their "@company.com" address. They can still use the LDAP 67 + // account to login, they just need to associate their account with an 68 + // allowed address. 69 + // 70 + // If the email address is fine, we just use it and don't prompt the user. 71 + if (!PhabricatorUserEmail::isAllowedAddress($new_email)) { 72 + $new_email = null; 73 + } 74 + } 75 + 76 + $show_email_input = ($new_email === null); 77 + 78 + if ($request->isFormPost()) { 79 + $user->setUsername($request->getStr('username')); 80 + $username = $user->getUsername(); 81 + if (!strlen($user->getUsername())) { 82 + $e_username = 'Required'; 83 + $errors[] = 'Username is required.'; 84 + } else if (!PhabricatorUser::validateUsername($username)) { 85 + $e_username = 'Invalid'; 86 + $errors[] = PhabricatorUser::describeValidUsername(); 87 + } else { 88 + $e_username = null; 89 + } 90 + 91 + if (!$new_email) { 92 + $new_email = trim($request->getStr('email')); 93 + if (!$new_email) { 94 + $e_email = 'Required'; 95 + $errors[] = 'Email is required.'; 96 + } else { 97 + $e_email = null; 98 + } 99 + } 100 + 101 + if ($new_email) { 102 + if (!PhabricatorUserEmail::isAllowedAddress($new_email)) { 103 + $e_email = 'Invalid'; 104 + $errors[] = PhabricatorUserEmail::describeAllowedAddresses(); 105 + } 106 + } 107 + 108 + if (!strlen($user->getRealName())) { 109 + $user->setRealName($request->getStr('realname')); 110 + if (!strlen($user->getRealName())) { 111 + $e_realname = 'Required'; 112 + $errors[] = 'Real name is required.'; 113 + } else { 114 + $e_realname = null; 115 + } 116 + } 117 + 118 + if (!$errors) { 119 + try { 120 + // NOTE: We don't verify LDAP email addresses by default because 121 + // LDAP providers might associate email addresses with accounts that 122 + // haven't actually verified they own them. We could selectively 123 + // auto-verify some providers that we trust here, but the stakes for 124 + // verifying an email address are high because having a corporate 125 + // address at a company is sometimes the key to the castle. 126 + 127 + $email_obj = id(new PhabricatorUserEmail()) 128 + ->setAddress($new_email) 129 + ->setIsVerified(0); 130 + 131 + id(new PhabricatorUserEditor()) 132 + ->setActor($user) 133 + ->createNewUser($user, $email_obj); 134 + 135 + $ldap_info->setUserID($user->getID()); 136 + $ldap_info->save(); 137 + 138 + $session_key = $user->establishSession('web'); 139 + $request->setCookie('phusr', $user->getUsername()); 140 + $request->setCookie('phsid', $session_key); 141 + 142 + $email_obj->sendVerificationEmail($user); 143 + 144 + return id(new AphrontRedirectResponse())->setURI('/'); 145 + } catch (AphrontQueryDuplicateKeyException $exception) { 146 + 147 + $same_username = id(new PhabricatorUser())->loadOneWhere( 148 + 'userName = %s', 149 + $user->getUserName()); 150 + 151 + $same_email = id(new PhabricatorUserEmail())->loadOneWhere( 152 + 'address = %s', 153 + $new_email); 154 + 155 + if ($same_username) { 156 + $e_username = 'Duplicate'; 157 + $errors[] = 'That username or email is not unique.'; 158 + } else if ($same_email) { 159 + $e_email = 'Duplicate'; 160 + $errors[] = 'That email is not unique.'; 161 + } else { 162 + throw $exception; 163 + } 164 + } 165 + } 166 + } 167 + 168 + 169 + $error_view = null; 170 + if ($errors) { 171 + $error_view = new AphrontErrorView(); 172 + $error_view->setTitle('Registration Failed'); 173 + $error_view->setErrors($errors); 174 + } 175 + 176 + // Strip the URI down to the path, because otherwise we'll trigger 177 + // external CSRF protection (by having a protocol in the form "action") 178 + // and generate a form with no CSRF token. 179 + $action_uri = new PhutilURI('/ldap/login/'); 180 + $action_path = $action_uri->getPath(); 181 + 182 + $form = new AphrontFormView(); 183 + $form 184 + ->setUser($request->getUser()) 185 + ->setAction($action_path) 186 + ->appendChild( 187 + id(new AphrontFormTextControl()) 188 + ->setLabel('Username') 189 + ->setName('username') 190 + ->setValue($user->getUsername()) 191 + ->setError($e_username)); 192 + 193 + $form->appendChild( 194 + id(new AphrontFormPasswordControl()) 195 + ->setLabel('Password') 196 + ->setName('password')); 197 + 198 + if ($show_email_input) { 199 + $form->appendChild( 200 + id(new AphrontFormTextControl()) 201 + ->setLabel('Email') 202 + ->setName('email') 203 + ->setValue($request->getStr('email')) 204 + ->setError($e_email)); 205 + } 206 + 207 + if ($provider->retrieveUserRealName() === null) { 208 + $form->appendChild( 209 + id(new AphrontFormTextControl()) 210 + ->setLabel('Real Name') 211 + ->setName('realname') 212 + ->setValue($request->getStr('realname')) 213 + ->setError($e_realname)); 214 + } 215 + 216 + $form 217 + ->appendChild( 218 + id(new AphrontFormSubmitControl()) 219 + ->setValue('Create Account')); 220 + 221 + $panel = new AphrontPanelView(); 222 + $panel->setHeader('Create New Account'); 223 + $panel->setWidth(AphrontPanelView::WIDTH_FORM); 224 + $panel->appendChild($form); 225 + 226 + return $this->buildStandardPageResponse( 227 + array( 228 + $error_view, 229 + $panel, 230 + ), 231 + array( 232 + 'title' => 'Create New Account', 233 + )); 234 + } 235 + 236 + }
+52
src/applications/auth/controller/PhabricatorLDAPUnlinkController.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + final class PhabricatorLDAPUnlinkController extends PhabricatorAuthController { 20 + 21 + public function processRequest() { 22 + $request = $this->getRequest(); 23 + $user = $request->getUser(); 24 + 25 + $ldap_info = id(new PhabricatorUserLDAPInfo())->loadOneWhere( 26 + 'userID = %d', 27 + $user->getID()); 28 + 29 + if (!$ldap_info) { 30 + return new Aphront400Response(); 31 + } 32 + 33 + if (!$request->isDialogFormPost()) { 34 + $dialog = new AphrontDialogView(); 35 + $dialog->setUser($user); 36 + $dialog->setTitle('Really unlink account?'); 37 + $dialog->appendChild( 38 + '<p><strong>You will not be able to login</strong> using this account '. 39 + 'once you unlink it. Continue?</p>'); 40 + $dialog->addSubmitButton('Unlink Account'); 41 + $dialog->addCancelButton('/settings/page/ldap/'); 42 + 43 + return id(new AphrontDialogResponse())->setDialog($dialog); 44 + } 45 + 46 + $ldap_info->delete(); 47 + 48 + return id(new AphrontRedirectResponse()) 49 + ->setURI('/settings/page/ldap/'); 50 + } 51 + 52 + }
+24
src/applications/auth/controller/PhabricatorLoginController.php
··· 187 187 188 188 // $panel->setCreateButton('Register New Account', '/login/register/'); 189 189 $forms['Phabricator Login'] = $form; 190 + 191 + $ldap_provider = new PhabricatorLDAPProvider(); 192 + if ($ldap_provider->isProviderEnabled()) { 193 + $ldap_form = new AphrontFormView(); 194 + $ldap_form 195 + ->setUser($request->getUser()) 196 + ->setAction('/ldap/login/') 197 + ->appendChild( 198 + id(new AphrontFormTextControl()) 199 + ->setLabel('LDAP username') 200 + ->setName('username') 201 + ->setValue($username_or_email)) 202 + ->appendChild( 203 + id(new AphrontFormPasswordControl()) 204 + ->setLabel('Password') 205 + ->setName('password')); 206 + 207 + $ldap_form 208 + ->appendChild( 209 + id(new AphrontFormSubmitControl()) 210 + ->setValue('Login')); 211 + 212 + $forms['LDAP Login'] = $ldap_form; 213 + } 190 214 } 191 215 192 216 $providers = PhabricatorOAuthProvider::getAllProviders();
+149
src/applications/auth/ldap/PhabricatorLDAPProvider.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + final class PhabricatorLDAPProvider { 20 + private $userData; 21 + private $connection; 22 + 23 + public function __construct() { 24 + 25 + } 26 + 27 + public function __destruct() { 28 + if (isset($this->connection)) { 29 + ldap_unbind($this->connection); 30 + } 31 + } 32 + 33 + public function isProviderEnabled() { 34 + return PhabricatorEnv::getEnvConfig('ldap.auth-enabled'); 35 + } 36 + 37 + public function getHostname() { 38 + return PhabricatorEnv::getEnvConfig('ldap.hostname'); 39 + } 40 + 41 + public function getBaseDN() { 42 + return PhabricatorEnv::getEnvConfig('ldap.base_dn'); 43 + } 44 + 45 + public function getSearchAttribute() { 46 + return PhabricatorEnv::getEnvConfig('ldap.search_attribute'); 47 + } 48 + 49 + public function getLDAPVersion() { 50 + return PhabricatorEnv::getEnvConfig('ldap.version'); 51 + } 52 + 53 + public function retrieveUserEmail() { 54 + return $this->userData['mail'][0]; 55 + } 56 + 57 + public function retrieveUserRealName() { 58 + $name_attributes = PhabricatorEnv::getEnvConfig( 59 + 'ldap.real_name_attributes'); 60 + 61 + $real_name = ''; 62 + if (is_array($name_attributes)) { 63 + foreach ($name_attributes AS $attribute) { 64 + if (isset($this->userData[$attribute][0])) { 65 + $real_name .= $this->userData[$attribute][0] . ' '; 66 + } 67 + } 68 + 69 + trim($real_name); 70 + } else if (isset($this->userData[$name_attributes][0])) { 71 + $real_name = $this->userData[$name_attributes][0]; 72 + } 73 + 74 + if ($real_name == '') { 75 + return null; 76 + } 77 + 78 + return $real_name; 79 + } 80 + 81 + public function retrieveUsername() { 82 + return $this->userData[$this->getSearchAttribute()][0]; 83 + } 84 + 85 + public function getConnection() { 86 + if (!isset($this->connection)) { 87 + $this->connection = ldap_connect($this->getHostname()); 88 + 89 + if (!$this->connection) { 90 + throw new Exception('Could not connect to LDAP host at ' . 91 + $this->getHostname()); 92 + } 93 + 94 + ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, 95 + $this->getLDAPVersion()); 96 + } 97 + 98 + return $this->connection; 99 + } 100 + 101 + public function getUserData() { 102 + return $this->userData; 103 + } 104 + 105 + public function auth($username, $password) { 106 + if (strlen(trim($username)) == 0 || strlen(trim($password)) == 0) { 107 + throw new Exception('Username and/or password can not be empty'); 108 + } 109 + 110 + $result = ldap_bind($this->getConnection(), 111 + $this->getSearchAttribute() . '=' . $username . ',' . 112 + $this->getBaseDN(), 113 + $password); 114 + 115 + if (!$result) { 116 + throw new Exception('Bad username/password.'); 117 + } 118 + 119 + $this->userData = $this->getUser($username); 120 + return $this->userData; 121 + } 122 + 123 + private function getUser($username) { 124 + $result = ldap_search($this->getConnection(), $this->getBaseDN(), 125 + $this->getSearchAttribute() . '=' . $username); 126 + 127 + if (!$result) { 128 + throw new Exception('Search failed. Please check your LDAP and HTTP '. 129 + 'logs for more information.'); 130 + } 131 + 132 + $entries = ldap_get_entries($this->getConnection(), $result); 133 + 134 + if ($entries === false) { 135 + throw new Exception('Could not get entries'); 136 + } 137 + 138 + if ($entries['count'] > 1) { 139 + throw new Exception('Found more then one user with this ' . 140 + $this->getSearchAttribute()); 141 + } 142 + 143 + if ($entries['count'] == 0) { 144 + throw new Exception('Could not find user'); 145 + } 146 + 147 + return $entries[0]; 148 + } 149 + }
+8
src/applications/people/controller/PhabricatorUserSettingsController.php
··· 65 65 case 'search': 66 66 $delegate = new PhabricatorUserSearchSettingsPanelController($request); 67 67 break; 68 + case 'ldap': 69 + $delegate = new PhabricatorUserLDAPSettingsPanelController($request); 70 + break; 68 71 default: 69 72 $delegate = new PhabricatorUserOAuthSettingsPanelController($request); 70 73 $delegate->setOAuthProvider($oauth_providers[$this->page]); ··· 123 126 $key = $provider->getProviderKey(); 124 127 $name = $provider->getProviderName(); 125 128 $items[$key] = $name.' Account'; 129 + } 130 + 131 + $ldap_provider = new PhabricatorLDAPProvider(); 132 + if ($ldap_provider->isProviderEnabled()) { 133 + $items['ldap'] = 'LDAP Account'; 126 134 } 127 135 128 136 if ($items) {
+87
src/applications/people/controller/settings/panels/PhabricatorUserLDAPSettingsPanelController.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + final class PhabricatorUserLDAPSettingsPanelController 20 + extends PhabricatorUserSettingsPanelController { 21 + 22 + public function processRequest() { 23 + $request = $this->getRequest(); 24 + $user = $request->getUser(); 25 + 26 + $ldap_info = id(new PhabricatorUserLDAPInfo())->loadOneWhere( 27 + 'userID = %d', 28 + $user->getID()); 29 + 30 + $forms = array(); 31 + 32 + if (!$ldap_info) { 33 + $unlink = 'Link LDAP Account'; 34 + $unlink_form = new AphrontFormView(); 35 + $unlink_form 36 + ->setUser($user) 37 + ->setAction('/ldap/login/') 38 + ->appendChild( 39 + '<p class="aphront-form-instructions">There is currently no '. 40 + 'LDAP account linked to your Phabricator account. You can link an ' . 41 + 'account, which will allow you to use it to log into Phabricator</p>') 42 + ->appendChild( 43 + id(new AphrontFormTextControl()) 44 + ->setLabel('LDAP username') 45 + ->setName('username')) 46 + ->appendChild( 47 + id(new AphrontFormPasswordControl()) 48 + ->setLabel('Password') 49 + ->setName('password')) 50 + ->appendChild( 51 + id(new AphrontFormSubmitControl()) 52 + ->setValue("Link LDAP Account \xC2\xBB")); 53 + 54 + $forms['Link Account'] = $unlink_form; 55 + } else { 56 + $unlink = 'Unlink LDAP Account'; 57 + $unlink_form = new AphrontFormView(); 58 + $unlink_form 59 + ->setUser($user) 60 + ->appendChild( 61 + '<p class="aphront-form-instructions">You may unlink this account '. 62 + 'from your LDAP account. This will prevent you from logging in with '. 63 + 'your LDAP credentials.</p>') 64 + ->appendChild( 65 + id(new AphrontFormSubmitControl()) 66 + ->addCancelButton('/ldap/unlink/', $unlink)); 67 + 68 + $forms['Unlink Account'] = $unlink_form; 69 + } 70 + 71 + $panel = new AphrontPanelView(); 72 + $panel->setHeader('LDAP Account Settings'); 73 + $panel->setWidth(AphrontPanelView::WIDTH_FORM); 74 + foreach ($forms as $name => $form) { 75 + if ($name) { 76 + $panel->appendChild('<br /><h1>'.$name.'</h1><br />'); 77 + } 78 + $panel->appendChild($form); 79 + } 80 + 81 + return id(new AphrontNullView()) 82 + ->appendChild( 83 + array( 84 + $panel, 85 + )); 86 + } 87 + }
+22
src/applications/people/storage/PhabricatorUserLDAPInfo.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + final class PhabricatorUserLDAPInfo extends PhabricatorUserDAO { 20 + protected $userID; 21 + protected $ldapUsername; 22 + }
+4
src/infrastructure/setup/sql/PhabricatorBuiltinPatchList.php
··· 883 883 'type' => 'sql', 884 884 'name' => $this->getPatchPath('testdatabase.sql'), 885 885 ), 886 + 'ldapinfo.sql' => array( 887 + 'type' => 'sql', 888 + 'name' => $this->getPatchPath('ldapinfo.sql'), 889 + ), 886 890 ); 887 891 } 888 892