Completed
Push — master ( a70ca1...835655 )
by Jafar
06:00
created

JWS   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 220
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 220
rs 10
c 0
b 0
f 0
wmc 26

12 Methods

Rating   Name   Duplication   Size   Complexity  
A getSigner() 0 10 2
A sign() 0 6 1
A getTokenString() 0 5 1
A getEncodedSignature() 0 3 1
A __construct() 0 9 2
A getSigninInput() 0 9 2
A verify() 0 10 4
A setOriginalToken() 0 5 1
A setEncodedSignature() 0 5 1
C load() 0 34 8
A isSigned() 0 3 1
A getSignature() 0 7 2
1
<?php
2
/*
3
 * This file is part of the Guarded Authentication package.
4
 *
5
 * (c) Jafar Jabr <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace Jafar\Bundle\GuardedAuthenticationBundle\Api\JWTSigner;
11
12
use InvalidArgumentException;
13
use Jafar\Bundle\GuardedAuthenticationBundle\Api\JWTSigner\Base64\Base64Encoder;
14
use Jafar\Bundle\GuardedAuthenticationBundle\Api\JWTSigner\Base64\Base64UrlSafeEncoder;
15
use Jafar\Bundle\GuardedAuthenticationBundle\Api\JWTSigner\Base64\EncoderInterface;
16
use Jafar\Bundle\GuardedAuthenticationBundle\Api\JWTSigner\Signer\SignerInterface;
17
18
/**
19
 * Class JWS.
20
 *
21
 * @author Jafar Jabr <[email protected]>
22
 */
23
class JWS extends JWT
24
{
25
    protected $signature;
26
    protected $isSigned = false;
27
    protected $originalToken;
28
    protected $encodedSignature;
29
    protected $encryptionEngine;
30
    protected $supportedEncryptionEngines = 'OpenSSL';
31
32
    /**
33
     * Constructor.
34
     *
35
     * @param array $header An associative array of headers. The value can be any type accepted by json_encode or a JSON serializable object
36
     *
37
     * @param string $encryptionEngine
38
     */
39
    public function __construct($header = array(), $encryptionEngine = 'OpenSSL')
40
    {
41
        if ($encryptionEngine !== $this->supportedEncryptionEngines) {
0 ignored issues
show
introduced by
The condition $encryptionEngine !== $t...portedEncryptionEngines is always true.
Loading history...
42
            throw new InvalidArgumentException(sprintf('Encryption engine %s is not supported', $encryptionEngine));
43
        }
44
45
        $this->encryptionEngine = $encryptionEngine;
46
47
        parent::__construct(array(), $header);
48
    }
49
50
    /**
51
     * Signs the JWS signininput.
52
     *
53
     * @param resource|string $key
54
     * @param null | string $password
55
     *
56
     * @return string
57
     */
58
    public function sign($key, $password = null)
59
    {
60
        $this->signature = $this->getSigner()->sign($this->generateSigninInput(), $key, $password);
0 ignored issues
show
Unused Code introduced by
The call to Jafar\Bundle\GuardedAuth...SignerInterface::sign() has too many arguments starting with $password. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

60
        $this->signature = $this->getSigner()->/** @scrutinizer ignore-call */ sign($this->generateSigninInput(), $key, $password);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
61
        $this->isSigned = true;
62
63
        return $this->signature;
64
    }
65
66
    /**
67
     * Returns the signature representation of the JWS.
68
     *
69
     * @return string
70
     */
71
    public function getSignature()
72
    {
73
        if ($this->isSigned()) {
74
            return $this->signature;
75
        }
76
77
        return;
78
    }
79
80
    /**
81
     * Checks whether the JSW has already been signed.
82
     *
83
     * @return bool
84
     */
85
    public function isSigned()
86
    {
87
        return (bool) $this->isSigned;
88
    }
89
90
    /**
91
     * Returns the string representing the JWT.
92
     *
93
     * @return string
94
     */
95
    public function getTokenString()
96
    {
97
        $signinInput = $this->generateSigninInput();
98
99
        return sprintf('%s.%s', $signinInput, $this->encoder->encode($this->getSignature()));
100
    }
101
102
    /**
103
     * Creates an instance of a JWS from a JWT.
104
     *
105
     * @param string           $jwsTokenString
106
     * @param bool             $allowUnsecure
107
     * @param EncoderInterface $encoder
108
     * @param string           $encryptionEngine
109
     *
110
     * @return JWS
111
     *
112
     * @throws \InvalidArgumentException
113
     */
114
    public static function load(
115
        $jwsTokenString,
116
        $allowUnsecure = false,
117
        EncoderInterface $encoder = null,
118
        $encryptionEngine = 'OpenSSL'
119
    ) {
120
        if ($encoder === null) {
121
            $encoder = strpbrk($jwsTokenString, '+/=') ? new Base64Encoder() : new Base64UrlSafeEncoder();
122
        }
123
124
        $parts = explode('.', $jwsTokenString);
125
126
        if (count($parts) === 3) {
127
            $header = json_decode($encoder->decode($parts[0]), true);
128
            $payload = json_decode($encoder->decode($parts[1]), true);
129
130
            if (is_array($header) && is_array($payload)) {
131
                if (strtolower($header['alg']) === 'none' && !$allowUnsecure) {
132
                    throw new InvalidArgumentException(sprintf('The token "%s" cannot be validated in a secure context, as it uses the unallowed "none" algorithm', $jwsTokenString));
133
                }
134
135
                $jws = new static($header, $encryptionEngine);
136
137
                $jws->setEncoder($encoder)
138
                    ->setHeader($header)
139
                    ->setPayload($payload)
140
                    ->setOriginalToken($jwsTokenString)
141
                    ->setEncodedSignature($parts[2]);
142
143
                return $jws;
144
            }
145
        }
146
147
        throw new InvalidArgumentException(sprintf('The token "%s" is an invalid JWS', $jwsTokenString));
148
    }
149
150
    /**
151
     * Verifies that the internal signin input corresponds to the encoded
152
     * signature previously stored (@see JWS::load).
153
     *
154
     * @param resource|string $key
155
     * @param string          $algo The algorithms this JWS should be signed with. Use it if you want to restrict which algorithms you want to allow to be validated.
156
     *
157
     * @return bool
158
     */
159
    public function verify($key, $algo = null)
160
    {
161
        if (empty($key) || ($algo && $this->header['alg'] !== $algo)) {
162
            return false;
163
        }
164
165
        $decodedSignature = $this->encoder->decode($this->getEncodedSignature());
166
        $signinInput = $this->getSigninInput();
167
168
        return $this->getSigner()->verify($key, $decodedSignature, $signinInput);
169
    }
170
171
    /**
172
     * Get the original token signin input if it exists, otherwise generate the
173
     * signin input for the current JWS
174
     *
175
     * @return string
176
     */
177
    private function getSigninInput()
178
    {
179
        $parts = explode('.', $this->originalToken);
180
181
        if (count($parts) >= 2) {
182
            return sprintf('%s.%s', $parts[0], $parts[1]);
183
        }
184
185
        return $this->generateSigninInput();
186
    }
187
188
    /**
189
     * Sets the original base64 encoded token.
190
     *
191
     * @param string $originalToken
192
     *
193
     * @return JWS
194
     */
195
    private function setOriginalToken($originalToken)
196
    {
197
        $this->originalToken = $originalToken;
198
199
        return $this;
200
    }
201
202
    /**
203
     * Returns the base64 encoded signature.
204
     *
205
     * @return string
206
     */
207
    public function getEncodedSignature()
208
    {
209
        return $this->encodedSignature;
210
    }
211
212
    /**
213
     * Sets the base64 encoded signature.
214
     *
215
     * @param string $encodedSignature
216
     *
217
     * @return JWS
218
     */
219
    public function setEncodedSignature($encodedSignature)
220
    {
221
        $this->encodedSignature = $encodedSignature;
222
223
        return $this;
224
    }
225
226
    /**
227
     * Returns the signer responsible to encrypting / decrypting this JWS.
228
     *
229
     * @return SignerInterface
230
     *
231
     * @throws \InvalidArgumentException
232
     */
233
    protected function getSigner()
234
    {
235
        $signerClass = sprintf('Jafar\\Bundle\\GuardedAuthenticationBundle\\Api\\JWTSigner\\Signer\\%s\\%s', $this->encryptionEngine, $this->header['alg']);
236
237
        if (class_exists($signerClass)) {
238
            return new $signerClass();
239
        }
240
241
        throw new InvalidArgumentException(
242
            sprintf("The algorithm '%s' is not supported for %s", $this->header['alg'], $this->encryptionEngine)
243
        );
244
    }
245
}
246