Passed
Pull Request — master (#22)
by Tim
20:50 queued 06:14
created

MetadataBuilder::buildKeyDescriptor()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 9
nc 2
nop 3
dl 0
loc 16
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\adfs\IdP;
6
7
use Beste\Clock\LocalizedClock;
8
use Exception;
9
use Psr\Clock\ClockInterface;
10
use SimpleSAML\{Configuration, Logger, Module, Utils};
11
use SimpleSAML\Assert\Assert;
12
use SimpleSAML\SAML2\Exception\ArrayValidationException;
13
use SimpleSAML\SAML2\XML\md\AbstractMetadataDocument;
14
use SimpleSAML\SAML2\XML\md\ContactPerson;
15
use SimpleSAML\SAML2\XML\md\EntityDescriptor;
16
use SimpleSAML\SAML2\XML\md\KeyDescriptor;
17
use SimpleSAML\SAML2\XML\md\Organization;
18
use SimpleSAML\XML\Chunk;
19
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
20
use SimpleSAML\XMLSecurity\Key\PrivateKey;
21
use SimpleSAML\XMLSecurity\XML\ds\{KeyInfo, KeyName, X509Certificate, X509Data};
22
use SimpleSAML\WSSecurity\Constants as C;
23
use SimpleSAML\WSSecurity\XML\fed\{
24
    PassiveRequestorEndpoint,
25
    SecurityTokenServiceEndpoint,
26
    SecurityTokenServiceType,
27
    TokenTypesOffered,
28
    TokenType,
29
};
30
use SimpleSAML\WSSecurity\XML\wsa_200508\{Address, EndpointReference};
31
32
use function array_key_exists;
33
use function preg_match;
34
35
/**
36
 * Common code for building SAML 2 metadata based on the available configuration.
37
 *
38
 * @package simplesamlphp/simplesamlphp-module-adfs
39
 */
40
class MetadataBuilder
41
{
42
    /** @var \Psr\Clock\ClockInterface */
43
    protected ClockInterface $clock;
44
45
    /**
46
     * Constructor.
47
     *
48
     * @param \SimpleSAML\Configuration $config The general configuration
49
     * @param \SimpleSAML\Configuration $metadata The metadata configuration
50
     */
51
    public function __construct(
52
        protected Configuration $config,
53
        protected Configuration $metadata,
54
    ) {
55
        $this->clock = LocalizedClock::in('Z');
56
    }
57
58
59
    /**
60
     * Build a metadata document
61
     *
62
     * @return \SimpleSAML\SAML2\XML\md\EntityDescriptor
63
     */
64
    public function buildDocument(): EntityDescriptor
65
    {
66
        $entityId = $this->metadata->getString('entityid');
67
        $contactPerson = $this->getContactPerson();
68
        $organization = $this->getOrganization();
69
        $roleDescriptor = $this->getRoleDescriptor();
70
71
        $randomUtils = new Utils\Random();
72
        $entityDescriptor = new EntityDescriptor(
73
            id: $randomUtils->generateID(),
74
            entityId: $entityId,
75
            contactPerson: $contactPerson,
76
            organization: $organization,
77
            roleDescriptor: $roleDescriptor,
78
        );
79
80
        if ($this->config->getOptionalBoolean('metadata.sign.enable', false) === true) {
81
            $this->signDocument($entityDescriptor);
82
        }
83
84
        return $entityDescriptor;
85
    }
86
87
88
    /**
89
     * @param \SimpleSAML\SAML2\XML\md\AbstractMetadataDocument $document
90
     * @return \SimpleSAML\SAML2\XML\md\AbstractMetadataDocument
91
     */
92
    protected function signDocument(AbstractMetadataDocument $document): AbstractMetadataDocument
93
    {
94
        $cryptoUtils = new Utils\Crypto();
95
96
        /** @var array $keyArray */
97
        $keyArray = $cryptoUtils->loadPrivateKey($this->config, true, 'metadata.sign.');
98
        $certArray = $cryptoUtils->loadPublicKey($this->config, false, 'metadata.sign.');
99
        $algo = $this->config->getOptionalString('metadata.sign.algorithm', C::SIG_RSA_SHA256);
100
101
        $key = PrivateKey::fromFile($keyArray['PEM'], $keyArray['password'] ?? '');
102
        $signer = (new SignatureAlgorithmFactory())->getAlgorithm($algo, $key);
0 ignored issues
show
Bug introduced by
It seems like $algo can also be of type null; however, parameter $algId of SimpleSAML\XMLSecurity\A...Factory::getAlgorithm() 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

102
        $signer = (new SignatureAlgorithmFactory())->getAlgorithm(/** @scrutinizer ignore-type */ $algo, $key);
Loading history...
103
104
        $keyInfo = null;
105
        if ($certArray !== null) {
106
            $keyInfo = new KeyInfo([
107
                new X509Data([
108
                    new X509Certificate($certArray['certData']),
109
                ]),
110
            ]);
111
        }
112
113
        $document->sign($signer, C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, $keyInfo);
114
        return $document;
115
    }
116
117
118
    /**
119
     * This method builds the md:Organization element, if any
120
     */
121
    private function getOrganization(): ?Organization
122
    {
123
        if (
124
            !$this->metadata->hasValue('OrganizationName') ||
125
            !$this->metadata->hasValue('OrganizationDisplayName') ||
126
            !$this->metadata->hasValue('OrganizationURL')
127
        ) {
128
            // empty or incomplete organization information
129
            return null;
130
        }
131
132
        $arrayUtils = new Utils\Arrays();
133
        $org = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $org is dead and can be removed.
Loading history...
134
135
        try {
136
            $org = Organization::fromArray([
137
                'OrganizationName' => $arrayUtils->arrayize($this->metadata->getArray('OrganizationName'), 'en'),
138
                'OrganizationDisplayName' => $arrayUtils->arrayize(
139
                    $this->metadata->getArray('OrganizationDisplayName'),
140
                    'en',
141
                ),
142
                'OrganizationURL' => $arrayUtils->arrayize($this->metadata->getArray('OrganizationURL'), 'en'),
143
            ]);
144
        } catch (ArrayValidationException $e) {
145
            Logger::error('Federation: invalid content found in contact: ' . $e->getMessage());
146
        }
147
148
        return $org;
149
    }
150
151
152
    /**
153
     * This method builds the role descriptor elements
154
     */
155
    private function getRoleDescriptor(): array
156
    {
157
        $descriptors = [];
158
159
        $set = $this->metadata->getString('metadata-set');
160
        switch ($set) {
161
            case 'adfs-idp-hosted':
162
                $descriptors[] = $this->getSecurityTokenService();
163
                break;
164
            default:
165
                throw new Exception('Not implemented');
166
        }
167
168
        return $descriptors;
169
    }
170
171
172
    /**
173
     * This method builds the SecurityTokenService element
174
     */
175
    public function getSecurityTokenService(): SecurityTokenServiceType
176
    {
177
        $defaultEndpoint = Module::getModuleURL('adfs') . '/idp/prp.php';
178
179
        return new SecurityTokenServiceType(
180
            protocolSupportEnumeration: [C::NS_TRUST, C::NS_FED],
0 ignored issues
show
Bug introduced by
The constant SimpleSAML\WSSecurity\Constants::NS_TRUST was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
181
            keyDescriptors: $this->getKeyDescriptor(),
182
            tokenTypesOffered: new TokenTypesOffered([new TokenType('urn:oasis:names:tc:SAML:1.0:assertion')]),
183
            securityTokenServiceEndpoint: [
184
                new SecurityTokenServiceEndpoint([
185
                    new EndpointReference(new Address($defaultEndpoint)),
186
                ]),
187
            ],
188
            passiveRequestorEndpoint: [
189
                new PassiveRequestorEndpoint([
190
                    new EndpointReference(new Address($defaultEndpoint)),
191
                ]),
192
            ],
193
        );
194
    }
195
196
197
    /**
198
     * This method builds the md:KeyDescriptor elements, if any
199
     */
200
    private function getKeyDescriptor(): array
201
    {
202
        $keyDescriptor = [];
203
204
        $keys = $this->metadata->getPublicKeys();
205
        foreach ($keys as $key) {
206
            if ($key['type'] !== 'X509Certificate') {
207
                continue;
208
            }
209
            if (!isset($key['signing']) || $key['signing'] === true) {
210
                $keyDescriptor[] = self::buildKeyDescriptor(
211
                    'signing',
212
                    $key['X509Certificate'],
213
                    $key['name'] ?? null,
214
                );
215
            }
216
            if (!isset($key['encryption']) || $key['encryption'] === true) {
217
                $keyDescriptor[] = self::buildKeyDescriptor(
218
                    'encryption',
219
                    $key['X509Certificate'],
220
                    $key['name'] ?? null,
221
                );
222
            }
223
        }
224
225
        if ($this->metadata->hasValue('https.certData')) {
226
            $keyDescriptor[] = self::buildKeyDescriptor('signing', $this->metadata->getString('https.certData'), null);
227
        }
228
229
        return $keyDescriptor;
230
    }
231
232
233
    /**
234
     * This method builds the md:ContactPerson elements, if any
235
     */
236
    private function getContactPerson(): array
237
    {
238
        $contacts = [];
239
240
        foreach ($this->metadata->getOptionalArray('contacts', []) as $contact) {
241
            if (array_key_exists('ContactType', $contact) && array_key_exists('EmailAddress', $contact)) {
242
                $contacts[] = ContactPerson::fromArray($contact);
243
            }
244
        }
245
246
        return $contacts;
247
    }
248
249
250
    /**
251
     * This method builds the md:Extensions, if any
252
     */
253
    private function getExtensions(): ?Extensions
0 ignored issues
show
Unused Code introduced by
The method getExtensions() 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...
Bug introduced by
The type SimpleSAML\Module\adfs\IdP\Extensions was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
254
    {
255
        $extensions = [];
256
257
        if ($this->metadata->hasValue('scope')) {
258
            foreach ($this->metadata->getArray('scope') as $scopetext) {
259
                $isRegexpScope = (1 === preg_match('/[\$\^\)\(\*\|\\\\]/', $scopetext));
260
                $extensions[] = new Scope($scopetext, $isRegexpScope);
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Module\adfs\IdP\Scope was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
261
            }
262
        }
263
264
        if ($this->metadata->hasValue('EntityAttributes')) {
265
            $attr = [];
266
            foreach ($this->metadata->getArray('EntityAttributes') as $attributeName => $attributeValues) {
267
                $attrValues = [];
268
                foreach ($attributeValues as $attributeValue) {
269
                    $attrValues[] = new AttributeValue($attributeValue);
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Module\adfs\IdP\AttributeValue was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
270
                }
271
272
                // Attribute names that is not URI is prefixed as this: '{nameformat}name'
273
                if (preg_match('/^\{(.*?)\}(.*)$/', $attributeName, $matches)) {
274
                    $attr[] = new Attribute(
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Module\adfs\IdP\Attribute was not found. Did you mean Attribute? If so, make sure to prefix the type with \.
Loading history...
275
                        name: $matches[2],
276
                        nameFormat: $matches[1] === C::NAMEFORMAT_UNSPECIFIED ? null : $matches[1],
277
                        attributeValue: $attrValues,
278
                    );
279
                } else {
280
                    $attr[] = new Attribute(
281
                        name: $attributeName,
282
                        nameFormat: C::NAMEFORMAT_UNSPECIFIED,
283
                        attributeValue: $attrValues,
284
                    );
285
                }
286
            }
287
288
            $extensions[] = new EntityAttributes($attr);
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Module\adfs\IdP\EntityAttributes was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
289
        }
290
291
        if ($this->metadata->hasValue('saml:Extensions')) {
292
            $chunks = $this->metadata->getArray('saml:Extensions');
293
            Assert::allIsInstanceOf($chunks, Chunk::class);
294
            $extensions = array_merge($extensions, $chunks);
295
        }
296
297
        if ($this->metadata->hasValue('RegistrationInfo')) {
298
            try {
299
                $extensions[] = RegistrationInfo::fromArray($this->metadata->getArray('RegistrationInfo'));
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Module\adfs\IdP\RegistrationInfo was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
300
            } catch (ArrayValidationException $err) {
301
                Logger::error('Metadata: invalid content found in RegistrationInfo: ' . $err->getMessage());
302
            }
303
        }
304
305
        if ($this->metadata->hasValue('UIInfo')) {
306
            try {
307
                $extensions[] = UIInfo::fromArray($this->metadata->getArray('UIInfo'));
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Module\adfs\IdP\UIInfo was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
308
            } catch (ArrayValidationException $err) {
309
                Logger::error('Metadata: invalid content found in UIInfo: ' . $err->getMessage());
310
            }
311
        }
312
313
        if ($this->metadata->hasValue('DiscoHints')) {
314
            try {
315
                $extensions[] = DiscoHints::fromArray($this->metadata->getArray('DiscoHints'));
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Module\adfs\IdP\DiscoHints was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
316
            } catch (ArrayValidationException $err) {
317
                Logger::error('Metadata: invalid content found in DiscoHints: ' . $err->getMessage());
318
            }
319
        }
320
321
        if ($extensions !== []) {
322
            return new Extensions($extensions);
323
        }
324
325
        return null;
326
    }
327
328
329
    private static function buildKeyDescriptor(string $use, string $x509Cert, ?string $keyName): KeyDescriptor
330
    {
331
        Assert::oneOf($use, ['encryption', 'signing']);
332
        $info = [
333
            new X509Data([
334
                new X509Certificate($x509Cert),
335
            ]),
336
        ];
337
338
        if ($keyName !== null) {
339
            $info[] = new KeyName($keyName);
340
        }
341
342
        return new KeyDescriptor(
343
            new KeyInfo($info),
344
            $use,
345
        );
346
    }
347
}
348