diff --git a/.travis.yml b/.travis.yml index cd59baa..46ad0dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ before_script: script: - if [[ $PHPUNIT_TEST ]]; then vendor/bin/phpunit; fi - if [[ $PHPUNIT_COVERAGE_TEST ]]; then phpdbg -qrr vendor/bin/phpunit --coverage-clover=coverage.xml; fi - - if [[ $PHPCS_TEST ]]; then vendor/bin/phpcs --standard=framework/phpcs.xml.dist src/ tests/ ; fi + - if [[ $PHPCS_TEST ]]; then vendor/bin/phpcs --standard=vendor/silverstripe/framework/phpcs.xml.dist src/ tests/ ; fi after_success: - if [[ $PHPUNIT_COVERAGE_TEST ]]; then bash <(curl -s https://codecov.io/bash) -f coverage.xml; fi diff --git a/composer.json b/composer.json index 8438486..77f612a 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ "autoload": { "psr-4": { "SilverStripe\\LDAP\\": "src/", - "SilverStripe\\LDAP\\Tests\\": "tests/" + "SilverStripe\\LDAP\\Tests\\": "tests/php/" } }, "minimum-stability": "dev", diff --git a/docs/en/developer.md b/docs/en/developer.md index ba47405..267e99c 100644 --- a/docs/en/developer.md +++ b/docs/en/developer.md @@ -1,6 +1,6 @@ # Developer guide -This guide will step you through configuring your SilverStripe project to function as a SAML 2.0 Service Provider (SP). It will also show you a typical way to synchronise user details and group memberships from LDAP. +This guide will step you through configuring your SilverStripe project to use an LDAP authentication backend. It will also show you a typical way to synchronise user details and group memberships from LDAP. As a SilverStripe developer after reading this guide, you should be able to correctly configure your site to integrate with the Identity Provider (IdP). You will also be able to authorise users based on their AD group memberships, and synchronise their personal details. @@ -48,7 +48,15 @@ We assume ADFS 2.0 or greater is used as an IdP. First step is to add this module into your SilverStripe project. You can use composer for this: - composer require "silverstripe/activedirectory:*" +``` +composer require silverstripe/ldap +``` + +You may need to specify a version constraint, e.g.: + +``` +composer require silverstripe/ldap ~1.0 +``` Commit the changes. @@ -511,10 +519,12 @@ that the "Username" field must be filled in, otherwise it will not be created, d You can also programatically create a user. For example: - $member = new \SilverStripe\Security\Member(); - $member->FirstName = 'Joe'; - $member->Username = 'jbloggs'; - $member->write(); +```php +$member = new \SilverStripe\Security\Member(); +$member->FirstName = 'Joe'; +$member->Username = 'jbloggs'; +$member->write(); +``` If you enable `update_ldap_from_local` saving a user in the Security section of the CMS or calling `write()` on a Member object will push up the mapped fields to LDAP, assuming that Member record has a `GUID` field. diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 94af341..8700f65 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,4 +1,4 @@ - + tests/ diff --git a/src/Authenticators/LDAPAuthenticator.php b/src/Authenticators/LDAPAuthenticator.php index 198aeb7..1fb518c 100644 --- a/src/Authenticators/LDAPAuthenticator.php +++ b/src/Authenticators/LDAPAuthenticator.php @@ -198,10 +198,10 @@ public function getLoginHandler($link) public function supportedServices() { - $result = Authenticator::LOGIN | Authenticator::LOGOUT | Authenticator::RESET_PASSWORD; + $result = Authenticator::LOGIN | Authenticator::LOGOUT | Authenticator::CHECK_PASSWORD; if ((bool)LDAPService::config()->get('allow_password_change')) { - $result |= Authenticator::CHANGE_PASSWORD; + $result |= Authenticator::RESET_PASSWORD | Authenticator::CHANGE_PASSWORD; } return $result; } @@ -219,4 +219,24 @@ public function getChangePasswordHandler($link) { return LDAPChangePasswordHandler::create($link, $this); } + + public function checkPassword(Member $member, $password, ValidationResult &$result = null) + { + $result = $result ?: ValidationResult::create(); + + /** @var LDAPService $service */ + $service = Injector::inst()->get(LDAPService::class); + + // Support email or username + $handle = Config::inst()->get(self::class, 'allow_email_login') === 'yes' ? 'Email' : 'Username'; + + /** @var array $ldapResult */ + $ldapResult = $service->authenticate($member->{$handle}, $password); + + if (empty($ldapResult['success'])) { + $result->addError($ldapResult['message']); + } + + return $result; + } } diff --git a/src/Authenticators/LDAPChangePasswordHandler.php b/src/Authenticators/LDAPChangePasswordHandler.php index 7e23c9d..9f2e1bb 100644 --- a/src/Authenticators/LDAPChangePasswordHandler.php +++ b/src/Authenticators/LDAPChangePasswordHandler.php @@ -2,24 +2,11 @@ namespace SilverStripe\LDAP\Authenticators; -use Exception; -use Psr\Log\LoggerInterface; -use SilverStripe\Control\Director; -use SilverStripe\Control\HTTP; -use SilverStripe\Control\HTTPResponse; -use SilverStripe\Core\Injector\Injector; use SilverStripe\LDAP\Forms\LDAPChangePasswordForm; -use SilverStripe\LDAP\Services\LDAPService; -use SilverStripe\ORM\ValidationResult; -use SilverStripe\Security\Member; use SilverStripe\Security\MemberAuthenticator\ChangePasswordHandler; -use SilverStripe\Security\Security; class LDAPChangePasswordHandler extends ChangePasswordHandler { - /** - * @var array Allowed Actions - */ private static $allowed_actions = [ 'changepassword', 'changePasswordForm', @@ -28,137 +15,10 @@ class LDAPChangePasswordHandler extends ChangePasswordHandler /** * Factory method for the lost password form * - * @return LDAPChangePasswordForm Returns the lost password form + * @return LDAPChangePasswordForm */ public function changePasswordForm() { return LDAPChangePasswordForm::create($this, 'ChangePasswordForm'); } - - /** - * Change the password - * - * @param array $data The user submitted data - * @param LDAPChangePasswordForm $form - * @return HTTPResponse - */ - public function doChangePassword(array $data, $form) - { - /** - * @var LDAPService $service - */ - $service = Injector::inst()->get(LDAPService::class); - $member = Security::getCurrentUser(); - if ($member) { - try { - $userData = $service->getUserByGUID($member->GUID); - } catch (Exception $e) { - Injector::inst()->get(LoggerInterface::class)->error($e->getMessage()); - - $form->clearMessage(); - $form->sessionMessage( - _t( - __CLASS__ . '.NOUSER', - 'Your account hasn\'t been setup properly, please contact an administrator.' - ), - 'bad' - ); - return $form->getController()->redirect($form->getController()->Link('changepassword')); - } - $loginResult = $service->authenticate($userData['samaccountname'], $data['OldPassword']); - if (!$loginResult['success']) { - $form->clearMessage(); - $form->sessionMessage( - _t( - 'SilverStripe\\Security\\Member.ERRORPASSWORDNOTMATCH', - 'Your current password does not match, please try again' - ), - 'bad' - ); - // redirect back to the form, instead of using redirectBack() which could send the user elsewhere. - return $form->getController()->redirect($form->getController()->Link('changepassword')); - } - } - - if (!$member) { - if ($this->getRequest()->getSession()->get('AutoLoginHash')) { - $member = Member::member_from_autologinhash($this->getRequest()->getSession()->get('AutoLoginHash')); - } - - // The user is not logged in and no valid auto login hash is available - if (!$member) { - $this->getRequest()->getSession()->clear('AutoLoginHash'); - return $form->getController()->redirect($form->getController()->Link('login')); - } - } - - // Check the new password - if (empty($data['NewPassword1'])) { - $form->clearMessage(); - $form->sessionMessage( - _t( - 'SilverStripe\\Security\\Member.EMPTYNEWPASSWORD', - "The new password can't be empty, please try again" - ), - 'bad' - ); - - // redirect back to the form, instead of using redirectBack() which could send the user elsewhere. - return $form->getController()->redirect($form->getController()->Link('changepassword')); - } elseif ($data['NewPassword1'] == $data['NewPassword2']) { - // Providing OldPassword to perform password _change_ operation. This will respect the - // password history policy. Unfortunately we cannot support password history policy on password _reset_ - // at the moment, which means it will not be enforced on SilverStripe-driven email password reset. - $oldPassword = !empty($data['OldPassword']) ? $data['OldPassword']: null; - - /** @var ValidationResult $validationResult */ - $validationResult = $service->setPassword($member, $data['NewPassword1'], $oldPassword); - - // try to catch connection and other errors that the ldap service can through - if ($validationResult->isValid()) { - Security::setCurrentUser($member); - - $this->getRequest()->getSession()->clear('AutoLoginHash'); - - // Clear locked out status - $member->LockedOutUntil = null; - $member->FailedLoginCount = null; - $member->write(); - - if (!empty($this->getRequest()->requestVar('BackURL')) - // absolute redirection URLs may cause spoofing - && Director::is_site_url($this->getRequest()->requestVar('BackURL')) - ) { - $url = Director::absoluteURL($this->getRequest()->requestVar('BackURL')); - return $form->getController()->redirect($url); - } else { - // Redirect to default location - the login form saying "You are logged in as..." - $redirectURL = HTTP::setGetVar( - 'BackURL', - Director::absoluteBaseURL(), - $form->getController()->Link('login') - ); - return $form->getController()->redirect($redirectURL); - } - } else { - $form->clearMessage(); - $messages = implode('. ', array_column($validationResult->getMessages(), 'message')); - $form->sessionMessage($messages, 'bad'); - // redirect back to the form, instead of using redirectBack() which could send the user elsewhere. - return $form->getController()->redirect($form->getController()->Link('changepassword')); - } - } else { - $form->clearMessage(); - $form->sessionMessage( - _t( - 'SilverStripe\\Security\\Member.ERRORNEWPASSWORD', - 'You have entered your new password differently, try again' - ), - 'bad' - ); - - // redirect back to the form, instead of using redirectBack() which could send the user elsewhere. - return $form->getController()->redirect($form->getController()->Link('changepassword')); - } - } } diff --git a/src/Authenticators/LDAPLostPasswordHandler.php b/src/Authenticators/LDAPLostPasswordHandler.php index 748e1ab..4fa87f0 100644 --- a/src/Authenticators/LDAPLostPasswordHandler.php +++ b/src/Authenticators/LDAPLostPasswordHandler.php @@ -21,6 +21,8 @@ class LDAPLostPasswordHandler extends LostPasswordHandler { + protected $authenticatorClass = LDAPAuthenticator::class; + /** * Since the logout and dologin actions may be conditionally removed, it's necessary to ensure these * remain valid actions regardless of the member login state. @@ -34,6 +36,22 @@ class LDAPLostPasswordHandler extends LostPasswordHandler 'passwordsent', ]; + private static $dependencies = [ + 'service' => '%$' . LDAPService::class, + ]; + + /** + * @var LDAPService + */ + protected $service; + + /** + * LDAP data for the provided member - is loaded by validateForgotPasswordData + * + * @var array + */ + protected $ldapUserData = []; + /** * @param string $link The URL to recreate this request handler @@ -46,29 +64,36 @@ public function __construct($link, LDAPAuthenticator $authenticator) parent::__construct($link); } - /** - * Forgot password form handler method. - * - * Called when the user clicks on "I've lost my password". - * - * Extensions can use the 'forgotPassword' method to veto executing - * the logic, by returning FALSE. In this case, the user will be redirected back - * to the form without further action. It is recommended to set a message - * in the form detailing why the action was denied. - * - * @param array $data Submitted data - * @param LostPasswordForm $form - * @return HTTPResponse - */ - public function forgotPassword($data, $form) + protected function validateForgotPasswordData(array $data, LostPasswordForm $form) { - /** @var Controller $controller */ - $controller = $form->getController(); + Config::modify()->set(Member::class, 'unique_identifier_field', 'Login'); // No need to protect against injections, LDAPService will ensure that this is safe - $login = trim($data['Login']); + $login = isset($data['Login']) ? trim($data['Login']) : ''; - $service = Injector::inst()->get(LDAPService::class); + // Ensure something was provided + if (empty($login)) { + if (Config::inst()->get(LDAPAuthenticator::class, 'allow_email_login') === 'yes') { + $form->sessionMessage( + _t( + 'SilverStripe\\LDAP\\Forms\\LDAPLoginForm.ENTERUSERNAMEOREMAIL', + 'Please enter your username or your email address to get a password reset link.' + ), + 'bad' + ); + } else { + $form->sessionMessage( + _t( + 'SilverStripe\\LDAP\\Forms\\LDAPLoginForm.ENTERUSERNAME', + 'Please enter your username to get a password reset link.' + ), + 'bad' + ); + } + return $this->redirectBack(); + } + + // Look up the user and store it if (Email::is_valid_address($login)) { if (Config::inst()->get(LDAPAuthenticator::class, 'allow_email_login') != 'yes') { $form->sessionMessage( @@ -78,77 +103,29 @@ public function forgotPassword($data, $form) ), 'bad' ); - return $controller->redirect($controller->Link('lostpassword')); + return $this->redirect($this->Link('lostpassword')); } - $userData = $service->getUserByEmail($login); + $this->ldapUserData = $this->getService()->getUserByEmail($login); } else { - $userData = $service->getUserByUsername($login); - } - // Avoid information disclosure by displaying the same status, - // regardless whether the email address actually exists - if (!isset($userData['objectguid'])) { - return $controller->redirect($controller->Link('passwordsent/') - . urlencode($data['Login'])); + $this->ldapUserData = $this->getService()->getUserByUsername($login); } + } + + protected function getMemberFromData(array $data, $uniqueIdentifier) + { + $member = Member::get()->filter('GUID', $this->ldapUserData['objectguid'])->limit(1)->first(); - $member = Member::get()->filter('GUID', $userData['objectguid'])->limit(1)->first(); // User haven't been imported yet so do that now - if (!($member && $member->exists())) { - $member = new Member(); - $member->GUID = $userData['objectguid']; + if (!$member || !$member->exists()) { + $member = Member::create(); + $member->GUID = $this->ldapUserData['objectguid']; } // Update the users from LDAP so we are sure that the email is correct. // This will also write the Member record. - $service->updateMemberFromLDAP($member, $userData, false); + $this->getService()->updateMemberFromLDAP($member, $this->ldapUserData, false); - // Allow vetoing forgot password requests - $results = $this->extend('forgotPassword', $member); - if ($results && is_array($results) && in_array(false, $results, true)) { - return $controller->redirect('lostpassword'); - } - - if ($member) { - /** @see MemberLoginForm::forgotPassword */ - $token = $member->generateAutologinTokenAndStoreHash(); - $e = Email::create() - ->setSubject( - _t( - 'Silverstripe\\Security\\Member.SUBJECTPASSWORDRESET', - 'Your password reset link', - 'Email subject' - ) - ) - ->setHTMLTemplate('SilverStripe\\Control\\Email\\ForgotPasswordEmail') - ->setData($member) - ->setData(['PasswordResetLink' => Security::getPasswordResetLink($member, $token)]); - $e->setTo($member->Email); - $e->send(); - return $controller->redirect($controller->Link('passwordsent/') . urlencode($data['Login'])); - } elseif ($data['Login']) { - // Avoid information disclosure by displaying the same status, - // regardless whether the email address actually exists - return $controller->redirect($controller->Link('passwordsent/') . urlencode($data['Login'])); - } else { - if (Config::inst()->get(LDAPAuthenticator::class, 'allow_email_login') === 'yes') { - $form->sessionMessage( - _t( - 'SilverStripe\\LDAP\\Forms\\LDAPLoginForm.ENTERUSERNAMEOREMAIL', - 'Please enter your username or your email address to get a password reset link.' - ), - 'bad' - ); - } else { - $form->sessionMessage( - _t( - 'SilverStripe\\LDAP\\Forms\\LDAPLoginForm.ENTERUSERNAME', - 'Please enter your username to get a password reset link.' - ), - 'bad' - ); - } - return $controller->redirect($controller->Link('lostpassword')); - } + return $member; } /** @@ -203,7 +180,7 @@ public function passwordsent() $username = Convert::raw2xml( rawurldecode($this->getRequest()->param('OtherID')) ); - $username .= ($extension = $this->request->getExtension()) ? '.' . $extension : ''; + $username .= ($extension = $this->getRequest()->getExtension()) ? '.' . $extension : ''; return [ 'Title' => _t( @@ -220,4 +197,26 @@ public function passwordsent() 'Username' => $username ]; } + + /** + * Get the LDAP service + * + * @return LDAPService + */ + public function getService() + { + return $this->service; + } + + /** + * Set the LDAP service + * + * @param LDAPService $service + * @return $this + */ + public function setService(LDAPService $service) + { + $this->service = $service; + return $this; + } } diff --git a/src/Authenticators/LDAPMemberLoginHandler.php b/src/Authenticators/LDAPMemberLoginHandler.php deleted file mode 100644 index 59d22fe..0000000 --- a/src/Authenticators/LDAPMemberLoginHandler.php +++ /dev/null @@ -1,13 +0,0 @@ -owner->LDAPGroupMappings() ); $config = GridFieldConfig_RecordEditor::create(); - $config->getComponentByType('GridFieldAddNewButton') + $config->getComponentByType(GridFieldAddNewButton::class) ->setButtonName(_t(__CLASS__ . '.ADDMAPPEDGROUP', 'Add LDAP group mapping')); $field->setConfig($config); diff --git a/src/Extensions/LDAPMemberExtension.php b/src/Extensions/LDAPMemberExtension.php index 92e24b7..7068073 100644 --- a/src/Extensions/LDAPMemberExtension.php +++ b/src/Extensions/LDAPMemberExtension.php @@ -11,6 +11,7 @@ use SilverStripe\ORM\DataExtension; use SilverStripe\ORM\ValidationResult; use SilverStripe\ORM\ValidationException; +use SilverStripe\Security\Member; /** * Class LDAPMemberExtension. @@ -274,4 +275,21 @@ public function memberLoggedIn() ->updateMemberFromLDAP($this->owner); } } + + /** + * Synchronise password changes to AD when they happen in SilverStripe + * + * @param string $newPassword + * @param ValidationResult $validation + */ + public function onBeforeChangePassword($newPassword, $validation) + { + // Don't do anything if there's already a validation failure + if (!$validation->isValid()) { + return; + } + + Injector::inst()->get(LDAPService::class) + ->setPassword($this->owner, $newPassword); + } } diff --git a/src/Forms/LDAPLoginForm.php b/src/Forms/LDAPLoginForm.php index afb1f0a..aaf17ce 100644 --- a/src/Forms/LDAPLoginForm.php +++ b/src/Forms/LDAPLoginForm.php @@ -67,8 +67,7 @@ public function __construct(RequestHandler $controller, $authenticatorClass, $na // Users can't change passwords unless appropriate a LDAP user with write permissions is // configured the LDAP connection binding $this->Actions()->remove($this->Actions()->fieldByName('forgotPassword')); - $allowPasswordChange = Config::inst() - ->get(LDAPService::class, 'allow_password_change'); + $allowPasswordChange = Config::inst()->get(LDAPService::class, 'allow_password_change'); if ($allowPasswordChange && $name != 'LostPasswordForm' && !Security::getCurrentUser()) { $forgotPasswordLink = sprintf( '

%s

', diff --git a/src/Model/LDAPGroupMapping.php b/src/Model/LDAPGroupMapping.php index 9836ddb..60ab3be 100644 --- a/src/Model/LDAPGroupMapping.php +++ b/src/Model/LDAPGroupMapping.php @@ -66,6 +66,7 @@ public function getCMSFields() $field = DropdownField::create('DN', _t(__CLASS__ . '.LDAPGROUP', 'LDAP Group')); $field->setEmptyString(_t(__CLASS__ . '.SELECTONE', 'Select one')); $groups = $this->ldapService->getGroups(true, ['dn', 'name']); + $source = []; if ($groups) { foreach ($groups as $dn => $record) { $source[$dn] = sprintf('%s (%s)', $record['name'], $dn); diff --git a/src/Services/LDAPService.php b/src/Services/LDAPService.php index 1a0c60b..63c6f53 100644 --- a/src/Services/LDAPService.php +++ b/src/Services/LDAPService.php @@ -591,6 +591,11 @@ public function updateMemberFromLDAP(Member $member, $data = null, $updateGroups ) ); } else { + // The member must exist before we can use it as a relation: + if (!$member->exists()) { + $member->write(); + } + $group->Members()->add($member, [ 'IsImportedFromLDAP' => '1' ]); diff --git a/src/Tasks/LDAPGroupSyncTask.php b/src/Tasks/LDAPGroupSyncTask.php index d88cbf4..9f8e9d5 100644 --- a/src/Tasks/LDAPGroupSyncTask.php +++ b/src/Tasks/LDAPGroupSyncTask.php @@ -12,9 +12,7 @@ /** * Class LDAPGroupSyncTask * - * A task to sync all groups to the site using LDAP. - * - * @package activedirectory + * A task to sync all groups from a specific DN in LDAP to the SilverStripe site in Group models */ class LDAPGroupSyncTask extends BuildTask { @@ -45,7 +43,7 @@ class LDAPGroupSyncTask extends BuildTask */ public function getTitle() { - return _t(__CLASS__ . '.SYNCTITLE', 'Sync all groups from Active Directory'); + return _t(__CLASS__ . '.SYNCTITLE', 'Sync all groups from LDAP'); } /** diff --git a/src/Tasks/LDAPMemberSyncTask.php b/src/Tasks/LDAPMemberSyncTask.php index 4039686..2312646 100644 --- a/src/Tasks/LDAPMemberSyncTask.php +++ b/src/Tasks/LDAPMemberSyncTask.php @@ -14,7 +14,7 @@ /** * Class LDAPMemberSyncTask * - * A task to sync all users to the site using LDAP. + * A task to sync all users from a specific DN in LDAP to the SilverStripe site, stored in Member objects */ class LDAPMemberSyncTask extends BuildTask { @@ -59,7 +59,7 @@ public function run($request) // especially in the case where getUser() would return a lot of users $users = $this->ldapService->getUsers(array_merge( ['objectguid', 'samaccountname', 'useraccountcontrol', 'memberof'], - array_keys(Config::inst()->get('SilverStripe\\Security\\Member', 'ldap_field_mappings')) + array_keys(Config::inst()->get(Member::class, 'ldap_field_mappings')) )); $start = time(); @@ -73,7 +73,7 @@ public function run($request) if (!($member && $member->exists())) { // create the initial Member with some internal fields - $member = new Member(); + $member = Member::create(); $member->GUID = $data['objectguid']; $this->log(sprintf( diff --git a/src/Tasks/LDAPMigrateExistingMembersTask.php b/src/Tasks/LDAPMigrateExistingMembersTask.php index e62f2ee..f6773f4 100644 --- a/src/Tasks/LDAPMigrateExistingMembersTask.php +++ b/src/Tasks/LDAPMigrateExistingMembersTask.php @@ -12,8 +12,8 @@ /** * Class LDAPMigrateExistingMembersTask * - * Migrate existing Member records in SilverStripe into "LDAP Members" - * by matching existing emails with ones that exist in AD. + * Migrate existing Member records in SilverStripe into "LDAP Members" by matching existing emails + * with ones that exist in a LDAP database for a given DN. */ class LDAPMigrateExistingMembersTask extends BuildTask { @@ -30,6 +30,14 @@ class LDAPMigrateExistingMembersTask extends BuildTask 'ldapService' => '%$' . LDAPService::class, ]; + /** + * @return string + */ + public function getTitle() + { + return _t(__CLASS__ . '.TITLE', 'Migrate existing members in SilverStripe into LDAP members'); + } + /** * {@inheritDoc} * @param HTTPRequest $request diff --git a/tests/php/LDAPAuthenticatorTest.php b/tests/php/Authenticators/LDAPAuthenticatorTest.php similarity index 88% rename from tests/php/LDAPAuthenticatorTest.php rename to tests/php/Authenticators/LDAPAuthenticatorTest.php index 0af9e90..0b41c18 100644 --- a/tests/php/LDAPAuthenticatorTest.php +++ b/tests/php/Authenticators/LDAPAuthenticatorTest.php @@ -2,16 +2,19 @@ namespace SilverStripe\LDAP\Tests\Authenticators; -use SilverStripe\LDAP\Authenticators\LDAPAuthenticator; -use SilverStripe\LDAP\Tests\FakeGatewayTest; use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\Session; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Injector\Injector; +use SilverStripe\Dev\SapphireTest; +use SilverStripe\LDAP\Authenticators\LDAPAuthenticator; +use SilverStripe\LDAP\Model\LDAPGateway; +use SilverStripe\LDAP\Services\LDAPService; +use SilverStripe\LDAP\Tests\Model\LDAPFakeGateway; use SilverStripe\ORM\ValidationResult; use SilverStripe\Security\Member; -class LDAPAuthenticatorTest extends FakeGatewayTest +class LDAPAuthenticatorTest extends SapphireTest { /** * @var LDAPAuthenticator @@ -38,8 +41,19 @@ class LDAPAuthenticatorTest extends FakeGatewayTest protected function setUp() { parent::setUp(); + + $gateway = new LDAPFakeGateway(); + Injector::inst()->registerService($gateway, LDAPGateway::class); + + $service = Injector::inst()->get(LDAPService::class); + $service->setGateway($gateway); + + $this->service = $service; + $this->authenticator = Injector::inst()->create(LDAPAuthenticator::class); + Config::modify()->set(LDAPAuthenticator::class, 'allow_email_login', 'yes'); + $this->request = new HTTPRequest('get', '/'); $this->request->setSession(new Session([])); $this->result = new ValidationResult(); diff --git a/tests/php/LDAPAuthenticatorTest.yml b/tests/php/Authenticators/LDAPAuthenticatorTest.yml similarity index 100% rename from tests/php/LDAPAuthenticatorTest.yml rename to tests/php/Authenticators/LDAPAuthenticatorTest.yml diff --git a/tests/php/Authenticators/LDAPChangePasswordHandlerTest.php b/tests/php/Authenticators/LDAPChangePasswordHandlerTest.php new file mode 100644 index 0000000..3e68112 --- /dev/null +++ b/tests/php/Authenticators/LDAPChangePasswordHandlerTest.php @@ -0,0 +1,27 @@ +logOut(); + + $request = (new HTTPRequest('GET', '/')) + ->setSession(new Session([])); + + $handler = LDAPChangePasswordHandler::create('foo', new LDAPAuthenticator) + ->setRequest($request); + + $this->assertInstanceOf(LDAPChangePasswordForm::class, $handler->changePasswordForm()); + } +} diff --git a/tests/php/Authenticators/LDAPLoginHandlerTest.php b/tests/php/Authenticators/LDAPLoginHandlerTest.php new file mode 100644 index 0000000..0f20e3c --- /dev/null +++ b/tests/php/Authenticators/LDAPLoginHandlerTest.php @@ -0,0 +1,18 @@ +assertInstanceOf(LDAPLoginForm::class, $handler->loginForm()); + } +} diff --git a/tests/php/Authenticators/LDAPLostPasswordHandlerTest.php b/tests/php/Authenticators/LDAPLostPasswordHandlerTest.php new file mode 100644 index 0000000..765ea90 --- /dev/null +++ b/tests/php/Authenticators/LDAPLostPasswordHandlerTest.php @@ -0,0 +1,67 @@ +handler = LDAPLostPasswordHandler::create('foo', $authenticator); + } + + public function testGetAndSetLdapService() + { + $service = new LDAPService; + + $this->handler->setService($service); + $this->assertSame($service, $this->handler->getService()); + } + + public function testLostPasswordFormLabels() + { + Config::modify()->set(LDAPAuthenticator::class, 'allow_email_login', 'no'); + $result = $this->handler->lostpassword(); + + $this->assertInternalType('array', $result); + $this->assertInstanceOf(DBField::class, $result['Content']); + $this->assertInstanceOf(Form::class, $result['Form']); + $this->assertContains('Enter your username and we will send you a link', $result['Content']->getValue()); + + Config::modify()->set(LDAPAuthenticator::class, 'allow_email_login', 'yes'); + $result = $this->handler->lostpassword(); + $this->assertInternalType('array', $result); + $this->assertContains('Enter your username or your email address', $result['Content']->getValue()); + } + + public function testPasswordSentMessage() + { + $request = (new HTTPRequest('GET', '/'))->setRouteParams(['OtherID' => 'banana']); + + $this->handler->setRequest($request); + + $result = $this->handler->passwordsent(); + + $this->assertSame([ + 'Title' => "Password reset link sent to 'banana'", + 'Content' => "Thank you! A reset link has been sent to 'banana', provided an account exists.", + 'Username' => 'banana' + ], $this->handler->passwordsent()); + } +} diff --git a/tests/php/FakeGatewayTest.php b/tests/php/FakeGatewayTest.php deleted file mode 100644 index 3004f86..0000000 --- a/tests/php/FakeGatewayTest.php +++ /dev/null @@ -1,30 +0,0 @@ -registerService($gateway, LDAPGateway::class); - - $service = Injector::inst()->get(LDAPService::class); - $service->setGateway($gateway); - - $this->service = $service; - } -} diff --git a/tests/php/LDAPServiceTest.php b/tests/php/Services/LDAPServiceTest.php similarity index 88% rename from tests/php/LDAPServiceTest.php rename to tests/php/Services/LDAPServiceTest.php index 55f16e8..56470b1 100644 --- a/tests/php/LDAPServiceTest.php +++ b/tests/php/Services/LDAPServiceTest.php @@ -2,30 +2,33 @@ namespace SilverStripe\LDAP\Tests\Services; +use SilverStripe\Core\Config\Config; +use SilverStripe\Core\Injector\Injector; +use SilverStripe\Dev\SapphireTest; use SilverStripe\LDAP\Extensions\LDAPGroupExtension; use SilverStripe\LDAP\Extensions\LDAPMemberExtension; use SilverStripe\LDAP\Model\LDAPGateway; use SilverStripe\LDAP\Services\LDAPService; -use SilverStripe\LDAP\Tests\FakeGatewayTest; -use SilverStripe\Core\Config\Config; +use SilverStripe\LDAP\Tests\Model\LDAPFakeGateway; use SilverStripe\Security\Group; use SilverStripe\Security\Member; -class LDAPServiceTest extends FakeGatewayTest +class LDAPServiceTest extends SapphireTest { - /** - * {@inheritDoc} - * @var bool - */ protected $usesDatabase = true; - /** - * {@inheritDoc} - */ - public function setUp() + protected function setUp() { parent::setUp(); + $gateway = new LDAPFakeGateway(); + Injector::inst()->registerService($gateway, LDAPGateway::class); + + $service = Injector::inst()->get(LDAPService::class); + $service->setGateway($gateway); + + $this->service = $service; + Config::modify()->set(LDAPGateway::class, 'options', ['host' => '1.2.3.4']); Config::modify()->set(LDAPService::class, 'groups_search_locations', [ 'CN=Users,DC=playpen,DC=local',