This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace SimpleSAML\Metadata; |
||
6 | |||
7 | use DOMElement; |
||
8 | use SimpleSAML\{Configuration, Module, Logger, Utils}; |
||
9 | use SimpleSAML\Assert\{Assert, AssertionFailedException}; |
||
10 | use SimpleSAML\Module\adfs\SAML2\XML\fed\SecurityTokenServiceType; |
||
11 | use SimpleSAML\SAML2\Constants as C; |
||
12 | use SimpleSAML\SAML2\Exception\ArrayValidationException; |
||
13 | use SimpleSAML\SAML2\XML\idpdisc\DiscoveryResponse; |
||
14 | use SimpleSAML\SAML2\XML\md\{AbstractIndexedEndpointType, ContactPerson, Extensions, KeyDescriptor, NameIDFormat}; |
||
15 | use SimpleSAML\SAML2\XML\md\{ArtifactResolutionService, AssertionConsumerService, AssertionIDRequestService}; |
||
16 | use SimpleSAML\SAML2\XML\md\{AttributeConsumingService, AttributeService, SingleLogoutService, SingleSignOnService}; |
||
17 | use SimpleSAML\SAML2\XML\md\{AttributeAuthorityDescriptor, EntityDescriptor, IDPSSODescriptor, SPSSODescriptor}; |
||
18 | use SimpleSAML\SAML2\XML\md\{Organization, RequestedAttribute, RoleDescriptor, ServiceDescription, ServiceName}; |
||
19 | use SimpleSAML\SAML2\XML\mdattr\EntityAttributes; |
||
20 | use SimpleSAML\SAML2\XML\mdrpi\RegistrationInfo; |
||
21 | use SimpleSAML\SAML2\XML\mdui\{DiscoHints, UIInfo}; |
||
22 | use SimpleSAML\SAML2\XML\saml\{Attribute, AttributeValue}; |
||
23 | use SimpleSAML\SAML2\XML\shibmd\Scope; |
||
24 | use SimpleSAML\XML\Chunk; |
||
25 | use SimpleSAML\XMLSecurity\XML\ds\{KeyInfo, KeyName, X509Certificate, X509Data}; |
||
26 | |||
27 | use function array_key_exists; |
||
28 | use function array_keys; |
||
29 | use function array_map; |
||
30 | use function array_merge; |
||
31 | use function class_parents; |
||
32 | use function count; |
||
33 | use function in_array; |
||
34 | use function is_int; |
||
35 | use function preg_match; |
||
36 | use function time; |
||
37 | |||
38 | /** |
||
39 | * Class for generating SAML 2.0 metadata from SimpleSAMLphp metadata arrays. |
||
40 | * |
||
41 | * This class builds SAML 2.0 metadata for an entity by examining the metadata for the entity. |
||
42 | * |
||
43 | * @package SimpleSAMLphp |
||
44 | */ |
||
45 | |||
46 | class SAMLBuilder |
||
47 | { |
||
48 | /** |
||
49 | * The EntityDescriptor we are building. |
||
50 | * |
||
51 | * @var \SimpleSAML\SAML2\XML\md\EntityDescriptor |
||
52 | */ |
||
53 | private EntityDescriptor $entityDescriptor; |
||
54 | |||
55 | |||
56 | /** |
||
57 | * Initialize the SAML builder. |
||
58 | * |
||
59 | * @param string $entityId The entity id of the entity. |
||
60 | * @param int|null $maxCache The maximum time in seconds the metadata should be cached. Defaults to null |
||
61 | * @param int|null $maxDuration The maximum time in seconds this metadata should be considered valid. Defaults |
||
62 | * to null. |
||
63 | */ |
||
64 | public function __construct( |
||
65 | string $entityId, |
||
66 | private ?int $maxCache = null, |
||
67 | private ?int $maxDuration = null, |
||
68 | ) { |
||
69 | $this->entityDescriptor = new EntityDescriptor(); |
||
70 | $this->entityDescriptor->setEntityID($entityId); |
||
71 | } |
||
72 | |||
73 | |||
74 | /** |
||
75 | * @param array $metadata |
||
76 | */ |
||
77 | private function setExpiration(array $metadata): void |
||
78 | { |
||
79 | if (array_key_exists('expire', $metadata)) { |
||
80 | if ($metadata['expire'] - time() < $this->maxDuration) { |
||
81 | $this->maxDuration = $metadata['expire'] - time(); |
||
82 | } |
||
83 | } |
||
84 | |||
85 | if ($this->maxCache !== null) { |
||
86 | $this->entityDescriptor->setCacheDuration('PT' . $this->maxCache . 'S'); |
||
87 | } |
||
88 | if ($this->maxDuration !== null) { |
||
89 | $this->entityDescriptor->setValidUntil(time() + $this->maxDuration); |
||
90 | } |
||
91 | } |
||
92 | |||
93 | |||
94 | /** |
||
95 | * Retrieve the EntityDescriptor element which is generated for this entity. |
||
96 | * |
||
97 | * @return \DOMElement The EntityDescriptor element of this entity. |
||
98 | */ |
||
99 | public function getEntityDescriptor(): DOMElement |
||
100 | { |
||
101 | $xml = $this->entityDescriptor->toXML(); |
||
102 | $xml->ownerDocument->appendChild($xml); |
||
103 | |||
104 | return $xml; |
||
105 | } |
||
106 | |||
107 | |||
108 | /** |
||
109 | * Retrieve the EntityDescriptor as text. |
||
110 | * |
||
111 | * This function serializes this EntityDescriptor, and returns it as text. |
||
112 | * |
||
113 | * @param bool $formatted Whether the returned EntityDescriptor should be formatted first. |
||
114 | * |
||
115 | * @return string The serialized EntityDescriptor. |
||
116 | */ |
||
117 | public function getEntityDescriptorText(bool $formatted = true): string |
||
118 | { |
||
119 | $xml = $this->getEntityDescriptor(); |
||
120 | if ($formatted) { |
||
121 | $xmlUtils = new Utils\XML(); |
||
122 | $xmlUtils->formatDOMElement($xml); |
||
123 | } |
||
124 | |||
125 | $xml->ownerDocument->encoding = "utf-8"; |
||
126 | |||
127 | return $xml->ownerDocument->saveXML(); |
||
128 | } |
||
129 | |||
130 | |||
131 | /** |
||
132 | * Add a SecurityTokenServiceType for ADFS metadata. |
||
133 | * |
||
134 | * @param array $metadata The metadata with the information about the SecurityTokenServiceType. |
||
135 | */ |
||
136 | public function addSecurityTokenServiceType(array $metadata): void |
||
137 | { |
||
138 | Assert::notNull($metadata['entityid']); |
||
139 | Assert::notNull($metadata['metadata-set']); |
||
140 | |||
141 | $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']); |
||
142 | $defaultEndpoint = $metadata->getDefaultEndpoint('SingleSignOnService'); |
||
143 | |||
144 | $e = new SecurityTokenServiceType(); |
||
145 | $e->setLocation($defaultEndpoint['Location']); |
||
146 | |||
147 | $this->addCertificate($e, $metadata); |
||
148 | |||
149 | $this->entityDescriptor->addRoleDescriptor($e); |
||
150 | } |
||
151 | |||
152 | |||
153 | /** |
||
154 | * Add extensions to the metadata. |
||
155 | * |
||
156 | * @param \SimpleSAML\Configuration $metadata The metadata to get extensions from. |
||
157 | * @param \SimpleSAML\SAML2\XML\md\RoleDescriptor $e Reference to the element where the |
||
158 | * Extensions element should be included. |
||
159 | */ |
||
160 | private function addExtensions(Configuration $metadata, RoleDescriptor $e): void |
||
161 | { |
||
162 | $extensions = []; |
||
163 | |||
164 | if ($metadata->hasValue('scope')) { |
||
165 | foreach ($metadata->getArray('scope') as $scopetext) { |
||
166 | $isRegexpScope = (1 === preg_match('/[\$\^\)\(\*\|\\\\]/', $scopetext)); |
||
167 | $extensions[] = new Scope($scopetext, $isRegexpScope); |
||
168 | } |
||
169 | } |
||
170 | |||
171 | if ($metadata->hasValue('EntityAttributes')) { |
||
172 | $attr = []; |
||
173 | foreach ($metadata->getArray('EntityAttributes') as $attributeName => $attributeValues) { |
||
174 | $attrValues = []; |
||
175 | foreach ($attributeValues as $attributeValue) { |
||
176 | $attrValues[] = new AttributeValue($attributeValue); |
||
177 | } |
||
178 | |||
179 | // Attribute names that is not URI is prefixed as this: '{nameformat}name' |
||
180 | if (preg_match('/^\{(.*?)\}(.*)$/', $attributeName, $matches)) { |
||
181 | $attr[] = new Attribute( |
||
182 | name: $matches[2], |
||
183 | nameFormat: $matches[1] === C::NAMEFORMAT_UNSPECIFIED ? null : $matches[1], |
||
184 | attributeValue: $attrValues, |
||
185 | ); |
||
186 | } else { |
||
187 | $attr[] = new Attribute( |
||
188 | name: $attributeName, |
||
189 | nameFormat: C::NAMEFORMAT_URI, |
||
190 | attributeValue: $attrValues, |
||
191 | ); |
||
192 | } |
||
193 | } |
||
194 | |||
195 | $extensions[] = new EntityAttributes($attr); |
||
196 | } |
||
197 | |||
198 | if ($metadata->hasValue('saml:Extensions')) { |
||
199 | $chunks = $metadata->getArray('saml:Extensions'); |
||
200 | Assert::allIsInstanceOf($chunks, Chunk::class); |
||
201 | $extensions = array_merge($extensions, $chunks); |
||
202 | } |
||
203 | |||
204 | if ($metadata->hasValue('RegistrationInfo')) { |
||
205 | try { |
||
206 | $extensions[] = RegistrationInfo::fromArray($metadata->getArray('RegistrationInfo')); |
||
207 | } catch (ArrayValidationException $err) { |
||
208 | Logger::error('Metadata: invalid content found in RegistrationInfo: ' . $err->getMessage()); |
||
209 | } |
||
210 | } |
||
211 | |||
212 | if ($metadata->hasValue('UIInfo')) { |
||
213 | try { |
||
214 | $extensions[] = UIInfo::fromArray($metadata->getArray('UIInfo')); |
||
215 | } catch (ArrayValidationException $err) { |
||
216 | Logger::error('Metadata: invalid content found in UIInfo: ' . $err->getMessage()); |
||
217 | } |
||
218 | } |
||
219 | |||
220 | if ($metadata->hasValue('DiscoHints')) { |
||
221 | try { |
||
222 | $extensions[] = DiscoHints::fromArray($metadata->getArray('DiscoHints')); |
||
223 | } catch (ArrayValidationException $err) { |
||
224 | Logger::error('Metadata: invalid content found in DiscoHints: ' . $err->getMessage()); |
||
225 | } |
||
226 | } |
||
227 | |||
228 | $e->setExtensions(new Extensions($extensions)); |
||
229 | } |
||
230 | |||
231 | |||
232 | /** |
||
233 | * Add an Organization element based on metadata array. |
||
234 | * |
||
235 | * @param array $metadata The metadata we should extract the organization information from. |
||
236 | */ |
||
237 | public function addOrganizationInfo(array $metadata): void |
||
238 | { |
||
239 | if ( |
||
240 | empty($metadata['OrganizationName']) || |
||
241 | empty($metadata['OrganizationDisplayName']) || |
||
242 | empty($metadata['OrganizationURL']) |
||
243 | ) { |
||
244 | // empty or incomplete organization information |
||
245 | return; |
||
246 | } |
||
247 | |||
248 | $arrayUtils = new Utils\Arrays(); |
||
249 | $org = null; |
||
250 | |||
251 | try { |
||
252 | $org = Organization::fromArray([ |
||
253 | 'OrganizationName' => $arrayUtils->arrayize($metadata['OrganizationName'], 'en'), |
||
254 | 'OrganizationDisplayName' => $arrayUtils->arrayize($metadata['OrganizationDisplayName'], 'en'), |
||
255 | 'OrganizationURL' => $arrayUtils->arrayize($metadata['OrganizationURL'], 'en'), |
||
256 | ]); |
||
257 | } catch (ArrayValidationException $e) { |
||
258 | Logger::error('Federation: invalid content found in contact: ' . $e->getMessage()); |
||
259 | } |
||
260 | |||
261 | $this->entityDescriptor->setOrganization($org); |
||
262 | } |
||
263 | |||
264 | |||
265 | /** |
||
266 | * Add a list of endpoints to metadata. |
||
267 | * |
||
268 | * @param array $endpoints The endpoints. |
||
269 | * @param class-string $class The type of endpoint to create |
||
270 | * |
||
271 | * @return array An array of endpoint objects, |
||
272 | * either \SimpleSAML\SAML2\XML\md\AbstractEndpointType or \SimpleSAML\SAML2\XML\md\AbstractIndexedEndpointType. |
||
273 | */ |
||
274 | private static function createEndpoints(array $endpoints, string $class): array |
||
275 | { |
||
276 | $indexed = in_array(AbstractIndexedEndpointType::class, class_parents($class), true); |
||
277 | $ret = []; |
||
278 | |||
279 | // Set an index if it wasn't already set |
||
280 | if ($indexed) { |
||
281 | foreach ($endpoints as &$ep) { |
||
282 | if (!isset($ep['index'])) { |
||
283 | // Find the maximum index |
||
284 | $maxIndex = -1; |
||
285 | foreach ($endpoints as $ep) { |
||
0 ignored issues
–
show
Comprehensibility
Bug
introduced
by
![]() |
|||
286 | if (!isset($ep['index'])) { |
||
287 | continue; |
||
288 | } |
||
289 | |||
290 | if ($ep['index'] > $maxIndex) { |
||
291 | $maxIndex = $ep['index']; |
||
292 | } |
||
293 | } |
||
294 | |||
295 | $ep['index'] = $maxIndex + 1; |
||
296 | } |
||
297 | } |
||
298 | } |
||
299 | |||
300 | foreach ($endpoints as $endpoint) { |
||
301 | $ret[] = $class::fromArray($endpoint); |
||
302 | } |
||
303 | |||
304 | return $ret; |
||
305 | } |
||
306 | |||
307 | |||
308 | /** |
||
309 | * Add an AttributeConsumingService element to the metadata. |
||
310 | * |
||
311 | * @param \SimpleSAML\SAML2\XML\md\SPSSODescriptor $spDesc The SPSSODescriptor element. |
||
312 | * @param \SimpleSAML\Configuration $metadata The metadata. |
||
313 | */ |
||
314 | private function addAttributeConsumingService( |
||
315 | SPSSODescriptor $spDesc, |
||
316 | Configuration $metadata, |
||
317 | ): void { |
||
318 | $attributes = $metadata->getOptionalArray('attributes', []); |
||
319 | $serviceName = $metadata->getOptionalLocalizedString('name', []); |
||
320 | |||
321 | if (count($serviceName) === 0 || count($attributes) == 0) { |
||
322 | // we cannot add an AttributeConsumingService without name and attributes |
||
323 | return; |
||
324 | } |
||
325 | |||
326 | $attributesrequired = $metadata->getOptionalArray('attributes.required', []); |
||
327 | $nameFormat = $metadata->getOptionalString('attributes.NameFormat', C::NAMEFORMAT_URI); |
||
328 | $serviceDescription = $metadata->getOptionalLocalizedString('description', []); |
||
329 | |||
330 | $requestedAttributes = []; |
||
331 | foreach ($attributes as $friendlyName => $attribute) { |
||
332 | $requestedAttributes[] = new RequestedAttribute( |
||
333 | $attribute, |
||
334 | in_array($attribute, $attributesrequired, true) ?: null, |
||
335 | $nameFormat !== C::NAMEFORMAT_UNSPECIFIED ? $nameFormat : null, |
||
336 | !is_int($friendlyName) ? $friendlyName : null, |
||
337 | ); |
||
338 | } |
||
339 | |||
340 | /** |
||
341 | * Add an AttributeConsumingService element with information as name and description and list |
||
342 | * of requested attributes |
||
343 | */ |
||
344 | $attributeconsumer = new AttributeConsumingService( |
||
345 | $metadata->getOptionalInteger('attributes.index', 0), |
||
346 | array_map( |
||
347 | function ($lang, $sName) { |
||
348 | return new ServiceName($lang, $sName); |
||
349 | }, |
||
350 | array_keys($serviceName), |
||
351 | $serviceName, |
||
352 | ), |
||
353 | $requestedAttributes, |
||
354 | $metadata->hasValue('attributes.isDefault') |
||
355 | ? $metadata->getOptionalBoolean('attributes.isDefault', false) |
||
356 | : null, |
||
357 | array_map( |
||
358 | function ($lang, $sDesc) { |
||
359 | return new ServiceDescription($lang, $sDesc); |
||
360 | }, |
||
361 | array_keys($serviceDescription), |
||
362 | $serviceDescription, |
||
363 | ), |
||
364 | ); |
||
365 | |||
366 | $spDesc->addAttributeConsumingService($attributeconsumer); |
||
367 | } |
||
368 | |||
369 | |||
370 | /** |
||
371 | * Add a specific type of metadata to an entity. |
||
372 | * |
||
373 | * @param string $set The metadata set this metadata comes from. |
||
374 | * @param array $metadata The metadata. |
||
375 | */ |
||
376 | public function addMetadata(string $set, array $metadata): void |
||
377 | { |
||
378 | $this->setExpiration($metadata); |
||
379 | |||
380 | switch ($set) { |
||
381 | case 'saml20-sp-remote': |
||
382 | $this->addMetadataSP20($metadata); |
||
383 | break; |
||
384 | case 'saml20-idp-remote': |
||
385 | $this->addMetadataIdP20($metadata); |
||
386 | break; |
||
387 | case 'attributeauthority-remote': |
||
388 | $this->addAttributeAuthority($metadata); |
||
389 | break; |
||
390 | default: |
||
391 | Logger::warning('Unable to generate metadata for unknown type \'' . $set . '\'.'); |
||
392 | } |
||
393 | } |
||
394 | |||
395 | |||
396 | /** |
||
397 | * Add SAML 2.0 SP metadata. |
||
398 | * |
||
399 | * @param array $metadata The metadata. |
||
400 | * @param string[] $protocols The protocols supported. Defaults to \SimpleSAML\SAML2\Constants::NS_SAMLP. |
||
401 | */ |
||
402 | public function addMetadataSP20(array $metadata, array $protocols = [C::NS_SAMLP]): void |
||
403 | { |
||
404 | Assert::notNull($metadata['entityid']); |
||
405 | Assert::notNull($metadata['metadata-set']); |
||
406 | |||
407 | $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']); |
||
408 | |||
409 | $e = new SPSSODescriptor(); |
||
410 | $e->setProtocolSupportEnumeration($protocols); |
||
411 | |||
412 | if ($metadata->hasValue('saml20.sign.assertion')) { |
||
413 | $e->setWantAssertionsSigned($metadata->getBoolean('saml20.sign.assertion')); |
||
414 | } |
||
415 | |||
416 | if ($metadata->hasValue('redirect.validate')) { |
||
417 | $e->setAuthnRequestsSigned($metadata->getBoolean('redirect.validate')); |
||
418 | } elseif ($metadata->hasValue('validate.authnrequest')) { |
||
419 | $e->setAuthnRequestsSigned($metadata->getBoolean('validate.authnrequest')); |
||
420 | } |
||
421 | |||
422 | $this->addExtensions($metadata, $e); |
||
423 | |||
424 | $this->addCertificate($e, $metadata); |
||
425 | |||
426 | $e->setSingleLogoutService(self::createEndpoints( |
||
427 | $metadata->getEndpoints('SingleLogoutService'), |
||
428 | SingleLogoutService::class, |
||
429 | )); |
||
430 | |||
431 | $nids = []; |
||
432 | foreach ($metadata->getOptionalArrayizeString('NameIDFormat', []) as $nid) { |
||
433 | $nids[] = new NameIDFormat($nid); |
||
434 | } |
||
435 | $e->setNameIDFormat($nids); |
||
436 | |||
437 | $endpoints = $metadata->getEndpoints('AssertionConsumerService'); |
||
438 | foreach ($metadata->getOptionalArrayizeString('AssertionConsumerService.artifact', []) as $acs) { |
||
439 | $endpoints[] = [ |
||
440 | 'Binding' => C::BINDING_HTTP_ARTIFACT, |
||
441 | 'Location' => $acs, |
||
442 | ]; |
||
443 | } |
||
444 | $e->setAssertionConsumerService(self::createEndpoints($endpoints, AssertionConsumerService::class)); |
||
445 | |||
446 | $this->addAttributeConsumingService($e, $metadata); |
||
447 | |||
448 | $this->entityDescriptor->addRoleDescriptor($e); |
||
449 | |||
450 | foreach ($metadata->getOptionalArray('contacts', []) as $contact) { |
||
451 | if (array_key_exists('ContactType', $contact) && array_key_exists('EmailAddress', $contact)) { |
||
452 | $this->addContact(ContactPerson::fromArray($contact)); |
||
453 | } |
||
454 | } |
||
455 | } |
||
456 | |||
457 | |||
458 | /** |
||
459 | * Add metadata of a SAML 2.0 identity provider. |
||
460 | * |
||
461 | * @param array $metadata The metadata. |
||
462 | */ |
||
463 | public function addMetadataIdP20(array $metadata): void |
||
464 | { |
||
465 | Assert::notNull($metadata['entityid']); |
||
466 | Assert::notNull($metadata['metadata-set']); |
||
467 | |||
468 | $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']); |
||
469 | |||
470 | $e = new IDPSSODescriptor(); |
||
471 | $e->setProtocolSupportEnumeration(array_merge($e->getProtocolSupportEnumeration(), [C::NS_SAMLP])); |
||
472 | |||
473 | if ($metadata->hasValue('sign.authnrequest')) { |
||
474 | $e->setWantAuthnRequestsSigned($metadata->getBoolean('sign.authnrequest')); |
||
475 | } elseif ($metadata->hasValue('redirect.sign')) { |
||
476 | $e->setWantAuthnRequestsSigned($metadata->getBoolean('redirect.sign')); |
||
477 | } |
||
478 | |||
479 | if ($metadata->hasValue('errorURL')) { |
||
480 | $e->setErrorURL($metadata->getString('errorURL')); |
||
481 | } else { |
||
482 | $e->setErrorURL(Module::getModuleURL( |
||
483 | 'core/error/ERRORURL_CODE?ts=ERRORURL_TS&rp=ERRORURL_RP&tid=ERRORURL_TID&ctx=ERRORURL_CTX', |
||
484 | )); |
||
485 | } |
||
486 | |||
487 | $this->addExtensions($metadata, $e); |
||
488 | |||
489 | $this->addCertificate($e, $metadata); |
||
490 | |||
491 | if ($metadata->hasValue('ArtifactResolutionService')) { |
||
492 | $e->setArtifactResolutionService(self::createEndpoints( |
||
493 | $metadata->getEndpoints('ArtifactResolutionService'), |
||
494 | ArtifactResolutionService::class, |
||
495 | )); |
||
496 | } |
||
497 | |||
498 | $e->setSingleLogoutService(self::createEndpoints( |
||
499 | $metadata->getEndpoints('SingleLogoutService'), |
||
500 | SingleLogoutService::class, |
||
501 | )); |
||
502 | |||
503 | $nids = []; |
||
504 | foreach ($metadata->getOptionalArrayizeString('NameIDFormat', []) as $nid) { |
||
505 | $nids[] = new NameIDFormat($nid); |
||
506 | } |
||
507 | $e->setNameIDFormat($nids); |
||
508 | |||
509 | $e->setSingleSignOnService(self::createEndpoints( |
||
510 | $metadata->getEndpoints('SingleSignOnService'), |
||
511 | SingleSignOnService::class, |
||
512 | )); |
||
513 | |||
514 | $this->entityDescriptor->addRoleDescriptor($e); |
||
515 | |||
516 | foreach ($metadata->getOptionalArray('contacts', []) as $contact) { |
||
517 | if (array_key_exists('ContactType', $contact) && array_key_exists('EmailAddress', $contact)) { |
||
518 | try { |
||
519 | $this->addContact(ContactPerson::fromArray($contact)); |
||
520 | } catch (ArrayValidationException $e) { |
||
521 | Logger::error('IdP Metadata: invalid content found in contact: ' . $e->getMessage()); |
||
522 | continue; |
||
523 | } |
||
524 | } |
||
525 | } |
||
526 | } |
||
527 | |||
528 | |||
529 | /** |
||
530 | * Add metadata of a SAML attribute authority. |
||
531 | * |
||
532 | * @param array $metadata The AttributeAuthorityDescriptor, in the format returned by |
||
533 | * \SimpleSAML\Metadata\SAMLParser. |
||
534 | */ |
||
535 | public function addAttributeAuthority(array $metadata): void |
||
536 | { |
||
537 | Assert::notNull($metadata['entityid']); |
||
538 | Assert::notNull($metadata['metadata-set']); |
||
539 | |||
540 | $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']); |
||
541 | |||
542 | $e = new AttributeAuthorityDescriptor(); |
||
543 | $e->setProtocolSupportEnumeration($metadata->getOptionalArray('protocols', [C::NS_SAMLP])); |
||
544 | |||
545 | $this->addExtensions($metadata, $e); |
||
546 | $this->addCertificate($e, $metadata); |
||
547 | |||
548 | $e->setAttributeService(self::createEndpoints( |
||
549 | $metadata->getEndpoints('AttributeService'), |
||
550 | AttributeService::class, |
||
551 | )); |
||
552 | $e->setAssertionIDRequestService(self::createEndpoints( |
||
553 | $metadata->getEndpoints('AssertionIDRequestService'), |
||
554 | AssertionIDRequestService::class, |
||
555 | )); |
||
556 | |||
557 | $nids = []; |
||
558 | foreach ($metadata->getOptionalArrayizeString('NameIDFormat', []) as $nid) { |
||
559 | $nids[] = new NameIDFormat($nid); |
||
560 | } |
||
561 | $e->setNameIDFormat($nids); |
||
562 | |||
563 | $this->entityDescriptor->addRoleDescriptor($e); |
||
564 | } |
||
565 | |||
566 | |||
567 | /** |
||
568 | * Add contact information. |
||
569 | * |
||
570 | * @param \SimpleSAML\SAML2\XML\md\ContactPerson $contact The details about the contact. |
||
571 | */ |
||
572 | public function addContact(ContactPerson $contact): void |
||
573 | { |
||
574 | $this->entityDescriptor->addContactPerson($contact); |
||
575 | } |
||
576 | |||
577 | |||
578 | /** |
||
579 | * Add a KeyDescriptor with an X509 certificate. |
||
580 | * |
||
581 | * @param \SimpleSAML\SAML2\XML\md\RoleDescriptor $rd The RoleDescriptor the certificate should be added to. |
||
582 | * @param string $use The value of the 'use' attribute. |
||
583 | * @param string $x509cert The certificate data. |
||
584 | * @param string|null $keyName The name of the key. Should be valid for usage in an ID attribute, |
||
585 | * e.g. not start with a digit. |
||
586 | */ |
||
587 | private function addX509KeyDescriptor( |
||
588 | RoleDescriptor $rd, |
||
589 | string $use, |
||
590 | string $x509cert, |
||
591 | ?string $keyName = null, |
||
592 | ): void { |
||
593 | Assert::oneOf($use, ['encryption', 'signing']); |
||
594 | $info = [ |
||
595 | new X509Data([ |
||
596 | new X509Certificate($x509cert), |
||
597 | ]), |
||
598 | ]; |
||
599 | if ($keyName !== null) { |
||
600 | $info[] = new KeyName($keyName); |
||
601 | } |
||
602 | $keyDescriptor = new KeyDescriptor( |
||
603 | new KeyInfo($info), |
||
604 | $use, |
||
605 | ); |
||
606 | $rd->addKeyDescriptor($keyDescriptor); |
||
607 | } |
||
608 | |||
609 | |||
610 | /** |
||
611 | * Add a certificate. |
||
612 | * |
||
613 | * Helper function for adding a certificate to the metadata. |
||
614 | * |
||
615 | * @param \SimpleSAML\SAML2\XML\md\RoleDescriptor $rd The RoleDescriptor the certificate should be added to. |
||
616 | * @param \SimpleSAML\Configuration $metadata The metadata of the entity. |
||
617 | */ |
||
618 | private function addCertificate(RoleDescriptor $rd, Configuration $metadata): void |
||
619 | { |
||
620 | $keys = $metadata->getPublicKeys(); |
||
621 | foreach ($keys as $key) { |
||
622 | if ($key['type'] !== 'X509Certificate') { |
||
623 | continue; |
||
624 | } |
||
625 | if (!isset($key['signing']) || $key['signing'] === true) { |
||
626 | $this->addX509KeyDescriptor($rd, 'signing', $key['X509Certificate'], $key['name'] ?? null); |
||
627 | } |
||
628 | if (!isset($key['encryption']) || $key['encryption'] === true) { |
||
629 | $this->addX509KeyDescriptor($rd, 'encryption', $key['X509Certificate'], $key['name'] ?? null); |
||
630 | } |
||
631 | } |
||
632 | |||
633 | if ($metadata->hasValue('https.certData')) { |
||
634 | $this->addX509KeyDescriptor($rd, 'signing', $metadata->getString('https.certData')); |
||
635 | } |
||
636 | } |
||
637 | } |
||
638 |