Skip to content

Commit

Permalink
API Add change password functionality via LDAP server
Browse files Browse the repository at this point in the history
  • Loading branch information
robbieaverill committed Oct 4, 2017
1 parent 25c7e2d commit 3c7897b
Show file tree
Hide file tree
Showing 23 changed files with 307 additions and 302 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"autoload": {
"psr-4": {
"SilverStripe\\LDAP\\": "src/",
"SilverStripe\\LDAP\\Tests\\": "tests/"
"SilverStripe\\LDAP\\Tests\\": "tests/php/"
}
},
"minimum-stability": "dev",
Expand Down
22 changes: 16 additions & 6 deletions docs/en/developer.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<phpunit bootstrap="framework/tests/bootstrap.php" colors="true">
<phpunit bootstrap="vendor/silverstripe/framework/tests/bootstrap.php" colors="true">
<testsuite name="Default">
<directory>tests/</directory>
</testsuite>
Expand Down
24 changes: 22 additions & 2 deletions src/Authenticators/LDAPAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}
}
142 changes: 1 addition & 141 deletions src/Authenticators/LDAPChangePasswordHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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'));
}
}
}
Loading

0 comments on commit 3c7897b

Please sign in to comment.