Passed
Push — master ( 68b106...6cdc56 )
by Thomas
01:58
created

ServerBuilder::strictSupportedFormats()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 0
cts 3
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace MadWizard\WebAuthn\Builder;
4
5
use Closure;
6
use GuzzleHttp\Client;
7
use MadWizard\WebAuthn\Attestation\AttestationType;
8
use MadWizard\WebAuthn\Attestation\Registry\AttestationFormatRegistry;
9
use MadWizard\WebAuthn\Attestation\Registry\AttestationFormatRegistryInterface;
10
use MadWizard\WebAuthn\Attestation\TrustAnchor\TrustPathValidator;
11
use MadWizard\WebAuthn\Attestation\TrustAnchor\TrustPathValidatorInterface;
12
use MadWizard\WebAuthn\Attestation\Verifier\AndroidKeyAttestationVerifier;
13
use MadWizard\WebAuthn\Attestation\Verifier\AndroidSafetyNetAttestationVerifier;
14
use MadWizard\WebAuthn\Attestation\Verifier\FidoU2fAttestationVerifier;
15
use MadWizard\WebAuthn\Attestation\Verifier\NoneAttestationVerifier;
16
use MadWizard\WebAuthn\Attestation\Verifier\PackedAttestationVerifier;
17
use MadWizard\WebAuthn\Attestation\Verifier\TpmAttestationVerifier;
18
use MadWizard\WebAuthn\Cache\CacheProviderInterface;
19
use MadWizard\WebAuthn\Cache\FileCacheProvider;
20
use MadWizard\WebAuthn\Config\RelyingParty;
21
use MadWizard\WebAuthn\Config\RelyingPartyInterface;
22
use MadWizard\WebAuthn\Credential\CredentialStoreInterface;
23
use MadWizard\WebAuthn\Exception\ConfigurationException;
24
use MadWizard\WebAuthn\Exception\UnsupportedException;
25
use MadWizard\WebAuthn\Metadata\MetadataResolver;
26
use MadWizard\WebAuthn\Metadata\MetadataResolverInterface;
27
use MadWizard\WebAuthn\Metadata\NullMetadataResolver;
28
use MadWizard\WebAuthn\Metadata\Provider\FileProvider;
29
use MadWizard\WebAuthn\Metadata\Provider\MetadataServiceProvider;
30
use MadWizard\WebAuthn\Metadata\Source\MetadataServiceSource;
31
use MadWizard\WebAuthn\Metadata\Source\MetadataSourceInterface;
32
use MadWizard\WebAuthn\Metadata\Source\StatementDirectorySource;
33
use MadWizard\WebAuthn\Pki\ChainValidator;
34
use MadWizard\WebAuthn\Pki\ChainValidatorInterface;
35
use MadWizard\WebAuthn\Policy\Policy;
36
use MadWizard\WebAuthn\Policy\PolicyInterface;
37
use MadWizard\WebAuthn\Policy\Trust\TrustDecisionManager;
38
use MadWizard\WebAuthn\Policy\Trust\TrustDecisionManagerInterface;
39
use MadWizard\WebAuthn\Policy\Trust\Voter\AllowEmptyMetadataVoter;
40
use MadWizard\WebAuthn\Policy\Trust\Voter\SupportedAttestationTypeVoter;
41
use MadWizard\WebAuthn\Policy\Trust\Voter\TrustAttestationTypeVoter;
42
use MadWizard\WebAuthn\Policy\Trust\Voter\TrustChainVoter;
43
use MadWizard\WebAuthn\Policy\Trust\Voter\UndesiredStatusReportVoter;
44
use MadWizard\WebAuthn\Remote\CachingClientFactory;
45
use MadWizard\WebAuthn\Remote\Downloader;
46
use MadWizard\WebAuthn\Remote\DownloaderInterface;
47
use MadWizard\WebAuthn\Server\ServerInterface;
48
use MadWizard\WebAuthn\Server\WebAuthnServer;
49
use Psr\Log\LoggerAwareInterface;
50
use Psr\Log\LoggerInterface;
51
use Psr\Log\NullLogger;
52
53
final class ServerBuilder
54
{
55
    /**
56
     * @var RelyingParty|null
57
     */
58
    private $rp;
59
60
    /**
61
     * @var CredentialStoreInterface|null
62
     */
63
    private $store;
64
65
    /**
66
     * @var string|null;
67
     */
68
    private $cacheDir;
69
70
    /**
71
     * @var callable|PolicyCallbackInterface|null
72
     */
73
    private $policyCallback;
74
75
    /**
76
     * @var MetadataSourceInterface[]
77
     */
78
    private $metadataSources = [];
79
80
    /**
81
     * @var LoggerInterface|null
82
     */
83
    private $logger;
84
85
    /**
86
     * @var bool
87
     */
88
    private $allowNoneAttestation = true;
89
90
    /**
91
     * @var bool
92
     */
93
    private $allowSelfAttestation = true;
94
95
    /**
96
     * @var bool
97
     */
98
    private $trustWithoutMetadata = true;
99
100
    /**
101
     * @var bool
102
     */
103
    private $useMetadata = true;
104
105
    /**
106
     * @var bool
107
     */
108
    private $strictSupportedFormats = false;
109
110 18
    public function __construct()
111
    {
112 18
    }
113
114
    // TODO: conistent set/with methods
115 18
    public function setRelyingParty(RelyingParty $rp): self
116
    {
117 18
        $this->rp = $rp;
118 18
        return $this;
119
    }
120
121 18
    public function setCredentialStore(CredentialStoreInterface $store): self
122
    {
123 18
        $this->store = $store;
124 18
        return $this;
125
    }
126
127
    public function setCacheDirectory(string $directory): self
128
    {
129
        $this->cacheDir = $directory;
130
        return $this;
131
    }
132
133
    public function useSystemTempCache(string $subDirectory = 'webauthn-server-cache'): self
134
    {
135
        $this->cacheDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $subDirectory;
136
        return $this;
137
    }
138
139
    /**
140
     * @param callable|PolicyCallbackInterface $policyCallback
141
     *
142
     * @return $this
143
     */
144
    public function configurePolicy(callable $policyCallback): self
145
    {
146
        $this->policyCallback = $policyCallback;
147
        return $this;
148
    }
149
150
    /**
151
     * @return $this
152
     */
153
    public function allowNoneAttestation(bool $allow): self
154
    {
155
        $this->allowNoneAttestation = $allow;
156
        return $this;
157
    }
158
159
    /**
160
     * @return $this
161
     */
162
    public function strictSupportedFormats(bool $strict): self
163
    {
164
        $this->strictSupportedFormats = $strict;
165
        return $this;
166
    }
167
168
    /**
169
     * @return $this
170
     */
171
    public function useMetadata(bool $use): self
172
    {
173
        $this->useMetadata = $use;
174
        return $this;
175
    }
176
177
    /**
178
     * @return $this
179
     */
180
    public function allowSelfAttestation(bool $allow): self
181
    {
182
        $this->allowSelfAttestation = $allow;
183
        return $this;
184
    }
185
186
    /**
187
     * @return $this
188
     */
189
    public function trustWithoutMetadata(bool $trust): self
190
    {
191
        $this->trustWithoutMetadata = $trust;
192
        return $this;
193
    }
194
195
    /**
196
     * @return $this
197
     */
198
    public function withLogger(LoggerInterface $logger): self
199
    {
200
        $this->logger = $logger;
201
        return $this;
202
    }
203
204
    private function assignLogger(LoggerAwareInterface $service): void
205
    {
206
        if ($this->logger !== null) {
207
            $service->setLogger($this->logger);
208
        }
209
    }
210
211 18
    public function build(): ServerInterface
212
    {
213 18
        $c = $this->setupContainer();
214
215 18
        return $c[ServerInterface::class];
216
    }
217
218 18
    private function setupContainer(): ServiceContainer
219
    {
220 18
        $c = new ServiceContainer();
221
222 18
        $this->setupConfiguredServices($c);
223 18
        $this->setupFormats($c);
224 18
        $this->setupTrustDecisionManager($c);
225
226
        $c[TrustPathValidatorInterface::class] = static function (ServiceContainer $c): TrustPathValidatorInterface {
227 18
            return new TrustPathValidator($c[ChainValidatorInterface::class]);
228
        };
229
230
        $c[ChainValidatorInterface::class] = static function (ServiceContainer $c): ChainValidatorInterface {
0 ignored issues
show
Unused Code introduced by
The parameter $c is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

230
        $c[ChainValidatorInterface::class] = static function (/** @scrutinizer ignore-unused */ ServiceContainer $c): ChainValidatorInterface {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
231
            // TODO
232
            //return new ChainValidator($c[CertificateStatusResolverInterface::class]);
233 18
            return new ChainValidator(null);
234
        };
235
236
        // TODO
237
//        $c[CertificateStatusResolverInterface::class] = static function (ServiceContainer $c) {
238
//            return new CertificateStatusResolver($c[DownloaderInterface::class], $c[CacheProviderInterface::class]);
239
//        };
240
241 18
        $c[PolicyInterface::class] = Closure::fromCallable([$this, 'createPolicy']);
242 18
        $c[MetadataResolverInterface::class] = Closure::fromCallable([$this, 'createMetadataResolver']);
243 18
        $c[ServerInterface::class] = Closure::fromCallable([$this, 'createServer']);
244
245 18
        return $c;
246
    }
247
248
    private function setupDownloader(ServiceContainer $c)
249
    {
250
        $this->setupCache($c);
251
        if (isset($c[DownloaderInterface::class])) {
252
            return;
253
        }
254
        $c[DownloaderInterface::class] = static function (ServiceContainer $c): DownloaderInterface {
255
            return new Downloader($c[Client::class]);
256
        };
257
        $c[Client::class] = static function (ServiceContainer $c): Client {
258
            $factory = new CachingClientFactory($c[CacheProviderInterface::class]);
259
            return $factory->createClient();
260
        };
261
    }
262
263
    private function setupCache(ServiceContainer $c)
264
    {
265
        if (isset($c[CacheProviderInterface::class])) {
266
            return;
267
        }
268
269
        $cacheDir = $this->cacheDir;
270
        if ($cacheDir === null) {
271
            throw new ConfigurationException('No cache directory configured. Use useCacheDirectory or useSystemTempCache.');
272
        }
273
        $c[CacheProviderInterface::class] = static function (ServiceContainer $c) use ($cacheDir) {
0 ignored issues
show
Unused Code introduced by
The parameter $c is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

273
        $c[CacheProviderInterface::class] = static function (/** @scrutinizer ignore-unused */ ServiceContainer $c) use ($cacheDir) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
274
            return new FileCacheProvider($cacheDir);
275
        };
276
    }
277
278 18
    private function setupConfiguredServices(ServiceContainer $c): void
279
    {
280 18
        if ($this->rp === null) {
281
            throw new ConfigurationException('Relying party not configured. Use setRelyingParty.');
282
        }
283
284
        $c[RelyingPartyInterface::class] = function () { return $this->rp; };
285
286 18
        if ($this->store === null) {
287
            throw new ConfigurationException('Credential store not configured. Use setCredentialStore.');
288
        }
289
290
        $c[CredentialStoreInterface::class] = function () { return $this->store; };
291
        $c[LoggerInterface::class] = function () { return $this->logger ?? new NullLogger(); };
292 18
    }
293
294 18
    private function createPolicy(ServiceContainer $c): PolicyInterface
0 ignored issues
show
Unused Code introduced by
The parameter $c is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

294
    private function createPolicy(/** @scrutinizer ignore-unused */ ServiceContainer $c): PolicyInterface

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
295
    {
296 18
        $policy = new Policy();
297
298 18
        if ($this->policyCallback !== null) {
299
            ($this->policyCallback)($policy);
300
        }
301
302 18
        return $policy;
303
    }
304
305 18
    private function createServer(ServiceContainer $c): ServerInterface
306
    {
307 18
        return new WebAuthnServer(
308 18
            $c[RelyingPartyInterface::class],
309 18
            $c[PolicyInterface::class],
310 18
            $c[CredentialStoreInterface::class],
311 18
            $c[AttestationFormatRegistryInterface::class],
312 18
            $c[MetadataResolverInterface::class],
313 18
            $c[TrustDecisionManagerInterface::class]);
314
    }
315
316
    public function addMetadataSource(MetadataSourceInterface $metadataSource): self
317
    {
318
        $this->metadataSources[] = $metadataSource;
319
        return $this;
320
    }
321
322 18
    private function createMetadataResolver(ServiceContainer $c): MetadataResolverInterface
323
    {
324 18
        if (count($this->metadataSources) === 0) {
325 18
            return new NullMetadataResolver();
326
        }
327
        return new MetadataResolver($this->createMetadataProviders($c));
328
    }
329
330 18
    private function setupTrustDecisionManager(ServiceContainer $c)
331
    {
332
        $c[TrustDecisionManagerInterface::class] = function (ServiceContainer $c) {
333 18
            $tdm = new TrustDecisionManager();
334
335 18
            if ($this->allowNoneAttestation) {
336 18
                $tdm->addVoter(new TrustAttestationTypeVoter(AttestationType::NONE));
337
            }
338 18
            if ($this->allowSelfAttestation) {
339 18
                $tdm->addVoter(new TrustAttestationTypeVoter(AttestationType::SELF));
340
            }
341 18
            if ($this->trustWithoutMetadata) {
342 18
                $tdm->addVoter(new AllowEmptyMetadataVoter());
343
            }
344 18
            if ($this->useMetadata) {
345 18
                $tdm->addVoter(new SupportedAttestationTypeVoter());
346 18
                $tdm->addVoter(new UndesiredStatusReportVoter());
347 18
                $tdm->addVoter(new TrustChainVoter($c[TrustPathValidatorInterface::class]));
348
            }
349 18
            return $tdm;
350
        };
351 18
    }
352
353
    private function createMetadataProviders(ServiceContainer $c): array
354
    {
355
        $providers = [];
356
        foreach ($this->metadataSources as $source) {
357
            if ($source instanceof StatementDirectorySource) {
358
                $provider = new FileProvider($source);
359
            } elseif ($source instanceof MetadataServiceSource) {
360
                $this->setupDownloader($c);
361
                $provider = new MetadataServiceProvider($source, $c[DownloaderInterface::class], $c[CacheProviderInterface::class], $c[ChainValidatorInterface::class]);
362
            } else {
363
                throw new UnsupportedException(sprintf('No provider available for metadata source of type %s.', get_class($source)));
364
            }
365
366
            if ($provider instanceof LoggerAwareInterface) {
367
                $this->assignLogger($provider);
368
            }
369
            $providers[] = $provider;
370
        }
371
        return $providers;
372
    }
373
374 18
    private function setupFormats(ServiceContainer $c)
375
    {
376
        $c[PackedAttestationVerifier::class] = static function () {
377 18
            return new PackedAttestationVerifier();
378
        };
379
        $c[FidoU2fAttestationVerifier::class] = static function () {
380 18
            return new FidoU2fAttestationVerifier();
381
        };
382
        $c[NoneAttestationVerifier::class] = static function () {
383 18
            return new NoneAttestationVerifier();
384
        };
385
        $c[TpmAttestationVerifier::class] = static function () {
386 18
            return new TpmAttestationVerifier();
387
        };
388
        $c[AndroidSafetyNetAttestationVerifier::class] = static function () {
389 18
            return new AndroidSafetyNetAttestationVerifier();
390
        };
391
        $c[AndroidKeyAttestationVerifier::class] = static function () {
392 18
            return new AndroidKeyAttestationVerifier();
393
        };
394
395
        $c[AttestationFormatRegistryInterface::class] = function (ServiceContainer $c) {
396 18
            $registry = new AttestationFormatRegistry();
397
398 18
            $registry->strictSupportedFormats($this->strictSupportedFormats);
399
400 18
            $registry->addFormat($c[PackedAttestationVerifier::class]->getSupportedFormat());
401 18
            $registry->addFormat($c[FidoU2fAttestationVerifier::class]->getSupportedFormat());
402 18
            $registry->addFormat($c[NoneAttestationVerifier::class]->getSupportedFormat());
403 18
            $registry->addFormat($c[TpmAttestationVerifier::class]->getSupportedFormat());
404 18
            $registry->addFormat($c[AndroidSafetyNetAttestationVerifier::class]->getSupportedFormat());
405 18
            $registry->addFormat($c[AndroidKeyAttestationVerifier::class]->getSupportedFormat());
406
407 18
            return $registry;
408
        };
409 18
    }
410
}
411