diff --git a/i18n b/i18n index 8af40e7cba..4134d60ca6 160000 --- a/i18n +++ b/i18n @@ -1 +1 @@ -Subproject commit 8af40e7cba4fc61273ed3e11b9ecf90f96f8c74e +Subproject commit 4134d60ca62ee29fcdffb57ce91f34b9af47ca02 diff --git a/login.php b/login.php index 4b425a0e72..2dfa8b8d57 100644 --- a/login.php +++ b/login.php @@ -28,6 +28,7 @@ use Gibbon\Auth\Adapter\OAuthGoogleAdapter; use Gibbon\Auth\Adapter\OAuthMicrosoftAdapter; use Gibbon\Auth\Adapter\OAuthGenericAdapter; +use Gibbon\Auth\Adapter\LDAPAdapter; use Gibbon\Domain\System\LogGateway; use League\Container\Exception\NotFoundException; @@ -73,6 +74,9 @@ case 'mfa': $authAdapter = $container->get(MFAAdapter::class); break; + case 'ldap': + $authAdapter = $container->get(LDAPAdapter::class); + break; default: $authAdapter = $container->get(DefaultAdapter::class); } @@ -104,6 +108,7 @@ // Handle login try { $loginService = $authFactory->newLoginService($authAdapter); + $loginService->login($auth, [ 'username' => $_POST['username'] ?? '', 'password' => $_POST['password'] ?? '', @@ -179,6 +184,9 @@ } catch (Exception\MFATokenInvalid $e) { header("Location: {$URL->withQueryParam('loginReturn', 'fail11')}"); exit; +} catch (Exception\LDAPBindFailed $e) { + header("Location: {$URL->withQueryParam('loginReturn', 'fail12')}"); + exit; } catch (Exception\MFATokenRequired $e) { header("Location: {$URL->withQueryParam('method', 'mfa')}"); exit; diff --git a/modules/System Admin/thirdPartySettings.php b/modules/System Admin/thirdPartySettings.php index 0e1976ff61..443d9b2e3a 100644 --- a/modules/System Admin/thirdPartySettings.php +++ b/modules/System Admin/thirdPartySettings.php @@ -60,6 +60,7 @@ $settingGateway = $container->get(SettingGateway::class); $ssoGoogle = json_decode($settingGateway->getSettingByScope('System Admin', 'ssoGoogle'), true); $ssoMicrosoft = json_decode($settingGateway->getSettingByScope('System Admin', 'ssoMicrosoft'), true); + $ssoLDAP = json_decode($settingGateway->getSettingByScope('System Admin', 'ssoLDAP'), true); $ssoOther = json_decode($settingGateway->getSettingByScope('System Admin', 'ssoOther'), true); $ssoList = [ @@ -77,6 +78,13 @@ 'url' => 'https://portal.azure.com', 'enabled' => $ssoMicrosoft['enabled'] ?? 'N', ], + [ + 'sso' => 'LDAP', + 'name' => __('LDAP'), + 'service' => __('Generic LDAP Connections'), + 'url' => '', + 'enabled' => $ssoLDAP['enabled'] ?? 'N', + ], [ 'sso' => 'Other', 'name' => !empty($ssoOther['clientName']) ? $ssoOther['clientName'] : __('Other'), diff --git a/modules/System Admin/thirdPartySettings_ssoEdit.php b/modules/System Admin/thirdPartySettings_ssoEdit.php index 275955986a..6ab3110a90 100644 --- a/modules/System Admin/thirdPartySettings_ssoEdit.php +++ b/modules/System Admin/thirdPartySettings_ssoEdit.php @@ -96,6 +96,22 @@ $row->addLabel('enabled', __('API Enabled'))->description(__('Enable Gibbon-wide integration with the Microsoft APIs?')); $row->addYesNo('enabled')->required(); + } else if ($sso == 'LDAP') { + // LDAP + $form->addRow()->addHeading('LDAP Integration', __('LDAP Integration'))->append(sprintf(__('todo: this'))); + + $row = $form->addRow(); + $row->addLabel('enabled', __('API Enabled'))->description(__('Enable Gibbon-wide login integration with LDAP?')); + $row->addYesNo('enabled')->required(); + + $row = $form->addRow()->addClass('settingActive'); + $row->addLabel('ldapServer', __('LDAP Server')); + $row->addTextField('ldapServer')->required(); + + $row = $form->addRow()->addClass('settingActive'); + $row->addLabel('ldapDN', __('LDAP Distinguished Name (DN)')); + $row->addTextField('ldapDN')->required(); + } else if ($sso == 'Other') { $form->addRow()->addHeading('Generic OAuth2 Provider', __('Generic OAuth2 Provider'))->append(__('This setting offers a generic implementation of industry-standard OAuth2 protocols. It uses standard Client ID and Client Secret parameters to connect to an OAuth2 API server. You will need to specify the API endpoints of your chosen service, which can often be found in that service\'s documentation. If your OAuth2 service requires specific API parameters, this feature is unlikely to work.')); diff --git a/modules/System Admin/thirdPartySettings_ssoEditProcess.php b/modules/System Admin/thirdPartySettings_ssoEditProcess.php index d4bf064572..82cd8fac06 100644 --- a/modules/System Admin/thirdPartySettings_ssoEditProcess.php +++ b/modules/System Admin/thirdPartySettings_ssoEditProcess.php @@ -53,11 +53,13 @@ 'authorizeEndpoint' => $_POST['authorizeEndpoint'] ?? '', 'tokenEndpoint' => $_POST['tokenEndpoint'] ?? '', 'userEndpoint' => $_POST['userEndpoint'] ?? '', + 'ldapServer' => $_POST['ldapServer'] ?? '', + 'ldapDN' => $_POST['ldapDN'] ?? '' ]; $calendarFeed = $_POST['calendarFeed'] ?? ''; - if ($data['enabled'] == 'Y' && (empty($data['clientID']) || empty($data['clientSecret']))) { + if ($data['enabled'] == 'Y' && ((empty($data['clientID']) || empty($data['clientSecret'])) && ((empty($data['ldapServer']) || empty($data['ldapDN']))))) { $URL .= '&return=error1'; header("Location: {$URL}"); exit; diff --git a/src/Auth/Adapter/LDAPAdapter.php b/src/Auth/Adapter/LDAPAdapter.php new file mode 100644 index 0000000000..2abfd19ea8 --- /dev/null +++ b/src/Auth/Adapter/LDAPAdapter.php @@ -0,0 +1,94 @@ +. +*/ + +namespace Gibbon\Auth\Adapter; + +use Gibbon\Http\Url; +use Gibbon\Auth\Exception; +use Gibbon\Auth\Adapter\AuthenticationAdapter; +use Gibbon\Contracts\Services\Session; +use Aura\Auth\Exception as AuraException; +use Gibbon\Domain\User\UserGateway; +use Aura\Auth\AuthFactory; + +/** + * Generic OAuth2 adapter for Aura/Auth + * + * @version v23 + * @since v23 + */ +class LDAPAdapter extends AuthenticationAdapter +{ + /** + * Constructor + * + * + */ + public function __construct() + { + + } + + /** + * Attempts to connect to the LDAP server using the provided credentials. Exceptions are thrown + * if any credentials are not valid. + * + * @param array $input Credential input. + * + * @return array An array of login data on success. + * + * + * + * + */ + public function login(array $input) + { + $this->userGateway = $this->getContainer()->get(UserGateway::class); + + // Validate that the username and password are both present + + $authFactory = $this->getAuthFactory(); + $auth = $authFactory->newInstance(); + $ldapAdapter = $authFactory->newLdapAdapter( + 'ip address', //TODO: GET THESE FROM SETTINGS + '%s@'.'bind domain', //TODO: GET THESE FROM SETTINGS + [LDAP_OPT_PROTOCOL_VERSION => 3] + ); + $loginService = $authFactory->newLoginService($ldapAdapter); + try { + $loginService->login($auth, array( + 'username' => $input['username'], + 'password' => $input['password'] + )); + } catch (AuraException\BindFailed $e) { + throw new Exception\LDAPBindFailed; + } + + // Get basic user data needed to verify login access + $userData = $this->getUserData($input); + return parent::verifyLogin($userData); + } + + + + private function getAuthFactory() + { + return $this->getContainer()->get(AuthFactory::class); + } +} diff --git a/src/Auth/Exception/LDAPBindFailed.php b/src/Auth/Exception/LDAPBindFailed.php new file mode 100644 index 0000000000..3b5183f99b --- /dev/null +++ b/src/Auth/Exception/LDAPBindFailed.php @@ -0,0 +1,24 @@ +. +*/ + +namespace Gibbon\Auth\Exception; + +use Exception; + +class LDAPBindFailed extends Exception {}