RequestAuthenticator::authenticate()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 40
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 5

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 5
eloc 21
nc 5
nop 1
dl 0
loc 40
ccs 22
cts 22
cp 1
crap 5
rs 9.2728
c 3
b 0
f 0
1
<?php
2
3
namespace Acquia\Hmac;
4
5
use Acquia\Hmac\Exception\InvalidSignatureException;
6
use Acquia\Hmac\Exception\KeyNotFoundException;
7
use Acquia\Hmac\Exception\MalformedRequestException;
8
use Acquia\Hmac\Exception\TimestampOutOfRangeException;
9
use Psr\Http\Message\RequestInterface;
10
11
class RequestAuthenticator implements RequestAuthenticatorInterface
12
{
13
    /**
14
     * @var \Acquia\Hmac\KeyLoaderInterface
15
     *   The key loader.
16
     */
17
    protected $keyLoader;
18
19
    /**
20
     * @var int|string
21
     *   The amount of time drift requests can be made when compared to the server.
22
     */
23
    protected $expiry;
24
25
    /**
26
     * @param \Acquia\Hmac\KeyLoaderInterface $keyLoader
27
     *   A datastore used to locate secrets for corresponding IDs.
28
     */
29 11
    public function __construct(KeyLoaderInterface $keyLoader)
30
    {
31 11
        $this->keyLoader = $keyLoader;
32 11
        $this->expiry    = '+15 min';
33 11
    }
34
35
    /**
36
     * {@inheritDoc}
37
     */
38 11
    public function authenticate(RequestInterface $request)
39
    {
40 11
        $authHeader = AuthorizationHeader::createFromRequest($request);
41 11
        $signature = $authHeader->getSignature();
42
43
        // Check whether the timestamp is valid.
44 11
        $comparison = $this->compareTimestamp($request, $this->expiry);
45
46 10
        if (-1 == $comparison) {
47 1
            throw new TimestampOutOfRangeException('Request is too old');
48 9
        } elseif (1 == $comparison) {
49 1
            throw new TimestampOutOfRangeException('Request is too far in the future');
50
        }
51
52
        // Load the API Key and sign the request.
53 8
        if (!$key = $this->keyLoader->load($authHeader->getId())) {
54 1
            throw new KeyNotFoundException('API key not found');
55
        }
56
57
        // Generate the signature from the passed authorization header.
58
        // If it matches the request signature, the request is authenticated.
59 7
        $compareRequest = $request->withoutHeader('Authorization');
60
61
62 7
        $authHeaderBuilder = new AuthorizationHeaderBuilder($compareRequest, $key);
63 7
        $authHeaderBuilder->setRealm($authHeader->getRealm());
64 7
        $authHeaderBuilder->setId($authHeader->getId());
65 7
        $authHeaderBuilder->setNonce($authHeader->getNonce());
66 7
        $authHeaderBuilder->setVersion($authHeader->getVersion());
67 7
        $authHeaderBuilder->setCustomHeaders($authHeader->getCustomHeaders());
68
69 7
        $compareAuthHeader = $authHeaderBuilder->getAuthorizationHeader();
70 7
        $compareSignature = $compareAuthHeader->getSignature();
71
72
73 7
        if (!hash_equals($compareSignature, $signature)) {
74 1
            throw new InvalidSignatureException('Signature not valid');
75
        }
76
77 6
        return $key;
78
    }
79
80
    /**
81
     * Retrieves the current timestamp.
82
     *
83
     * This is provided as a method to allow mocking during unit tests.
84
     *
85
     * @return int
86
     *   The current timestamp.
87
     */
88 2
    protected function getCurrentTimestamp()
89
    {
90 2
        return time();
91
    }
92
93
94
    /**
95
     * {@inheritDoc}
96
     *
97
     * @throws \InvalidArgumentException
98
     */
99 11
    protected function compareTimestamp(RequestInterface $request, $expiry)
100
    {
101 11
        if (!$request->hasHeader('X-Authorization-Timestamp')) {
102 1
            throw new MalformedRequestException('Request is missing X-Authorization-Timestamp.', null, 0, $request);
103
        }
104
105 10
        $timestamp = (int) $request->getHeaderLine('X-Authorization-Timestamp');
106 10
        $current   = $this->getCurrentTimestamp();
107
108
        // Is the request too old?
109 10
        $lowerLimit = $this->getExpiry($expiry, $timestamp);
110 10
        if ($current > $lowerLimit) {
111 1
            return -1;
112
        }
113
114
        // Is the request too far in the future?
115 9
        $upperLimit = $this->getExpiry($expiry, $current);
116 9
        if ($timestamp > $upperLimit) {
117 1
            return 1;
118
        }
119
120
        // Timestamp is within the expected range.
121 8
        return 0;
122
    }
123
124
    /**
125
     * Retrieves the request expiry as a timestamp.
126
     *
127
     * @param int|string $expiry
128
     *   The passed expiry.
129
     * @param int $relativeTimestamp
130
     *   The timestamp from which to base the expiry.
131
     *
132
     * @return int
133
     *   The expiry as a timestamp.
134
     *
135
     */
136 10
    protected function getExpiry($expiry, $relativeTimestamp)
137
    {
138 10
        if (!is_int($expiry)) {
139 10
            $expiry = strtotime($expiry, $relativeTimestamp);
140
        }
141
142 10
        return $expiry;
143
    }
144
}
145