Use Config for authenticator settings

This commit is contained in:
Simon Erkelens 2017-04-17 15:07:28 +12:00 committed by Sam Minnée
parent c21f71405f
commit ff3ad6eb6b
7 changed files with 107 additions and 138 deletions

View File

@ -1015,6 +1015,23 @@ to update those with the appropriate function or config call. See
[CMS architecture](/developer_guides/customising_the_admin_interface/cms-architecture#the-admin-url) for language [CMS architecture](/developer_guides/customising_the_admin_interface/cms-architecture#the-admin-url) for language
specific functions. specific functions.
#### Upgrading custom Authenticators
The methods `register` and `unregister` on `Authenticator` are deprecated in favor of the `Config` system. This means that any custom Authenticator needs to be registered through the yml config:
```yaml
SilverStripe\Security\Authenticator;
authenticators:
- MyVendor\MyModule\MyAuthenticator
```
If there is no authenticator registered, `Authenticator` will try to fall back on the `default_authenticator`, which can be changed using the following config, replacing the MemberAuthenticator with your authenticator:
```yaml
SilverStripe\Security\Authenticator:
default_authenticator: SilverStripe\Security\MemberAuthenticator
```
As soon as a custom authenticator is registered, the default authenticator will not be available anymore, unless enabled specifically in the config.
By default, the `SilverStripe\Security\MemberAuthenticator` is seen as the default authenticator until it's explicitly set in the config.
#### Upgrading Config API usages #### Upgrading Config API usages
@ -1784,3 +1801,4 @@ New `TimeField` methods replace `getConfig()` / `setConfig()`
you will need to define this method and return a short name describing the login method. you will need to define this method and return a short name describing the login method.
* `MemberLoginForm` has a new constructor argument for the authenticator class, athough tis is usually * `MemberLoginForm` has a new constructor argument for the authenticator class, athough tis is usually
constructed by `MemberAuthenticator` and won't affect normal use. constructed by `MemberAuthenticator` and won't affect normal use.
* `Authenticator` methods `register` and `unregister` are deprecated in favor of using `Config`

View File

@ -4,6 +4,7 @@ namespace SilverStripe\Security;
use SilverStripe\Core\Object; use SilverStripe\Core\Object;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\Dev\Deprecation;
use SilverStripe\Forms\Form; use SilverStripe\Forms\Form;
/** /**
@ -22,7 +23,7 @@ abstract class Authenticator extends Object
* *
* @var array * @var array
*/ */
private static $authenticators = array(MemberAuthenticator::class); private static $authenticators = [];
/** /**
* Used to influence the order of authenticators on the login-screen * Used to influence the order of authenticators on the login-screen
@ -78,68 +79,6 @@ abstract class Authenticator extends Object
return false; return false;
} }
public static function register($authenticator)
{
self::register_authenticator($authenticator);
}
/**
* Register a new authenticator
*
* The new authenticator has to exist and to be derived from the
* {@link Authenticator}.
* Every authenticator can be registered only once.
*
* @param string $authenticator Name of the authenticator class to
* register
* @return bool Returns TRUE on success, FALSE otherwise.
*/
public static function register_authenticator($authenticator)
{
$authenticator = trim($authenticator);
if (class_exists($authenticator) == false) {
return false;
}
if (is_subclass_of($authenticator, self::class) == false) {
return false;
}
if (in_array($authenticator, self::$authenticators) == false) {
if (call_user_func(array($authenticator, 'on_register')) === true) {
array_push(self::$authenticators, $authenticator);
} else {
return false;
}
}
return true;
}
public static function unregister($authenticator)
{
self::unregister_authenticator($authenticator);
}
/**
* Remove a previously registered authenticator
*
* @param string $authenticator Name of the authenticator class to register
* @return bool Returns TRUE on success, FALSE otherwise.
*/
public static function unregister_authenticator($authenticator)
{
if (call_user_func(array($authenticator, 'on_unregister')) === true) {
if (in_array($authenticator, self::$authenticators)) {
unset(self::$authenticators[array_search($authenticator, self::$authenticators)]);
}
}
}
/** /**
* Check if a given authenticator is registered * Check if a given authenticator is registered
* *
@ -149,7 +88,12 @@ abstract class Authenticator extends Object
*/ */
public static function is_registered($authenticator) public static function is_registered($authenticator)
{ {
return in_array($authenticator, self::$authenticators); $authenticators = self::config()->get('authenticators');
if (count($authenticators) === 0) {
$authenticators = [self::config()->get('default_authenticator')];
}
return in_array($authenticator, $authenticators, true);
} }
@ -161,23 +105,20 @@ abstract class Authenticator extends Object
*/ */
public static function get_authenticators() public static function get_authenticators()
{ {
$authenticators = self::config()->get('authenticators');
$default = self::config()->get('default_authenticator');
if (count($authenticators) === 0) {
$authenticators = [$default];
}
// put default authenticator first (mainly for tab-order on loginform) // put default authenticator first (mainly for tab-order on loginform)
if ($key = array_search(self::$default_authenticator, self::$authenticators)) { // But only if there's no other authenticator
unset(self::$authenticators[$key]); if (($key = array_search($default, $authenticators, true)) && count($authenticators) > 1) {
array_unshift(self::$authenticators, self::$default_authenticator); unset($authenticators[$key]);
array_unshift($authenticators, $default);
} }
return self::$authenticators; return $authenticators;
}
/**
* Set a default authenticator (shows first in tabs)
*
* @param string
*/
public static function set_default_authenticator($authenticator)
{
self::$default_authenticator = $authenticator;
} }
/** /**
@ -185,33 +126,6 @@ abstract class Authenticator extends Object
*/ */
public static function get_default_authenticator() public static function get_default_authenticator()
{ {
return self::$default_authenticator; return self::config()->get('default_authenticator');
}
/**
* Callback function that is called when the authenticator is registered
*
* Use this method for initialization of a newly registered authenticator.
* Just overload this method and it will be called when the authenticator
* is registered.
* <b>If the method returns FALSE, the authenticator won't be
* registered!</b>
*
* @return bool Returns TRUE on success, FALSE otherwise.
*/
protected static function on_register()
{
return true;
}
/**
* Callback function that is called when an authenticator is removed.
*
* @return bool
*/
protected static function on_unregister()
{
return true;
} }
} }

View File

@ -2,16 +2,13 @@
namespace SilverStripe\Security; namespace SilverStripe\Security;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Convert;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\Control\Session;
use SilverStripe\Forms\HiddenField;
use SilverStripe\Forms\PasswordField;
use SilverStripe\Forms\LiteralField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\CheckboxField; use SilverStripe\Forms\CheckboxField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\FormAction; use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\HiddenField;
use SilverStripe\Forms\LiteralField;
use SilverStripe\Forms\PasswordField;
/** /**
* Provides the in-cms session re-authentication form for the "member" authenticator * Provides the in-cms session re-authentication form for the "member" authenticator
@ -29,12 +26,34 @@ class CMSMemberLoginForm extends LoginForm
return Security::singleton()->Link($action); return Security::singleton()->Link($action);
} }
public function __construct(Controller $controller, $name) /**
* CMSMemberLoginForm constructor.
* @param Controller $controller
* @param string $authenticatorClass
* @param FieldList $name
*/
public function __construct(Controller $controller, $authenticatorClass, $name)
{
$this->controller = $controller;
$this->authenticator_class = $authenticatorClass;
$fields = $this->getFormFields();
$actions = $this->getFormActions();
parent::__construct($controller, $name, $fields, $actions);
}
/**
* @return FieldList
*/
public function getFormFields()
{ {
// Set default fields // Set default fields
$fields = new FieldList( $fields = new FieldList(
HiddenField::create("AuthenticationMethod", null, $this->authenticator_class, $this), HiddenField::create("AuthenticationMethod", null, $this->authenticator_class, $this),
HiddenField::create('tempid', null, $controller->getRequest()->requestVar('tempid')), HiddenField::create('tempid', null, $this->controller->getRequest()->requestVar('tempid')),
PasswordField::create("Password", _t('Member.PASSWORD', 'Password')), PasswordField::create("Password", _t('Member.PASSWORD', 'Password')),
LiteralField::create( LiteralField::create(
'forgotPassword', 'forgotPassword',
@ -53,9 +72,18 @@ class CMSMemberLoginForm extends LoginForm
)); ));
} }
return $fields;
}
/**
* @return FieldList
*/
public function getFormActions()
{
// Determine returnurl to redirect to parent page // Determine returnurl to redirect to parent page
$logoutLink = $this->getExternalLink('logout'); $logoutLink = $this->getExternalLink('logout');
if ($returnURL = $controller->getRequest()->requestVar('BackURL')) { if ($returnURL = $this->controller->getRequest()->requestVar('BackURL')) {
$logoutLink = Controller::join_links($logoutLink, '?BackURL=' . urlencode($returnURL)); $logoutLink = Controller::join_links($logoutLink, '?BackURL=' . urlencode($returnURL));
} }
@ -72,7 +100,7 @@ class CMSMemberLoginForm extends LoginForm
) )
); );
parent::__construct($controller, $name, $fields, $actions); return $actions;
} }
protected function buildRequestHandler() protected function buildRequestHandler()

View File

@ -3,6 +3,7 @@
namespace SilverStripe\Security; namespace SilverStripe\Security;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form; use SilverStripe\Forms\Form;
/** /**
@ -32,4 +33,18 @@ abstract class LoginForm extends Form
* @return string * @return string
*/ */
abstract public function getAuthenticatorName(); abstract public function getAuthenticatorName();
/**
* Required FieldList creation on a LoginForm
*
* @return FieldList
*/
abstract protected function getFormFields();
/**
* Required FieldList creation for the login actions on this LoginForm
*
* @return FieldList
*/
abstract protected function getFormActions();
} }

View File

@ -208,7 +208,7 @@ class MemberAuthenticator extends Authenticator
public static function get_cms_login_form(Controller $controller) public static function get_cms_login_form(Controller $controller)
{ {
/** @skipUpgrade */ /** @skipUpgrade */
return CMSMemberLoginForm::create($controller, "LoginForm"); return CMSMemberLoginForm::create($controller, self::class, "LoginForm");
} }
public static function supports_cms() public static function supports_cms()

View File

@ -369,7 +369,7 @@ class Security extends Controller implements TemplateGlobalProvider
$authenticator = $this->getRequest()->requestVar('AuthenticationMethod'); $authenticator = $this->getRequest()->requestVar('AuthenticationMethod');
if ($authenticator && Authenticator::is_registered($authenticator)) { if ($authenticator && Authenticator::is_registered($authenticator)) {
return $authenticator; return $authenticator;
} elseif ($authenticator !== "" && Authenticator::is_registered(Authenticator::get_default_authenticator())) { } elseif ($authenticator !== '' && Authenticator::is_registered(Authenticator::get_default_authenticator())) {
return Authenticator::get_default_authenticator(); return Authenticator::get_default_authenticator();
} }

View File

@ -2,6 +2,7 @@
namespace SilverStripe\Security\Tests; namespace SilverStripe\Security\Tests;
use PhpConsole\Auth;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\FieldType\DBClassName; use SilverStripe\ORM\FieldType\DBClassName;
@ -50,12 +51,10 @@ class SecurityTest extends FunctionalTest
// This test assumes that MemberAuthenticator is present and the default // This test assumes that MemberAuthenticator is present and the default
$this->priorAuthenticators = Authenticator::get_authenticators(); $this->priorAuthenticators = Authenticator::get_authenticators();
$this->priorDefaultAuthenticator = Authenticator::get_default_authenticator(); $this->priorDefaultAuthenticator = Authenticator::get_default_authenticator();
foreach ($this->priorAuthenticators as $authenticator) {
Authenticator::unregister($authenticator);
}
Authenticator::register(MemberAuthenticator::class); // Set to an empty array of authenticators to enable the default
Authenticator::set_default_authenticator(MemberAuthenticator::class); Config::modify()->set(Authenticator::class, 'authenticators', []);
Config::modify()->set(Authenticator::class, 'default_authenticator', MemberAuthenticator::class);
// And that the unique identified field is 'Email' // And that the unique identified field is 'Email'
$this->priorUniqueIdentifierField = Member::config()->unique_identifier_field; $this->priorUniqueIdentifierField = Member::config()->unique_identifier_field;
@ -67,7 +66,7 @@ class SecurityTest extends FunctionalTest
parent::setUp(); parent::setUp();
Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', '/'); Config::modify()->merge('SilverStripe\\Control\\Director', 'alternate_base_url', '/');
} }
protected function tearDown() protected function tearDown()
@ -75,13 +74,8 @@ class SecurityTest extends FunctionalTest
// Restore selected authenticator // Restore selected authenticator
// MemberAuthenticator might not actually be present // MemberAuthenticator might not actually be present
if (!in_array(MemberAuthenticator::class, $this->priorAuthenticators)) { Config::modify()->set(Authenticator::class, 'authenticators', $this->priorAuthenticators);
Authenticator::unregister(MemberAuthenticator::class); Config::modify()->set(Authenticator::class, 'default_authenticator', $this->priorDefaultAuthenticator);
}
foreach ($this->priorAuthenticators as $authenticator) {
Authenticator::register($authenticator);
}
Authenticator::set_default_authenticator($this->priorDefaultAuthenticator);
// Restore unique identifier field // Restore unique identifier field
Member::config()->unique_identifier_field = $this->priorUniqueIdentifierField; Member::config()->unique_identifier_field = $this->priorUniqueIdentifierField;
@ -129,8 +123,8 @@ class SecurityTest extends FunctionalTest
'Default permission failure message value was not present' 'Default permission failure message value was not present'
); );
Config::inst()->remove(Security::class, 'default_message_set'); Config::modify()->remove(Security::class, 'default_message_set');
Config::inst()->update(Security::class, 'default_message_set', array('default' => 'arrayvalue')); Config::modify()->merge(Security::class, 'default_message_set', array('default' => 'arrayvalue'));
Security::permissionFailure($controller); Security::permissionFailure($controller);
$this->assertEquals( $this->assertEquals(
'arrayvalue', 'arrayvalue',