1<?php
2
3namespace SimpleSAML\Metadata;
4
5use SAML2\Constants;
6use SAML2\XML\md\AttributeAuthorityDescriptor;
7use SAML2\XML\md\AttributeConsumingService;
8use SAML2\XML\md\EndpointType;
9use SAML2\XML\md\EntityDescriptor;
10use SAML2\XML\md\IDPSSODescriptor;
11use SAML2\XML\md\IndexedEndpointType;
12use SAML2\XML\md\Organization;
13use SAML2\XML\md\RequestedAttribute;
14use SAML2\XML\md\RoleDescriptor;
15use SAML2\XML\md\SPSSODescriptor;
16use SAML2\XML\mdattr\EntityAttributes;
17use SAML2\XML\mdrpi\RegistrationInfo;
18use SAML2\XML\mdui\DiscoHints;
19use SAML2\XML\mdui\Keywords;
20use SAML2\XML\mdui\Logo;
21use SAML2\XML\mdui\UIInfo;
22use SAML2\XML\saml\Attribute;
23use SAML2\XML\saml\AttributeValue;
24use SAML2\XML\shibmd\Scope;
25use SimpleSAML\Configuration;
26use SimpleSAML\Logger;
27use SimpleSAML\Module\adfs\SAML2\XML\fed\SecurityTokenServiceType;
28use SimpleSAML\Utils;
29
30/**
31 * Class for generating SAML 2.0 metadata from SimpleSAMLphp metadata arrays.
32 *
33 * This class builds SAML 2.0 metadata for an entity by examining the metadata for the entity.
34 *
35 * @package SimpleSAMLphp
36 */
37
38class SAMLBuilder
39{
40    /**
41     * The EntityDescriptor we are building.
42     *
43     * @var \SAML2\XML\md\EntityDescriptor
44     */
45    private $entityDescriptor;
46
47
48    /**
49     * The maximum time in seconds the metadata should be cached.
50     *
51     * @var int|null
52     */
53    private $maxCache = null;
54
55
56    /**
57     * The maximum time in seconds since the current time that this metadata should be considered valid.
58     *
59     * @var int|null
60     */
61    private $maxDuration = null;
62
63
64    /**
65     * Initialize the SAML builder.
66     *
67     * @param string   $entityId The entity id of the entity.
68     * @param int|null $maxCache The maximum time in seconds the metadata should be cached. Defaults to null
69     * @param int|null $maxDuration The maximum time in seconds this metadata should be considered valid. Defaults
70     * to null.
71     * @return void
72     */
73    public function __construct($entityId, $maxCache = null, $maxDuration = null)
74    {
75        assert(is_string($entityId));
76
77        $this->maxCache = $maxCache;
78        $this->maxDuration = $maxDuration;
79
80        $this->entityDescriptor = new EntityDescriptor();
81        $this->entityDescriptor->setEntityID($entityId);
82    }
83
84
85    /**
86     * @param array $metadata
87     * @return void
88     */
89    private function setExpiration($metadata)
90    {
91        if (array_key_exists('expire', $metadata)) {
92            if ($metadata['expire'] - time() < $this->maxDuration) {
93                $this->maxDuration = $metadata['expire'] - time();
94            }
95        }
96
97        if ($this->maxCache !== null) {
98            $this->entityDescriptor->setCacheDuration('PT' . $this->maxCache . 'S');
99        }
100        if ($this->maxDuration !== null) {
101            $this->entityDescriptor->setValidUntil(time() + $this->maxDuration);
102        }
103    }
104
105
106    /**
107     * Retrieve the EntityDescriptor element which is generated for this entity.
108     *
109     * @return \DOMElement The EntityDescriptor element of this entity.
110     */
111    public function getEntityDescriptor()
112    {
113        $xml = $this->entityDescriptor->toXML();
114        $xml->ownerDocument->appendChild($xml);
115
116        return $xml;
117    }
118
119
120    /**
121     * Retrieve the EntityDescriptor as text.
122     *
123     * This function serializes this EntityDescriptor, and returns it as text.
124     *
125     * @param bool $formatted Whether the returned EntityDescriptor should be formatted first.
126     *
127     * @return string The serialized EntityDescriptor.
128     */
129    public function getEntityDescriptorText($formatted = true)
130    {
131        assert(is_bool($formatted));
132
133        $xml = $this->getEntityDescriptor();
134        if ($formatted) {
135            Utils\XML::formatDOMElement($xml);
136        }
137
138        return $xml->ownerDocument->saveXML();
139    }
140
141
142    /**
143     * Add a SecurityTokenServiceType for ADFS metadata.
144     *
145     * @param array $metadata The metadata with the information about the SecurityTokenServiceType.
146     * @return void
147     */
148    public function addSecurityTokenServiceType($metadata)
149    {
150        assert(is_array($metadata));
151        assert(isset($metadata['entityid']));
152        assert(isset($metadata['metadata-set']));
153
154        $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']);
155        $defaultEndpoint = $metadata->getDefaultEndpoint('SingleSignOnService');
156        $e = new SecurityTokenServiceType();
157        $e->setLocation($defaultEndpoint['Location']);
158
159        $this->addCertificate($e, $metadata);
160
161        $this->entityDescriptor->addRoleDescriptor($e);
162    }
163
164
165    /**
166     * Add extensions to the metadata.
167     *
168     * @param \SimpleSAML\Configuration    $metadata The metadata to get extensions from.
169     * @param \SAML2\XML\md\RoleDescriptor $e Reference to the element where the Extensions element should be included.
170     * @return void
171     */
172    private function addExtensions(Configuration $metadata, RoleDescriptor $e)
173    {
174        if ($metadata->hasValue('tags')) {
175            $a = new Attribute();
176            $a->setName('tags');
177            foreach ($metadata->getArray('tags') as $tag) {
178                $a->addAttributeValue(new AttributeValue($tag));
179            }
180            $e->setExtensions(array_merge($e->getExtensions(), [$a]));
181        }
182
183        if ($metadata->hasValue('hint.cidr')) {
184            $a = new Attribute();
185            $a->setName('hint.cidr');
186            foreach ($metadata->getArray('hint.cidr') as $hint) {
187                $a->addAttributeValue(new AttributeValue($hint));
188            }
189            $e->setExtensions(array_merge($e->getExtensions(), [$a]));
190        }
191
192        if ($metadata->hasValue('scope')) {
193            foreach ($metadata->getArray('scope') as $scopetext) {
194                $s = new Scope();
195                $s->setScope($scopetext);
196                // Check whether $ ^ ( ) * | \ are in a scope -> assume regex.
197                if (1 === preg_match('/[\$\^\)\(\*\|\\\\]/', $scopetext)) {
198                    $s->setIsRegexpScope(true);
199                } else {
200                    $s->setIsRegexpScope(false);
201                }
202                $e->setExtensions(array_merge($e->getExtensions(), [$s]));
203            }
204        }
205
206        if ($metadata->hasValue('EntityAttributes')) {
207            $ea = new EntityAttributes();
208            foreach ($metadata->getArray('EntityAttributes') as $attributeName => $attributeValues) {
209                $a = new Attribute();
210                $a->setName($attributeName);
211                $a->setNameFormat('urn:oasis:names:tc:SAML:2.0:attrname-format:uri');
212
213                // Attribute names that is not URI is prefixed as this: '{nameformat}name'
214                if (preg_match('/^\{(.*?)\}(.*)$/', $attributeName, $matches)) {
215                    $a->setName($matches[2]);
216                    $nameFormat = $matches[1];
217                    if ($nameFormat !== Constants::NAMEFORMAT_UNSPECIFIED) {
218                        $a->setNameFormat($nameFormat);
219                    }
220                }
221                foreach ($attributeValues as $attributeValue) {
222                    $a->addAttributeValue(new AttributeValue($attributeValue));
223                }
224                $ea->addChildren($a);
225            }
226            $this->entityDescriptor->setExtensions(
227                array_merge($this->entityDescriptor->getExtensions(), [$ea])
228            );
229        }
230
231        if ($metadata->hasValue('RegistrationInfo')) {
232            $ri = new RegistrationInfo();
233            foreach ($metadata->getArray('RegistrationInfo') as $riName => $riValues) {
234                switch ($riName) {
235                    case 'authority':
236                        $ri->setRegistrationAuthority($riValues);
237                        break;
238                    case 'instant':
239                        $ri->setRegistrationInstant(\SAML2\Utils::xsDateTimeToTimestamp($riValues));
240                        break;
241                    case 'policies':
242                        $ri->setRegistrationPolicy($riValues);
243                        break;
244                }
245            }
246            $this->entityDescriptor->setExtensions(
247                array_merge($this->entityDescriptor->getExtensions(), [$ri])
248            );
249        }
250
251        if ($metadata->hasValue('UIInfo')) {
252            $ui = new UIInfo();
253            foreach ($metadata->getArray('UIInfo') as $uiName => $uiValues) {
254                switch ($uiName) {
255                    case 'DisplayName':
256                        $ui->setDisplayName($uiValues);
257                        break;
258                    case 'Description':
259                        $ui->setDescription($uiValues);
260                        break;
261                    case 'InformationURL':
262                        $ui->setInformationURL($uiValues);
263                        break;
264                    case 'PrivacyStatementURL':
265                        $ui->setPrivacyStatementURL($uiValues);
266                        break;
267                    case 'Keywords':
268                        foreach ($uiValues as $lang => $keywords) {
269                            $uiItem = new Keywords();
270                            $uiItem->setLanguage($lang);
271                            $uiItem->setKeywords($keywords);
272                            $ui->addKeyword($uiItem);
273                        }
274                        break;
275                    case 'Logo':
276                        foreach ($uiValues as $logo) {
277                            $uiItem = new Logo();
278                            $uiItem->setUrl($logo['url']);
279                            $uiItem->setWidth($logo['width']);
280                            $uiItem->setHeight($logo['height']);
281                            if (isset($logo['lang'])) {
282                                $uiItem->setLanguage($logo['lang']);
283                            }
284                            $ui->addLogo($uiItem);
285                        }
286                        break;
287                }
288            }
289            $e->setExtensions(array_merge($e->getExtensions(), [$ui]));
290        }
291
292        if ($metadata->hasValue('DiscoHints')) {
293            $dh = new DiscoHints();
294            foreach ($metadata->getArray('DiscoHints') as $dhName => $dhValues) {
295                switch ($dhName) {
296                    case 'IPHint':
297                        $dh->setIPHint($dhValues);
298                        break;
299                    case 'DomainHint':
300                        $dh->setDomainHint($dhValues);
301                        break;
302                    case 'GeolocationHint':
303                        $dh->setGeolocationHint($dhValues);
304                        break;
305                }
306            }
307            $e->setExtensions(array_merge($e->getExtensions(), [$dh]));
308        }
309    }
310
311
312    /**
313     * Add an Organization element based on data passed as parameters
314     *
315     * @param array $orgName An array with the localized OrganizationName.
316     * @param array $orgDisplayName An array with the localized OrganizationDisplayName.
317     * @param array $orgURL An array with the localized OrganizationURL.
318     * @return void
319     */
320    public function addOrganization(array $orgName, array $orgDisplayName, array $orgURL)
321    {
322        $org = new Organization();
323
324        $org->setOrganizationName($orgName);
325        $org->setOrganizationDisplayName($orgDisplayName);
326        $org->setOrganizationURL($orgURL);
327
328        $this->entityDescriptor->setOrganization($org);
329    }
330
331
332    /**
333     * Add an Organization element based on metadata array.
334     *
335     * @param array $metadata The metadata we should extract the organization information from.
336     * @return void
337     */
338    public function addOrganizationInfo(array $metadata)
339    {
340        if (
341            empty($metadata['OrganizationName']) ||
342            empty($metadata['OrganizationDisplayName']) ||
343            empty($metadata['OrganizationURL'])
344        ) {
345            // empty or incomplete organization information
346            return;
347        }
348
349        $orgName = Utils\Arrays::arrayize($metadata['OrganizationName'], 'en');
350        $orgDisplayName = Utils\Arrays::arrayize($metadata['OrganizationDisplayName'], 'en');
351        $orgURL = Utils\Arrays::arrayize($metadata['OrganizationURL'], 'en');
352
353        $this->addOrganization($orgName, $orgDisplayName, $orgURL);
354    }
355
356
357    /**
358     * Add a list of endpoints to metadata.
359     *
360     * @param array $endpoints The endpoints.
361     * @param bool  $indexed Whether the endpoints should be indexed.
362     *
363     * @return array An array of endpoint objects,
364     *     either \SAML2\XML\md\EndpointType or \SAML2\XML\md\IndexedEndpointType.
365     */
366    private static function createEndpoints(array $endpoints, $indexed)
367    {
368        assert(is_bool($indexed));
369
370        $ret = [];
371
372        foreach ($endpoints as &$ep) {
373            if ($indexed) {
374                $t = new IndexedEndpointType();
375                if (!isset($ep['index'])) {
376                    // Find the maximum index
377                    $maxIndex = -1;
378                    foreach ($endpoints as $ep) {
379                        if (!isset($ep['index'])) {
380                            continue;
381                        }
382
383                        if ($ep['index'] > $maxIndex) {
384                            $maxIndex = $ep['index'];
385                        }
386                    }
387
388                    $ep['index'] = $maxIndex + 1;
389                }
390
391                $t->setIndex($ep['index']);
392            } else {
393                $t = new EndpointType();
394            }
395
396            $t->setBinding($ep['Binding']);
397            $t->setLocation($ep['Location']);
398            if (isset($ep['ResponseLocation'])) {
399                $t->setResponseLocation($ep['ResponseLocation']);
400            }
401            if (isset($ep['hoksso:ProtocolBinding'])) {
402                $t->setAttributeNS(
403                    Constants::NS_HOK,
404                    'hoksso:ProtocolBinding',
405                    Constants::BINDING_HTTP_REDIRECT
406                );
407            }
408
409            $ret[] = $t;
410        }
411
412        return $ret;
413    }
414
415
416    /**
417     * Add an AttributeConsumingService element to the metadata.
418     *
419     * @param \SAML2\XML\md\SPSSODescriptor $spDesc The SPSSODescriptor element.
420     * @param \SimpleSAML\Configuration     $metadata The metadata.
421     * @return void
422     */
423    private function addAttributeConsumingService(
424        SPSSODescriptor $spDesc,
425        Configuration $metadata
426    ) {
427        $attributes = $metadata->getArray('attributes', []);
428        $name = $metadata->getLocalizedString('name', null);
429
430        if ($name === null || count($attributes) == 0) {
431            // we cannot add an AttributeConsumingService without name and attributes
432            return;
433        }
434
435        $attributesrequired = $metadata->getArray('attributes.required', []);
436
437        /*
438         * Add an AttributeConsumingService element with information as name and description and list
439         * of requested attributes
440         */
441        $attributeconsumer = new AttributeConsumingService();
442
443        $attributeconsumer->setIndex($metadata->getInteger('attributes.index', 0));
444
445        if ($metadata->hasValue('attributes.isDefault')) {
446            $attributeconsumer->setIsDefault($metadata->getBoolean('attributes.isDefault', false));
447        }
448
449        $attributeconsumer->setServiceName($name);
450        $attributeconsumer->setServiceDescription($metadata->getLocalizedString('description', []));
451
452        $nameFormat = $metadata->getString('attributes.NameFormat', Constants::NAMEFORMAT_UNSPECIFIED);
453        foreach ($attributes as $friendlyName => $attribute) {
454            $t = new RequestedAttribute();
455            $t->setName($attribute);
456            if (!is_int($friendlyName)) {
457                $t->setFriendlyName($friendlyName);
458            }
459            if ($nameFormat !== Constants::NAMEFORMAT_UNSPECIFIED) {
460                $t->setNameFormat($nameFormat);
461            }
462            if (in_array($attribute, $attributesrequired, true)) {
463                $t->setIsRequired(true);
464            }
465            $attributeconsumer->addRequestedAttribute($t);
466        }
467
468        $spDesc->addAttributeConsumingService($attributeconsumer);
469    }
470
471
472    /**
473     * Add a specific type of metadata to an entity.
474     *
475     * @param string $set The metadata set this metadata comes from.
476     * @param array  $metadata The metadata.
477     * @return void
478     */
479    public function addMetadata($set, $metadata)
480    {
481        assert(is_string($set));
482        assert(is_array($metadata));
483
484        $this->setExpiration($metadata);
485
486        switch ($set) {
487            case 'saml20-sp-remote':
488                $this->addMetadataSP20($metadata);
489                break;
490            case 'saml20-idp-remote':
491                $this->addMetadataIdP20($metadata);
492                break;
493            case 'shib13-sp-remote':
494                $this->addMetadataSP11($metadata);
495                break;
496            case 'shib13-idp-remote':
497                $this->addMetadataIdP11($metadata);
498                break;
499            case 'attributeauthority-remote':
500                $this->addAttributeAuthority($metadata);
501                break;
502            default:
503                Logger::warning('Unable to generate metadata for unknown type \'' . $set . '\'.');
504        }
505    }
506
507
508    /**
509     * Add SAML 2.0 SP metadata.
510     *
511     * @param array $metadata The metadata.
512     * @param array $protocols The protocols supported. Defaults to \SAML2\Constants::NS_SAMLP.
513     * @return void
514     */
515    public function addMetadataSP20($metadata, $protocols = [Constants::NS_SAMLP])
516    {
517        assert(is_array($metadata));
518        assert(is_array($protocols));
519        assert(isset($metadata['entityid']));
520        assert(isset($metadata['metadata-set']));
521
522        $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']);
523
524        $e = new SPSSODescriptor();
525        $e->setProtocolSupportEnumeration($protocols);
526
527        if ($metadata->hasValue('saml20.sign.assertion')) {
528            $e->setWantAssertionsSigned($metadata->getBoolean('saml20.sign.assertion'));
529        }
530
531        if ($metadata->hasValue('redirect.validate')) {
532            $e->setAuthnRequestsSigned($metadata->getBoolean('redirect.validate'));
533        } elseif ($metadata->hasValue('validate.authnrequest')) {
534            $e->setAuthnRequestsSigned($metadata->getBoolean('validate.authnrequest'));
535        }
536
537        $this->addExtensions($metadata, $e);
538
539        $this->addCertificate($e, $metadata);
540
541        $e->setSingleLogoutService(self::createEndpoints($metadata->getEndpoints('SingleLogoutService'), false));
542
543        $e->setNameIDFormat($metadata->getArrayizeString('NameIDFormat', []));
544
545        $endpoints = $metadata->getEndpoints('AssertionConsumerService');
546        foreach ($metadata->getArrayizeString('AssertionConsumerService.artifact', []) as $acs) {
547            $endpoints[] = [
548                'Binding'  => Constants::BINDING_HTTP_ARTIFACT,
549                'Location' => $acs,
550            ];
551        }
552        $e->setAssertionConsumerService(self::createEndpoints($endpoints, true));
553
554        $this->addAttributeConsumingService($e, $metadata);
555
556        $this->entityDescriptor->addRoleDescriptor($e);
557
558        foreach ($metadata->getArray('contacts', []) as $contact) {
559            if (array_key_exists('contactType', $contact) && array_key_exists('emailAddress', $contact)) {
560                $this->addContact($contact['contactType'], Utils\Config\Metadata::getContact($contact));
561            }
562        }
563    }
564
565
566    /**
567     * Add metadata of a SAML 2.0 identity provider.
568     *
569     * @param array $metadata The metadata.
570     * @return void
571     */
572    public function addMetadataIdP20($metadata)
573    {
574        assert(is_array($metadata));
575        assert(isset($metadata['entityid']));
576        assert(isset($metadata['metadata-set']));
577
578        $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']);
579
580        $e = new IDPSSODescriptor();
581        $e->setProtocolSupportEnumeration(array_merge($e->getProtocolSupportEnumeration(), [Constants::NS_SAMLP]));
582
583        if ($metadata->hasValue('sign.authnrequest')) {
584            $e->setWantAuthnRequestsSigned($metadata->getBoolean('sign.authnrequest'));
585        } elseif ($metadata->hasValue('redirect.sign')) {
586            $e->setWantAuthnRequestsSigned($metadata->getBoolean('redirect.sign'));
587        }
588
589        $this->addExtensions($metadata, $e);
590
591        $this->addCertificate($e, $metadata);
592
593        if ($metadata->hasValue('ArtifactResolutionService')) {
594            $e->setArtifactResolutionService(self::createEndpoints(
595                $metadata->getEndpoints('ArtifactResolutionService'),
596                true
597            ));
598        }
599
600        $e->setSingleLogoutService(self::createEndpoints($metadata->getEndpoints('SingleLogoutService'), false));
601
602        $e->setNameIDFormat($metadata->getArrayizeString('NameIDFormat', []));
603
604        $e->setSingleSignOnService(self::createEndpoints($metadata->getEndpoints('SingleSignOnService'), false));
605
606        $this->entityDescriptor->addRoleDescriptor($e);
607
608        foreach ($metadata->getArray('contacts', []) as $contact) {
609            if (array_key_exists('contactType', $contact) && array_key_exists('emailAddress', $contact)) {
610                $this->addContact($contact['contactType'], Utils\Config\Metadata::getContact($contact));
611            }
612        }
613    }
614
615
616    /**
617     * Add metadata of a SAML 1.1 service provider.
618     *
619     * @param array $metadata The metadata.
620     * @return void
621     */
622    public function addMetadataSP11($metadata)
623    {
624        assert(is_array($metadata));
625        assert(isset($metadata['entityid']));
626        assert(isset($metadata['metadata-set']));
627
628        $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']);
629
630        $e = new SPSSODescriptor();
631        $e->setProtocolSupportEnumeration(
632            array_merge(
633                $e->getProtocolSupportEnumeration(),
634                ['urn:oasis:names:tc:SAML:1.1:protocol']
635            )
636        );
637
638        $this->addCertificate($e, $metadata);
639
640        $e->setNameIDFormat($metadata->getArrayizeString('NameIDFormat', []));
641
642        $endpoints = $metadata->getEndpoints('AssertionConsumerService');
643        foreach ($metadata->getArrayizeString('AssertionConsumerService.artifact', []) as $acs) {
644            $endpoints[] = [
645                'Binding'  => 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01',
646                'Location' => $acs,
647            ];
648        }
649        $e->setAssertionConsumerService(self::createEndpoints($endpoints, true));
650
651        $this->addAttributeConsumingService($e, $metadata);
652
653        $this->entityDescriptor->addRoleDescriptor($e);
654    }
655
656
657    /**
658     * Add metadata of a SAML 1.1 identity provider.
659     *
660     * @param array $metadata The metadata.
661     * @return void
662     */
663    public function addMetadataIdP11($metadata)
664    {
665        assert(is_array($metadata));
666        assert(isset($metadata['entityid']));
667        assert(isset($metadata['metadata-set']));
668
669        $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']);
670
671        $e = new IDPSSODescriptor();
672        $e->setProtocolSupportEnumeration(
673            array_merge($e->getProtocolSupportEnumeration(), [
674                'urn:oasis:names:tc:SAML:1.1:protocol',
675                'urn:mace:shibboleth:1.0'
676            ])
677        );
678
679        $this->addCertificate($e, $metadata);
680
681        $e->setNameIDFormat($metadata->getArrayizeString('NameIDFormat', []));
682
683        $e->setSingleSignOnService(self::createEndpoints($metadata->getEndpoints('SingleSignOnService'), false));
684
685        $this->entityDescriptor->addRoleDescriptor($e);
686    }
687
688
689    /**
690     * Add metadata of a SAML attribute authority.
691     *
692     * @param array $metadata The AttributeAuthorityDescriptor, in the format returned by
693     * \SimpleSAML\Metadata\SAMLParser.
694     * @return void
695     */
696    public function addAttributeAuthority(array $metadata)
697    {
698        assert(is_array($metadata));
699        assert(isset($metadata['entityid']));
700        assert(isset($metadata['metadata-set']));
701
702        $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']);
703
704        $e = new AttributeAuthorityDescriptor();
705        $e->setProtocolSupportEnumeration($metadata->getArray('protocols', [Constants::NS_SAMLP]));
706
707        $this->addExtensions($metadata, $e);
708        $this->addCertificate($e, $metadata);
709
710        $e->setAttributeService(self::createEndpoints($metadata->getEndpoints('AttributeService'), false));
711        $e->setAssertionIDRequestService(self::createEndpoints(
712            $metadata->getEndpoints('AssertionIDRequestService'),
713            false
714        ));
715
716        $e->setNameIDFormat($metadata->getArrayizeString('NameIDFormat', []));
717
718        $this->entityDescriptor->addRoleDescriptor($e);
719    }
720
721
722    /**
723     * Add contact information.
724     *
725     * Accepts a contact type, and a contact array that must be previously sanitized.
726     *
727     * WARNING: This function will change its signature and no longer parse a 'name' element.
728     *
729     * @param string $type The type of contact. Deprecated.
730     * @param array  $details The details about the contact.
731     *
732     * @return void
733     * @todo Change the signature to remove $type.
734     * @todo Remove the capability to pass a name and parse it inside the method.
735     */
736    public function addContact($type, $details)
737    {
738        assert(is_string($type));
739        assert(is_array($details));
740        assert(in_array($type, ['technical', 'support', 'administrative', 'billing', 'other'], true));
741
742        // TODO: remove this check as soon as getContact() is called always before calling this function
743        $details = Utils\Config\Metadata::getContact($details);
744
745        $e = new \SAML2\XML\md\ContactPerson();
746        $e->setContactType($type);
747
748        if (!empty($details['attributes'])) {
749            $e->setContactPersonAttributes($details['attributes']);
750        }
751
752        if (isset($details['company'])) {
753            $e->setCompany($details['company']);
754        }
755        if (isset($details['givenName'])) {
756            $e->setGivenName($details['givenName']);
757        }
758        if (isset($details['surName'])) {
759            $e->setSurName($details['surName']);
760        }
761
762        if (isset($details['emailAddress'])) {
763            $eas = $details['emailAddress'];
764            if (!is_array($eas)) {
765                $eas = [$eas];
766            }
767            foreach ($eas as $ea) {
768                $e->addEmailAddress($ea);
769            }
770        }
771
772        if (isset($details['telephoneNumber'])) {
773            $tlfNrs = $details['telephoneNumber'];
774            if (!is_array($tlfNrs)) {
775                $tlfNrs = [$tlfNrs];
776            }
777            foreach ($tlfNrs as $tlfNr) {
778                $e->addTelephoneNumber($tlfNr);
779            }
780        }
781
782        $this->entityDescriptor->addContactPerson($e);
783    }
784
785
786    /**
787     * Add a KeyDescriptor with an X509 certificate.
788     *
789     * @param \SAML2\XML\md\RoleDescriptor $rd The RoleDescriptor the certificate should be added to.
790     * @param string                      $use The value of the 'use' attribute.
791     * @param string                      $x509data The certificate data.
792     * @return void
793     */
794    private function addX509KeyDescriptor(RoleDescriptor $rd, $use, $x509data)
795    {
796        assert(in_array($use, ['encryption', 'signing'], true));
797        assert(is_string($x509data));
798
799        $keyDescriptor = \SAML2\Utils::createKeyDescriptor($x509data);
800        $keyDescriptor->setUse($use);
801        $rd->addKeyDescriptor($keyDescriptor);
802    }
803
804
805    /**
806     * Add a certificate.
807     *
808     * Helper function for adding a certificate to the metadata.
809     *
810     * @param \SAML2\XML\md\RoleDescriptor $rd The RoleDescriptor the certificate should be added to.
811     * @param \SimpleSAML\Configuration    $metadata The metadata of the entity.
812     * @return void
813     */
814    private function addCertificate(RoleDescriptor $rd, Configuration $metadata)
815    {
816        $keys = $metadata->getPublicKeys();
817        foreach ($keys as $key) {
818            if ($key['type'] !== 'X509Certificate') {
819                continue;
820            }
821            if (!isset($key['signing']) || $key['signing'] === true) {
822                $this->addX509KeyDescriptor($rd, 'signing', $key['X509Certificate']);
823            }
824            if (!isset($key['encryption']) || $key['encryption'] === true) {
825                $this->addX509KeyDescriptor($rd, 'encryption', $key['X509Certificate']);
826            }
827        }
828
829        if ($metadata->hasValue('https.certData')) {
830            $this->addX509KeyDescriptor($rd, 'signing', $metadata->getString('https.certData'));
831        }
832    }
833}
834