Passed
Push — master ( 8d86ce...bd9937 )
by Thomas
07:31
created

ServerBuilder::setupDownloader()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 1
dl 0
loc 12
ccs 0
cts 7
cp 0
crap 6
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\CertificateStatusResolver;
34
use MadWizard\WebAuthn\Pki\CertificateStatusResolverInterface;
35
use MadWizard\WebAuthn\Pki\ChainValidator;
36
use MadWizard\WebAuthn\Pki\ChainValidatorInterface;
37
use MadWizard\WebAuthn\Policy\Policy;
38
use MadWizard\WebAuthn\Policy\PolicyInterface;
39
use MadWizard\WebAuthn\Policy\Trust\TrustDecisionManager;
40
use MadWizard\WebAuthn\Policy\Trust\TrustDecisionManagerInterface;
41
use MadWizard\WebAuthn\Policy\Trust\Voter\AllowEmptyMetadataVoter;
42
use MadWizard\WebAuthn\Policy\Trust\Voter\SupportedAttestationTypeVoter;
43
use MadWizard\WebAuthn\Policy\Trust\Voter\TrustAttestationTypeVoter;
44
use MadWizard\WebAuthn\Policy\Trust\Voter\TrustChainVoter;
45
use MadWizard\WebAuthn\Policy\Trust\Voter\UndesiredStatusReportVoter;
46
use MadWizard\WebAuthn\Remote\CachingClientFactory;
47
use MadWizard\WebAuthn\Remote\Downloader;
48
use MadWizard\WebAuthn\Remote\DownloaderInterface;
49
use MadWizard\WebAuthn\Server\ServerInterface;
50
use MadWizard\WebAuthn\Server\WebAuthnServer;
51
use Psr\Log\LoggerAwareInterface;
52
use Psr\Log\LoggerInterface;
53
use Psr\Log\NullLogger;
54
55
final class ServerBuilder
56
{
57
    /**
58
     * @var RelyingParty|null
59
     */
60
    private $rp;
61
62
    /**
63
     * @var CredentialStoreInterface|null
64
     */
65
    private $store;
66
67
    /**
68
     * @var string|null;
69
     */
70
    private $cacheDir;
71
72
    /**
73
     * @var callable|PolicyCallbackInterface|null
74
     */
75
    private $policyCallback;
76
77
    /**
78
     * @var MetadataSourceInterface[]
79
     */
80
    private $metadataSources = [];
81
82
    /**
83
     * @var LoggerInterface|null
84
     */
85
    private $logger;
86
87
    /**
88
     * @var bool
89
     */
90
    private $allowNoneAttestation = true;
91
92
    /**
93
     * @var bool
94
     */
95
    private $allowSelfAttestation = true;
96
97
    /**
98
     * @var bool
99
     */
100
    private $trustWithoutMetadata = true;
101
102
    /**
103
     * @var bool
104
     */
105
    private $useMetadata = true;
106
107 18
    public function __construct()
108
    {
109 18
    }
110
111
    // TODO: conistent set/with methods
112 18
    public function setRelyingParty(RelyingParty $rp): self
113
    {
114 18
        $this->rp = $rp;
115 18
        return $this;
116
    }
117
118 18
    public function setCredentialStore(CredentialStoreInterface $store): self
119
    {
120 18
        $this->store = $store;
121 18
        return $this;
122
    }
123
124
    public function setCacheDirectory(string $directory): self
125
    {
126
        $this->cacheDir = $directory;
127
        return $this;
128
    }
129
130
    public function useSystemTempCache(string $subDirectory = 'webauthn-server-cache'): self
131
    {
132
        $this->cacheDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $subDirectory;
133
        return $this;
134
    }
135
136
    /**
137
     * @param callable|PolicyCallbackInterface $policyCallback
138
     *
139
     * @return $this
140
     */
141
    public function configurePolicy(callable $policyCallback): self
142
    {
143
        $this->policyCallback = $policyCallback;
144
        return $this;
145
    }
146
147
//    public function withTrustPreset(string $preset)
148
//    {
149
//    }
150
151
    public function allowNoneAttestation(bool $allow): self
152
    {
153
        $this->allowNoneAttestation = $allow;
154
        return $this;
155
    }
156
157
    public function useMetadata(bool $use): self
158
    {
159
        $this->useMetadata = $use;
160
        return $this;
161
    }
162
163
    public function allowSelfAttestation(bool $allow): self
164
    {
165
        $this->allowSelfAttestation = $allow;
166
        return $this;
167
    }
168
169
    public function trustWithoutMetadata(bool $trust): self
170
    {
171
        $this->trustWithoutMetadata = $trust;
172
        return $this;
173
    }
174
175
    public function withLogger(LoggerInterface $logger): self
176
    {
177
        $this->logger = $logger;
178
        return $this;
179
    }
180
181
    private function assignLogger(LoggerAwareInterface $service): void
182
    {
183
        if ($this->logger !== null) {
184
            $service->setLogger($this->logger);
185
        }
186
    }
187
188 18
    public function build(): ServerInterface
189
    {
190 18
        $c = $this->setupContainer();
191
192 18
        return $c[ServerInterface::class];
193
    }
194
195 18
    private function setupContainer(): ServiceContainer
196
    {
197 18
        $c = new ServiceContainer();
198
199 18
        $this->setupConfiguredServices($c);
200 18
        $this->setupFormats($c);
201 18
        $this->setupTrustDecisionManager($c);
202
203
        $c[TrustPathValidatorInterface::class] = static function (ServiceContainer $c) {
204 18
            return new TrustPathValidator($c[ChainValidatorInterface::class]);
205
        };
206
207
        $c[ChainValidatorInterface::class] = static function (ServiceContainer $c) {
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

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

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...
208
            // TODO
209
            //return new ChainValidator($c[CertificateStatusResolverInterface::class]);
210 18
            return new ChainValidator(null);
211
        };
212
213
        // TODO
214
//        $c[CertificateStatusResolverInterface::class] = static function (ServiceContainer $c) {
215
//            return new CertificateStatusResolver($c[DownloaderInterface::class], $c[CacheProviderInterface::class]);
216
//        };
217
218 18
        $c[PolicyInterface::class] = Closure::fromCallable([$this, 'createPolicy']);
219 18
        $c[MetadataResolverInterface::class] = Closure::fromCallable([$this, 'createMetadataResolver']);
220 18
        $c[ServerInterface::class] = Closure::fromCallable([$this, 'createServer']);
221
222 18
        return $c;
223
    }
224
225
    private function setupDownloader(ServiceContainer $c)
226
    {
227
        $this->setupCache($c);
228
        if (isset($c[DownloaderInterface::class])) {
229
            return;
230
        }
231
        $c[DownloaderInterface::class] = static function (ServiceContainer $c) {
232
            return new Downloader($c[Client::class]);
233
        };
234
        $c[Client::class] = static function (ServiceContainer $c) {
235
            $factory = new CachingClientFactory($c[CacheProviderInterface::class]);
236
            return $factory->createClient();
237
        };
238
    }
239
240
    private function setupCache(ServiceContainer $c)
241
    {
242
        if (isset($c[CacheProviderInterface::class])) {
243
            return;
244
        }
245
        if ($this->cacheDir === null) {
246
            throw new ConfigurationException('No cache directory configured. Use useCacheDirectory or useSystemTempCache.');
247
        }
248
        $c[CacheProviderInterface::class] = function (ServiceContainer $c) {
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

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

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...
249
            return new FileCacheProvider($this->cacheDir);
0 ignored issues
show
Bug introduced by
It seems like $this->cacheDir can also be of type null; however, parameter $cacheDir of MadWizard\WebAuthn\Cache...Provider::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

249
            return new FileCacheProvider(/** @scrutinizer ignore-type */ $this->cacheDir);
Loading history...
250
        };
251
    }
252
253 18
    private function setupConfiguredServices(ServiceContainer $c): void
254
    {
255 18
        if ($this->rp === null) {
256
            throw new ConfigurationException('Relying party not configured. Use setRelyingParty.');
257
        }
258
259
        $c[RelyingPartyInterface::class] = function () { return $this->rp; };
260
261 18
        if ($this->store === null) {
262
            throw new ConfigurationException('Credential store not configured. Use setCredentialStore.');
263
        }
264
265
        $c[CredentialStoreInterface::class] = function () { return $this->store; };
266
        $c[LoggerInterface::class] = function () { return $this->logger ?? new NullLogger(); };
267 18
    }
268
269 18
    private function createPolicy(ServiceContainer $c): PolicyInterface
270
    {
271 18
        $policy = new Policy(
272 18
            $c[RelyingPartyInterface::class],
273 18
            $c[MetadataResolverInterface::class],
274 18
            $c[TrustDecisionManagerInterface::class],
275 18
            $c[AttestationFormatRegistryInterface::class]
276
        );
277
278 18
        if ($this->policyCallback !== null) {
279
            ($this->policyCallback)($policy);
280
        }
281
282 18
        return $policy;
283
    }
284
285 18
    private function createServer(ServiceContainer $c): ServerInterface
286
    {
287 18
        return new WebAuthnServer($c[PolicyInterface::class], $c[CredentialStoreInterface::class]);
288
    }
289
290
    private function getRelyingParty(): RelyingPartyInterface
0 ignored issues
show
Unused Code introduced by
The method getRelyingParty() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
291
    {
292
        return $this->rp;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->rp could return the type null which is incompatible with the type-hinted return MadWizard\WebAuthn\Config\RelyingPartyInterface. Consider adding an additional type-check to rule them out.
Loading history...
293
    }
294
295
    public function addMetadataSource(MetadataSourceInterface $metadataSource): self
296
    {
297
        $this->metadataSources[] = $metadataSource;
298
        return $this;
299
    }
300
301 18
    private function createMetadataResolver(ServiceContainer $c): MetadataResolverInterface
302
    {
303 18
        if (count($this->metadataSources) === 0) {
304 18
            return new NullMetadataResolver();
305
        }
306
        return new MetadataResolver($this->createMetadataProviders($c));
307
    }
308
309 18
    private function setupTrustDecisionManager(ServiceContainer $c)
310
    {
311
        $c[TrustDecisionManagerInterface::class] = function (ServiceContainer $c) {
312 18
            $tdm = new TrustDecisionManager();
313
314 18
            if ($this->allowNoneAttestation) {
315 18
                $tdm->addVoter(new TrustAttestationTypeVoter(AttestationType::NONE));
316
            }
317 18
            if ($this->allowSelfAttestation) {
318 18
                $tdm->addVoter(new TrustAttestationTypeVoter(AttestationType::SELF));
319
            }
320 18
            if ($this->trustWithoutMetadata) {
321 18
                $tdm->addVoter(new AllowEmptyMetadataVoter());
322
            }
323 18
            if ($this->useMetadata) {
324 18
                $tdm->addVoter(new SupportedAttestationTypeVoter());
325 18
                $tdm->addVoter(new UndesiredStatusReportVoter());
326 18
                $tdm->addVoter(new TrustChainVoter($c[TrustPathValidatorInterface::class]));
327
            }
328 18
            return $tdm;
329
        };
330 18
    }
331
332
    private function createMetadataProviders(ServiceContainer $c): array
333
    {
334
        $providers = [];
335
        foreach ($this->metadataSources as $source) {
336
            if ($source instanceof StatementDirectorySource) {
337
                $provider = new FileProvider($source);
338
            } elseif ($source instanceof MetadataServiceSource) {
339
                $this->setupDownloader($c);
340
                $provider = new MetadataServiceProvider($source, $c[DownloaderInterface::class], $c[CacheProviderInterface::class], $c[ChainValidatorInterface::class]);
341
            } else {
342
                throw new UnsupportedException(sprintf('No provider available for metadata source of type %s.', get_class($source)));
343
            }
344
345
            if ($provider instanceof LoggerAwareInterface) {
346
                $this->assignLogger($provider);
347
            }
348
            $providers[] = $provider;
349
        }
350
        return $providers;
351
    }
352
353 18
    private function setupFormats(ServiceContainer $c)
354
    {
355
        $c[PackedAttestationVerifier::class] = static function () {
356 18
            return new AndroidSafetyNetAttestationVerifier();
357
        };
358
        $c[FidoU2fAttestationVerifier::class] = static function () {
359 18
            return new FidoU2fAttestationVerifier();
360
        };
361
        $c[NoneAttestationVerifier::class] = static function () {
362 18
            return new NoneAttestationVerifier();
363
        };
364
        $c[TpmAttestationVerifier::class] = static function () {
365 18
            return new TpmAttestationVerifier();
366
        };
367
        $c[AndroidSafetyNetAttestationVerifier::class] = static function () {
368 18
            return new AndroidSafetyNetAttestationVerifier();
369
        };
370
        $c[AndroidKeyAttestationVerifier::class] = static function () {
371 18
            return new AndroidKeyAttestationVerifier();
372
        };
373
374
        $c[AttestationFormatRegistryInterface::class] = static function (ServiceContainer $c) {
375 18
            $registry = new AttestationFormatRegistry();
376
377 18
            $registry->addFormat($c[PackedAttestationVerifier::class]->getSupportedFormat());
378 18
            $registry->addFormat($c[FidoU2fAttestationVerifier::class]->getSupportedFormat());
379 18
            $registry->addFormat($c[NoneAttestationVerifier::class]->getSupportedFormat());
380 18
            $registry->addFormat($c[TpmAttestationVerifier::class]->getSupportedFormat());
381 18
            $registry->addFormat($c[AndroidSafetyNetAttestationVerifier::class]->getSupportedFormat());
382 18
            $registry->addFormat($c[AndroidKeyAttestationVerifier::class]->getSupportedFormat());
383
384 18
            return $registry;
385
        };
386 18
    }
387
}
388