Passed
Push — master ( 1b20af...48b0b4 )
by Charles
02:38
created

AbstractAuthentication::process()   B

Complexity

Conditions 9
Paths 18

Size

Total Lines 45
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 27
dl 0
loc 45
rs 8.0555
c 0
b 0
f 0
cc 9
nc 18
nop 2
1
<?php declare(strict_types=1);
2
3
namespace ncryptf\middleware;
4
5
use DateTime;
6
use Exception;
7
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\ServerRequestInterface;
10
use Psr\Http\Server\MiddlewareInterface;
11
use Psr\Http\Server\RequestHandlerInterface;
12
use ncryptf\Authorization;
13
use ncryptf\Response;
14
use ncryptf\Token;
15
16
/**
17
 * PSR-15 Authentication middleware for handling ncryptf Authorization requests
18
 * Abstract class should be extended and implement `getTokenFromAccessToken` and `getRequestBody`
19
 */
20
abstract class AbstractAuthentication implements MiddlewareInterface
21
{
22
    use \Middlewares\Utils\Traits\HasResponseFactory;
23
24
    // The date header
25
    const DATE_HEADER = 'X-DATE';
26
27
    // The authorization header
28
    const AUTHORIZATION_HEADER = 'Authorization';
29
30
    // The amount of the seconds the request is permitted to differ from the server time
31
    const DRIFT_TIME_ALLOWANCE = 90;
32
33
    /**
34
     * Process a request
35
     * @param ServerRequestInterface $request
36
     * @param RequestHandlerInterface $handler
37
     * @return ResponseInterface
38
     */
39
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
40
    {
41
        $params = Authorization::extractParamsFromHeaderString(
42
            $request->getHeaderLine(self::AUTHORIZATION_HEADER)
43
        );
44
45
        if ($params) {
46
            if ($token = $this->getTokenFromAccessToken($params['access_token'])) {
47
                try {
48
                    $date = new DateTime($params['date'] ?? $request->getHeaderLine(self::DATE_HEADER));
49
50
                    $auth = new Authorization(
51
                        $request->getMethod(),
52
                        $this->getRequestUri($request),
53
                        $token,
54
                        $date,
55
                        $this->getRequestBody(clone $request),
56
                        $params['v'],
57
                        \base64_decode($params['salt'])
58
                    );
59
60
                    if ($auth->verify(\base64_decode($params['hmac']), $auth, static::DRIFT_TIME_ALLOWANCE)) {
61
                        // For encrypted requests, we perform an additional integrity check by verifying the public key
62
                        // used to sign the message matches the key issued by this instance
63
                        if (($contentType = $request->getHeaderLine('Content-Type')) === 'application/vnd.ncryptf+json') {
0 ignored issues
show
Unused Code introduced by
The assignment to $contentType is dead and can be removed.
Loading history...
64
                            $rawBody = (string)$request->getBody();
65
                            if ($rawBody !== '' && Response::getVersion(\base64_decode($rawBody)) >= 2) {
66
                                $publicKey = Response::getSigningPublicKeyFromResponse(\base64_decode($rawBody));
67
                                if (\sodium_compare($publicKey, $token->getSignaturePublicKey()) !== 0) {
68
                                    throw new Exception('Authenticate request was not signed with the expected key.');
69
                                }
70
                            }
71
                        }
72
                        return $handler->handle(
73
                            $request->withAttribute('ncryptf-token', $token)
74
                                ->withAttribute('ncryptf-user', $this->getUserFromToken($token))
75
                        );
76
                    }
77
                } catch (Exception $e) {
78
                    return $this->createResponse(401);
79
                }
80
            }
81
        }
82
83
        return $this->createResponse(401);
84
    }
85
86
    /**
87
     * Returns the full URI
88
     * @param ServerRequestInterface $request
89
     * @return string
90
     */
91
    private function getRequestUri(ServerRequestInterface $request) : string
92
    {
93
        $uri = $request->getUri()->getPath();
94
        $query = $request->getUri()->getQuery();
95
96
        if (!empty($query)) {
97
            return $uri . '?' . \urldecode($query);
98
        }
99
100
        return $uri;
101
    }
102
103
    /**
104
     * Returns the plaintext request body.
105
     *
106
     * @param ServerRequestInterface $request
107
     * @return string
108
     */
109
    protected function getRequestBody(ServerRequestInterface $request) : string
110
    {
111
        if ($decryptedBody = $request->getAttribute('ncryptf-decrypted-body', false)) {
112
            return $decryptedBody;
113
        }
114
115
        return $request->getBody()->getContents();
116
    }
117
118
    /**
119
     * Returns the \ncryptf\Token associated to the given access token.
120
     * If the access token is not found, `NULL` should be returned
121
     *
122
     * @param string $accessToken
123
     * @return \ncryptf\Token
124
     */
125
    abstract protected function getTokenFromAccessToken(string $accessToken) :? Token;
126
127
    /**
128
     * Given a particular token, returns an object, array, or integer representing the user
129
     *
130
     * @param \ncryptf\Token $token
131
     * @return integer|array|object
132
     */
133
    abstract protected function getUserFromToken(Token $token);
134
}
135