protected static function SamlService::reformatConfig in SAML Authentication 8.3
Same name and namespace in other branches
- 8.2 src/SamlService.php \Drupal\samlauth\SamlService::reformatConfig()
- 4.x src/SamlService.php \Drupal\samlauth\SamlService::reformatConfig()
Returns a configuration array as used by the external library.
Some of these arguments are just added because the method is static (which will change in v4.x).
Parameters
\Drupal\Core\Config\ImmutableConfig $config: The module configuration.
string $base_url: (Optional) base URL to set.
string $purpose: (Optional) purpose for the config: 'metadata' / 'login' / 'acs' / 'logout' / 'sls-request' / 'sls-response'.
\Drupal\key\KeyRepositoryInterface $key_repository: (Optional) the service's Key repository.
Return value
array The library configuration array.
1 call to SamlService::reformatConfig()
- SamlService::getSamlAuth in src/
SamlService.php - Returns an initialized Auth class from the SAML Toolkit.
File
- src/
SamlService.php, line 898
Class
- SamlService
- Governs communication between the SAML toolkit and the IdP / login behavior.
Namespace
Drupal\samlauthCode
protected static function reformatConfig(ImmutableConfig $config, $base_url = '', $purpose = '', KeyRepositoryInterface $key_repository = NULL) {
$library_config = [
'debug' => (bool) $config
->get('debug_phpsaml'),
'sp' => [
'entityId' => $config
->get('sp_entity_id'),
'assertionConsumerService' => [
// See ExecuteInRenderContextTrait if curious why the long chained
// call is necessary.
'url' => Url::fromRoute('samlauth.saml_controller_acs', [], [
'absolute' => TRUE,
])
->toString(TRUE)
->getGeneratedUrl(),
],
'singleLogoutService' => [
'url' => Url::fromRoute('samlauth.saml_controller_sls', [], [
'absolute' => TRUE,
])
->toString(TRUE)
->getGeneratedUrl(),
],
'NameIDFormat' => $config
->get('sp_name_id_format') ?: NULL,
],
'idp' => [
'entityId' => $config
->get('idp_entity_id'),
'singleSignOnService' => [
'url' => $config
->get('idp_single_sign_on_service'),
],
'singleLogoutService' => [
'url' => $config
->get('idp_single_log_out_service'),
],
],
'security' => [
// Used for metadata (adds a whole signature section):
'signMetadata' => (bool) $config
->get('security_metadata_sign'),
// Used for metadata (value goes into attribute
// AuthnRequestsSigned="true/false" of SPSSODescriptor) / login(*):
'authnRequestsSigned' => (bool) $config
->get('security_authn_requests_sign'),
// Used for logout(*):
'logoutRequestSigned' => (bool) $config
->get('security_logout_requests_sign'),
// Used for SLO response, sent after processing incoming SLO request(*):
'logoutResponseSigned' => (bool) $config
->get('security_logout_responses_sign'),
// Used for logout; also influences Settings:__construct() checks for
// presence of IDP cert:
'nameIdEncrypted' => (bool) $config
->get('security_nameid_encrypt'),
// Used for acs:
// TRUE by default AND must be TRUE on existing installations that
// didn't have the setting before, so it's the first one to get a
// default value. (If we didn't have the (bool) operator, we wouldn't
// necessarily need the default - but leaving it out would just invite
// a bug later on.)
'wantNameId' => (bool) ($config
->get('security_want_name_id') ?? TRUE),
// Used for login / acs(*); indirectly influences metadata(**):
'wantNameIdEncrypted' => (bool) $config
->get('security_nameid_encrypted'),
// Used for acs(*); indirectly influences metadata(**):
'wantAssertionsEncrypted' => (bool) $config
->get('security_assertions_encrypt'),
// Used for metadata (value goes into attribute
// WantAssertionsSigned="true/false" of SPSSODescriptor) / acs(*):
'wantAssertionsSigned' => (bool) $config
->get('security_assertions_signed'),
// Used for acs / sls (processing incoming SLO responses and requests):
'wantMessagesSigned' => (bool) $config
->get('security_messages_sign'),
// Used for login:
'requestedAuthnContext' => (bool) $config
->get('security_request_authn_context'),
// Used for login / logout / SLO response, sent after processing
// incoming SLO request; should be deprecated:
'lowercaseUrlencoding' => (bool) $config
->get('security_lowercase_url_encoding'),
],
'strict' => (bool) $config
->get('strict'),
];
$sig_alg = $config
->get('security_signature_algorithm');
if ($sig_alg) {
$library_config['security']['signatureAlgorithm'] = $sig_alg;
}
$enc_alg = $config
->get('security_encryption_algorithm');
if ($enc_alg) {
// Not a typo; this is snake_case in the library too.
$library_config['security']['encryption_algorithm'] = $enc_alg;
}
if ($base_url) {
$library_config['baseurl'] = $base_url;
}
// 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
// time' reading key/cert values from storage. However (leaving aside the
// fact that we can't do that in v3.x because it will break compatibility),
// - the Auth class cannot use Settings subclasses: its constructor only
// accepts an array and its $_settings variable is private (so we cannot
// get around this by subclassing Auth).
// - while the design of Settings supports 'just in time reading', (and the
// Settings class even does it, in some cases), its setup _in principle_
// isn't well suited for handling that. So if we end up doing this, we'll
// need to add good comments about e.g. getSPData() not returning
// complete data, format*() not working, ... (Basically, my conclusion is
// like my conclusion elsewhere about the Auth object: from the design of
// the Settings object it isn't clear whether it wants to be a value
// object or whether it wants to contain the logic of how to read the
// key/cert values. It would be nice if the code was... clearer.)
// So for now, we are adding logic to this method that 'knows' when the key
// / certs are used.
$add_key = $add_cert = $add_idp_cert = TRUE;
$add_new_cert = in_array($purpose, [
'metadata',
'',
]);
$add_idp_encryption_cert = FALSE;
switch ($purpose) {
case 'metadata':
// signMetadata / wantNameIdEncrypted are not implemented yet but that
// doesn't mean we can't already use them here: they potentially
// influence metadata but then we can't turn off $add_key.
$add_key = !empty($library_config['security']['signMetadata']) || !empty($library_config['security']['wantNameIdEncrypted']) || !empty($library_config['security']['wantAssertionsEncrypted']);
// We cannot prevent Settings::checkIdPSettings() from being called
// while using the standard Auth class, so cannot do this:
// $add_idp_cert = FALSE;.
// @todo this is a good enough reason for opening a PR to amend
// Auth::construct() (to accept a Settings that was constructed with
// (, TRUE)), regardless of considerations outlined in AuthVolatile.
break;
case 'login':
// We don't need a cert for operation; we just need to set it to a
// value if certain security settings are set (which need the private
// key), or otherwise checkSPCerts() will freak out.
$add_cert = !empty($library_config['security']['authnRequestsSigned']) || !empty($library_config['security']['wantNameIdEncrypted']) ? 'FAKE' : FALSE;
// We cannot prevent Settings::checkIdPSettings() from being called
// while using the standard Auth class, so cannot do this:
// $add_idp_cert = FALSE;.
break;
case 'logout':
// We don't need a cert for operation; we just need to set it to a
// value if certain security settings are set (which need the private
// key), or otherwise checkSPCerts() will freak out.
$add_cert = !empty($library_config['security']['logoutRequestSigned']) ? 'FAKE' : FALSE;
// We cannot prevent Settings::checkIdPSettings() from being called
// while using the standard Auth class, so cannot do this:
// $add_idp_cert=!empty($library_config['security']['nameIdEncrypted']);
// ^ This would also need the 2nd parameter to Settings::__construct()
// to be !$add_idp_cert.
// That just means we should generally add at least 1 IdP cert, though.
// We don't need to add the encryption cert specifically - only if:
$add_idp_encryption_cert = !empty($library_config['security']['nameIdEncrypted']);
break;
case 'acs':
// $add_key depends on the presence of any EncryptedAssertion elements,
// which is too bothersome for us to check (and which is a reason to
// have the key reading inside a child Settings object instead) so
// we'll assume it's TRUE. Same with $add_idp_cert which depends on
// the presence of various signed elements.
$add_cert = !empty($library_config['security']['wantAssertionsEncrypted']) || !empty($library_config['security']['wantAssertionsSigned']) || !empty($library_config['security']['wantNameIdEncrypted']) ? 'FAKE' : FALSE;
break;
case 'sls-request':
// We need to both interpret the incoming request and probably create a
// new outgoing one, so we need key and cert.
// We cannot prevent Settings::checkIdPSettings() from being called
// while using the standard Auth class, so cannot do this:
// $add_idp_cert = isset($_GET['Signature']); // This would also need
// the 2nd parameter to Settings::__construct() to be !$add_idp_cert.
break;
case 'sls-response':
$add_key = $add_cert = FALSE;
}
if (!$add_key || !$add_cert) {
// If these are set, checkSPCerts() will get called, which requires key +
// cert presence. Neither of these two variables are (should have been)
// set to FALSE if any of these security settings is both true and
// relevant for our $purpose. (The full logic including the switch{}
// implies the library default is FALSE for all these security settings.)
unset($library_config['security']['authnRequestsSigned']);
unset($library_config['security']['logoutRequestSigned']);
unset($library_config['security']['logoutResponseSigned']);
unset($library_config['security']['wantAssertionsEncrypted']);
unset($library_config['security']['wantNameIdEncrypted']);
}
if (!$add_idp_cert) {
// Same for IdP cert.
unset($library_config['security']['nameIdEncrypted']);
}
// Check if we want to load the certificates from a folder. Either folder
// or cert+key settings should be defined. If both are defined, "folder" is
// the preferred method and we ignore cert/path values; we don't do more
// complicated validation like checking whether the cert/key files exist.
// Initializing cert/key properties to '' (rather than not setting them at
// all) should be fine, because the standard Settings also does that.
$cert_folder = $config
->get('sp_cert_folder');
if ($cert_folder) {
// Set the folder so the SAML toolkit knows where to look. This is the
// old method which only reads key/cert when we actually need it, because
// the logic for it is inside the Settings class. We're phasing it out in
// favor of the hardcoded above $add_* logic and reading the key/cert
// files inside this function, because this old method relies on a
// specific hardcoded folder name / file names in the Settings class.
// @todo remove in 4.x: not applicable after samlauth_update_8304().
if (!defined('ONELOGIN_CUSTOMPATH')) {
define('ONELOGIN_CUSTOMPATH', "{$cert_folder}/");
}
}
else {
if ($add_key) {
$key = $config
->get('sp_private_key');
if (isset($key) && !is_string($key)) {
throw new SamlError('SP private key setting is not a string.', SamlError::SETTINGS_INVALID);
}
$type = strstr($key, ':', TRUE);
if ($type === 'key') {
if ($key_repository) {
$key = substr($key, 4);
$key_entity = $key_repository
->getKey($key);
if (!$key_entity) {
throw new SamlError("SP private key '{$key}' not found.", SamlError::SETTINGS_INVALID);
}
$key = $key_entity
->getKeyValue();
}
else {
throw new SamlError('SP private key setting is of type "key" but the Key module is not installed.', SamlError::SETTINGS_INVALID);
}
}
elseif ($type === 'file') {
$key = file_get_contents(substr($key, 5));
if ($key === FALSE) {
throw new SamlError('SP private key not found.', SamlError::PRIVATE_KEY_FILE_NOT_FOUND);
}
}
$library_config['sp']['privateKey'] = $key;
}
if ($add_cert) {
$cert = $add_cert === 'FAKE' ? 'dummy-value-to-subvert-validation' : $config
->get('sp_x509_certificate');
if (isset($cert) && !is_string($cert)) {
throw new SamlError('SP public cert setting is not a string.', SamlError::SETTINGS_INVALID);
}
if ($cert) {
$type = strstr($cert, ':', TRUE);
if ($type === 'key') {
if ($key_repository) {
$cert = substr($cert, 4);
$key_entity = $key_repository
->getKey($cert);
if (!$key_entity) {
throw new SamlError("SP public cert '{$cert}' not found.", SamlError::SETTINGS_INVALID);
}
$cert = $key_entity
->getKeyValue();
}
else {
throw new SamlError('SP public cert setting is of type "key" but the Key module is not installed.', SamlError::SETTINGS_INVALID);
}
}
elseif ($type === 'file') {
$cert = file_get_contents(substr($cert, 5));
if ($cert === FALSE) {
throw new SamlError('SP public cert not found.', SamlError::PUBLIC_CERT_FILE_NOT_FOUND);
}
}
$library_config['sp']['x509cert'] = $cert;
}
}
if ($add_new_cert) {
$cert = $config
->get('sp_new_certificate');
if (isset($cert) && !is_string($cert)) {
throw new SamlError('SP new public cert setting is not a string.', SamlError::SETTINGS_INVALID);
}
if ($cert) {
$type = strstr($cert, ':', TRUE);
if ($type === 'key') {
if ($key_repository) {
$cert = substr($cert, 4);
$key_entity = $key_repository
->getKey($cert);
if (!$key_entity) {
throw new SamlError("SP new public cert '{$cert}' not found.", SamlError::SETTINGS_INVALID);
}
$cert = $key_entity
->getKeyValue();
}
else {
throw new SamlError('SP new public cert setting is of type "key" but the Key module is not installed.', SamlError::SETTINGS_INVALID);
}
}
elseif ($type === 'file') {
$cert = file_get_contents(substr($cert, 5));
if ($cert === FALSE) {
throw new SamlError('SP new public cert not found.', SamlError::PUBLIC_CERT_FILE_NOT_FOUND);
}
}
$library_config['sp']['x509certNew'] = $cert;
}
}
}
$encryption_cert = '';
$certs = [];
if ($add_idp_encryption_cert) {
$encryption_cert = $config
->get('idp_cert_encryption');
if (isset($encryption_cert) && !is_string($encryption_cert)) {
throw new SamlError('IdP encryption cert setting is not a string.', SamlError::SETTINGS_INVALID);
}
$type = strstr($encryption_cert, ':', TRUE);
if ($type === 'key') {
if ($key_repository) {
$encryption_cert = substr($encryption_cert, 4);
$key_entity = $key_repository
->getKey($encryption_cert);
if (!$key_entity) {
throw new SamlError("IdP encryption cert '{$encryption_cert}' not found.", SamlError::SETTINGS_INVALID);
}
$encryption_cert = $key_entity
->getKeyValue();
}
else {
throw new SamlError('IdP encryption cert setting is of type "key" but the Key module is not installed.', SamlError::SETTINGS_INVALID);
}
}
elseif ($type === 'file') {
$encryption_cert = file_get_contents(substr($encryption_cert, 5));
if ($encryption_cert === FALSE) {
throw new SamlError('IdP encryption cert not found.', SamlError::PRIVATE_KEY_FILE_NOT_FOUND);
}
}
}
if ($add_idp_cert || $add_idp_encryption_cert && !$encryption_cert) {
$certs = $config
->get('idp_certs');
foreach ($certs as $i => $cert) {
if (isset($certs[$i]) && !is_string($certs[$i])) {
$nr = $i ? " {$i}" : '';
throw new SamlError("IdP cert setting{$nr} is not a string.", SamlError::SETTINGS_INVALID);
}
$type = strstr($cert, ':', TRUE);
if ($type === 'key') {
if ($key_repository) {
$cert = substr($cert, 4);
$key_entity = $key_repository
->getKey($cert);
if (!$key_entity) {
throw new SamlError("IdP cert '{$cert}' not found.", SamlError::SETTINGS_INVALID);
}
$certs[$i] = $key_entity
->getKeyValue();
}
else {
throw new SamlError('IdP cert setting is of type "key" but the Key module is not installed.', SamlError::SETTINGS_INVALID);
}
}
elseif ($type === 'file') {
$certs[$i] = file_get_contents(substr($cert, 5));
if ($certs[$i] === FALSE) {
$nr = $i ? " {$i}" : '';
throw new SamlError("IdP cert{$nr} not found.", SamlError::PRIVATE_KEY_FILE_NOT_FOUND);
}
}
}
}
// @todo remove in 4.x: not applicable after samlauth_update_8304().
// @todo at the same time as removing this, uncomment samlauth_update_8400.
if (!$certs && !$encryption_cert) {
$old_cert = $config
->get('idp_x509_certificate');
$old_cert_multi = $config
->get('idp_x509_certificate_multi');
if ($old_cert || $old_cert_multi) {
$certs = $old_cert ? [
$old_cert,
] : [];
if ($old_cert_multi) {
if ($config
->get('idp_cert_type') === 'encryption') {
$encryption_cert = $old_cert_multi;
}
else {
$certs[] = $old_cert_multi;
}
}
}
}
// If we don't set a separate 'x509certMulti > encryption' cert, the
// 'main' cert (not 'x509certMulti > signing') is used for encryption so
// it must be set. If we have a single 'main/signing' cert, we can set it
// in either 'x509certMulti > signing' or as the main cert - both is not
// necessary. This can be encoded in several arbitrary ways, e.g.:
if ($encryption_cert) {
$library_config['idp']['x509certMulti'] = [
// This is an array, but the library never uses anything but the
// first value.
'encryption' => [
$encryption_cert,
],
'signing' => $certs,
];
}
elseif ($certs) {
if (count($certs) == 1 || $add_idp_encryption_cert) {
$library_config['idp']['x509cert'] = reset($certs);
}
if (count($certs) > 1) {
$library_config['idp']['x509certMulti']['signing'] = $certs;
}
}
return $library_config;
}