Failed Conditions
Push — v7 ( 6b9564...530848 )
by Florent
04:53
created

JWS::checkPayloadEncoding()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 8
nc 7
nop 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 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, ?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 string|null $encodedPayload
61
     * @param bool        $isPayloadDetached
62
     *
63
     * @return JWS
64
     */
65
    public static function create(?string $payload, ?string $encodedPayload = null, bool $isPayloadDetached = false): JWS
66
    {
67
        return new self($payload, $encodedPayload, $isPayloadDetached);
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73
    public function getPayload(): ?string
74
    {
75
        return $this->payload;
76
    }
77
78
    /**
79
     * @return bool
80
     */
81
    public function isPayloadDetached(): bool
82
    {
83
        return $this->isPayloadDetached;
84
    }
85
86
    /**
87
     * @return string|null
88
     */
89
    public function getEncodedPayload(): ?string
90
    {
91
        if (true === $this->isPayloadDetached()) {
92
            return null;
93
        }
94
95
        return $this->encodedPayload;
96
    }
97
98
    /**
99
     * Returns the signature associated with the JWS.
100
     *
101
     * @return Signature[]
102
     */
103
    public function getSignatures(): array
104
    {
105
        return $this->signatures;
106
    }
107
108
    /**
109
     * @param int $id
110
     *
111
     * @return Signature
112
     */
113
    public function getSignature(int $id): Signature
114
    {
115
        if (isset($this->signatures[$id])) {
116
            return $this->signatures[$id];
117
        }
118
119
        throw new \InvalidArgumentException('The signature does not exist.');
120
    }
121
122
    /**
123
     * @param string      $signature
124
     * @param array       $protectedHeaders
125
     * @param string|null $encodedProtectedHeaders
126
     * @param array       $headers
127
     *
128
     * @return JWS
129
     */
130
    public function addSignature(string $signature, array $protectedHeaders, ?string $encodedProtectedHeaders, array $headers = []): JWS
131
    {
132
        $jws = clone $this;
133
        $jws->signatures[] = Signature::create($signature, $protectedHeaders, $encodedProtectedHeaders, $headers);
134
135
        return $jws;
136
    }
137
138
    /**
139
     * Returns the number of signature associated with the JWS.
140
     *
141
     *
142
     * @return int
143
     */
144
    public function countSignatures(): int
145
    {
146
        return count($this->signatures);
147
    }
148
149
    /**
150
     * @param int $id
151
     *
152
     * @return string
153
     */
154
    public function toCompactJSON(int $id): string
155
    {
156
        $signature = $this->getSignature($id);
157
        if (!empty($signature->getHeaders())) {
158
            throw new \LogicException('The signature contains unprotected headers and cannot be converted into compact JSON.');
159
        }
160
        if (!$this->isPayloadEncoded($signature) && !empty($this->getEncodedPayload())) {
161
            throw new \LogicException('Unable to convert the JWS with non-encoded payload.');
162
        }
163
164
        return sprintf(
165
            '%s.%s.%s',
166
            $signature->getEncodedProtectedHeaders(),
167
            $this->getEncodedPayload(),
168
            Base64Url::encode($signature->getSignature())
169
        );
170
    }
171
172
    /**
173
     * @param int $id
174
     *
175
     * @return string
176
     */
177
    public function toFlattenedJSON(int $id): string
178
    {
179
        $signature = $this->getSignature($id);
180
181
        $data = [];
182
        $values = [
183
            'payload' => $this->getEncodedPayload(),
184
            'protected' => $signature->getEncodedProtectedHeaders(),
185
            'header' => $signature->getHeaders(),
186
        ];
187
188
        foreach ($values as $key => $value) {
189
            if (!empty($value)) {
190
                $data[$key] = $value;
191
            }
192
        }
193
        $data['signature'] = Base64Url::encode($signature->getSignature());
194
195
        return json_encode($data);
196
    }
197
198
    /**
199
     * @return string
200
     */
201
    public function toJSON(): string
202
    {
203
        if (0 === $this->countSignatures()) {
204
            throw new \LogicException('No signature.');
205
        }
206
207
        $data = [];
208
        $this->checkPayloadEncoding();
209
210
        if (false === $this->isPayloadDetached()) {
211
            $data['payload'] = $this->getEncodedPayload();
212
        }
213
214
        $data['signatures'] = [];
215
        foreach ($this->getSignatures() as $signature) {
216
            $tmp = ['signature' => Base64Url::encode($signature->getSignature())];
217
            $values = [
218
                'protected' => $signature->getEncodedProtectedHeaders(),
219
                'header' => $signature->getHeaders(),
220
            ];
221
222
            foreach ($values as $key => $value) {
223
                if (!empty($value)) {
224
                    $tmp[$key] = $value;
225
                }
226
            }
227
            $data['signatures'][] = $tmp;
228
        }
229
230
        return json_encode($data);
231
    }
232
233
    /**
234
     * @param Signature $signature
235
     *
236
     * @return bool
237
     */
238
    private function isPayloadEncoded(Signature $signature): bool
239
    {
240
        return !$signature->hasProtectedHeader('b64') || true === $signature->getProtectedHeader('b64');
241
    }
242
243
    private function checkPayloadEncoding()
244
    {
245
        $is_encoded = null;
246
        foreach ($this->getSignatures() as $signature) {
247
            if (null === $is_encoded) {
248
                $is_encoded = $this->isPayloadEncoded($signature);
249
            }
250
            if (false === $this->isPayloadDetached()) {
251
                if ($is_encoded !== $this->isPayloadEncoded($signature)) {
252
                    throw new \LogicException('Foreign payload encoding detected.');
253
                }
254
            }
255
        }
256
    }
257
}
258