Passed
Push — master ( 7581d2...f8c35d )
by Thomas
01:38
created

ServerBuilder::createPolicy()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
ccs 4
cts 5
cp 0.8
crap 2.032
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 18
    public function __construct()
106
    {
107 18
    }
108
109
    // TODO: conistent set/with methods
110 18
    public function setRelyingParty(RelyingParty $rp): self
111
    {
112 18
        $this->rp = $rp;
113 18
        return $this;
114
    }
115
116 18
    public function setCredentialStore(CredentialStoreInterface $store): self
117
    {
118 18
        $this->store = $store;
119 18
        return $this;
120
    }
121
122
    public function setCacheDirectory(string $directory): self
123
    {
124
        $this->cacheDir = $directory;
125
        return $this;
126
    }
127
128
    public function useSystemTempCache(string $subDirectory = 'webauthn-server-cache'): self
129
    {
130
        $this->cacheDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $subDirectory;
131
        return $this;
132
    }
133
134
    /**
135
     * @param callable|PolicyCallbackInterface $policyCallback
136
     *
137
     * @return $this
138
     */
139
    public function configurePolicy(callable $policyCallback): self
140
    {
141
        $this->policyCallback = $policyCallback;
142
        return $this;
143
    }
144
145
    public function allowNoneAttestation(bool $allow): self
146
    {
147
        $this->allowNoneAttestation = $allow;
148
        return $this;
149
    }
150
151
    public function useMetadata(bool $use): self
152
    {
153
        $this->useMetadata = $use;
154
        return $this;
155
    }
156
157
    public function allowSelfAttestation(bool $allow): self
158
    {
159
        $this->allowSelfAttestation = $allow;
160
        return $this;
161
    }
162
163
    public function trustWithoutMetadata(bool $trust): self
164
    {
165
        $this->trustWithoutMetadata = $trust;
166
        return $this;
167
    }
168
169
    public function withLogger(LoggerInterface $logger): self
170
    {
171
        $this->logger = $logger;
172
        return $this;
173
    }
174
175
    private function assignLogger(LoggerAwareInterface $service): void
176
    {
177
        if ($this->logger !== null) {
178
            $service->setLogger($this->logger);
179
        }
180
    }
181
182 18
    public function build(): ServerInterface
183
    {
184 18
        $c = $this->setupContainer();
185
186 18
        return $c[ServerInterface::class];
187
    }
188
189 18
    private function setupContainer(): ServiceContainer
190
    {
191 18
        $c = new ServiceContainer();
192
193 18
        $this->setupConfiguredServices($c);
194 18
        $this->setupFormats($c);
195 18
        $this->setupTrustDecisionManager($c);
196
197
        $c[TrustPathValidatorInterface::class] = static function (ServiceContainer $c): TrustPathValidatorInterface {
198 18
            return new TrustPathValidator($c[ChainValidatorInterface::class]);
199
        };
200
201
        $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

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

244
        $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...
245
            return new FileCacheProvider($cacheDir);
246
        };
247
    }
248
249 18
    private function setupConfiguredServices(ServiceContainer $c): void
250
    {
251 18
        if ($this->rp === null) {
252
            throw new ConfigurationException('Relying party not configured. Use setRelyingParty.');
253
        }
254
255
        $c[RelyingPartyInterface::class] = function () { return $this->rp; };
256
257 18
        if ($this->store === null) {
258
            throw new ConfigurationException('Credential store not configured. Use setCredentialStore.');
259
        }
260
261
        $c[CredentialStoreInterface::class] = function () { return $this->store; };
262
        $c[LoggerInterface::class] = function () { return $this->logger ?? new NullLogger(); };
263 18
    }
264
265 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

265
    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...
266
    {
267 18
        $policy = new Policy();
268
269 18
        if ($this->policyCallback !== null) {
270
            ($this->policyCallback)($policy);
271
        }
272
273 18
        return $policy;
274
    }
275
276 18
    private function createServer(ServiceContainer $c): ServerInterface
277
    {
278 18
        return new WebAuthnServer(
279 18
            $c[RelyingPartyInterface::class],
280 18
            $c[PolicyInterface::class],
281 18
            $c[CredentialStoreInterface::class],
282 18
            $c[AttestationFormatRegistryInterface::class],
283 18
            $c[MetadataResolverInterface::class],
284 18
            $c[TrustDecisionManagerInterface::class]);
285
    }
286
287
    public function addMetadataSource(MetadataSourceInterface $metadataSource): self
288
    {
289
        $this->metadataSources[] = $metadataSource;
290
        return $this;
291
    }
292
293 18
    private function createMetadataResolver(ServiceContainer $c): MetadataResolverInterface
294
    {
295 18
        if (count($this->metadataSources) === 0) {
296 18
            return new NullMetadataResolver();
297
        }
298
        return new MetadataResolver($this->createMetadataProviders($c));
299
    }
300
301 18
    private function setupTrustDecisionManager(ServiceContainer $c)
302
    {
303
        $c[TrustDecisionManagerInterface::class] = function (ServiceContainer $c) {
304 18
            $tdm = new TrustDecisionManager();
305
306 18
            if ($this->allowNoneAttestation) {
307 18
                $tdm->addVoter(new TrustAttestationTypeVoter(AttestationType::NONE));
308
            }
309 18
            if ($this->allowSelfAttestation) {
310 18
                $tdm->addVoter(new TrustAttestationTypeVoter(AttestationType::SELF));
311
            }
312 18
            if ($this->trustWithoutMetadata) {
313 18
                $tdm->addVoter(new AllowEmptyMetadataVoter());
314
            }
315 18
            if ($this->useMetadata) {
316 18
                $tdm->addVoter(new SupportedAttestationTypeVoter());
317 18
                $tdm->addVoter(new UndesiredStatusReportVoter());
318 18
                $tdm->addVoter(new TrustChainVoter($c[TrustPathValidatorInterface::class]));
319
            }
320 18
            return $tdm;
321
        };
322 18
    }
323
324
    private function createMetadataProviders(ServiceContainer $c): array
325
    {
326
        $providers = [];
327
        foreach ($this->metadataSources as $source) {
328
            if ($source instanceof StatementDirectorySource) {
329
                $provider = new FileProvider($source);
330
            } elseif ($source instanceof MetadataServiceSource) {
331
                $this->setupDownloader($c);
332
                $provider = new MetadataServiceProvider($source, $c[DownloaderInterface::class], $c[CacheProviderInterface::class], $c[ChainValidatorInterface::class]);
333
            } else {
334
                throw new UnsupportedException(sprintf('No provider available for metadata source of type %s.', get_class($source)));
335
            }
336
337
            if ($provider instanceof LoggerAwareInterface) {
338
                $this->assignLogger($provider);
339
            }
340
            $providers[] = $provider;
341
        }
342
        return $providers;
343
    }
344
345 18
    private function setupFormats(ServiceContainer $c)
346
    {
347
        $c[PackedAttestationVerifier::class] = static function () {
348 18
            return new PackedAttestationVerifier();
349
        };
350
        $c[FidoU2fAttestationVerifier::class] = static function () {
351 18
            return new FidoU2fAttestationVerifier();
352
        };
353
        $c[NoneAttestationVerifier::class] = static function () {
354 18
            return new NoneAttestationVerifier();
355
        };
356
        $c[TpmAttestationVerifier::class] = static function () {
357 18
            return new TpmAttestationVerifier();
358
        };
359
        $c[AndroidSafetyNetAttestationVerifier::class] = static function () {
360 18
            return new AndroidSafetyNetAttestationVerifier();
361
        };
362
        $c[AndroidKeyAttestationVerifier::class] = static function () {
363 18
            return new AndroidKeyAttestationVerifier();
364
        };
365
366
        $c[AttestationFormatRegistryInterface::class] = static function (ServiceContainer $c) {
367 18
            $registry = new AttestationFormatRegistry();
368
369 18
            $registry->addFormat($c[PackedAttestationVerifier::class]->getSupportedFormat());
370 18
            $registry->addFormat($c[FidoU2fAttestationVerifier::class]->getSupportedFormat());
371 18
            $registry->addFormat($c[NoneAttestationVerifier::class]->getSupportedFormat());
372 18
            $registry->addFormat($c[TpmAttestationVerifier::class]->getSupportedFormat());
373 18
            $registry->addFormat($c[AndroidSafetyNetAttestationVerifier::class]->getSupportedFormat());
374 18
            $registry->addFormat($c[AndroidKeyAttestationVerifier::class]->getSupportedFormat());
375
376 18
            return $registry;
377
        };
378 18
    }
379
}
380