Skip to content

Commit

Permalink
feature: PSR-17 support for PhpSession authentication
Browse files Browse the repository at this point in the history
Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com>
  • Loading branch information
boesing committed Jul 25, 2021
1 parent 4362ab9 commit 76bb46c
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 53 deletions.
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
"mezzio/mezzio-authentication": "^1.0",
"mezzio/mezzio-session": "^1.0",
"psr/container": "^1.0",
"psr/http-message": "^1.0.1"
"psr/http-factory": "^1.0",
"psr/http-message": "^1.0.1",
"webmozart/assert": "^1.10"
},
"require-dev": {
"laminas/laminas-coding-standard": "~2.2.0",
Expand Down
59 changes: 57 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 30 additions & 10 deletions src/PhpSession.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
namespace Mezzio\Authentication\Session;

use Mezzio\Authentication\AuthenticationInterface;
use Mezzio\Authentication\Session\Response\CallableResponseFactoryDecorator;
use Mezzio\Authentication\UserInterface;
use Mezzio\Authentication\UserRepositoryInterface;
use Mezzio\Session\SessionInterface;
use Mezzio\Session\SessionMiddleware;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Traversable;

use function is_array;
use function is_callable;
use function iterator_to_array;
use function strtoupper;

Expand All @@ -25,28 +28,37 @@ class PhpSession implements AuthenticationInterface
/** @var array */
private $config;

/** @var callable */
/** @var ResponseFactoryInterface */
private $responseFactory;

/** @var callable */
private $userFactory;

/**
* @param (callable():ResponseInterface)|ResponseFactoryInter $responseFactory
*/
public function __construct(
UserRepositoryInterface $repository,
array $config,
callable $responseFactory,
$responseFactory,
callable $userFactory
) {
$this->repository = $repository;
$this->config = $config;

// Ensures type safety of the composed factory
$this->responseFactory = function () use ($responseFactory): ResponseInterface {
return $responseFactory();
};
if (is_callable($responseFactory)) {
// Ensures type safety of the composed factory
$responseFactory = new CallableResponseFactoryDecorator(
static function () use ($responseFactory): ResponseInterface {
return $responseFactory();
}
);
}

$this->responseFactory = $responseFactory;

// Ensures type safety of the composed factory
$this->userFactory = function (
$this->userFactory = static function (
string $identity,
array $roles = [],
array $details = []
Expand Down Expand Up @@ -99,12 +111,12 @@ public function authenticate(ServerRequestInterface $request): ?UserInterface

public function unauthorizedResponse(ServerRequestInterface $request): ResponseInterface
{
return ($this->responseFactory)()
return $this->responseFactory
->createResponse(302)
->withHeader(
'Location',
$this->config['redirect']
)
->withStatus(302);
);
}

/**
Expand Down Expand Up @@ -133,4 +145,12 @@ private function getUserRoles(UserInterface $user): Traversable
{
return yield from $user->getRoles();
}

/**
* @internal This should only be used in unit tests.
*/
public function getResponseFactory(): ResponseFactoryInterface
{
return $this->responseFactory;
}
}
30 changes: 22 additions & 8 deletions src/PhpSessionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,23 @@
use Mezzio\Authentication\UserInterface;
use Mezzio\Authentication\UserRepositoryInterface;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;

class PhpSessionFactory
{
use Psr17ResponseFactoryTrait;

public function __invoke(ContainerInterface $container): PhpSession
{
$hasUserRepository = $container->has(UserRepositoryInterface::class);
$hasDeprecatedUserRepository = false;
if (! $hasUserRepository) {
$hasDeprecatedUserRepository = $container->has(
\Zend\Expressive\Authentication\UserRepositoryInterface::class
);
}
if (
! $container->has(UserRepositoryInterface::class)
&& ! $container->has(\Zend\Expressive\Authentication\UserRepositoryInterface::class)
! $hasUserRepository
&& ! $hasDeprecatedUserRepository
) {
throw new Exception\InvalidConfigException(
'UserRepositoryInterface service is missing for authentication'
Expand All @@ -31,22 +39,28 @@ public function __invoke(ContainerInterface $container): PhpSession
);
}

$hasUser = $container->has(UserInterface::class);
$hasDeprecatedUser = false;
if (! $hasUser) {
$hasDeprecatedUser = $container->has(\Zend\Expressive\Authentication\UserInterface::class);
}

if (
! $container->has(UserInterface::class)
&& ! $container->has(\Zend\Expressive\Authentication\UserInterface::class)
! $hasUser
&& ! $hasDeprecatedUser
) {
throw new Exception\InvalidConfigException(
'UserInterface factory service is missing for authentication'
);
}

return new PhpSession(
$container->has(UserRepositoryInterface::class)
$hasUserRepository
? $container->get(UserRepositoryInterface::class)
: $container->get(\Zend\Expressive\Authentication\UserRepositoryInterface::class),
$config,
$container->get(ResponseInterface::class),
$container->has(UserInterface::class)
$this->detectResponseFactory($container),
$hasUser
? $container->get(UserInterface::class)
: $container->get(\Zend\Expressive\Authentication\UserInterface::class)
);
Expand Down
73 changes: 73 additions & 0 deletions src/Psr17ResponseFactoryTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace Mezzio\Authentication\Session;

use Mezzio\Authentication\Session\Response\CallableResponseFactoryDecorator;
use Mezzio\Container\ResponseFactoryFactory;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Webmozart\Assert\Assert;

/**
* @internal
* @deprecated Will be removed with v2.0.0
*/
trait Psr17ResponseFactoryTrait
{
private function detectResponseFactory(ContainerInterface $container): ResponseFactoryInterface
{
$psr17FactoryAvailable = $container->has(ResponseFactoryInterface::class);

if (! $psr17FactoryAvailable) {
return $this->createResponseFactoryFromDeprecatedCallable($container);
}

if ($this->doesConfigurationProvidesDedicatedResponseFactory($container)) {
return $this->createResponseFactoryFromDeprecatedCallable($container);
}

$responseFactory = $container->get(ResponseFactoryInterface::class);
Assert::isInstanceOf($responseFactory, ResponseFactoryInterface::class);
return $responseFactory;
}

private function createResponseFactoryFromDeprecatedCallable(
ContainerInterface $container
): ResponseFactoryInterface {
/** @var callable():ResponseInterface $responseFactory */
$responseFactory = $container->get(ResponseInterface::class);

return new CallableResponseFactoryDecorator($responseFactory);
}

private function doesConfigurationProvidesDedicatedResponseFactory(ContainerInterface $container): bool
{
if (! $container->has('config')) {
return false;
}

$config = $container->get('config');
Assert::isArrayAccessible($config);
$dependencies = $config['dependencies'] ?? [];
Assert::isMap($dependencies);

$delegators = $dependencies['delegators'] ?? [];
$aliases = $dependencies['aliases'] ?? [];
Assert::isArrayAccessible($delegators);
Assert::isArrayAccessible($aliases);

if (isset($delegators[ResponseInterface::class]) || isset($aliases[ResponseInterface::class])) {
// Even tho, aliases could point to a different service, we assume that there is a dedicated factory
// available. The alias resolving is not worth it.
return true;
}

/** @psalm-suppress MixedAssignment */
$deprecatedResponseFactory = $dependencies['factories'][ResponseInterface::class] ?? null;

return $deprecatedResponseFactory !== null && $deprecatedResponseFactory !== ResponseFactoryFactory::class;
}
}
36 changes: 36 additions & 0 deletions src/Response/CallableResponseFactoryDecorator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Mezzio\Authentication\Session\Response;

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;

/**
* @internal
* @deprecated Will be removed with v2.0.0
*/
final class CallableResponseFactoryDecorator implements ResponseFactoryInterface
{
/** @var callable():ResponseInterface */
private $responseFactory;

/**
* @param callable():ResponseInterface $responseFactory
*/
public function __construct(callable $responseFactory)
{
$this->responseFactory = $responseFactory;
}

public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
{
return $this->getResponseFromCallable()->withStatus($code, $reasonPhrase);
}

public function getResponseFromCallable(): ResponseInterface
{
return ($this->responseFactory)();
}
}

0 comments on commit 76bb46c

Please sign in to comment.