Passed
Push — master ( 6cdc56...c1fd3a )
by Thomas
04:06
created

ServerBuilder::withLogger()   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
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 4
ccs 0
cts 3
cp 0
crap 2
rs 10
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 18
    public function setRelyingParty(RelyingParty $rp): self
115
    {
116 18
        $this->rp = $rp;
117 18
        return $this;
118
    }
119
120 18
    public function setCredentialStore(CredentialStoreInterface $store): self
121
    {
122 18
        $this->store = $store;
123 18
        return $this;
124
    }
125
126
    public function setCacheDirectory(string $directory): self
127
    {
128
        $this->cacheDir = $directory;
129
        return $this;
130
    }
131
132
    public function useSystemTempCache(string $subDirectory = 'webauthn-server-cache'): self
133
    {
134
        $this->cacheDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $subDirectory;
135
        return $this;
136
    }
137
138
    /**
139
     * @param callable|PolicyCallbackInterface $policyCallback
140
     *
141
     * @return $this
142
     */
143
    public function configurePolicy(callable $policyCallback): self
144
    {
145
        $this->policyCallback = $policyCallback;
146
        return $this;
147
    }
148
149
    /**
150
     * @return $this
151
     */
152
    public function allowNoneAttestation(bool $allow): self
153
    {
154
        $this->allowNoneAttestation = $allow;
155
        return $this;
156
    }
157
158
    /**
159
     * @return $this
160
     */
161
    public function strictSupportedFormats(bool $strict): self
162
    {
163
        $this->strictSupportedFormats = $strict;
164
        return $this;
165
    }
166
167
    /**
168
     * @return $this
169
     */
170
    public function useMetadata(bool $use): self
171
    {
172
        $this->useMetadata = $use;
173
        return $this;
174
    }
175
176
    /**
177
     * @return $this
178
     */
179
    public function allowSelfAttestation(bool $allow): self
180
    {
181
        $this->allowSelfAttestation = $allow;
182
        return $this;
183
    }
184
185
    /**
186
     * @return $this
187
     */
188
    public function trustWithoutMetadata(bool $trust): self
189
    {
190
        $this->trustWithoutMetadata = $trust;
191
        return $this;
192
    }
193
194
    /**
195
     * @return $this
196
     */
197
    public function setLogger(LoggerInterface $logger): self
198
    {
199
        $this->logger = $logger;
200
        return $this;
201
    }
202
203
    private function assignLogger(LoggerAwareInterface $service): void
204
    {
205
        if ($this->logger !== null) {
206
            $service->setLogger($this->logger);
207
        }
208
    }
209
210 18
    public function build(): ServerInterface
211
    {
212 18
        $c = $this->setupContainer();
213
214 18
        return $c[ServerInterface::class];
215
    }
216
217 18
    private function setupContainer(): ServiceContainer
218
    {
219 18
        $c = new ServiceContainer();
220
221 18
        $this->setupConfiguredServices($c);
222 18
        $this->setupFormats($c);
223 18
        $this->setupTrustDecisionManager($c);
224
225
        $c[TrustPathValidatorInterface::class] = static function (ServiceContainer $c): TrustPathValidatorInterface {
226 18
            return new TrustPathValidator($c[ChainValidatorInterface::class]);
227
        };
228
229
        $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

229
        $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...
230
            // TODO
231
            //return new ChainValidator($c[CertificateStatusResolverInterface::class]);
232 18
            return new ChainValidator(null);
233
        };
234
235
        // TODO
236
//        $c[CertificateStatusResolverInterface::class] = static function (ServiceContainer $c) {
237
//            return new CertificateStatusResolver($c[DownloaderInterface::class], $c[CacheProviderInterface::class]);
238
//        };
239
240 18
        $c[PolicyInterface::class] = Closure::fromCallable([$this, 'createPolicy']);
241 18
        $c[MetadataResolverInterface::class] = Closure::fromCallable([$this, 'createMetadataResolver']);
242 18
        $c[ServerInterface::class] = Closure::fromCallable([$this, 'createServer']);
243
244 18
        return $c;
245
    }
246
247
    private function setupDownloader(ServiceContainer $c)
248
    {
249
        $this->setupCache($c);
250
        if (isset($c[DownloaderInterface::class])) {
251
            return;
252
        }
253
        $c[DownloaderInterface::class] = static function (ServiceContainer $c): DownloaderInterface {
254
            return new Downloader($c[Client::class]);
255
        };
256
        $c[Client::class] = static function (ServiceContainer $c): Client {
257
            $factory = new CachingClientFactory($c[CacheProviderInterface::class]);
258
            return $factory->createClient();
259
        };
260
    }
261
262
    private function setupCache(ServiceContainer $c)
263
    {
264
        if (isset($c[CacheProviderInterface::class])) {
265
            return;
266
        }
267
268
        $cacheDir = $this->cacheDir;
269
        if ($cacheDir === null) {
270
            throw new ConfigurationException('No cache directory configured. Use useCacheDirectory or useSystemTempCache.');
271
        }
272
        $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

272
        $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...
273
            return new FileCacheProvider($cacheDir);
274
        };
275
    }
276
277 18
    private function setupConfiguredServices(ServiceContainer $c): void
278
    {
279 18
        if ($this->rp === null) {
280
            throw new ConfigurationException('Relying party not configured. Use setRelyingParty.');
281
        }
282
283
        $c[RelyingPartyInterface::class] = function () { return $this->rp; };
284
285 18
        if ($this->store === null) {
286
            throw new ConfigurationException('Credential store not configured. Use setCredentialStore.');
287
        }
288
289
        $c[CredentialStoreInterface::class] = function () { return $this->store; };
290
        $c[LoggerInterface::class] = function () { return $this->logger ?? new NullLogger(); };
291 18
    }
292
293 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

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