Failed Conditions
Push — ng ( 7370de...a36c25 )
by Florent
19:12
created

Client::createFromJson()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 11
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2018 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 OAuth2Framework\Component\Server\Core\Client;
15
16
use Jose\Component\Core\JWK;
17
use Jose\Component\Core\JWKSet;
18
use OAuth2Framework\Component\Server\Core\Client\Event as ClientEvent;
19
use OAuth2Framework\Component\Server\Core\DataBag\DataBag;
20
use OAuth2Framework\Component\Server\Core\Event\Event;
21
use OAuth2Framework\Component\Server\Core\ResourceOwner\ResourceOwnerId;
22
use OAuth2Framework\Component\Server\Core\ResourceOwner\ResourceOwner;
23
use OAuth2Framework\Component\Server\Core\UserAccount\UserAccountId;
24
use OAuth2Framework\Component\Server\Core\DomainObject;
25
use SimpleBus\Message\Recorder\ContainsRecordedMessages;
26
use SimpleBus\Message\Recorder\PrivateMessageRecorderCapabilities;
27
28
/**
29
 * Class Client.
30
 *
31
 * This class is used for every client types.
32
 * A client is a resource owner with a set of allowed grant types and can perform requests against
33
 * available endpoints.
34
 */
35
final class Client implements ResourceOwner, ContainsRecordedMessages, DomainObject
36
{
37
    use PrivateMessageRecorderCapabilities;
38
39
    /**
40
     * @var bool
41
     */
42
    private $deleted = false;
43
44
    /**
45
     * @var UserAccountId|null
46
     */
47
    private $ownerId = null;
48
49
    /**
50
     * @var ClientId|null
51
     */
52
    private $clientId = null;
53
54
    /**
55
     * @var DataBag
56
     */
57
    protected $parameters;
58
59
    /**
60
     * Client constructor.
61
     */
62
    private function __construct()
63
    {
64
        $this->parameters = DataBag::create([]);
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70
    public static function getSchema(): string
71
    {
72
        return 'https://oauth2-framework.spomky-labs.com/schemas/model/client/1.0/schema';
73
    }
74
75
    /**
76
     * @return Client
77
     */
78
    public static function createEmpty(): self
79
    {
80
        return new self();
81
    }
82
83
    /**
84
     * @param ClientId           $clientId
85
     * @param DataBag            $parameters
86
     * @param UserAccountId|null $ownerId
87
     *
88
     * @return Client
89
     */
90
    public function create(ClientId $clientId, DataBag $parameters, ? UserAccountId $ownerId): self
91
    {
92
        $clone = clone $this;
93
        $clone->clientId = $clientId;
94
        $clone->parameters = $parameters;
95
        $clone->ownerId = $ownerId;
96
97
        $event = ClientEvent\ClientCreatedEvent::create($clone->clientId, $parameters, $ownerId);
98
        $clone->record($event);
99
100
        return $clone;
101
    }
102
103
    /**
104
     * @return UserAccountId|null
105
     */
106
    public function getOwnerId(): ? UserAccountId
107
    {
108
        return $this->ownerId;
109
    }
110
111
    /**
112
     * @param UserAccountId $ownerId
113
     *
114
     * @return Client
115
     */
116
    public function withOwnerId(UserAccountId $ownerId): self
117
    {
118
        if ($this->getOwnerId()->getValue() === $ownerId->getValue()) {
119
            return $this;
120
        }
121
122
        $clone = clone $this;
123
        $clone->ownerId = $ownerId;
124
        $event = ClientEvent\ClientOwnerChangedEvent::create($clone->getPublicId(), $ownerId);
125
        $clone->record($event);
126
127
        return $clone;
128
    }
129
130
    /**
131
     * @param DataBag $parameters
132
     *
133
     * @return Client
134
     */
135
    public function withParameters(DataBag $parameters): self
136
    {
137
        $clone = clone $this;
138
        $clone->parameters = $parameters;
139
        $event = ClientEvent\ClientParametersUpdatedEvent::create($clone->getPublicId(), $parameters);
140
        $clone->record($event);
141
142
        return $clone;
143
    }
144
145
    /**
146
     * @return Client
147
     */
148
    public function markAsDeleted(): self
149
    {
150
        $clone = clone $this;
151
        $clone->deleted = true;
152
        $event = ClientEvent\ClientDeletedEvent::create($clone->getPublicId());
153
        $clone->record($event);
154
155
        return $clone;
156
    }
157
158
    /**
159
     * @return bool
160
     */
161
    public function isDeleted(): bool
162
    {
163
        return $this->deleted;
164
    }
165
166
    /**
167
     * @param string $grant_type
168
     *
169
     * @return bool
170
     */
171
    public function isGrantTypeAllowed(string $grant_type): bool
172
    {
173
        $grant_types = $this->has('grant_types') ? $this->get('grant_types') : [];
174
        if (!is_array($grant_types)) {
175
            throw new \InvalidArgumentException('The metadata "grant_types" must be an array.');
176
        }
177
178
        return in_array($grant_type, $grant_types);
179
    }
180
181
    /**
182
     * @param string $response_type
183
     *
184
     * @return bool
185
     */
186
    public function isResponseTypeAllowed(string $response_type): bool
187
    {
188
        $response_types = $this->has('response_types') ? $this->get('response_types') : [];
189
        if (!is_array($response_type)) {
190
            throw new \InvalidArgumentException('The metadata "response_types" must be an array.');
191
        }
192
193
        return in_array($response_type, $response_types);
194
    }
195
196
    /**
197
     * @param string $token_type
198
     *
199
     * @return bool
200
     */
201
    public function isTokenTypeAllowed(string $token_type): bool
202
    {
203
        if (!$this->has('token_types')) {
204
            return true;
205
        }
206
        $token_types = $this->get('token_types');
207
        if (!is_array($token_types)) {
208
            throw new \InvalidArgumentException('The metadata "token_types" must be an array.');
209
        }
210
211
        return in_array($token_type, $token_types);
212
    }
213
214
    /**
215
     * @return bool
216
     */
217
    public function isPublic(): bool
218
    {
219
        return 'none' === $this->getTokenEndpointAuthenticationMethod();
220
    }
221
222
    /**
223
     * @return string
224
     */
225
    public function getTokenEndpointAuthenticationMethod(): string
226
    {
227
        if ($this->has('token_endpoint_auth_method')) {
228
            return $this->get('token_endpoint_auth_method');
229
        }
230
231
        return 'client_secret_basic';
232
    }
233
234
    /**
235
     * @return int
236
     */
237
    public function getClientCredentialsExpiresAt(): int
238
    {
239
        if ($this->has('client_secret_expires_at')) {
240
            return $this->get('client_secret_expires_at');
241
        }
242
243
        return 0;
244
    }
245
246
    /**
247
     * @return bool
248
     */
249
    public function areClientCredentialsExpired(): bool
250
    {
251
        if (0 === $this->getClientCredentialsExpiresAt()) {
252
            return false;
253
        }
254
255
        return time() > $this->getClientCredentialsExpiresAt();
256
    }
257
258
    /**
259
     * @return bool
260
     */
261
    public function hasPublicKeySet(): bool
262
    {
263
        return $this->has('jwks') || $this->has('jwks_uri') || $this->has('client_secret');
264
    }
265
266
    /**
267
     * @return JWKSet
268
     */
269
    public function getPublicKeySet(): JWKSet
270
    {
271
        if (!$this->hasPublicKeySet()) {
272
            throw new \LogicException('The client has no public key set');
273
        }
274
275
        $jwkset = null;
276
        if ($this->has('jwks')) {
277
            $jwkset = JWKSet::createFromJson($this->get('jwks'));
278
        }
279
        //FIXME: find a way to allow jku
280
        /*if ($this->has('jwks_uri')) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
281
            $jwkset = JKUFactory::createFromJKU($this->get('jwks_uri'));
282
        }*/
283
        if ($this->has('client_secret')) {
284
            $key = JWK::create([
285
                'kty' => 'oct',
286
                'use' => 'sig',
287
                'k' => $this->get('client_secret'),
288
            ]);
289
            if (null === $jwkset) {
290
                $jwkset = JWKSet::createFromKeys([]);
291
                $jwkset = $jwkset->with($key);
292
293
                return $jwkset;
294
            } else {
295
                $jwkset = $jwkset->with($key);
296
            }
297
        }
298
299
        return $jwkset;
300
    }
301
302
    /**
303
     * {@inheritdoc}
304
     */
305
    public function getPublicId(): ResourceOwnerId
306
    {
307
        if (null === $this->clientId) {
308
            throw new \RuntimeException('Client not initialized.');
309
        }
310
311
        return $this->clientId;
312
    }
313
314
    /**
315
     * {@inheritdoc}
316
     */
317
    public function has(string $key): bool
318
    {
319
        return $this->parameters->has($key);
320
    }
321
322
    /**
323
     * {@inheritdoc}
324
     */
325
    public function get(string $key)
326
    {
327
        if (!$this->has($key)) {
328
            throw new \InvalidArgumentException(sprintf('Configuration value with key "%s" does not exist.', $key));
329
        }
330
331
        return $this->parameters->get($key);
332
    }
333
334
    /**
335
     * @return array
336
     */
337
    public function all(): array
338
    {
339
        $all = $this->parameters->all();
340
        $all['client_id'] = $this->getPublicId()->getValue();
341
342
        return $all;
343
    }
344
345
    /**
346
     * {@inheritdoc}
347
     */
348
    public function jsonSerialize()
349
    {
350
        $data = [
351
            '$schema' => $this->getSchema(),
352
            'type' => get_class($this),
353
            'client_id' => $this->getPublicId()->getValue(),
354
            'owner_id' => $this->getOwnerId() ? $this->getOwnerId()->getValue() : null,
355
            'parameters' => (object) $this->all(),
356
            'is_deleted' => $this->isDeleted(),
357
        ];
358
359
        return $data;
360
    }
361
362
    /**
363
     * {@inheritdoc}
364
     */
365
    public static function createFromJson(\stdClass $json): DomainObject
366
    {
367
        $clientId = ClientId::create($json->client_id);
368
        $ownerId = null !== $json->owner_id ? UserAccountId::create($json->owner_id) : null;
369
        $parameters = DataBag::create((array) $json->parameters);
370
        $deleted = $json->is_deleted;
371
372
        $client = new self();
373
        $client->clientId = $clientId;
374
        $client->ownerId = $ownerId;
375
        $client->parameters = $parameters;
376
        $client->deleted = $deleted;
377
378
        return $client;
379
    }
380
381
    /**
382
     * @param Event $event
383
     *
384
     * @return Client
385
     */
386
    public function apply(Event $event): self
387
    {
388
        $map = $this->getEventMap();
389
        if (!array_key_exists($event->getType(), $map)) {
390
            throw new \InvalidArgumentException('Unsupported event.');
391
        }
392
        if (null !== $this->clientId && $this->clientId->getValue() !== $event->getDomainId()->getValue()) {
393
            throw new \InvalidArgumentException('Event not applicable for this client.');
394
        }
395
        $method = $map[$event->getType()];
396
397
        return $this->$method($event);
398
    }
399
400
    /**
401
     * @return array
402
     */
403
    private function getEventMap(): array
404
    {
405
        return [
406
            ClientEvent\ClientCreatedEvent::class => 'applyClientCreatedEvent',
407
            ClientEvent\ClientOwnerChangedEvent::class => 'applyClientOwnerChangedEvent',
408
            ClientEvent\ClientDeletedEvent::class => 'applyClientDeletedEvent',
409
            ClientEvent\ClientParametersUpdatedEvent::class => 'applyClientParametersUpdatedEvent',
410
        ];
411
    }
412
413
    /**
414
     * @param ClientEvent\ClientCreatedEvent $event
415
     *
416
     * @return Client
417
     */
418
    protected function applyClientCreatedEvent(ClientEvent\ClientCreatedEvent $event): self
419
    {
420
        $clone = clone $this;
421
        $clone->clientId = $event->getClientId();
422
        $clone->ownerId = $event->getOwnerId();
423
        $clone->parameters = $event->getParameters();
424
425
        return $clone;
426
    }
427
428
    /**
429
     * @param ClientEvent\ClientOwnerChangedEvent $event
430
     *
431
     * @return Client
432
     */
433
    protected function applyClientOwnerChangedEvent(ClientEvent\ClientOwnerChangedEvent $event): self
434
    {
435
        $clone = clone $this;
436
        $clone->ownerId = $event->getNewOwnerId();
437
438
        return $clone;
439
    }
440
441
    /**
442
     * @param ClientEvent\ClientDeletedEvent $event
443
     *
444
     * @return Client
445
     */
446
    protected function applyClientDeletedEvent(ClientEvent\ClientDeletedEvent $event): self
447
    {
448
        $clone = clone $this;
449
        $clone->deleted = true;
450
451
        return $clone;
452
    }
453
454
    /**
455
     * @param ClientEvent\ClientParametersUpdatedEvent $event
456
     *
457
     * @return Client
458
     */
459
    protected function applyClientParametersUpdatedEvent(ClientEvent\ClientParametersUpdatedEvent $event): self
460
    {
461
        $clone = clone $this;
462
        $clone->parameters = $event->getParameters();
463
464
        return $clone;
465
    }
466
}
467