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