Failed Conditions
Push — v7 ( 5d1eb6...6055df )
by Florent
02:59
created

JWS::createFromEncodedPayload()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 2
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2017 Spomky-Labs
9
 *
10
 * This software may be modified and distributed under the terms
11
 * of the MIT license.  See the LICENSE file for details.
12
 */
13
14
namespace Jose\Component\Signature;
15
16
use Assert\Assertion;
17
use Base64Url\Base64Url;
18
use Jose\Component\Core\JWTInterface;
19
20
/**
21
 * Class JWS.
22
 */
23
final class JWS implements JWTInterface
24
{
25
    /**
26
     * @var bool
27
     */
28
    private $isPayloadDetached = false;
29
30
    /**
31
     * @var string|null
32
     */
33
    private $encodedPayload = null;
34
35
    /**
36
     * @var Signature[]
37
     */
38
    private $signatures = [];
39
40
    /**
41
     * @var mixed|null
42
     */
43
    private $payload = null;
44
45
    /**
46
     * JWS constructor.
47
     *
48
     * @param mixed|null $payload
49
     * @param mixed|null $encodedPayload
50
     * @param bool       $isPayloadDetached
51
     */
52
    private function __construct($payload = null, $encodedPayload = null, bool $isPayloadDetached = false)
53
    {
54
        $this->payload = $payload;
55
        $this->encodedPayload = $encodedPayload;
56
        $this->isPayloadDetached = $isPayloadDetached;
57
    }
58
59
    /**
60
     * @param mixed|null $payload
61
     * @param bool       $isPayloadDetached
62
     *
63
     * @return JWS
64
     */
65
    public static function create($payload = null, bool $isPayloadDetached = false): JWS
66
    {
67
        return new self($payload, null, $isPayloadDetached);
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73
    public function getPayload()
74
    {
75
        return $this->payload;
76
    }
77
78
    /**
79
     * @param mixed $payload
80
     *
81
     * @return JWS
82
     */
83
    public function withPayload($payload): JWS
84
    {
85
        $jwt = clone $this;
86
        $jwt->payload = $payload;
87
88
        return $jwt;
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94
    public function getClaim(string $key)
95
    {
96
        if ($this->hasClaim($key)) {
97
            return $this->payload[$key];
98
        }
99
        throw new \InvalidArgumentException(sprintf('The payload does not contain claim "%s".', $key));
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105
    public function getClaims(): array
106
    {
107
        if (!$this->hasClaims()) {
108
            throw new \InvalidArgumentException('The payload does not contain claims.');
109
        }
110
111
        return $this->payload;
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function hasClaim(string $key): bool
118
    {
119
        return $this->hasClaims() && array_key_exists($key, $this->payload);
120
    }
121
122
    /**
123
     * {@inheritdoc}
124
     */
125
    public function hasClaims(): bool
126
    {
127
        return is_array($this->payload);
128
    }
129
130
    /**
131
     * @return bool
132
     */
133
    public function isPayloadDetached(): bool
134
    {
135
        return $this->isPayloadDetached;
136
    }
137
138
    /**
139
     * @return JWS
140
     */
141
    public function withDetachedPayload(): JWS
142
    {
143
        $jwt = clone $this;
144
        $jwt->isPayloadDetached = true;
145
146
        return $jwt;
147
    }
148
149
    /**
150
     * @return JWS
151
     */
152
    public function withAttachedPayload(): JWS
153
    {
154
        $jwt = clone $this;
155
        $jwt->isPayloadDetached = false;
156
157
        return $jwt;
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     */
163
    public function withEncodedPayload(string $encoded_payload): JWS
164
    {
165
        $jwt = clone $this;
166
        $jwt->encodedPayload = $encoded_payload;
167
168
        return $jwt;
169
    }
170
171
    /**
172
     * @param Signature $signature
173
     *
174
     * @return string|null
175
     */
176
    public function getEncodedPayload(Signature $signature): ?string
177
    {
178
        if (true === $this->isPayloadDetached()) {
179
            return null;
180
        }
181
        if (null !== $this->encodedPayload) {
182
            return $this->encodedPayload;
183
        }
184
        $payload = $this->getPayload();
185
        if (!is_string($payload)) {
186
            $payload = json_encode($payload);
187
        }
188
        Assertion::notNull($payload, 'Unsupported payload.');
189
190
        return $this->isPayloadEncoded($signature) ? Base64Url::encode($payload) : $payload;
191
    }
192
193
    /**
194
     * Returns the signature associated with the JWS.
195
     *
196
     * @return Signature[]
197
     */
198
    public function getSignatures(): array
199
    {
200
        return $this->signatures;
201
    }
202
203
    /**
204
     * @param int $id
205
     *
206
     * @return Signature
207
     */
208
    public function getSignature(int $id): Signature
209
    {
210
        if (isset($this->signatures[$id])) {
211
            return $this->signatures[$id];
212
        }
213
        throw new \InvalidArgumentException('The signature does not exist.');
214
    }
215
216
    /**
217
     * @param string      $signature
218
     * @param string|null $encoded_protected_headers
219
     * @param array       $headers
220
     *
221
     * @return JWS
222
     */
223
    public function addSignature(string $signature, ?string $encoded_protected_headers, array $headers = []): JWS
224
    {
225
        $jws = clone $this;
226
        $jws->signatures[] = Signature::create($signature, $encoded_protected_headers, $headers);
227
228
        return $jws;
229
    }
230
231
    /**
232
     * Returns the number of signature associated with the JWS.
233
     *
234
     *
235
     * @return int
236
     */
237
    public function countSignatures(): int
238
    {
239
        return count($this->signatures);
240
    }
241
242
    /**
243
     * @param int $id
244
     *
245
     * @return string
246
     */
247
    public function toCompactJSON(int $id): string
248
    {
249
        $signature = $this->getSignature($id);
250
251
        Assertion::true(
252
            empty($signature->getHeaders()),
253
            'The signature contains unprotected headers and cannot be converted into compact JSON'
254
        );
255
        Assertion::true($this->isPayloadEncoded($signature) || empty($this->getEncodedPayload($signature)), 'Unable to convert the JWS with non-encoded payload.');
256
257
        return sprintf(
258
            '%s.%s.%s',
259
            $signature->getEncodedProtectedHeaders(),
260
            $this->getEncodedPayload($signature),
261
            Base64Url::encode($signature->getSignature())
262
        );
263
    }
264
265
    /**
266
     * @param int $id
267
     *
268
     * @return string
269
     */
270
    public function toFlattenedJSON(int $id): string
271
    {
272
        $signature = $this->getSignature($id);
273
274
        $data = [];
275
        $values = [
276
            'payload' => $this->getEncodedPayload($signature),
277
            'protected' => $signature->getEncodedProtectedHeaders(),
278
            'header' => $signature->getHeaders(),
279
        ];
280
281
        foreach ($values as $key => $value) {
282
            if (!empty($value)) {
283
                $data[$key] = $value;
284
            }
285
        }
286
        $data['signature'] = Base64Url::encode($signature->getSignature());
287
288
        return json_encode($data);
289
    }
290
291
    /**
292
     * @return string
293
     */
294
    public function toJSON(): string
295
    {
296
        Assertion::greaterThan($this->countSignatures(), 0, 'No signature.');
297
298
        $data = [];
299
        $this->checkPayloadEncoding();
300
301
        if (false === $this->isPayloadDetached()) {
302
            $data['payload'] = $this->getEncodedPayload($this->getSignature(0));
303
        }
304
305
        $data['signatures'] = [];
306
        foreach ($this->getSignatures() as $signature) {
307
            $tmp = ['signature' => Base64Url::encode($signature->getSignature())];
308
            $values = [
309
                'protected' => $signature->getEncodedProtectedHeaders(),
310
                'header' => $signature->getHeaders(),
311
            ];
312
313
            foreach ($values as $key => $value) {
314
                if (!empty($value)) {
315
                    $tmp[$key] = $value;
316
                }
317
            }
318
            $data['signatures'][] = $tmp;
319
        }
320
321
        return json_encode($data);
322
    }
323
324
    /**
325
     * @param Signature $signature
326
     *
327
     * @return bool
328
     */
329
    private function isPayloadEncoded(Signature $signature): bool
330
    {
331
        return !$signature->hasProtectedHeader('b64') || true === $signature->getProtectedHeader('b64');
332
    }
333
334
    private function checkPayloadEncoding()
335
    {
336
        $is_encoded = null;
337
        foreach ($this->getSignatures() as $signature) {
338
            if (null === $is_encoded) {
339
                $is_encoded = $this->isPayloadEncoded($signature);
340
            }
341
            if (false === $this->isPayloadDetached()) {
342
                Assertion::eq($is_encoded, $this->isPayloadEncoded($signature), 'Foreign payload encoding detected. The JWS cannot be converted.');
343
            }
344
        }
345
    }
346
}
347