Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file, in reverse

### Added

- Nothing.
- [#62](https://github.com/zendframework/zend-expressive-authentication-oauth2/pull/62) adds the ability to configure and add event listeners for the underlying league/oauth2 implementation. See the [event listeners configuration documentation](https://docs.zendframework.com/zend-expressive-authentication-oauth2/intro/#configure-event-listeners) for more information.

### Changed

Expand Down
46 changes: 45 additions & 1 deletion docs/book/v1/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,50 @@ grants are configured to be available. If you would like to disable any of the
supplied grants, change the value for the grant to `null`. Additionally,
you can extend this array to add your own custom grants.

### Configure Event Listeners

- **Since 1.3.0**

_Optional_ The `event_listeners` and `event_listener_providers` arrays may be used to enable event listeners for events published by `league\oauth2-server`. See the [Authorization Server Domain Events documentation](https://oauth2.thephpleague.com/authorization-server/events/). The possible event names can be found [in `League\OAuth2\Server\RequestEvent`](https://github.com/thephpleague/oauth2-server/blob/0b0b43d43342c0909b3b32fb7a09d502c368d2ec/src/RequestEvent.php#L17-L22).

#### Event Listeners

The `event_listeners` key must contain an array of arrays. Each array element must contain at least 2 elements and may include a 3rd element. These roughly correspond to the arguments passed to [`League\Event\ListenerAcceptorInterface::addListener()`](https://github.com/thephpleague/event/blob/d2cc124cf9a3fab2bb4ff963307f60361ce4d119/src/ListenerAcceptorInterface.php#L43). The first element must be a string -- either the [wildcard (`*`)](https://event.thephpleague.com/2.0/listeners/wildcard/) or a [single event name](https://event.thephpleague.com/2.0/events/named/). The second element must be either a callable, a concrete instance of `League\Event\ListenerInterface`, or a string pointing to your listener service instance in the container. The third element is optional, and must be an integer if provided.

See the [documentation for callable listeners](https://event.thephpleague.com/2.0/listeners/callables/).

#### Event Listener Providers

The `event_listener_providers` key must contain an array. Each array element must contain either a concrete instance of `League\Event\ListenerProviderInterface` or a string pointing to your container service instance of a listener provider.

See the [documentation for listener providers](https://event.thephpleague.com/2.0/listeners/providers/).

Example config:

```php
return [
'event_listeners' => [
// using a container service
[
\League\OAuth2\Server\RequestEvent::CLIENT_AUTHENTICATION_FAILED,
\My\Event\Listener\Service::class,
],
// using a callable
[
\League\OAuth2\Server\RequestEvent::ACCESS_TOKEN_ISSUED,
function (\League\OAuth2\Server\RequestEvent $event) {
// do something
},
],
],
'event_listener_providers' => [
\My\Event\ListenerProvider\Service::class,
],
];
```

## OAuth2 Database

You need to provide an OAuth2 database yourself, or generate a [SQLite](https://www.sqlite.org)
database with the following command (using `sqlite3` for GNU/Linux):

Expand All @@ -152,7 +196,7 @@ For security reason, the client `secret` and the user `password` are stored
using the `bcrypt` algorithm as used by the [password_hash](http://php.net/manual/en/function.password-hash.php)
function.

## Configure OAuth2 routes
## Configure OAuth2 Routes

As the final step, in order to use the OAuth2 server you need to configure the routes
for the **token endpoint** and **authorization**.
Expand Down
92 changes: 86 additions & 6 deletions src/AuthorizationServerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,30 @@
namespace Zend\Expressive\Authentication\OAuth2;

use DateInterval;
use League\Event\ListenerProviderInterface;

use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Grant;
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
use Psr\Container\ContainerInterface;
use Zend\Expressive\Authentication\OAuth2\Exception\InvalidConfigException;

/**
* Factory for OAuth AuthorizationServer
*
* Initializes a new AuthorizationServer with required params from config.
* Then configured grant types are enabled with configured access token
* expiry. Then any optionally configured event listeners are attached to the
* AuthorizationServer.
*/
class AuthorizationServerFactory
{
use ConfigTrait;
use CryptKeyTrait;
use RepositoryTrait;

/**
* @param ContainerInterface $container
*
* @return AuthorizationServer
*/
public function __invoke(ContainerInterface $container) : AuthorizationServer
{
$clientRepository = $this->getClientRepository($container);
Expand All @@ -46,7 +56,7 @@ public function __invoke(ContainerInterface $container) : AuthorizationServer
$accessTokenInterval = new DateInterval($this->getAccessTokenExpire($container));

foreach ($grants as $grant) {
// Config may set this grant to null. Continue on if grant has been disabled
// Config may set this grant to null. Continue on if grant has been disabled
if (empty($grant)) {
continue;
}
Expand All @@ -57,6 +67,76 @@ public function __invoke(ContainerInterface $container) : AuthorizationServer
);
}

// add listeners if configured
$this->addListeners($authServer, $container);

// add listener providers if configured
$this->addListenerProviders($authServer, $container);

return $authServer;
}

/**
* Optionally add event listeners
*
* @param AuthorizationServer $authServer
* @param ContainerInterface $container
*/
private function addListeners(
AuthorizationServer $authServer,
ContainerInterface $container
): void {
$listeners = $this->getListenersConfig($container);

foreach ($listeners as $idx => $listenerConfig) {
$event = $listenerConfig[0];
$listener = $listenerConfig[1];
$priority = $listenerConfig[2] ?? null;
if (is_string($listener)) {
if (! $container->has($listener)) {
throw new Exception\InvalidConfigException(sprintf(
'The second element of event_listeners config at ' .
'index "%s" is a string and therefore expected to ' .
'be available as a service key in the container. ' .
'A service named "%s" was not found.',
$idx,
$listener
));
}
$listener = $container->get($listener);
}
$authServer->getEmitter()
->addListener($event, $listener, $priority);
}
}

/**
* Optionally add event listener providers
*
* @param AuthorizationServer $authServer
* @param ContainerInterface $container
*/
private function addListenerProviders(
AuthorizationServer $authServer,
ContainerInterface $container
): void {
$providers = $this->getListenerProvidersConfig($container);

foreach ($providers as $idx => $provider) {
if (is_string($provider)) {
if (! $container->has($provider)) {
throw new Exception\InvalidConfigException(sprintf(
'The event_listener_providers config at ' .
'index "%s" is a string and therefore expected to ' .
'be available as a service key in the container. ' .
'A service named "%s" was not found.',
$idx,
$provider
));
}
$provider = $container->get($provider);
}
$authServer->getEmitter()->useListenerProvider($provider);
}
}
}
42 changes: 42 additions & 0 deletions src/ConfigTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,46 @@ protected function getGrantsConfig(ContainerInterface $container) : array

return $config['grants'];
}

/**
* @param ContainerInterface $container
*
* @return array
*/
protected function getListenersConfig(ContainerInterface $container) : array
{
$config = $container->get('config')['authentication'] ?? [];

if (empty($config['event_listeners'])) {
return [];
}
if (! is_array($config['event_listeners'])) {
throw new InvalidConfigException(
'The event_listeners config must be an array value'
);
}

return $config['event_listeners'];
}

/**
* @param ContainerInterface $container
*
* @return array
*/
protected function getListenerProvidersConfig(ContainerInterface $container) : array
{
$config = $container->get('config')['authentication'] ?? [];

if (empty($config['event_listener_providers'])) {
return [];
}
if (! is_array($config['event_listener_providers'])) {
throw new InvalidConfigException(
'The event_listener_providers config must be an array value'
);
}

return $config['event_listener_providers'];
}
}
Loading