Completed
Pull Request — master (#11)
by Arnold
03:10
created

AuthMiddleware::initialize()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 3
nc 2
nop 0
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Jasny\Auth;
6
7
use Improved\IteratorPipeline\Pipeline;
8
use Jasny\Auth\AuthzInterface as Authz;
9
use Psr\Http\Message\ServerRequestInterface as ServerRequest;
10
use Psr\Http\Message\ResponseInterface as Response;
11
use Psr\Http\Message\ResponseFactoryInterface as ResponseFactory;
12
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
13
use Psr\Http\Server\MiddlewareInterface;
14
15
/**
16
 * Middleware for access control.
17
 */
18
class AuthMiddleware implements MiddlewareInterface
19
{
20
    protected Authz $auth;
21
    protected ?ResponseFactory $responseFactory = null;
22
23
    /** Function to get the required role from the request. */
24
    protected \Closure $getRequiredRole;
25
26
    /**
27
     * Class constructor
28
     *
29
     * @param Authz    $auth
30
     * @param callable $getRequiredRole
31
     */
32 16
    public function __construct(Authz $auth, callable $getRequiredRole, ?ResponseFactory $responseFactory = null)
33
    {
34 16
        $this->auth = $auth;
35 16
        $this->responseFactory = $responseFactory;
36 16
        $this->getRequiredRole = \Closure::fromCallable($getRequiredRole);
37 16
    }
38
39
    /**
40
     * Process an incoming server request (PSR-15).
41
     *
42
     * @param ServerRequest  $request
43
     * @param RequestHandler $handler
44
     * @return Response
45
     */
46 9
    public function process(ServerRequest $request, RequestHandler $handler): Response
47
    {
48 9
        $this->initialize();
49
50 9
        if (!$this->isAllowed($request)) {
51 3
            return $this->forbidden($request);
52
        }
53
54 6
        return $handler->handle($request);
55
    }
56
57
    /**
58
     * Get a callback that can be used as double pass middleware.
59
     *
60
     * @return callable
61
     */
62 7
    public function asDoublePass(): callable
63
    {
64
        return function (ServerRequest $request, Response $response, callable $next): Response {
65 7
            $this->initialize();
66
67 7
            if (!$this->isAllowed($request)) {
68 2
                return $this->forbidden($request, $response);
69
            }
70
71 5
            return $next($request, $response);
72 7
        };
73
    }
74
75
    /**
76
     * Initialize the auth service.
77
     */
78 16
    protected function initialize(): void
79
    {
80 16
        if ($this->auth instanceof Auth && !$this->auth->isInitialized()) {
1 ignored issue
show
Bug introduced by
The method isInitialized() does not exist on Jasny\Auth\AuthzInterface. It seems like you code against a sub-type of Jasny\Auth\AuthzInterface such as Jasny\Auth\Auth. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

80
        if ($this->auth instanceof Auth && !$this->auth->/** @scrutinizer ignore-call */ isInitialized()) {
Loading history...
81 2
            $this->auth->initialize();
1 ignored issue
show
Bug introduced by
The method initialize() does not exist on Jasny\Auth\AuthzInterface. It seems like you code against a sub-type of Jasny\Auth\AuthzInterface such as Jasny\Auth\Auth. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

81
            $this->auth->/** @scrutinizer ignore-call */ 
82
                         initialize();
Loading history...
82
        }
83 16
    }
84
85
    /**
86
     * Check if the request is allowed by the current user.
87
     */
88 16
    protected function isAllowed(ServerRequest $request): bool
89
    {
90 16
        $requiredRole = ($this->getRequiredRole)($request);
91
92 16
        if ($requiredRole === null) {
93 5
            return true;
94
        }
95
96 11
        if (is_bool($requiredRole)) {
97 7
            return ($this->auth->user() !== null) === $requiredRole;
98
        }
99
100 4
        return Pipeline::with(is_array($requiredRole) ? $requiredRole : [$requiredRole])
101 4
            ->hasAny(fn($role) => $this->auth->is($role));
102
    }
103
104
    /**
105
     * Respond with forbidden (or unauthorized).
106
     */
107 5
    protected function forbidden(ServerRequest $request, ?Response $response = null): Response
108
    {
109 5
        $unauthorized = $this->auth->user() === null;
110
111 5
        $forbiddenResponse = $this->createResponse($unauthorized ? 401 : 403, $response)
112 4
            ->withProtocolVersion($request->getProtocolVersion());
113 4
        $forbiddenResponse->getBody()->write('Access denied');
114
115 4
        return $forbiddenResponse;
116
    }
117
118
    /**
119
     * Create a response using the response factory.
120
     *
121
     * @param int           $status            Response status
122
     * @param Response|null $originalResponse
123
     * @return Response
124
     */
125 5
    protected function createResponse(int $status, ?Response $originalResponse = null): Response
126
    {
127 5
        if ($this->responseFactory !== null) {
128 2
            return $this->responseFactory->createResponse($status);
129 3
        } elseif ($originalResponse !== null) {
130 2
            return $originalResponse->withStatus($status)->withBody(clone $originalResponse->getBody());
131
            ;
132
        } else {
133 1
            throw new \LogicException('Response factory not set');
134
        }
135
    }
136
}
137