Skip to content

Commit

Permalink
Merge pull request #1 from verschoof/refresh-session
Browse files Browse the repository at this point in the history
Add RefreshSessionMiddleware
  • Loading branch information
verschoof authored Jun 8, 2017
2 parents 4ed38a1 + f873df4 commit e9a7972
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 11 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ language: php
php:
- 5.6
- 7.0
- hhvm

install:
- travis_retry composer install --no-interaction --prefer-source
Expand Down
21 changes: 18 additions & 3 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Bunq;

use Bunq\Exception\BunqException;
use Bunq\Exception\SessionWasExpiredException;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\ClientException;

Expand Down Expand Up @@ -67,11 +68,25 @@ public function put($url, array $options = [])
private function requestAPI($method, $url, array $options = [])
{
try {
$response = $this->httpClient->request((string)$method, $url, $options);

return json_decode($response->getBody(), true);
return $this->sendRequest((string)$method, (string)$url, $options);
} catch (SessionWasExpiredException $e) {
return $this->sendRequest((string)$method, (string)$url, $options);
} catch (ClientException $e) {
throw new BunqException($e);
}
}

/**
* @param string $method
* @param string $url
* @param array $options
*
* @return mixed
*/
private function sendRequest($method, $url, array $options = [])
{
$response = $this->httpClient->request((string)$method, $url, $options);

return json_decode($response->getBody(), true);
}
}
11 changes: 11 additions & 0 deletions src/Exception/SessionWasExpiredException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Bunq\Exception;

final class SessionWasExpiredException extends \Exception
{
public function __construct()
{
parent::__construct('Session has expired should now be refreshed', 400);
}
}
23 changes: 18 additions & 5 deletions src/HttpClientFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@

use Bunq\Certificate\CertificateType;
use Bunq\Certificate\Storage\CertificateStorage;
use Bunq\Middleware\RefreshSessionMiddleware;
use Bunq\Middleware\RequestAuthenticationMiddleware;
use Bunq\Middleware\RequestIdMiddleware;
use Bunq\Middleware\RequestSignatureMiddleware;
use Bunq\Middleware\ResponseSignatureMiddleware;
use Bunq\Service\InstallationService;
use Bunq\Service\TokenService;
use Bunq\Token\Storage\TokenStorage;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
Expand Down Expand Up @@ -39,14 +42,21 @@ public static function createInstallationClient($url, CertificateStorage $certif
/**
* Creates the HttpClient with all handlers
*
* @param string $url
* @param TokenService $tokenService
* @param CertificateStorage $certificateStorage
* @param string $url
* @param TokenService $tokenService
* @param CertificateStorage $certificateStorage
* @param InstallationService $installationService
* @param TokenStorage $tokenStorage
*
* @return ClientInterface
*/
public static function create($url, TokenService $tokenService, CertificateStorage $certificateStorage)
{
public static function create(
$url,
TokenService $tokenService,
CertificateStorage $certificateStorage,
InstallationService $installationService,
TokenStorage $tokenStorage
) {
$sessionToken = $tokenService->sessionToken();
$publicServerKey = $certificateStorage->load(CertificateType::BUNQ_SERVER_KEY());

Expand All @@ -63,6 +73,9 @@ public static function create($url, TokenService $tokenService, CertificateStora
$handlerStack->push(
Middleware::mapResponse(new ResponseSignatureMiddleware($publicServerKey))
);
$handlerStack->push(
Middleware::mapResponse(new RefreshSessionMiddleware($installationService, $tokenStorage))
);

$httpClient = self::createBaseClient($url, $handlerStack);

Expand Down
74 changes: 74 additions & 0 deletions src/Middleware/RefreshSessionMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace Bunq\Middleware;

use Bunq\Exception\SessionWasExpiredException;
use Bunq\Service\InstallationService;
use Bunq\Token\DefaultToken;
use Bunq\Token\Storage\TokenStorage;
use Bunq\Token\TokenType;
use Psr\Http\Message\ResponseInterface;

/**
* bunq documentation:
* A session expires after the same amount of time you have set for auto logout in your user account. If a request is
* made 30 seconds before a session expires, it will automatically be extended.
*
* This middleware detects if the session has expired, if so, the middleware will renew the session and throws an
* exception to let the client know it can be retried.
*/
final class RefreshSessionMiddleware
{
/**
* @var InstallationService
*/
private $installationService;

/**
* @var TokenStorage
*/
private $tokenStorage;

/**
* @param InstallationService $installationService
* @param TokenStorage $tokenStorage
*/
public function __construct(
InstallationService $installationService,
TokenStorage $tokenStorage
) {
$this->installationService = $installationService;
$this->tokenStorage = $tokenStorage;
}

/**
* @param ResponseInterface $response
*
* @return ResponseInterface
* @throws \Exception
*/
public function __invoke(ResponseInterface $response)
{
if ($response->getStatusCode() !== 401) {
return $response;
}

$responseBody = \json_decode($response->getBody()->__toString(), true);

if ($responseBody['Error'][0]['error_description'] !== 'Insufficient authorisation.') {
return $response;
}

// make new session

$installationToken = $currentInstalltionToken = $this->tokenStorage->load(TokenType::INSTALLATION_TOKEN());
$session = $this->installationService->createSession($installationToken);

$sessionToken = DefaultToken::fromString($session);

$this->tokenStorage->save($sessionToken, TokenType::SESSION_TOKEN());

throw new SessionWasExpiredException();
}

}
1 change: 0 additions & 1 deletion tests/Exception/BunqExceptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,5 @@ public function itRepresentsABunqException()
$this->assertInstanceOf(\Exception::class, $bunqException);
$this->assertSame('Path: /path, Message: body', $bunqException->getMessage());
$this->assertSame(403, $bunqException->getCode());

}
}
23 changes: 23 additions & 0 deletions tests/Exception/SessionExpiredExceptionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Bunq\Tests\Exception;

use Bunq\Exception\SessionWasExpiredException;
use PHPUnit\Framework\TestCase;

final class SessionWasExpiredExceptionTest extends TestCase
{
/**
* @test
*/
public function itRepresentsASessionWasExpiredException()
{
$sessionWasExpiredException = new SessionWasExpiredException();

$this->assertInstanceOf(SessionWasExpiredException::class, $sessionWasExpiredException);
$this->assertInstanceOf(\Exception::class, $sessionWasExpiredException);
$this->assertSame('Session has expired should now be refreshed', $sessionWasExpiredException->getMessage());
$this->assertSame(400, $sessionWasExpiredException->getCode());
}
}

20 changes: 19 additions & 1 deletion tests/HttpClientFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
use Bunq\Certificate\DefaultCertificate;
use Bunq\Certificate\Storage\CertificateStorage;
use Bunq\HttpClientFactory;
use Bunq\Middleware\RefreshSessionMiddleware;
use Bunq\Middleware\RequestAuthenticationMiddleware;
use Bunq\Middleware\RequestIdMiddleware;
use Bunq\Middleware\RequestSignatureMiddleware;
use Bunq\Middleware\ResponseSignatureMiddleware;
use Bunq\Service\InstallationService;
use Bunq\Service\TokenService;
use Bunq\Token\DefaultToken;
use Bunq\Token\Storage\TokenStorage;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -74,7 +77,19 @@ public function itCreatesADefaultClient()
$tokenService = $this->prophesize(TokenService::class);
$tokenService->sessionToken()->willReturn($sessionToken);

$client = HttpClientFactory::create($baseUrl, $tokenService->reveal(), $certificateStorage->reveal());
/** @var InstallationService|ObjectProphecy $installationService */
$installationService = $this->prophesize(InstallationService::class);

/** @var TokenStorage|ObjectProphecy $tokenStorage */
$tokenStorage = $this->prophesize(TokenStorage::class);

$client = HttpClientFactory::create(
$baseUrl,
$tokenService->reveal(),
$certificateStorage->reveal(),
$installationService->reveal(),
$tokenStorage->reveal()
);

$expectedHandlerStack = HandlerStack::create();
$expectedHandlerStack->push(
Expand All @@ -89,6 +104,9 @@ public function itCreatesADefaultClient()
$expectedHandlerStack->push(
Middleware::mapResponse(new ResponseSignatureMiddleware($bunqCertificate))
);
$expectedHandlerStack->push(
Middleware::mapResponse(new RefreshSessionMiddleware($installationService->reveal(), $tokenStorage->reveal()))
);

$expectedClient = new \GuzzleHttp\Client(
[
Expand Down
93 changes: 93 additions & 0 deletions tests/Middleware/RefreshSessionMiddlewareTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace Bunq\Tests\Middleware;

use Bunq\Middleware\RefreshSessionMiddleware;
use Bunq\Service\InstallationService;
use Bunq\Token\DefaultToken;
use Bunq\Token\Storage\TokenStorage;
use Bunq\Token\TokenType;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy;
use Psr\Http\Message\ResponseInterface;

final class RefreshSessionMiddlewareTest extends TestCase
{
/**
* @var InstallationService|ObjectProphecy
*/
private $installationService;

/**
* @var TokenStorage|ObjectProphecy
*/
private $tokenStorage;

/**
* @var RefreshSessionMiddleware
*/
private $middleware;

public function setUp()
{
$this->installationService = $this->prophesize(InstallationService::class);
$this->tokenStorage = $this->prophesize(TokenStorage::class);

$this->middleware = new RefreshSessionMiddleware(
$this->installationService->reveal(),
$this->tokenStorage->reveal()
);
}

/**
* @test
*/
public function itWontGetExecutedWhenResponseIsNot401()
{
$response = new Response(200, [], '{"ok"}');

$this->tokenStorage->load(Argument::any())->shouldNotBeCalled();

$resultResponse = $this->middleware->__invoke($response);

$this->assertSame($response, $resultResponse);
}

/**
* @test
*/
public function itWontGetExecutedWhenResponseIsNotInsufficientAuthorisation()
{
$body = '{"Error":[{"error_description": "An other error"}]}';
$response = new Response(401, [], $body);

$this->tokenStorage->load(Argument::any())->shouldNotBeCalled();

$resultResponse = $this->middleware->__invoke($response);

$this->assertEquals($response, $resultResponse);
}

/**
* @test
* @expectedException \Bunq\Exception\SessionWasExpiredException
*/
public function itWillRefreshSessionAndThrowsAnExceptionIfInsufficientAuthorisation()
{
$body = '{"Error":[{"error_description": "Insufficient authorisation."}]}';
$response = new Response(401, [], $body);

$installationToken = new DefaultToken('Installation Token');
$this->tokenStorage->load(TokenType::INSTALLATION_TOKEN())->willReturn($installationToken);
$this->installationService->createSession($installationToken)->willReturn('someSessionId');

$sessionToken = DefaultToken::fromString('someSessionId');

$this->tokenStorage->save($sessionToken, TokenType::SESSION_TOKEN())->shouldBeCalled();

$this->middleware->__invoke($response);
}
}

0 comments on commit e9a7972

Please sign in to comment.