Failed Conditions
Push — v7 ( e9e51b...d9c0af )
by Florent
03:20
created

JWS   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 285
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 37
lcom 1
cbo 2
dl 0
loc 285
rs 8.6
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A create() 0 4 1
A getPayload() 0 4 1
A withPayload() 0 7 1
A isPayloadDetached() 0 4 1
A withDetachedPayload() 0 7 1
A withAttachedPayload() 0 7 1
A withEncodedPayload() 0 7 1
A getEncodedPayload() 0 12 4
A getSignatures() 0 4 1
A getSignature() 0 7 2
A addSignature() 0 7 1
A countSignatures() 0 4 1
A toCompactJSON() 0 17 4
A toFlattenedJSON() 0 20 3
B toJSON() 0 31 6
A isPayloadEncoded() 0 4 2
B checkPayloadEncoding() 0 14 5
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 Base64Url\Base64Url;
17
use Jose\Component\Core\JWTInterface;
18
19
/**
20
 * Class JWS.
21
 */
22
final class JWS implements JWTInterface
23
{
24
    /**
25
     * @var bool
26
     */
27
    private $isPayloadDetached = false;
28
29
    /**
30
     * @var string|null
31
     */
32
    private $encodedPayload = null;
33
34
    /**
35
     * @var Signature[]
36
     */
37
    private $signatures = [];
38
39
    /**
40
     * @var string|null
41
     */
42
    private $payload = null;
43
44
    /**
45
     * JWS constructor.
46
     *
47
     * @param string|null $payload
48
     * @param string|null $encodedPayload
49
     * @param bool        $isPayloadDetached
50
     */
51
    private function __construct(?string $payload = null, ?string $encodedPayload = null, bool $isPayloadDetached = false)
52
    {
53
        $this->payload = $payload;
54
        $this->encodedPayload = $encodedPayload;
55
        $this->isPayloadDetached = $isPayloadDetached;
56
    }
57
58
    /**
59
     * @param string|null $payload
60
     * @param bool        $isPayloadDetached
61
     *
62
     * @return JWS
63
     */
64
    public static function create(?string $payload = null, bool $isPayloadDetached = false): JWS
65
    {
66
        return new self($payload, null, $isPayloadDetached);
67
    }
68
69
    /**
70
     * {@inheritdoc}
71
     */
72
    public function getPayload(): ?string
73
    {
74
        return $this->payload;
75
    }
76
77
    /**
78
     * @param string $payload
79
     *
80
     * @return JWS
81
     */
82
    public function withPayload(string $payload): JWS
83
    {
84
        $jwt = clone $this;
85
        $jwt->payload = $payload;
86
87
        return $jwt;
88
    }
89
90
    /**
91
     * @return bool
92
     */
93
    public function isPayloadDetached(): bool
94
    {
95
        return $this->isPayloadDetached;
96
    }
97
98
    /**
99
     * @return JWS
100
     */
101
    public function withDetachedPayload(): JWS
102
    {
103
        $jwt = clone $this;
104
        $jwt->isPayloadDetached = true;
105
106
        return $jwt;
107
    }
108
109
    /**
110
     * @return JWS
111
     */
112
    public function withAttachedPayload(): JWS
113
    {
114
        $jwt = clone $this;
115
        $jwt->isPayloadDetached = false;
116
117
        return $jwt;
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    public function withEncodedPayload(string $encoded_payload): JWS
124
    {
125
        $jwt = clone $this;
126
        $jwt->encodedPayload = $encoded_payload;
127
128
        return $jwt;
129
    }
130
131
    /**
132
     * @param Signature $signature
133
     *
134
     * @return string|null
135
     */
136
    public function getEncodedPayload(Signature $signature): ?string
137
    {
138
        if (true === $this->isPayloadDetached()) {
139
            return null;
140
        }
141
        if (null !== $this->encodedPayload) {
142
            return $this->encodedPayload;
143
        }
144
        $payload = $this->getPayload();
145
146
        return $this->isPayloadEncoded($signature) ? Base64Url::encode($payload) : $payload;
147
    }
148
149
    /**
150
     * Returns the signature associated with the JWS.
151
     *
152
     * @return Signature[]
153
     */
154
    public function getSignatures(): array
155
    {
156
        return $this->signatures;
157
    }
158
159
    /**
160
     * @param int $id
161
     *
162
     * @return Signature
163
     */
164
    public function getSignature(int $id): Signature
165
    {
166
        if (isset($this->signatures[$id])) {
167
            return $this->signatures[$id];
168
        }
169
        throw new \InvalidArgumentException('The signature does not exist.');
170
    }
171
172
    /**
173
     * @param string      $signature
174
     * @param string|null $encoded_protected_headers
175
     * @param array       $headers
176
     *
177
     * @return JWS
178
     */
179
    public function addSignature(string $signature, ?string $encoded_protected_headers, array $headers = []): JWS
180
    {
181
        $jws = clone $this;
182
        $jws->signatures[] = Signature::create($signature, $encoded_protected_headers, $headers);
183
184
        return $jws;
185
    }
186
187
    /**
188
     * Returns the number of signature associated with the JWS.
189
     *
190
     *
191
     * @return int
192
     */
193
    public function countSignatures(): int
194
    {
195
        return count($this->signatures);
196
    }
197
198
    /**
199
     * @param int $id
200
     *
201
     * @return string
202
     */
203
    public function toCompactJSON(int $id): string
204
    {
205
        $signature = $this->getSignature($id);
206
        if (!empty($signature->getHeaders())) {
207
            throw new \LogicException('The signature contains unprotected headers and cannot be converted into compact JSON.');
208
        }
209
        if (!$this->isPayloadEncoded($signature) && !empty($this->getEncodedPayload($signature))) {
210
            throw new \LogicException('Unable to convert the JWS with non-encoded payload.');
211
        }
212
213
        return sprintf(
214
            '%s.%s.%s',
215
            $signature->getEncodedProtectedHeaders(),
216
            $this->getEncodedPayload($signature),
217
            Base64Url::encode($signature->getSignature())
218
        );
219
    }
220
221
    /**
222
     * @param int $id
223
     *
224
     * @return string
225
     */
226
    public function toFlattenedJSON(int $id): string
227
    {
228
        $signature = $this->getSignature($id);
229
230
        $data = [];
231
        $values = [
232
            'payload' => $this->getEncodedPayload($signature),
233
            'protected' => $signature->getEncodedProtectedHeaders(),
234
            'header' => $signature->getHeaders(),
235
        ];
236
237
        foreach ($values as $key => $value) {
238
            if (!empty($value)) {
239
                $data[$key] = $value;
240
            }
241
        }
242
        $data['signature'] = Base64Url::encode($signature->getSignature());
243
244
        return json_encode($data);
245
    }
246
247
    /**
248
     * @return string
249
     */
250
    public function toJSON(): string
251
    {
252
        if (0 === $this->countSignatures()) {
253
            throw new \LogicException('No signature.');
254
        }
255
256
        $data = [];
257
        $this->checkPayloadEncoding();
258
259
        if (false === $this->isPayloadDetached()) {
260
            $data['payload'] = $this->getEncodedPayload($this->getSignature(0));
261
        }
262
263
        $data['signatures'] = [];
264
        foreach ($this->getSignatures() as $signature) {
265
            $tmp = ['signature' => Base64Url::encode($signature->getSignature())];
266
            $values = [
267
                'protected' => $signature->getEncodedProtectedHeaders(),
268
                'header' => $signature->getHeaders(),
269
            ];
270
271
            foreach ($values as $key => $value) {
272
                if (!empty($value)) {
273
                    $tmp[$key] = $value;
274
                }
275
            }
276
            $data['signatures'][] = $tmp;
277
        }
278
279
        return json_encode($data);
280
    }
281
282
    /**
283
     * @param Signature $signature
284
     *
285
     * @return bool
286
     */
287
    private function isPayloadEncoded(Signature $signature): bool
288
    {
289
        return !$signature->hasProtectedHeader('b64') || true === $signature->getProtectedHeader('b64');
290
    }
291
292
    private function checkPayloadEncoding()
293
    {
294
        $is_encoded = null;
295
        foreach ($this->getSignatures() as $signature) {
296
            if (null === $is_encoded) {
297
                $is_encoded = $this->isPayloadEncoded($signature);
298
            }
299
            if (false === $this->isPayloadDetached()) {
300
                if ($is_encoded !== $this->isPayloadEncoded($signature)) {
301
                    throw new \LogicException('Foreign payload encoding detected. The JWS cannot be converted.');
302
                }
303
            }
304
        }
305
    }
306
}
307