Failed Conditions
Push — v7 ( 9bed55...5d1eb6 )
by Florent
02:58
created

JWS::isPayloadDetached()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
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
     * @param mixed|null $encodedPayload
72
     * @param bool       $isPayloadDetached
73
     *
74
     * @return JWS
75
     */
76
    public static function createFromEncodedPayload($encodedPayload, bool $isPayloadDetached = false): JWS
77
    {
78
        $payload = Base64Url::decode($encodedPayload);
79
        $json = json_decode($payload, true);
80
        if (null !== $json && !empty($payload)) {
81
            $payload = $json;
82
        }
83
84
        return new self($payload, $encodedPayload, $isPayloadDetached);
85
    }
86
87
    /**
88
     * {@inheritdoc}
89
     */
90
    public function getPayload()
91
    {
92
        return $this->payload;
93
    }
94
95
    /**
96
     * @param mixed $payload
97
     *
98
     * @return JWS
99
     */
100
    public function withPayload($payload): JWS
101
    {
102
        $jwt = clone $this;
103
        $jwt->payload = $payload;
104
105
        return $jwt;
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111
    public function getClaim(string $key)
112
    {
113
        if ($this->hasClaim($key)) {
114
            return $this->payload[$key];
115
        }
116
        throw new \InvalidArgumentException(sprintf('The payload does not contain claim "%s".', $key));
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122
    public function getClaims(): array
123
    {
124
        if (!$this->hasClaims()) {
125
            throw new \InvalidArgumentException('The payload does not contain claims.');
126
        }
127
128
        return $this->payload;
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134
    public function hasClaim(string $key): bool
135
    {
136
        return $this->hasClaims() && array_key_exists($key, $this->payload);
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142
    public function hasClaims(): bool
143
    {
144
        return is_array($this->payload);
145
    }
146
147
    /**
148
     * @return bool
149
     */
150
    public function isPayloadDetached(): bool
151
    {
152
        return $this->isPayloadDetached;
153
    }
154
155
    /**
156
     * @return JWS
157
     */
158
    public function withDetachedPayload(): JWS
159
    {
160
        $jwt = clone $this;
161
        $jwt->isPayloadDetached = true;
162
163
        return $jwt;
164
    }
165
166
    /**
167
     * @return JWS
168
     */
169
    public function withAttachedPayload(): JWS
170
    {
171
        $jwt = clone $this;
172
        $jwt->isPayloadDetached = false;
173
174
        return $jwt;
175
    }
176
177
    /**
178
     * {@inheritdoc}
179
     */
180
    public function withEncodedPayload(string $encoded_payload): JWS
181
    {
182
        $jwt = clone $this;
183
        $jwt->encodedPayload = $encoded_payload;
184
185
        return $jwt;
186
    }
187
188
    /**
189
     * @param Signature $signature
190
     *
191
     * @return string|null
192
     */
193
    public function getEncodedPayload(Signature $signature): ?string
194
    {
195
        if (true === $this->isPayloadDetached()) {
196
            return null;
197
        }
198
        if (null !== $this->encodedPayload) {
199
            return $this->encodedPayload;
200
        }
201
        $payload = $this->getPayload();
202
        if (!is_string($payload)) {
203
            $payload = json_encode($payload);
204
        }
205
        Assertion::notNull($payload, 'Unsupported payload.');
206
207
        return $this->isPayloadEncoded($signature) ? Base64Url::encode($payload) : $payload;
208
    }
209
210
    /**
211
     * Returns the signature associated with the JWS.
212
     *
213
     * @return Signature[]
214
     */
215
    public function getSignatures(): array
216
    {
217
        return $this->signatures;
218
    }
219
220
    /**
221
     * @param int $id
222
     *
223
     * @return Signature
224
     */
225
    public function &getSignature(int $id): Signature
226
    {
227
        if (isset($this->signatures[$id])) {
228
            return $this->signatures[$id];
229
        }
230
        throw new \InvalidArgumentException('The signature does not exist.');
231
    }
232
233
    /**
234
     * @param string      $signature
235
     * @param string|null $encoded_protected_headers
236
     * @param array       $headers
237
     *
238
     * @return JWS
239
     */
240
    public function addSignature(string $signature, ?string $encoded_protected_headers, array $headers = []): JWS
241
    {
242
        $jws = clone $this;
243
        $jws->signatures[] = Signature::create($signature, $encoded_protected_headers, $headers);
244
245
        return $jws;
246
    }
247
248
    /**
249
     * Returns the number of signature associated with the JWS.
250
     *
251
     *
252
     * @return int
253
     */
254
    public function countSignatures(): int
255
    {
256
        return count($this->signatures);
257
    }
258
259
    /**
260
     * @param int $id
261
     *
262
     * @return string
263
     */
264
    public function toCompactJSON(int $id): string
265
    {
266
        $signature = $this->getSignature($id);
267
268
        Assertion::true(
269
            empty($signature->getHeaders()),
270
            'The signature contains unprotected headers and cannot be converted into compact JSON'
271
        );
272
        Assertion::true($this->isPayloadEncoded($signature) || empty($this->getEncodedPayload($signature)), 'Unable to convert the JWS with non-encoded payload.');
273
274
        return sprintf(
275
            '%s.%s.%s',
276
            $signature->getEncodedProtectedHeaders(),
277
            $this->getEncodedPayload($signature),
278
            Base64Url::encode($signature->getSignature())
279
        );
280
    }
281
282
    /**
283
     * @param int $id
284
     *
285
     * @return string
286
     */
287
    public function toFlattenedJSON(int $id): string
288
    {
289
        $signature = $this->getSignature($id);
290
291
        $data = [];
292
        $values = [
293
            'payload' => $this->getEncodedPayload($signature),
294
            'protected' => $signature->getEncodedProtectedHeaders(),
295
            'header' => $signature->getHeaders(),
296
        ];
297
298
        foreach ($values as $key => $value) {
299
            if (!empty($value)) {
300
                $data[$key] = $value;
301
            }
302
        }
303
        $data['signature'] = Base64Url::encode($signature->getSignature());
304
305
        return json_encode($data);
306
    }
307
308
    /**
309
     * @return string
310
     */
311
    public function toJSON(): string
312
    {
313
        Assertion::greaterThan($this->countSignatures(), 0, 'No signature.');
314
315
        $data = [];
316
        $this->checkPayloadEncoding();
317
318
        if (false === $this->isPayloadDetached()) {
319
            $data['payload'] = $this->getEncodedPayload($this->getSignature(0));
320
        }
321
322
        $data['signatures'] = [];
323
        foreach ($this->getSignatures() as $signature) {
324
            $tmp = ['signature' => Base64Url::encode($signature->getSignature())];
325
            $values = [
326
                'protected' => $signature->getEncodedProtectedHeaders(),
327
                'header' => $signature->getHeaders(),
328
            ];
329
330
            foreach ($values as $key => $value) {
331
                if (!empty($value)) {
332
                    $tmp[$key] = $value;
333
                }
334
            }
335
            $data['signatures'][] = $tmp;
336
        }
337
338
        return json_encode($data);
339
    }
340
341
    /**
342
     * @param Signature $signature
343
     *
344
     * @return bool
345
     */
346
    private function isPayloadEncoded(Signature $signature): bool
347
    {
348
        return !$signature->hasProtectedHeader('b64') || true === $signature->getProtectedHeader('b64');
349
    }
350
351
    private function checkPayloadEncoding()
352
    {
353
        $is_encoded = null;
354
        foreach ($this->getSignatures() as $signature) {
355
            if (null === $is_encoded) {
356
                $is_encoded = $this->isPayloadEncoded($signature);
357
            }
358
            if (false === $this->isPayloadDetached()) {
359
                Assertion::eq($is_encoded, $this->isPayloadEncoded($signature), 'Foreign payload encoding detected. The JWS cannot be converted.');
360
            }
361
        }
362
    }
363
}
364