Skip to content

Commit

Permalink
Merge branch 'release/1.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
turegjorup committed Jun 29, 2020
2 parents 79930f3 + 6d6fdee commit 2f28d5e
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 7 deletions.
10 changes: 8 additions & 2 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
on: pull_request
name: Review
name: Test & Code Style Review
jobs:
test-composer-install:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -50,7 +50,7 @@ jobs:
with:
php-version: ${{ matrix.php}}
extension: apcu, ctype, iconv, imagick, json, redis, soap, xmlreader, zip
coverage: none
coverage: xdebug

- name: Get composer cache directory
id: composer-cache
Expand All @@ -67,6 +67,12 @@ jobs:
- name: Unit tests
run: ./vendor/bin/simple-phpunit --coverage-clover=coverage/unit.xml

- name: Report coverage to Codecov
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: |
bash <(curl -s https://codecov.io/bash) -F Unit -f coverage/unit.xml
runner-phpcs:
runs-on: ubuntu-latest
strategy:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
/.php_cs
/.php_cs.cache
###< friendsofphp/php-cs-fixer ###
coverage
95 changes: 91 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,94 @@
# Agency Auth Bundle
# Agency Auth Bundle

This bundle enables agency authentication against the open platform.
![](https://github.com/danskernesdigitalebibliotek/agency-auth-bundle/workflows/Test%20%26%20Code%20Style%20Review/badge.svg)
[![codecov](https://codecov.io/gh/danskernesdigitalebibliotek/agency-auth-bundle/branch/develop/graph/badge.svg)](https://codecov.io/gh/danskernesdigitalebibliotek/agency-auth-bundle)

Authentication is done via OAuth2 against auth.dbc.dk. To obtain a valid token
follow the instructions here: [Open Platform](https://openplatform.dbc.dk/v3/).


This bundle enables _agency_ ("library") authentication against the [Open Platform](https://openplatform.dbc.dk/v3/) (Shared API for
danish public libraries). In order to use this bundle you must have a `CLIENT_ID / CLIENT_SECRET` pair from DBC.

The bundle validates _agency_ access tokens against the Open Platform introspection endpoint. If a supplied token is
valid a `User` object with `ROLE_OPENPLATFORM_AGENCY` will be available from Symfony's [security component](https://symfony.com/doc/4.4/security.html#b-fetching-the-user-from-a-service).

### Note:
If you need _user_ ("personal") authentication you should use [danskernesdigitalebibliotek/oauth2-adgangsplatformen](https://github.com/danskernesdigitalebibliotek/oauth2-adgangsplatformen)

## Installation

Use Composer to install the bundle: `composer require danskernesdigitalebibliotek/agency-auth-bundle`

## Bundle Configuration

Add a `config/packages/ddb_agency_auth.yaml` file:

```
ddb_agency_auth:
# Your client id supplied by DBC
openplatform_id: '%env(OPENPLATFORM_ID)%'
# Your client secret supplied by DBC
openplatform_secret: '%env(OPENPLATFORM_SECRET)%'
# The introspection URL to query against
openplatform_introspection_url: 'https://login.bib.dk/oauth/introspection'
# A comma separated allow list of CLIENT_IDs. An empty list allows all.
openplatform_allowed_clients: '%env(OPENPLATFORM_ALLOWED_CLIENTS)%'
# [Optional] A service id for the cache service to use for caching token/user pairs
auth_token_cache: token.cache
# [Optional] A service id for the logger to use for error logging.
auth_logger: logger
```

In your `.env` add:

```
###> Openplatform ###
OPENPLATFORM_ID=myId
OPENPLATFORM_SECRET=mySecret
OPENPLATFORM_INTROSPECTION_URL=https://login.bib.dk/oauth/introspection
OPENPLATFORM_ALLOWED_CLIENTS=''
###< Openplatform ###
```

Then set the actuel values in your `.env.local`. (See [configuration based on environment variables](https://symfony.com/doc/current/configuration.html#configuration-based-on-environment-variables))


## Security Configuration

Configure firewalls, access control and roles according to your needs in your `config/packages/security.yml`.
The bundle provides a `TokenAuthenticator` you can use as a [Symfony Guard](https://symfony.com/doc/4.4/security/guard_authentication.html).
If authenticated it will return a `User` with the `ROLE_OPENPLATFORM_AGENCY`. You can use Symfonys [hierarchical roles](https://symfony.com/doc/4.4/security.html#hierarchical-roles)
to map this role to your applications roles.

A working security configuration could be:
```
security:
providers:
in_memory: { memory: null }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api:
pattern: ^/api
stateless: true
anonymous: lazy
guard:
authenticators:
- DanskernesDigitaleBibliotek\AgencyAuthBundle\Security\TokenAuthenticator
main:
anonymous: true
access_control:
# Allows accessing the Swagger UI
- { path: '^/api/docs', roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: '^/api', roles: ROLE_OPENPLATFORM_AGENCY }
role_hierarchy:
ROLE_OPENPLATFORM_AGENCY: [ROLE_API_USER, ROLE_ENTRY_READ]
```
10 changes: 9 additions & 1 deletion src/Security/TokenAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,15 @@ public function getCredentials(Request $request)
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = $this->getToken($credentials);
try {
$token = $this->getToken($credentials);
if (null === $token) {
return null;
}
} catch (UnsupportedCredentialsTypeException $e) {
return null;
}

$user = $this->getCachedUser($token);

if (null === $user) {
Expand Down
81 changes: 81 additions & 0 deletions tests/TokenAuthenticatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
Expand Down Expand Up @@ -324,6 +328,32 @@ public function testCachedTokensClientIsNotAllowed(): void
$this->assertNull($user, 'Null should be returned when the agency is not allowed');
}

/**
* Test that null tokens returns null.
*/
public function testNullTokensIsNotAllowed(): void
{
$this->tokenAuthenticator = $this->getTokenAuthenticator('');

$this->cache->expects($this->never())->method('getItem');

$user = $this->tokenAuthenticator->getUser(null, $this->userProvider);
$this->assertNull($user, 'Null should be returned when token is null');
}

/**
* Test that invalid tokens returns null.
*/
public function testInvalidTokensIsNotAllowed(): void
{
$this->tokenAuthenticator = $this->getTokenAuthenticator('');

$this->cache->expects($this->never())->method('getItem');

$user = $this->tokenAuthenticator->getUser(12345678, $this->userProvider);
$this->assertNull($user, 'Null should be returned when token is invalid');
}

/**
* Test that a user is returned when client is on client allow list.
*
Expand Down Expand Up @@ -363,6 +393,57 @@ public function testAgencyShouldNotBeAllowed(): void
$this->assertNull($user, 'TokenAuthenticator should return null when client is not on allow list');
}

/**
* Test that checkCredentials always return true.
*/
public function testCheckCredentials(): void
{
$this->tokenAuthenticator = $this->getTokenAuthenticator('');
$user = $this->createMock(UserInterface::class);
// In case of a token, no credential check is needed.
// Return `true` to cause authentication success
$credentials = $this->tokenAuthenticator->checkCredentials([], $user);
$this->assertTrue($credentials, 'checkCredentials should always return true');
}

/**
* Test that onAuthenticationSuccess always return null.
*/
public function testOnAuthenticationSuccess(): void
{
$this->tokenAuthenticator = $this->getTokenAuthenticator('');
$request = $this->createMock(Request::class);
$token = $this->createMock(TokenInterface::class);
$result = $this->tokenAuthenticator->onAuthenticationSuccess($request, $token, 'key');
$this->assertNull($result, 'onAuthenticationSuccess should always return null');
}

/**
* Test that onAuthenticationFailure always return JsonResponse status 401.
*/
public function testOnAuthenticationFailure(): void
{
$this->tokenAuthenticator = $this->getTokenAuthenticator('');
$request = $this->createMock(Request::class);
$e = $this->createMock(AuthenticationException::class);
$response = $this->tokenAuthenticator->onAuthenticationFailure($request, $e);
$this->assertInstanceOf(JsonResponse::class, $response, 'checkCredentials should always return a JsonResponse');
$this->assertEquals(401, $response->getStatusCode(), 'onAuthenticationFailure should return 401');
}

/**
* Test that start() always return JsonResponse status 401.
*/
public function testStart(): void
{
$this->tokenAuthenticator = $this->getTokenAuthenticator('');
$request = $this->createMock(Request::class);
$e = $this->createMock(AuthenticationException::class);
$response = $this->tokenAuthenticator->start($request, $e);
$this->assertInstanceOf(JsonResponse::class, $response, 'checkCredentials should always return a JsonResponse');
$this->assertEquals(401, $response->getStatusCode(), 'onAuthenticationFailure should return 401');
}

/**
* Helper function to setup TokenAuthenticator with/without allowed clients.
*
Expand Down

0 comments on commit 2f28d5e

Please sign in to comment.