Authentication documentation rewrite

This commit is contained in:
Simon Erkelens 2017-07-02 15:27:17 +12:00
parent 2a0805dda7
commit 774d44a574
4 changed files with 161 additions and 80 deletions

View File

@ -8,17 +8,18 @@ The [Member](api:SilverStripe\Security\Member) class is used to represent user a
## Testing For Logged In Users
The [api:Security] class comes with a static method for getting information about the current logged in user.
**Security::getCurrentUser()**
Returns the full *Member* Object for the current user, returns *null* if user is not logged in.
Retrieves the current logged in member. Returns *null* if user is not logged in, otherwise, the Member object is returned.
```php
if( $member = Security::getCurrentUser() ) {
// Work with $member
} else {
// Do non-member stuff
}
if( $member = Security::getCurrentUser() ) {
// Work with $member
} else {
// Do non-member stuff
}
```
## Subclassing
@ -30,54 +31,48 @@ This is the least desirable way of extending the [Member](api:SilverStripe\Secur
You can define subclasses of [Member](api:SilverStripe\Security\Member) to add extra fields or functionality to the built-in membership system.
```php
use SilverStripe\Security\Member;
class MyMember extends Member
{
private static $db = [
"Age" => "Int",
"Address" => "Text",
];
}
class MyMember extends Member {
private static $db = array(
"Age" => "Int",
"Address" => "Text",
);
}
```
To ensure that all new members are created using this class, put it in `Injector`.
To ensure that all new members are created using this class, put a call to [api:Injector] in
`(project)/_config/_config.yml`:
```yaml
```yml
SilverStripe\Core\Injector\Injector:
SilverStripe\Security\Member:
class: My\Project\MyMember
SilverStripe\Security\Member:
class: MyVendor\MyNamespace\MyMemberClass
```
Note that if you want to look this class-name up, you can call `Injector::inst()->get('Member')->ClassName`
## Overloading getCMSFields()
If you overload the built-in public function getCMSFields(), then you can change the form that is used to view & edit member
details in the newsletter system. This function returns a [FieldList](api:SilverStripe\Forms\FieldList) object. You should generally start by calling
parent::getCMSFields() and manipulate the [FieldList](api:SilverStripe\Forms\FieldList) from there.
```php
public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->insertBefore("HTMLEmail", new TextField("Age"));
$fields->removeByName("JobTitle");
$fields->removeByName("Organisation");
return $fields;
}
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->insertBefore("HTMLEmail", new TextField("Age"));
$fields->removeByName("JobTitle");
$fields->removeByName("Organisation");
return $fields;
}
```
## Extending Member or DataObject?
Basic rule: Class [Member](api:SilverStripe\Security\Member) should just be extended for entities who have some kind of login.
If you have different types of [Member](api:SilverStripe\Security\Member)s in the system, you have to make sure that those with login-capabilities have
unique email-addresses (as this is used for login-credentials).
For persons without login-capabilities (e.g. for an address-database), you shouldn't extend [Member](api:SilverStripe\Security\Member) to avoid conflicts
with the Member-database. This enables us to have a different subclass of [Member](api:SilverStripe\Security\Member) for an email-address with login-data,
Basic rule: Class [api:Member] should just be extended for entities who have some kind of login.
If you have different types of [api:Member]s in the system, you have to make sure that those with login-capabilities a unique field to be used for the login.
For persons without login-capabilities (e.g. for an address-database), you shouldn't extend [api:Member] to avoid conflicts
with the Member-database. This enables us to have a different subclass of [api:Member] for an email-address with login-data,
and another subclass for the same email-address in the address-database.
## Member Role Extension
@ -86,12 +81,10 @@ Using inheritance to add extra behaviour or data fields to a member is limiting,
class. A better way is to use role extensions to add this behaviour. Add the following to your
`[config.yml](/developer_guides/configuration/configuration/#configuration-yaml-syntax-and-rules)`.
```yml
Member:
extensions:
- MyMemberExtension
SilverStripe\Security\Member:
extensions:
- MyMemberExtension
```
A role extension is simply a subclass of [DataExtension](api:SilverStripe\ORM\DataExtension) that is designed to be used to add behaviour to [Member](api:SilverStripe\Security\Member).
@ -100,36 +93,36 @@ things, you should add appropriate [Permission::checkMember()](api:SilverStripe\
```php
use SilverStripe\Security\Permission;
use SilverStripe\ORM\DataExtension;
use SilverStripe\Security\Permission;
use SilverStripe\ORM\DataExtension;
class MyMemberExtension extends DataExtension
class MyMemberExtension extends DataExtension
{
/**
* Modify the field set to be displayed in the CMS detail pop-up
*/
public function updateCMSFields(FieldList $currentFields)
{
/**
* Modify the field set to be displayed in the CMS detail pop-up
*/
public function updateCMSFields(FieldList $currentFields)
{
// Only show the additional fields on an appropriate kind of use
if(Permission::checkMember($this->owner->ID, "VIEW_FORUM")) {
// Edit the FieldList passed, adding or removing fields as necessary
// Edit the FieldList passed, adding or removing fields as necessary
}
}
// define additional properties
private static $db = [];
private static $has_one = [];
private static $has_many = [];
private static $many_many = [];
private static $belongs_many_many = [];
public function somethingElse()
{
// You can add any other methods you like, which you can call directly on the member object.
}
}
// define additional properties
private static $db = [];
private static $has_one = [];
private static $has_many = [];
private static $many_many = [];
private static $belongs_many_many = [];
public function somethingElse()
{
// You can add any other methods you like, which you can call directly on the member object.
}
}
```
## Saved User Logins ##
@ -165,18 +158,19 @@ E.g.
use SilverStripe\Security\Member;
use SilverStripe\Dev\BuildTask;
class CleanRecordsTask extends BuildTask
class CleanRecordsTask extends BuildTask
{
public function run($request)
{
public function run($request)
{
if (!Director::is_cli()) {
throw new BadMethodCallException('This task only runs on CLI');
}
$admin = Security::findAnAdministrator();
Member::actAs($admin, function() {
DataRecord::get()->filter('Dirty', true)->removeAll();
});
if (!Director::is_cli()) {
throw new BadMethodCallException('This task only runs on CLI');
}
$admin = Security::findAnAdministrator();
Member::actAs($admin, function() {
DataRecord::get()->filter('Dirty', true)->removeAll();
});
}
}
```
## API Documentation

View File

@ -47,8 +47,8 @@ class PageController implements PermissionProvider
```
This can then be used to add a dropdown for permission codes to the security panel. Permission::get_all_codes() will be
a helper method that will call providePermissions() on every applicable class, and collate the resuls into a single
This can then be used to add a dropdown for permission codes to the security panel. `Permission::get_all_codes()` will be
a helper method that will call `providePermissions()` on every applicable class, and collate the resuls into a single
dropdown.
## Default use
@ -60,8 +60,6 @@ By default, permissions are used in the following way:
* If not logged in, the 'View' permissions must be 'anyone logged in' for a page to be displayed in a menu
* If logged in, you must be allowed to view a page for it to be displayed in a menu
**NOTE:** Should the canView() method on SiteTree be updated to call Permission::check("SITETREE_VIEW", $this->ID)?
Making this work well is a subtle business and should be discussed with a few developers.
## Setting up permissions

View File

@ -46,3 +46,86 @@ It is advisable to configure this user in your `.env` file inside of the web roo
When a user logs in with these credentials, then a [Member](api:SilverStripe\Security\Member) with the Email 'admin' will be generated in
the database, but without any password information. This means that the password can be reset or changed by simply
updating the `.env` file.
## Registering a new Authenticator
```yaml
SilverStripe\Core\Injector\Injector:
SilverStripe\Security\Security:
properties:
Authenticators:
myauthenticator: %$MyVendor\MyProject\Authenticator\MyAuthenticator
```
If there is no authenticator registered, `Authenticator` will try to fall back on the default provided authenticator (`default`), which can be changed using the following config, replacing the MemberAuthenticator with your authenticator:
```yaml
SilverStripe\Core\Injector\Injector:
SilverStripe\Security\Security:
properties:
Authenticators:
default: %$MyVendor\MyProject\Authenticator\MyAuthenticator
```
By default, the `SilverStripe\Security\MemberAuthenticator\MemberAuthenticator` is seen as the default authenticator until it's explicitly set in the config.
Every Authenticator is expected to handle services. The `Authenticator` Interface provides the available services:
```php
const LOGIN = 1;
const LOGOUT = 2;
const CHANGE_PASSWORD = 4;
const RESET_PASSWORD = 8;
const CMS_LOGIN = 16;
/**
* Returns the services supported by this authenticator
*
* The number should be a bitwise-OR of 1 or more of the following constants:
* Authenticator::LOGIN, Authenticator::LOGOUT, Authenticator::CHANGE_PASSWORD,
* Authenticator::RESET_PASSWORD, or Authenticator::CMS_LOGIN
*
* @return int
*/
public function supportedServices();
```
If there is no available authenticator for the required action (either one of the constants above), an error will be thrown.
Custom Authenticators are expected to have the following methods implemented:
* `getLoginHandler()`
* `getLogoutHandler()`
* `getChangePasswordHandler()`
* `getLostPasswordHandler()`
All expect a `$link` variable, to handle the request.
Further, there is
* `authenticate()`
Which expects the data to be used for authentication as an array and a nullable variable `$result` by reference, which returns a `ValidationResult`.
If only a subset of the supportedServices() will be provided by the custom Authenticator, it is advised to extend `SilverStripe\Security\MemberAuthenticator\MemberAuthenticator`, as that default contains all required methods already and only an override or follow up needs to be written.
An example of how to write a multi-factor authentication [can be found here](https://gist.github.com/sminnee/bc646147f3941a764d0410f2044433c7).
## IdentityStore
A new IdentityStore, e.g. an LDAP IdentityStore can be registered as follows in a `security.yml` file (Not an actual valid LDAP configuration):
```yaml
SilverStripe\Core\Injector\Injector:
MyProject\LDAP\Authenticator\LDAPAuthenticator:
properties:
LDAPSettings:
- URL: https://my-ldap-location.com
CascadeInTo: %$SilverStripe\Security\MemberAuthenticator\SessionAuthenticationHandler
SilverStripe\Security\AuthenticationHandler:
class: SilverStripe\Security\RequestAuthenticationHandler
properties:
Handlers:
ldap: %$MyProject\LDAP\Authenticator\LDAPAuthenticator
```
CascadeInTo is used to defer login or logout actions to other authenticators, after the first one has been logged in. In the example of LDAP authenticator, this is useful to check e.g. the validity of the Session (is the user still logged in?) and if not, or it's LDAP login period has expired, only then validate against the external service again, limiting the amount of requests to the external service.
Upon request, the Member is authenticated against the given AuthenticatorHandlers. To override an Authenticator, override it's name in the `YML` to your own Handler.
To get applicable Authenticators for a certain request, refer to [API:Security:getApplicableAuthenticators()].
To register `CMS` authenticators, use the same procedure as above, only replace `SilverStripe\Security\Security` with `SilverStripe\Security\CMSSecurity`.

View File

@ -63,6 +63,7 @@ guide developers in preparing existing 3.x code for compatibility with 4.0
* The `GDBackend` and `ImagickBackend` classes have been replaced by a unified `InterventionBackend` which uses the
[intervention/image](https://github.com/intervention/image) library to power manipualations.
* Dependencies can managed via [recipe-plugin](https://github.com/silverstripe/recipe-plugin). See [recipe-core](https://github.com/silverstripe/recipe-core) and [recipe-cms](https://github.com/silverstripe/recipe-cms) as examples.
* Authentication has been upgraded to a modular approach using re-usable interfaces and easier to hook in to LoginHandlers.
## <a name="upgrading"></a>Upgrading
@ -1408,7 +1409,7 @@ 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:
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. For further information on how to create a custom authentication method, [see the Authentication documentation](/developer_guides/Security/Authentication).
```yml
SilverStripe\Security\Authenticator;
@ -1427,6 +1428,11 @@ As soon as a custom authenticator is registered, the default authenticator will
By default, the `SilverStripe\Security\MemberAuthenticator` is seen as the default authenticator until it's explicitly set in the config.
##### IdentityStore and RequestFilters
As of SilverStripe 4, every request is authenticated against an `IdentityStore`, by default a CookieAuthenticationHandler and a SessionAuthenticationHandler, which are called from the `AuthenticationHandler`. If there is a valid `Member`, it is set on `Security::setCurrentUser()`, which defaults no `null`.
IdentityStores are responsible for logging members in and out (e.g. destroy cookies and sessions, or instantiate them).
#### Upgrading Config API usages
Performance optimisations have been made to Config which, under certain circumstances, require developer