Passed
Push — master ( fec8fe...a9b124 )
by Arnold
02:51
created

ServerMiddleware::handleDigestRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 3
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Jasny\HttpDigest;
4
5
use Psr\Http\Message\ServerRequestInterface as ServerRequest;
6
use Psr\Http\Message\ResponseInterface as Response;
7
use Psr\Http\Message\ResponseFactoryInterface as ResponseFactory;
8
use Psr\Http\Server\MiddlewareInterface;
9
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
10
11
/**
12
 * Middleware to verify HTTP Digest header.
13
 * Can be used both as single pass (PSR-15) and double pass middleware.
14
 */
15
class ServerMiddleware implements MiddlewareInterface
16
{
17
    /**
18
     * @var HttpDigest
19
     */
20
    protected $service;
21
22
    /**
23
     * @var ResponseFactory|null
24
     */
25
    protected $responseFactory;
26
27
    /**
28
     * Class constructor.
29
     *
30
     * @param HttpDigest        $service
31
     * @param ResponseFactory|null $responseFactory
32
     */
33
    public function __construct(HttpDigest $service, ?ResponseFactory $responseFactory = null)
34
    {
35
        $this->service = $service;
36
        $this->responseFactory = $responseFactory;
37
    }
38
39
40
    /**
41
     * Process an incoming server request (PSR-15).
42
     *
43
     * @param ServerRequest  $request
44
     * @param RequestHandler $handler
45
     * @return Response
46
     * @throws \RuntimeException if unauthorized response can't be created
47
     */
48
    public function process(ServerRequest $request, RequestHandler $handler): Response
49
    {
50
        $next = function (ServerRequest $request) use ($handler) {
51
            return $handler->handle($request);
52
        };
53
54
        return $request->hasHeader('Digest')
55
            ? $this->handleDigestRequest($request, null, $next)
56
            : $this->handleNoDigestRequest($request, null, $next);
57
    }
58
59
    /**
60
     * Get a callback that can be used as double pass middleware.
61
     *
62
     * @return callable
63
     */
64
    public function asDoublePass(): callable
65
    {
66
        return function (ServerRequest $request, Response $response, callable $next): Response {
67
            return $request->hasHeader('Digest')
68
                ? $this->handleDigestRequest($request, $response, $next)
69
                : $this->handleNoDigestRequest($request, $response, $next);
70
        };
71
    }
72
73
74
    /**
75
     * Check if the should have a digest header.
76
     *
77
     * @param ServerRequest $request
78
     * @return bool
79
     */
80
    protected function shouldHaveDigest(ServerRequest $request)
81
    {
82
        switch (strtoupper($request->getMethod())) {
83
            case 'GET':
84
            case 'HEAD':
85
            case 'OPTIONS':
86
                return false;
87
88
            case 'PATCH':
89
            case 'POST':
90
            case 'PUT':
91
                return true;
92
93
            default:
94
                return $request->getBody()->getSize() > 0;
95
        }
96
    }
97
98
    /**
99
     * Handle request with a Digest header.
100
     *
101
     * @param ServerRequest  $request
102
     * @param Response|null  $response
103
     * @param callable       $next
104
     * @return Response
105
     * @throws \RuntimeException when the bad request response can't be created.
106
     */
107
    protected function handleDigestRequest(ServerRequest $request, ?Response $response, callable $next): Response
108
    {
109
        try {
110
            $this->service->verify($request->getBody()->getContents(), $request->getHeaderLine('Digest'));
111
        } catch (HttpDigestException $exception) {
112
            return $this->createBadRequestResponse($response, $exception->getMessage());
113
        }
114
115
        return $next($request, $response);
116
    }
117
118
    /**
119
     * Handle request without a Digest header.
120
     *
121
     * @param ServerRequest  $request
122
     * @param Response|null  $response
123
     * @param callable       $next
124
     * @return Response
125
     * @throws \RuntimeException when the bad request response can't be created.
126
     */
127
    protected function handleNoDigestRequest(ServerRequest $request, ?Response $response, callable $next): Response
128
    {
129
        if ($this->shouldHaveDigest($request)) {
130
            return $this->createBadRequestResponse($response, 'Digest header missing');
131
        }
132
133
        return $next($request, $response);
134
    }
135
136
    /**
137
     * Create a response using the response factory.
138
     *
139
     * @param int $status  Response status
140
     * @return Response
141
     */
142
    protected function createResponse(int $status): Response
143
    {
144
        if ($this->responseFactory === null) {
145
            throw new \BadMethodCallException('Response factory not set');
146
        }
147
148
        return $this->responseFactory->createResponse($status);
149
    }
150
151
    /**
152
     * Create a `401 Unauthorized` response.
153
     *
154
     * @param ServerRequest $request
155
     * @param Response|null $response
156
     * @param string        $message
157
     * @return Response
158
     * @throws \RuntimeException when can't write body.
159
     */
160
    protected function createBadRequestResponse(?Response $response, string $message): Response {
161
        $newResponse = $response === null
162
            ? $this->createResponse(400)
163
            : $response->withStatus(400)->withBody(clone $response->getBody());
164
165
        $errorResponse = $newResponse
166
            ->withHeader('Want-Digest', $this->service->getWantDigest())
167
            ->withHeader('Content-Type', 'text/plain');
168
169
        $errorResponse->getBody()->write($message);
170
171
        return $errorResponse;
172
    }
173
}
174