Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Commit

Permalink
Merge branch 'feature/28-user-interface-and-factory'
Browse files Browse the repository at this point in the history
Close #28
Close #25
Fixes #24
  • Loading branch information
weierophinney committed May 23, 2018
2 parents 2fea552 + 84c1079 commit 486cb20
Show file tree
Hide file tree
Showing 24 changed files with 663 additions and 171 deletions.
53 changes: 53 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,59 @@

All notable changes to this project will be documented in this file, in reverse chronological order by release.

## 0.5.0 - 2018-05-23

### Added

- [#28](https://github.com/zendframework/zend-expressive-authentication/pull/28) adds the final class `DefaultUser`, which provides an immutable version of `UserInterface`
that can be used in most situations.

- [#28](https://github.com/zendframework/zend-expressive-authentication/pull/28) adds the service factory `DefaultUserFactory`, which returns a PHP `callable`
capable of producing a `DefaultUser` instance from the provided `$identity`,
`$roles`, and `$details` arguments.

### Changed

- [#28](https://github.com/zendframework/zend-expressive-authentication/pull/28) updates the `PdoDatabase` user repository to accept an additional
configuration item, `sql_get_details`. This value should be a SQL statement
that may be used to retrieve additional user details to provide in the
`UserInterface` instance returned by the repository on successful
authentication.

- [#28](https://github.com/zendframework/zend-expressive-authentication/pull/28) updates `UserRepositoryInterface` to remove the method `getRolesFromUser()`;
this method is not needed, as `UserInterface` already provides access to user roles.

- [#28](https://github.com/zendframework/zend-expressive-authentication/pull/28) modifies each of the `Htpasswd` and `PdoDatabase` user repository
implementations to accept a new constructor argument, a callable
`$userFactory`. This factory should implement the following signature:

```php
function (string $identity, array $roles = [], array $details = []) : UserInterface
```

This factory will be called by the repository in order to produce a
`UserInterface` instance on successful authentication. You may provide the
factory via the service `Zend\Expressive\Authentication\UserInterface` if you
wish to use one other than the one returned by the provided
`DefaultUserFactory` class.

- [#28](https://github.com/zendframework/zend-expressive-authentication/pull/28) modifies `UserInterface` as follows:
- Renames `getUserRoles()` to `getRoles()`
- Adds `getDetail(string $name, mixed $default)`
- Adds `getDetails() : array`

### Deprecated

- Nothing.

### Removed

- [#28](https://github.com/zendframework/zend-expressive-authentication/pull/28) removes `UserTrait` in favor of the `DefaultUser` implementation.

### Fixed

- Nothing.

## 0.4.0 - 2018-03-15

### Added
Expand Down
89 changes: 67 additions & 22 deletions docs/book/v1/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,26 @@ namespace Zend\Expressive\Authentication;
interface UserInterface
{
/**
* Get the unique user identity (id, username, email address or ...)
*
* @return string
* Get the unique user identity (id, username, email address, etc.).
*/
public function getIdentity(): string;
public function getIdentity() : string;

/**
* Get all user roles
* Get all user roles.
*
* @return string[]
*/
public function getUserRoles() : array;
public function getRoles() : array;

/**
* Get the detail named $name if present; return $default otherwise.
*/
public function getDetail(string $name, $default = null);

/**
* Get all additional user details, if any.
*/
public function getDetails() : array;
}
```

Expand All @@ -42,6 +50,48 @@ if a user has been authenticated or not, e.g. it can be used to verify the
authorization level of a user (for this scope, it is consumed by
[zend-expressive-authorization](https://github.com/zendframework/zend-expressive-authorization)).

## Default User class

We provide a default implementation of `UserInterface` via the class
`Zend\Expressive\Authentication\DefaultUser`. The class is final and immutable,
in order to prevent runtime changes.

Repositories will fetch user information based on the identity, including any
associated roles, and optionally any additional details (full name, email,
profile information, etc.). Often, user data and the objects representing them
are unique to the application. As such, the default repository implementations
we provide allow you to inject a _factory_ for producing the user. This factory
should be a PHP callable with the following signature:

```php
function (string $identity, array $roles = [], array $details = []) : UserInterface
```

In order to notify the package to use your custom factory, you will need to
create a service factory that returns it, and map it to the
`Zend\Expressive\Authentication\UserInterface` service.

We provide a service factory named `Zend\Expressive\Authentication\DefaultUserFactory`
that returns a user factory that produces a `DefaultUser` instance from the
arguments provided. This is mapped as follows in the service configuration:

```php
use Zend\Expressive\Authentication\DefaultUserFactory;
use Zend\Expressive\Authentication\UserInterface;

return [
// ...
'dependencies' => [
'factories' => [
// ...
// Change the DefaultUserFactory::class with your custom service
// factory that produces a user factory:
UserInterface::class => DefaultUserFactory::class
]
]
];
```

## Usage in the route

The `AuthenticationMiddleware` can be used to authenticate a route. You just
Expand Down Expand Up @@ -75,26 +125,21 @@ Basic Access Authentication* adapter and the *htpasswd* file as the user
repository.

```php
// ConfigProvider.php

use Zend\Expressive\Authentication\AuthenticationInterface;
use Zend\Expressive\Authentication\Basic;
use Zend\Expressive\Authentication\UserRepository;
use Zend\Expressive\Authentication\UserRepositoryInterface;

class ConfigProvider
{
return [
// ...

public function getDependencies() : array
{
return [
'aliases' => [
AuthenticationInterface::class => Basic\BasicAccess::class,
UserRepositoryInterface::class => UserRepository\Htpasswd::class
],
'dependencies' => [
// ...
'aliases' => [
// ...
];
}
AuthenticationInterface::class => Basic\BasicAccess::class,
UserRepositoryInterface::class => UserRepository\Htpasswd::class
]
]
];

// ...
}
```
32 changes: 15 additions & 17 deletions docs/book/v1/user-repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,12 @@ interface UserRepositoryInterface
* @param string $credential can be also a token
*/
public function authenticate(string $credential, string $password = null) : ?UserInterface;

/**
* Get the user roles if present.
*
* @param string $username
* @return string[]
*/
public function getRolesFromUser(string $username) : array;
}
```

It contains two functions: `authenticate()` and `getRolesFromUser()`. The first
is used to authenticate using the user's credential. If authenticated, the
result will be a `UserInterface` instance, otherwise a null value is returned.

The second function is `getRolesFromUser()` and it specifies how to retrieve
the roles for a user. If a user does not have roles, this function will return
an empty array.

It contains only the `authenticate()` function, to authenticate the user's
credential. If authenticated, the result will be a `UserInterface` instance;
otherwise, a `null` value is returned.

## Configure the user repository

Expand Down Expand Up @@ -93,7 +80,8 @@ return [
'identity' => 'identity field name',
'password' => 'password field name',
],
'sql_get_roles' => 'SQL to retrieve roles with :identity parameter',
'sql_get_roles' => 'SQL to retrieve roles with :identity parameter',
'sql_get_details' => 'SQL to retrieve user details by :identity',
],
],
];
Expand Down Expand Up @@ -121,3 +109,13 @@ typical query might look like the following:
```sql
SELECT role FROM user WHERE username = :identity
```

The `sql_get_details` parameter is similar to `sql_get_roles`: it specifies the
SQL query for retrieving the user's additional details, if any.

For instance, if a user has an email field this can be returned as additional
detail using the following query:

```sql
SELECT email FROM user WHERE username = :identity
```
13 changes: 8 additions & 5 deletions src/ConfigProvider.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php
/**
* @see https://github.com/zendframework/zend-expressive-authentication for the canonical source repository
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (https://www.zend.com)
* @copyright Copyright (c) 2017-2018 Zend Technologies USA Inc. (https://www.zend.com)
* @license https://github.com/zendframework/zend-expressive-authentication/blob/master/LICENSE.md New BSD License
*/

Expand All @@ -25,7 +25,8 @@ public function __invoke() : array
public function getAuthenticationConfig() : array
{
return [
/* Values will depend on user repository and/or adapter.
/*
* Values will depend on user repository and/or adapter.
*
* Example: using htpasswd UserRepositoryInterface implementation:
*
Expand All @@ -40,7 +41,8 @@ public function getAuthenticationConfig() : array
* 'identity' => 'identity field name',
* 'password' => 'password field name',
* ],
* 'sql_get_roles' => 'SQL to retrieve roles by :identity',
* 'sql_get_roles' => 'SQL to retrieve user roles by :identity',
* 'sql_get_details' => 'SQL to retrieve user details by :identity',
* ],
* ]
*/
Expand All @@ -62,8 +64,9 @@ public function getDependencies() : array
'factories' => [
AuthenticationMiddleware::class => AuthenticationMiddlewareFactory::class,
UserRepository\Htpasswd::class => UserRepository\HtpasswdFactory::class,
UserRepository\PdoDatabase::class => UserRepository\PdoDatabaseFactory::class
]
UserRepository\PdoDatabase::class => UserRepository\PdoDatabaseFactory::class,
UserInterface::class => DefaultUserFactory::class,
],
];
}
}
68 changes: 68 additions & 0 deletions src/DefaultUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php
/**
* @see https://github.com/zendframework/zend-expressive-authentication for the canonical source repository
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (https://www.zend.com)
* @license https://github.com/zendframework/zend-expressive-authentication/blob/master/LICENSE.md New BSD License
*/

declare(strict_types=1);

namespace Zend\Expressive\Authentication;

/**
* Default implementation of UserInterface.
*
* This implementation is modeled as immutable, to prevent propagation of
* user state changes.
*
* We recommend that any details injected are serializable.
*/
final class DefaultUser implements UserInterface
{
/**
* @var string
*/
private $identity;

/**
* @var string[]
*/
private $roles;

/**
* @var array
*/
private $details;

public function __construct(string $identity, array $roles = [], array $details = [])
{
$this->identity = $identity;
$this->roles = $roles;
$this->details = $details;
}

public function getIdentity() : string
{
return $this->identity;
}

public function getRoles() : array
{
return $this->roles;
}

public function getDetails() : array
{
return $this->details;
}

/**
* @param mixed $default Default value to return if no detail matching
* $name is discovered.
* @return mixed
*/
public function getDetail(string $name, $default = null)
{
return $this->details[$name] ?? $default;
}
}
27 changes: 27 additions & 0 deletions src/DefaultUserFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
/**
* @see https://github.com/zendframework/zend-expressive-authentication for the canonical source repository
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (https://www.zend.com)
* @license https://github.com/zendframework/zend-expressive-authentication/blob/master/LICENSE.md New BSD License
*/

declare(strict_types=1);

namespace Zend\Expressive\Authentication;

use Psr\Container\ContainerInterface;

/**
* Produces a callable factory capable of itself producing a UserInterface
* instance; this approach is used to allow substituting alternative user
* implementations without requiring extensions to existing repositories.
*/
class DefaultUserFactory
{
public function __invoke(ContainerInterface $container) : callable
{
return function (string $identity, array $roles = [], array $details = []) : UserInterface {
return new DefaultUser($identity, $roles, $details);
};
}
}
14 changes: 12 additions & 2 deletions src/UserInterface.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php
/**
* @see https://github.com/zendframework/zend-expressive-authentication for the canonical source repository
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (https://www.zend.com)
* @copyright Copyright (c) 2017-2018 Zend Technologies USA Inc. (https://www.zend.com)
* @license https://github.com/zendframework/zend-expressive-authentication/blob/master/LICENSE.md New BSD License
*/

Expand All @@ -21,5 +21,15 @@ public function getIdentity() : string;
*
* @return string[]
*/
public function getUserRoles() : array;
public function getRoles() : array;

/**
* Get a detail $name if present, $default otherwise
*/
public function getDetail(string $name, $default = null);

/**
* Get all the details, if any
*/
public function getDetails() : array;
}
Loading

0 comments on commit 486cb20

Please sign in to comment.