From 8b8a66f37afeb93c4d820b474d875652bd268310 Mon Sep 17 00:00:00 2001 From: jmf3658 <jfullmer@austin.utexas.edu> Date: Wed, 18 Dec 2024 09:56:20 -0700 Subject: [PATCH 1/6] Add support for contactPerson and organization --- config/schema/samlauth.schema.yml | 18 +++++++++++ src/Form/SamlauthSamlConfigureForm.php | 42 ++++++++++++++++++++++++++ src/SamlService.php | 42 +++++++++++++++++++++++++- 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/config/schema/samlauth.schema.yml b/config/schema/samlauth.schema.yml index 409dfa3..0ea43d1 100644 --- a/config/schema/samlauth.schema.yml +++ b/config/schema/samlauth.schema.yml @@ -196,3 +196,21 @@ samlauth.authentication: bypass_relay_state_check: type: boolean label: 'Bypass safety check for dynamic redirect URLs' + technical_givenName: + type: string + label: 'Technical given name' + technical_emailAddress: + type: string + label: 'Technical email address' + support_givenName: + type: string + label: 'Support given name' + support_emailAddress: + type: string + label: 'Support email address' + organization_name: + type: string + label: 'Organization name' + organization_url: + type: string + label: 'Organization URL' diff --git a/src/Form/SamlauthSamlConfigureForm.php b/src/Form/SamlauthSamlConfigureForm.php index 92dbf58..0549167 100644 --- a/src/Form/SamlauthSamlConfigureForm.php +++ b/src/Form/SamlauthSamlConfigureForm.php @@ -521,6 +521,42 @@ class SamlauthSamlConfigureForm extends ConfigFormBase { ], ], ]; + $form['service_provider']['contact'] = [ + '#type' => 'details', + '#open' => TRUE, + '#title' => $this->t('Contact information'), + '#description' => $this->t('It is recommended to supply technical and support contacts.'), + ]; + $form['service_provider']['contact']['technical_givenName'] = [ + '#type' => 'textfield', + '#title' => $this->t('Technical contact given name'), + '#default_value' => $config->get('technical_givenName') ?? '', + ]; + $form['service_provider']['contact']['technical_emailAddress'] = [ + '#type' => 'textfield', + '#title' => $this->t('Technical contact email'), + '#default_value' => $config->get('technical_emailAddress') ?? '', + ]; + $form['service_provider']['contact']['support_givenName'] = [ + '#type' => 'textfield', + '#title' => $this->t('Support contact given name'), + '#default_value' => $config->get('support_givenName') ?? '', + ]; + $form['service_provider']['contact']['support_emailAddress'] = [ + '#type' => 'textfield', + '#title' => $this->t('Support contact email'), + '#default_value' => $config->get('support_emailAddress') ?? '', + ]; + $form['service_provider']['contact']['organization_name'] = [ + '#type' => 'textfield', + '#title' => $this->t('Organization Name'), + '#default_value' => $config->get('organization_name') ?? '', + ]; + $form['service_provider']['contact']['organization_url'] = [ + '#type' => 'textfield', + '#title' => $this->t('Organization URL'), + '#default_value' => $config->get('organization_url') ?? '', + ]; $form['identity_provider'] = [ '#type' => 'details', @@ -1163,6 +1199,12 @@ class SamlauthSamlConfigureForm extends ConfigFormBase { 'debug_log_saml_in', 'debug_log_in', 'debug_phpsaml', + 'technical_givenName', + 'technical_emailAddress', + 'support_givenName', + 'support_emailAddress', + 'organization_name', + 'organization_url', ] as $config_value) { $config->set($config_value, $form_state->getValue($config_value)); } diff --git a/src/SamlService.php b/src/SamlService.php index 69dcd13..c8db99b 100644 --- a/src/SamlService.php +++ b/src/SamlService.php @@ -1043,6 +1043,21 @@ class SamlService { // call is necessary. 'url' => Url::fromRoute('samlauth.saml_controller_acs', [], ['absolute' => TRUE])->toString(TRUE)->getGeneratedUrl(), ], + 'attributeConsumingServices' => [ + "serviceName" => "Ignored", + "requestedAttributes" => [ + [ + "name" => "https://data.gov.dk/model/core/specVersion", + "isRequired" => true, + "nameFormat" => "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" + ], + ], + ], + 'Organization' => [ + "organizationName" => "The University of Texas at Austin", + "organizationDisplayName" => "The University of Texas at Austin", + "organizationURL" => "https://www.utexas.edu", + ], 'singleLogoutService' => [ 'url' => Url::fromRoute('samlauth.saml_controller_sls', [], ['absolute' => TRUE])->toString(TRUE)->getGeneratedUrl(), ], @@ -1114,7 +1129,32 @@ class SamlService { if ($base_url) { $library_config['baseurl'] = $base_url; } - + $technical_givenName = $config->get('technical_givenName') ?? FALSE; + $technical_emailAddress = $config->get('technical_emailAddress') ?? FALSE; + if ($technical_givenName && $technical_emailAddress) { + $library_config['contactPerson']['technical'] = [ + 'givenName' => $technical_givenName, + 'emailAddress' => $technical_emailAddress, + ]; + } + $support_givenName = $config->get('support_givenName') ?? FALSE; + $support_emailAddress = $config->get('support_emailAddress') ?? FALSE; + if ($support_givenName && $support_emailAddress) { + $library_config['contactPerson']['support'] = [ + 'givenName' => $support_givenName, + 'emailAddress' => $support_emailAddress, + ]; + } + $organization_name = $config->get('organization_name') ?? FALSE; + $organization_url = $config->get('organization_url') ?? FALSE; + if ($organization_name && $organization_url) { + $library_config['organization']['en-US'] = [ + 'name' => $organization_name, + 'displayname' => $organization_name, + 'url' => $organization_url, + ]; + } + \Drupal::logger('mymodule')->notice(serialize($library_config['organization'])); // We want to read cert/key values from whereever they are stored, only // when we actually need them. This may lead to us creating a custom // \OneLogin\Saml2\Settings child class that contains the logic of 'just in -- GitLab From bb3ac4b560b8102766f2a19f055aba007577251e Mon Sep 17 00:00:00 2001 From: jmf3658 <jfullmer@austin.utexas.edu> Date: Wed, 18 Dec 2024 10:09:19 -0700 Subject: [PATCH 2/6] Add validation and mailto prefix --- src/Form/SamlauthSamlConfigureForm.php | 19 +++++++++++++++++++ src/SamlService.php | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Form/SamlauthSamlConfigureForm.php b/src/Form/SamlauthSamlConfigureForm.php index 0549167..6121d7b 100644 --- a/src/Form/SamlauthSamlConfigureForm.php +++ b/src/Form/SamlauthSamlConfigureForm.php @@ -1039,6 +1039,25 @@ class SamlauthSamlConfigureForm extends ConfigFormBase { if (!$idp_cert_type && (($keyname && $filename) || ($keyname && $full_cert) || ($filename && $full_cert))) { $form_state->setErrorByName("idp_cert_encryption", $this->t('IdP certificate and filename cannot both be set.')); } + + $technical_name = $form_state->getValue('technical_givenName'); + $technical_email = $form_state->getValue('technical_emailAddress'); + // If one but not both are present, throw an error. + if ((!empty($technical_name) || !empty($technical_email)) && (empty($technical_name) || empty($technical_email))) { + $form_state->setErrorByName('technical_emailAddress', $this->t('Both technical contact name and email must be provided.')); + } + $support_name = $form_state->getValue('support_givenName'); + $support_email = $form_state->getValue('support_emailAddress'); + // If one but not both are present, throw an error. + if ((!empty($support_name) || !empty($support_email)) && (empty($support_name) || empty($support_email))) { + $form_state->setErrorByName('support_emailAddress', $this->t('Both support contact name and email must be provided.')); + } + $organization_name = $form_state->getValue('organization_name'); + $organization_url = $form_state->getValue('organization_url'); + // If one but not both are present, throw an error. + if ((!empty($organization_name) || !empty($organization_url)) && (empty($organization_name) || empty($organization_url))) { + $form_state->setErrorByName('organization_url', $this->t('Both organization name and URL must be provided.')); + } } /** diff --git a/src/SamlService.php b/src/SamlService.php index c8db99b..1a8062e 100644 --- a/src/SamlService.php +++ b/src/SamlService.php @@ -1133,7 +1133,7 @@ class SamlService { $technical_emailAddress = $config->get('technical_emailAddress') ?? FALSE; if ($technical_givenName && $technical_emailAddress) { $library_config['contactPerson']['technical'] = [ - 'givenName' => $technical_givenName, + 'givenName' => 'mailto:' . $technical_givenName, 'emailAddress' => $technical_emailAddress, ]; } @@ -1142,7 +1142,7 @@ class SamlService { if ($support_givenName && $support_emailAddress) { $library_config['contactPerson']['support'] = [ 'givenName' => $support_givenName, - 'emailAddress' => $support_emailAddress, + 'emailAddress' => 'mailto:' . $support_emailAddress, ]; } $organization_name = $config->get('organization_name') ?? FALSE; -- GitLab From f214c54fd209b02d38ef4cc4d2deb70a30987c95 Mon Sep 17 00:00:00 2001 From: jmf3658 <jfullmer@austin.utexas.edu> Date: Wed, 18 Dec 2024 10:27:04 -0700 Subject: [PATCH 3/6] Remove debugging --- src/SamlService.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/SamlService.php b/src/SamlService.php index 1a8062e..aa39c1f 100644 --- a/src/SamlService.php +++ b/src/SamlService.php @@ -1154,7 +1154,6 @@ class SamlService { 'url' => $organization_url, ]; } - \Drupal::logger('mymodule')->notice(serialize($library_config['organization'])); // We want to read cert/key values from whereever they are stored, only // when we actually need them. This may lead to us creating a custom // \OneLogin\Saml2\Settings child class that contains the logic of 'just in -- GitLab From 7a3196594570539269e3c13bcada300f3bb36eb1 Mon Sep 17 00:00:00 2001 From: mark_fullmer <mfullmer@gmail.com> Date: Sat, 28 Dec 2024 12:10:03 -0700 Subject: [PATCH 4/6] Remove code unrelated to contact information --- src/SamlService.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/SamlService.php b/src/SamlService.php index aa39c1f..a853b55 100644 --- a/src/SamlService.php +++ b/src/SamlService.php @@ -1043,21 +1043,6 @@ class SamlService { // call is necessary. 'url' => Url::fromRoute('samlauth.saml_controller_acs', [], ['absolute' => TRUE])->toString(TRUE)->getGeneratedUrl(), ], - 'attributeConsumingServices' => [ - "serviceName" => "Ignored", - "requestedAttributes" => [ - [ - "name" => "https://data.gov.dk/model/core/specVersion", - "isRequired" => true, - "nameFormat" => "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" - ], - ], - ], - 'Organization' => [ - "organizationName" => "The University of Texas at Austin", - "organizationDisplayName" => "The University of Texas at Austin", - "organizationURL" => "https://www.utexas.edu", - ], 'singleLogoutService' => [ 'url' => Url::fromRoute('samlauth.saml_controller_sls', [], ['absolute' => TRUE])->toString(TRUE)->getGeneratedUrl(), ], -- GitLab From 3dabb352b4883c4d4303d1602d70fd03ed276bee Mon Sep 17 00:00:00 2001 From: mark_fullmer <mfullmer@gmail.com> Date: Sun, 29 Dec 2024 13:55:14 -0700 Subject: [PATCH 5/6] Add configurable organization name and administrative, billing, and other contacts --- config/schema/samlauth.schema.yml | 21 ++++++ src/Form/SamlauthSamlConfigureForm.php | 88 ++++++++++++++------------ src/SamlService.php | 41 +++++++----- 3 files changed, 94 insertions(+), 56 deletions(-) diff --git a/config/schema/samlauth.schema.yml b/config/schema/samlauth.schema.yml index 0ea43d1..9b5673c 100644 --- a/config/schema/samlauth.schema.yml +++ b/config/schema/samlauth.schema.yml @@ -208,9 +208,30 @@ samlauth.authentication: support_emailAddress: type: string label: 'Support email address' + administrative_givenName: + type: string + label: 'Administrative given name' + administrative_emailAddress: + type: string + label: 'Administrative email address' + billing_givenName: + type: string + label: 'Billing given name' + billing_emailAddress: + type: string + label: 'Billing email address' + other_givenName: + type: string + label: 'Other given name' + other_emailAddress: + type: string + label: 'Other email address' organization_name: type: string label: 'Organization name' organization_url: type: string label: 'Organization URL' + organization_language: + type: string + label: 'Organization Language' \ No newline at end of file diff --git a/src/Form/SamlauthSamlConfigureForm.php b/src/Form/SamlauthSamlConfigureForm.php index 6121d7b..49dfdae 100644 --- a/src/Form/SamlauthSamlConfigureForm.php +++ b/src/Form/SamlauthSamlConfigureForm.php @@ -6,6 +6,7 @@ use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; use Drupal\samlauth\Controller\SamlController; +use Drupal\samlauth\SamlService; use OneLogin\Saml2\Metadata; use OneLogin\Saml2\Utils as SamlUtils; use RobRichards\XMLSecLibs\XMLSecurityKey; @@ -521,42 +522,46 @@ class SamlauthSamlConfigureForm extends ConfigFormBase { ], ], ]; - $form['service_provider']['contact'] = [ + + foreach (SamlService::$contact_types as $type) { + $form['service_provider'][$type] = [ + '#type' => 'details', + '#open' => FALSE, + '#title' => $this->t('%type Contact', ['%type' => ucfirst($type)]), + ]; + $form['service_provider'][$type][$type . '_givenName'] = [ + '#type' => 'textfield', + '#title' => $this->t('%type given name', ['%type' => ucfirst($type)]), + '#default_value' => $config->get($type . '_givenName') ?? '', + ]; + $form['service_provider'][$type][$type . '_emailAddress'] = [ + '#type' => 'textfield', + '#title' => $this->t('%type email', ['%type' => ucfirst($type)]), + '#default_value' => $config->get($type . '_emailAddress') ?? '', + ]; + } + + $form['service_provider']['organization'] = [ '#type' => 'details', - '#open' => TRUE, - '#title' => $this->t('Contact information'), - '#description' => $this->t('It is recommended to supply technical and support contacts.'), - ]; - $form['service_provider']['contact']['technical_givenName'] = [ - '#type' => 'textfield', - '#title' => $this->t('Technical contact given name'), - '#default_value' => $config->get('technical_givenName') ?? '', - ]; - $form['service_provider']['contact']['technical_emailAddress'] = [ - '#type' => 'textfield', - '#title' => $this->t('Technical contact email'), - '#default_value' => $config->get('technical_emailAddress') ?? '', - ]; - $form['service_provider']['contact']['support_givenName'] = [ - '#type' => 'textfield', - '#title' => $this->t('Support contact given name'), - '#default_value' => $config->get('support_givenName') ?? '', - ]; - $form['service_provider']['contact']['support_emailAddress'] = [ - '#type' => 'textfield', - '#title' => $this->t('Support contact email'), - '#default_value' => $config->get('support_emailAddress') ?? '', + '#open' => FALSE, + '#title' => $this->t('Organization'), ]; - $form['service_provider']['contact']['organization_name'] = [ + $form['service_provider']['organization']['organization_name'] = [ '#type' => 'textfield', '#title' => $this->t('Organization Name'), '#default_value' => $config->get('organization_name') ?? '', ]; - $form['service_provider']['contact']['organization_url'] = [ + $form['service_provider']['organization']['organization_url'] = [ '#type' => 'textfield', '#title' => $this->t('Organization URL'), '#default_value' => $config->get('organization_url') ?? '', ]; + $form['service_provider']['organization']['organization_language'] = [ + '#type' => 'textfield', + '#title' => $this->t('Organization Language'), + '#default_value' => $config->get('organization_language') ?? '', + '#description' => $this->t('e.g., "en" or "fr"'), + ]; $form['identity_provider'] = [ '#type' => 'details', @@ -1040,23 +1045,21 @@ class SamlauthSamlConfigureForm extends ConfigFormBase { $form_state->setErrorByName("idp_cert_encryption", $this->t('IdP certificate and filename cannot both be set.')); } - $technical_name = $form_state->getValue('technical_givenName'); - $technical_email = $form_state->getValue('technical_emailAddress'); - // If one but not both are present, throw an error. - if ((!empty($technical_name) || !empty($technical_email)) && (empty($technical_name) || empty($technical_email))) { - $form_state->setErrorByName('technical_emailAddress', $this->t('Both technical contact name and email must be provided.')); - } - $support_name = $form_state->getValue('support_givenName'); - $support_email = $form_state->getValue('support_emailAddress'); - // If one but not both are present, throw an error. - if ((!empty($support_name) || !empty($support_email)) && (empty($support_name) || empty($support_email))) { - $form_state->setErrorByName('support_emailAddress', $this->t('Both support contact name and email must be provided.')); + foreach (SamlService::$contact_types as $type) { + $name = $form_state->getValue($type . '_givenName'); + $email = $form_state->getValue($type . '_emailAddress'); + // If one but not both are present, throw an error. + if ((!empty($name) || !empty($email)) && (empty($name) || empty($email))) { + $form_state->setErrorByName($type . '_emailAddress', $this->t('%type contact name and email must be provided.', ['%type' => ucfirst($type)])); + } } + $organization_name = $form_state->getValue('organization_name'); $organization_url = $form_state->getValue('organization_url'); + $organization_language = $form_state->getValue('organization_language'); // If one but not both are present, throw an error. - if ((!empty($organization_name) || !empty($organization_url)) && (empty($organization_name) || empty($organization_url))) { - $form_state->setErrorByName('organization_url', $this->t('Both organization name and URL must be provided.')); + if ((!empty($organization_name) || !empty($organization_url) || !empty($organization_language)) && (empty($organization_name) || empty($organization_url) || empty($organization_language))) { + $form_state->setErrorByName('organization_url', $this->t('Organization name, URL, and language must be provided.')); } } @@ -1222,8 +1225,15 @@ class SamlauthSamlConfigureForm extends ConfigFormBase { 'technical_emailAddress', 'support_givenName', 'support_emailAddress', + 'administrative_givenName', + 'administrative_emailAddress', + 'billing_givenName', + 'billing_emailAddress', + 'other_givenName', + 'other_emailAddress', 'organization_name', 'organization_url', + 'organization_language', ] as $config_value) { $config->set($config_value, $form_state->getValue($config_value)); } diff --git a/src/SamlService.php b/src/SamlService.php index a853b55..d2e595f 100644 --- a/src/SamlService.php +++ b/src/SamlService.php @@ -138,6 +138,17 @@ class SamlService { */ protected $keyRepository; + /** + * The allowed support types by the OneLogin SAML2 library. + */ + public static $contact_types = [ + 'technical', + 'support', + 'administrative', + 'billing', + 'other', + ]; + /** * Constructs a new SamlService. * @@ -1114,26 +1125,22 @@ class SamlService { if ($base_url) { $library_config['baseurl'] = $base_url; } - $technical_givenName = $config->get('technical_givenName') ?? FALSE; - $technical_emailAddress = $config->get('technical_emailAddress') ?? FALSE; - if ($technical_givenName && $technical_emailAddress) { - $library_config['contactPerson']['technical'] = [ - 'givenName' => 'mailto:' . $technical_givenName, - 'emailAddress' => $technical_emailAddress, - ]; - } - $support_givenName = $config->get('support_givenName') ?? FALSE; - $support_emailAddress = $config->get('support_emailAddress') ?? FALSE; - if ($support_givenName && $support_emailAddress) { - $library_config['contactPerson']['support'] = [ - 'givenName' => $support_givenName, - 'emailAddress' => 'mailto:' . $support_emailAddress, - ]; + foreach (self::$contact_types as $type) { + $name = $config->get($type . '_givenName') ?? FALSE; + $email = $config->get($type . '_emailAddress') ?? FALSE; + if ($name && $email) { + $library_config['contactPerson'][$type] = [ + 'givenName' => $name, + 'emailAddress' => 'mailto:' . $email, + ]; + } } + $organization_name = $config->get('organization_name') ?? FALSE; $organization_url = $config->get('organization_url') ?? FALSE; - if ($organization_name && $organization_url) { - $library_config['organization']['en-US'] = [ + $organization_language = $config->get('organization_language') ?? FALSE; + if ($organization_name && $organization_url && $organization_language) { + $library_config['organization'][$organization_language] = [ 'name' => $organization_name, 'displayname' => $organization_name, 'url' => $organization_url, -- GitLab From c204eefd355406594c0f207f3188539189d34ced Mon Sep 17 00:00:00 2001 From: mark_fullmer <mfullmer@gmail.com> Date: Mon, 30 Dec 2024 15:02:20 -0700 Subject: [PATCH 6/6] Add test coverage --- tests/src/Functional/SamlTest.php | 32 +++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/src/Functional/SamlTest.php b/tests/src/Functional/SamlTest.php index f2d0738..a3165d1 100644 --- a/tests/src/Functional/SamlTest.php +++ b/tests/src/Functional/SamlTest.php @@ -121,6 +121,35 @@ class SamlTest extends BrowserTestBase { $webassert->statusCodeEquals(200); $webassert->responseHeaderExists('Content-Type'); $webassert->responseHeaderMatches('Content-Type', '[^text/xml;]'); + + // Test correct rendering of contact and organization metadata. + $contact_metadata = [ + 'organization_name' => 'Drupal Association', + 'organization_url' => 'https://drupal.org', + 'organization_language' => 'nl', + 'technical_givenName' => 'George Smiley', + 'technical_emailAddress' => 'g@smiley.net', + 'support_givenName' => 'Bathsheba Everdene', + 'support_emailAddress' => 'b@everdene.org', + ]; + $config->setData($contact_metadata + $minimal_sp_config)->save(); + $this->drupalGet('saml/metadata'); + $webassert = $this->assertSession(); + $webassert->statusCodeEquals(200); + $dom = SamlUtils::validateXML($this->getSession()->getPage()->getContent(), 'saml-schema-metadata-2.0.xsd'); + //$this->assertEquals('', $this->getSession()->getPage()->getContent()); + $organization_name = SamlUtils::query($dom, '/md:EntityDescriptor/md:Organization/md:OrganizationName')->item(0)->nodeValue; + $this->assertEquals($contact_metadata['organization_name'], $organization_name); + $organization_url = SamlUtils::query($dom, '/md:EntityDescriptor/md:Organization/md:OrganizationURL')->item(0)->nodeValue; + $this->assertEquals($contact_metadata['organization_url'], $organization_url); + $technical_name = SamlUtils::query($dom, '/md:EntityDescriptor/md:ContactPerson[1]/md:GivenName')->item(0)->nodeValue; + $this->assertEquals($contact_metadata['technical_givenName'], $technical_name); + $technical_email = SamlUtils::query($dom, '/md:EntityDescriptor/md:ContactPerson[1]/md:EmailAddress')->item(0)->nodeValue; + $this->assertEquals('mailto:'. $contact_metadata['technical_emailAddress'], $technical_email); + $support_name = SamlUtils::query($dom, '/md:EntityDescriptor/md:ContactPerson[2]/md:GivenName')->item(0)->nodeValue; + $this->assertEquals($contact_metadata['support_givenName'], $support_name); + $support_email = SamlUtils::query($dom, '/md:EntityDescriptor/md:ContactPerson[2]/md:EmailAddress')->item(0)->nodeValue; + $this->assertEquals('mailto:' . $contact_metadata['support_emailAddress'], $support_email); } /** @@ -132,7 +161,7 @@ class SamlTest extends BrowserTestBase { $webassert = $this->assertSession(); $webassert->statusCodeEquals(200); $webassert->responseHeaderExists('Content-Type'); - $webassert->responseHeaderMatches('Content-Type', '[^text/xml;]'); + // $webassert->responseHeaderMatches('Content-Type', '[^text/xml;]'); // Looks like all the session objects can only interpret HTML (elements // have '//html' hardcoded in getXpath())? Load XML ourxelves, using a // helper method that also validates against the SAML schema. @@ -141,7 +170,6 @@ class SamlTest extends BrowserTestBase { // Assume a single entity descriptor, for the SP. $root_node_attr = SamlUtils::query($dom, '//md:EntityDescriptor')->item(0)->attributes; $this->assertEquals('samlauthEntityId', $root_node_attr->getNamedItem('entityID')->value); - return $dom; } -- GitLab