Failed Conditions
Push — v7 ( a94305...5a1c51 )
by Florent
02:15
created

JWS::withEncodedPayload()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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