Completed
Push — master ( b9dd72...c820a3 )
by Marcel
02:31
created

Authenticator::verify()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 27
ccs 17
cts 17
cp 1
rs 8.439
cc 5
eloc 14
nc 5
nop 1
crap 5
1
<?php
2
3
namespace UMA\Psr\Http\Message\HMAC;
4
5
use Psr\Http\Message\MessageInterface;
6
use Psr\Http\Message\RequestInterface;
7
use UMA\Psr\Http\Message\Serializer\MessageSerializer;
8
9
class Authenticator
10
{
11
    /**
12
     * @var string
13
     */
14
    private $secret;
15
16
    /**
17
     * @param string $secret
18
     */
19 72
    public function __construct($secret)
20
    {
21 72
        $this->secret = $secret;
22 72
    }
23
24
    /**
25
     * @param MessageInterface $message
26
     *
27
     * @return MessageInterface The signed message.
28
     *
29
     * @throws \InvalidArgumentException When $message is an implementation of
30
     *                                   MessageInterface that cannot be
31
     *                                   serialized and thus neither signed.
32
     */
33 70
    public function sign(MessageInterface $message)
34
    {
35 70
        $preSignedMessage = $message->withHeader(
36 70
            Specification::SIGN_HEADER,
37 70
            $this->getSignedHeadersString($message)
38 70
        );
39
40 70
        $serialization = MessageSerializer::serialize($preSignedMessage);
41
42 70
        return $preSignedMessage->withHeader(
43 70
            Specification::AUTH_HEADER,
44 70
            Specification::AUTH_PREFIX.' '.Specification::doHMACSignature($serialization, $this->secret)
45 70
        );
46
    }
47
48
    /**
49
     * @param MessageInterface $message
50
     *
51
     * @return bool Signature verification outcome.
52
     *
53
     * @throws \InvalidArgumentException When $message is an implementation of
54
     *                                   MessageInterface that cannot be
55
     *                                   serialized and thus neither verified.
56
     */
57 72
    public function verify(MessageInterface $message)
58
    {
59 72
        if (empty($authHeader = $message->getHeaderLine(Specification::AUTH_HEADER))) {
60 1
            return false;
61
        }
62
63 71
        if (0 === preg_match('#^'.Specification::AUTH_PREFIX.' ([+/0-9A-Za-z]{43}=)$#', $authHeader, $matches)) {
64 1
            return false;
65
        }
66
67 70
        $coveredHeaders = explode(',', $message->getHeaderLine(Specification::SIGN_HEADER));
68
69 70
        foreach ($message->getHeaders() as $name => $value) {
70 70
            if (!in_array(mb_strtolower($name), $coveredHeaders)) {
71 70
                $message = $message->withoutHeader($name);
72 70
            }
73 70
        }
74
75 70
        $clientSideSignature = $matches[1];
76
77 70
        $serverSideSignature = Specification::doHMACSignature(
78 70
            MessageSerializer::serialize($message),
79 70
            $this->secret
80 70
        );
81
82 70
        return hash_equals($serverSideSignature, $clientSideSignature);
83
    }
84
85
    /**
86
     * @param MessageInterface $message
87
     *
88
     * @return string
89
     */
90 70
    private function getSignedHeadersString(MessageInterface $message)
91
    {
92 70
        $headers = array_keys(array_change_key_case($message->getHeaders(), CASE_LOWER));
93 70
        array_push($headers, mb_strtolower(Specification::SIGN_HEADER));
94
95
        // Some of the tested RequestInterface implementations do not include
96
        // the Host header in $message->getHeaders(), so it is explicitly set when needed
97 70
        if ($message instanceof RequestInterface && !in_array('host', $headers)) {
98 6
            array_push($headers, 'host');
99 6
        }
100
101
        // There is no guarantee about the order of the headers returned by
102
        // $message->getHeaders(), so they are explicitly sorted in order
103
        // to produce the exact same string regardless of the underlying implementation
104 70
        sort($headers);
105
106 70
        return implode(',', $headers);
107
    }
108
}
109