TokenValidator::parse()   A
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 5
nop 1
dl 0
loc 29
rs 9.456
c 0
b 0
f 0
1
<?php
2
3
namespace Schnittstabil\Csrf\TokenService;
4
5
use Base64Url\Base64Url;
6
7
/**
8
 * A TokenValidator.
9
 */
10
class TokenValidator
11
{
12
    protected $sign;
13
    protected $base64url;
14
15
    /**
16
     * Create a new TokenValidator.
17
     *
18
     * @param callable $sign Callable used for generating the token signatures
19
     */
20
    public function __construct(callable $sign)
21
    {
22
        $this->sign = $sign;
23
        $this->base64url = new Base64Url();
24
    }
25
26
    /**
27
     * Determine constraint violations of a CSRF token.
28
     *
29
     * @param string $nonce  Value used to associate a client session
30
     * @param string $token  The token to validate
31
     * @param int    $now    The current time, defaults to `time()`
32
     * @param int    $leeway The leeway in seconds
33
     *
34
     * @return InvalidArgumentException[] Constraint violations; if $token is valid, an empty array
35
     */
36
    public function __invoke($nonce, $token, $now = null, $leeway = 0)
37
    {
38
        $parseResult = $this->parse($token);
39
40
        if ($violations = $parseResult->violations) {
41
            return $violations;
42
        }
43
44
        return $this->validatePayload($nonce, $parseResult->payload, $now, $leeway);
45
    }
46
47
    /**
48
     * Parse a CSRF token.
49
     *
50
     * @param string $token The token to parse
51
     *
52
     * @return \stdClass Parse result containing payload and constraint violations
53
     */
54
    protected function parse($token)
55
    {
56
        $result = new \stdClass();
57
        $result->violations = [];
58
        $result->payload = null;
59
        $segments = explode('.', $token);
60
61
        if (count($segments) !== 2) {
62
            $result->violations[] = new \InvalidArgumentException('Wrong number of segments');
63
64
            return $result;
65
        }
66
67
        list($payloadBase64, $signature) = $segments;
68
69
        $sign = $this->sign;
70
71
        if ($signature !== $sign($payloadBase64)) {
72
            $result->violations[] = new \InvalidArgumentException('Signature verification failed');
73
        }
74
75
        $result->payload = json_decode($this->base64url->decode($payloadBase64));
76
77
        if (!($result->payload instanceof \stdClass)) {
78
            $result->violations[] = new \InvalidArgumentException('Invalid payload encoding');
79
        }
80
81
        return $result;
82
    }
83
84
    /**
85
     * Validate the payload of a CSRF token.
86
     *
87
     * @param string    $nonce   Value used to associate a client session
88
     * @param \stdClass $payload The token payload to validate
89
     * @param int       $now     The current time, defaults to `time()`
90
     * @param int       $leeway  The leeway in seconds
91
     *
92
     * @return InvalidArgumentException[] Constraint violations; if $payload is valid, an empty array
93
     */
94
    protected function validatePayload($nonce, \stdClass $payload, $now = null, $leeway = 0)
95
    {
96
        $now = $now ?: time();
97
        $violations = [];
98
99 View Code Duplication
        if ($payload->exp + $leeway <= $now) {
100
            $exp = date(\DateTime::ISO8601, $payload->exp);
101
            $violations[] = new \InvalidArgumentException('Token already expired at '.$exp);
102
        }
103
104 View Code Duplication
        if ($now + $leeway < $payload->iat) {
105
            $issuedAt = date(\DateTime::ISO8601, $payload->iat);
106
            $violations[] = new \InvalidArgumentException('Cannot handle token prior to '.$issuedAt);
107
        }
108
109
        if ($payload->nonce !== $nonce) {
110
            $violations[] = new \InvalidArgumentException('Nonce mismatch');
111
        }
112
113
        return $violations;
114
    }
115
}
116