diff --git a/build.gradle b/build.gradle index 4c0c88d026..c2195cd441 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ buildscript { common_utils_version = System.getProperty("common_utils.version", '2.9.0.0-SNAPSHOT') kafka_version = '3.5.1' apache_cxf_version = '4.0.3' - open_saml_version = '3.4.5' + open_saml_version = '4.3.0' one_login_java_saml = '2.9.0' jjwt_version = '0.12.3' guava_version = '32.1.3-jre' @@ -609,7 +609,7 @@ dependencies { testImplementation 'org.apache.camel:camel-xmlsecurity:3.21.2' //OpenSAML - implementation 'net.shibboleth.utilities:java-support:7.5.1' + implementation 'net.shibboleth.utilities:java-support:8.4.0' implementation "com.onelogin:java-saml:${one_login_java_saml}" implementation "com.onelogin:java-saml-core:${one_login_java_saml}" implementation "org.opensaml:opensaml-core:${open_saml_version}" diff --git a/plugin-security.policy b/plugin-security.policy index 6391695390..2969e47b04 100644 --- a/plugin-security.policy +++ b/plugin-security.policy @@ -34,6 +34,8 @@ grant { permission javax.security.auth.AuthPermission "modifyPrivateCredentials"; permission javax.security.auth.AuthPermission "doAs"; permission javax.security.auth.kerberos.ServicePermission "*","accept"; + + //SAML and internal plugin policy permission java.util.PropertyPermission "*","read,write"; //Enable when we switch to UnboundID LDAP SDK @@ -59,7 +61,6 @@ grant { permission java.security.SecurityPermission "putProviderProperty.BC"; permission java.security.SecurityPermission "insertProvider.BC"; permission java.security.SecurityPermission "removeProviderProperty.BC"; - permission java.util.PropertyPermission "jdk.tls.rejectClientInitiatedRenegotiation", "write"; permission java.security.SecurityPermission "getProperty.org.bouncycastle.rsa.max_size"; permission java.security.SecurityPermission "getProperty.org.bouncycastle.rsa.max_mr_tests"; @@ -74,6 +75,7 @@ grant { //Enable this permission to debug unauthorized de-serialization attempt //permission java.io.SerializablePermission "enableSubstitution"; + }; grant codeBase "${codebase.netty-common}" { diff --git a/src/main/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticator.java index da3752b5f1..ae3d1c9128 100644 --- a/src/main/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/saml/HTTPSamlAuthenticator.java @@ -20,6 +20,7 @@ import java.security.PrivilegedExceptionAction; import java.util.Map; import java.util.Optional; +import java.util.ServiceLoader; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.parsers.ParserConfigurationException; @@ -42,6 +43,7 @@ import org.opensearch.security.filter.SecurityRequest; import org.opensearch.security.filter.SecurityRequestChannelUnsupported; import org.opensearch.security.filter.SecurityResponse; +import org.opensearch.security.opensaml.integration.SecurityXMLObjectProviderInitializer; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.support.PemKeyReader; import org.opensearch.security.user.AuthCredentials; @@ -61,9 +63,11 @@ import net.shibboleth.utilities.java.support.xml.BasicParserPool; import org.opensaml.core.config.InitializationException; import org.opensaml.core.config.InitializationService; +import org.opensaml.core.config.Initializer; import org.opensaml.saml.metadata.resolver.MetadataResolver; import org.opensaml.saml.metadata.resolver.impl.AbstractMetadataResolver; import org.opensaml.saml.metadata.resolver.impl.DOMMetadataResolver; +import org.opensaml.xmlsec.config.impl.XMLObjectProviderInitializer; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; @@ -111,12 +115,12 @@ public HTTPSamlAuthenticator(final Settings settings, final Path configPath) { spSignaturePrivateKey = getSpSignaturePrivateKey(settings, configPath); useForceAuthn = settings.getAsBoolean("sp.forceAuthn", null); - if (rolesKey == null || rolesKey.length() == 0) { + if (rolesKey == null || rolesKey.isEmpty()) { log.warn("roles_key is not configured, will only extract subject from SAML"); rolesKey = null; } - if (subjectKey == null || subjectKey.length() == 0) { + if (subjectKey == null || subjectKey.isEmpty()) { // If subjectKey == null, get subject from the NameID element. // Thus, this is a valid configuration. subjectKey = null; @@ -287,35 +291,40 @@ static void ensureOpenSamlInitialization() { } try { - AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public Void run() throws InitializationException { - - Thread thread = Thread.currentThread(); - ClassLoader originalClassLoader = thread.getContextClassLoader(); - - try { - - thread.setContextClassLoader(InitializationService.class.getClassLoader()); - - InitializationService.initialize(); - - new org.opensaml.saml.config.impl.XMLObjectProviderInitializer().init(); - new org.opensaml.saml.config.impl.SAMLConfigurationInitializer().init(); - new org.opensaml.xmlsec.config.impl.XMLObjectProviderInitializer().init(); - } finally { - thread.setContextClassLoader(originalClassLoader); - } - - openSamlInitialized = true; - return null; + AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + Thread thread = Thread.currentThread(); + ClassLoader originalClassLoader = thread.getContextClassLoader(); + try { + thread.setContextClassLoader(InitializationService.class.getClassLoader()); + initializeOpenSAMLConfiguration(); + } catch (InitializationException e) { + throw new RuntimeException(e.getCause()); + } finally { + thread.setContextClassLoader(originalClassLoader); } + + openSamlInitialized = true; + return null; }); } catch (PrivilegedActionException e) { throw new RuntimeException(e.getCause()); } } + private static void initializeOpenSAMLConfiguration() throws InitializationException { + log.info("Initializing OpenSAML using the Java Services API"); + + final ServiceLoader serviceLoader = ServiceLoader.load(Initializer.class); + for (Initializer initializer : serviceLoader) { + if (initializer instanceof XMLObjectProviderInitializer) { + // replace initialization of X509 builders which support Cleaner with our own solution + new SecurityXMLObjectProviderInitializer().init(); + } else { + initializer.init(); + } + } + } + @SuppressWarnings("removal") private MetadataResolver createMetadataResolver(final Settings settings, final Path configPath) throws Exception { final AbstractMetadataResolver metadataResolver; @@ -349,12 +358,9 @@ private MetadataResolver createMetadataResolver(final Settings settings, final P } try { - AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public Void run() throws ComponentInitializationException { - metadataResolver.initialize(); - return null; - } + AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + metadataResolver.initialize(); + return null; }); } catch (PrivilegedActionException e) { if (e.getCause() instanceof ComponentInitializationException) { diff --git a/src/main/java/com/amazon/dlic/auth/http/saml/Saml2SettingsProvider.java b/src/main/java/com/amazon/dlic/auth/http/saml/Saml2SettingsProvider.java index 312d87d4e8..0c7f56282e 100644 --- a/src/main/java/com/amazon/dlic/auth/http/saml/Saml2SettingsProvider.java +++ b/src/main/java/com/amazon/dlic/auth/http/saml/Saml2SettingsProvider.java @@ -14,6 +14,7 @@ import java.security.AccessController; import java.security.PrivateKey; import java.security.PrivilegedAction; +import java.time.Instant; import java.util.AbstractMap; import java.util.Collection; import java.util.HashMap; @@ -33,7 +34,6 @@ import com.onelogin.saml2.settings.SettingsBuilder; import net.shibboleth.utilities.java.support.resolver.CriteriaSet; import net.shibboleth.utilities.java.support.resolver.ResolverException; -import org.joda.time.DateTime; import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.saml.metadata.resolver.MetadataResolver; import org.opensaml.saml.metadata.resolver.RefreshableMetadataResolver; @@ -54,7 +54,7 @@ public class Saml2SettingsProvider { private final String idpEntityId; private final PrivateKey spSignaturePrivateKey; private Saml2Settings cachedSaml2Settings; - private DateTime metadataUpdateTime; + private Instant metadataUpdateTime; Saml2SettingsProvider(Settings opensearchSettings, MetadataResolver metadataResolver, PrivateKey spSignaturePrivateKey) { this.opensearchSettings = opensearchSettings; @@ -107,7 +107,7 @@ Saml2Settings get() throws SamlConfigException { } Saml2Settings getCached() throws SamlConfigException { - DateTime tempLastUpdate = null; + Instant tempLastUpdate = null; if (this.metadataResolver instanceof RefreshableMetadataResolver && this.isUpdateRequired()) { this.cachedSaml2Settings = null; diff --git a/src/main/java/com/amazon/dlic/auth/http/saml/SamlHTTPMetadataResolver.java b/src/main/java/com/amazon/dlic/auth/http/saml/SamlHTTPMetadataResolver.java index 6946881d8c..e7708e5c60 100644 --- a/src/main/java/com/amazon/dlic/auth/http/saml/SamlHTTPMetadataResolver.java +++ b/src/main/java/com/amazon/dlic/auth/http/saml/SamlHTTPMetadataResolver.java @@ -15,6 +15,7 @@ import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import java.time.Duration; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.HttpClientBuilder; @@ -31,8 +32,8 @@ public class SamlHTTPMetadataResolver extends HTTPMetadataResolver { SamlHTTPMetadataResolver(String idpMetadataUrl, Settings opensearchSettings, Path configPath) throws Exception { super(createHttpClient(opensearchSettings, configPath), idpMetadataUrl); - setMinRefreshDelay(opensearchSettings.getAsLong("idp.min_refresh_delay", 60L * 1000L)); - setMaxRefreshDelay(opensearchSettings.getAsLong("idp.max_refresh_delay", 14400000L)); + setMinRefreshDelay(Duration.ofMillis(opensearchSettings.getAsLong("idp.min_refresh_delay", 60L * 1000L))); + setMaxRefreshDelay(Duration.ofMillis(opensearchSettings.getAsLong("idp.max_refresh_delay", 14400000L))); setRefreshDelayFactor(opensearchSettings.getAsFloat("idp.refresh_delay_factor", 0.75f)); } @@ -40,12 +41,7 @@ public class SamlHTTPMetadataResolver extends HTTPMetadataResolver { @SuppressWarnings("removal") protected byte[] fetchMetadata() throws ResolverException { try { - return AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public byte[] run() throws ResolverException { - return SamlHTTPMetadataResolver.super.fetchMetadata(); - } - }); + return AccessController.doPrivileged((PrivilegedExceptionAction) () -> SamlHTTPMetadataResolver.super.fetchMetadata()); } catch (PrivilegedActionException e) { if (e.getCause() instanceof ResolverException) { diff --git a/src/main/java/org/opensearch/security/opensaml/integration/CleanerFactory.java b/src/main/java/org/opensearch/security/opensaml/integration/CleanerFactory.java new file mode 100644 index 0000000000..b6376c10bf --- /dev/null +++ b/src/main/java/org/opensearch/security/opensaml/integration/CleanerFactory.java @@ -0,0 +1,43 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.opensaml.integration; + +import java.lang.ref.Cleaner; +import java.util.concurrent.ThreadFactory; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.common.util.concurrent.OpenSearchExecutors; + +/** + * The class was adapted from {@link net.shibboleth.utilities.java.support.primitive.CleanerSupport}. + * The main reason is that it is only one way to set Cleaner.create() + * together with cleaners daemon thread factory which is required for OpenSearch + */ +public class CleanerFactory { + + protected final static Logger log = LogManager.getLogger(SecurityXMLObjectProviderInitializer.class); + + private static final ThreadFactory cleanersThreadFactory = OpenSearchExecutors.daemonThreadFactory("cleaners"); + + /** Constructor. */ + private CleanerFactory() {} + + public static Cleaner create(final Class requester) { + // Current approach here is to create a new Cleaner on each call. A given class requester/owner + // is assumed to call only once and store in static storage. + log.debug("Creating new java.lang.ref.Cleaner instance requested by class: {}", requester.getName()); + return Cleaner.create(cleanersThreadFactory); + } + +} diff --git a/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CRLBuilder.java b/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CRLBuilder.java new file mode 100644 index 0000000000..6df3ea8969 --- /dev/null +++ b/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CRLBuilder.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.opensaml.integration; + +import org.opensaml.xmlsec.signature.X509CRL; +import org.opensaml.xmlsec.signature.impl.X509CRLBuilder; + +public class SecurityX509CRLBuilder extends X509CRLBuilder { + + public X509CRL buildObject(final String namespaceURI, final String localName, final String namespacePrefix) { + return new SecurityX509CRLImpl(namespaceURI, localName, namespacePrefix); + } + +} diff --git a/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CRLImpl.java b/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CRLImpl.java new file mode 100644 index 0000000000..036b777e27 --- /dev/null +++ b/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CRLImpl.java @@ -0,0 +1,86 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.opensaml.integration; + +import java.lang.ref.Cleaner; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nonnull; + +import net.shibboleth.utilities.java.support.collection.IndexingObjectStore; +import org.opensaml.core.xml.AbstractXMLObject; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.xmlsec.signature.X509CRL; + +/** + * The class was adapted from {@link org.opensaml.xmlsec.signature.impl.X509CRLImpl}. + * The main reason is that it is only one way to set up {@link CleanerFactory} + * together with cleaners daemon thread factory which is required for OpenSearch + */ +public class SecurityX509CRLImpl extends AbstractXMLObject implements X509CRL { + + private static final IndexingObjectStore B64_CRL_STORE = new IndexingObjectStore<>(); + + private static final Cleaner CLEANER = CleanerFactory.create(SecurityX509CRLImpl.class); + + private Cleaner.Cleanable cleanable; + + private String b64CRLIndex; + + protected SecurityX509CRLImpl(final String namespaceURI, final String elementLocalName, final String namespacePrefix) { + super(namespaceURI, elementLocalName, namespacePrefix); + } + + public String getValue() { + return B64_CRL_STORE.get(b64CRLIndex); + } + + public void setValue(final String newValue) { + // Dump our cached DOM if the new value really is new + final String currentCRL = B64_CRL_STORE.get(b64CRLIndex); + final String newCRL = prepareForAssignment(currentCRL, newValue); + + // This is a new value, remove the old one, add the new one + if (!Objects.equals(currentCRL, newCRL)) { + if (cleanable != null) { + cleanable.clean(); + cleanable = null; + } + b64CRLIndex = B64_CRL_STORE.put(newCRL); + if (b64CRLIndex != null) { + cleanable = CLEANER.register(this, new SecurityX509CRLImpl.CleanerState(b64CRLIndex)); + } + } + } + + @Override + public List getOrderedChildren() { + return Collections.emptyList(); + } + + static class CleanerState implements Runnable { + + /** The index to remove from the store. */ + private String index; + + public CleanerState(@Nonnull final String idx) { + index = idx; + } + + /** {@inheritDoc} */ + public void run() { + SecurityX509CRLImpl.B64_CRL_STORE.remove(index); + } + + } +} diff --git a/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CertificateBuilder.java b/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CertificateBuilder.java new file mode 100644 index 0000000000..f8fd664830 --- /dev/null +++ b/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CertificateBuilder.java @@ -0,0 +1,24 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.opensaml.integration; + +import org.opensaml.xmlsec.signature.X509Certificate; +import org.opensaml.xmlsec.signature.impl.X509CertificateBuilder; + +public class SecurityX509CertificateBuilder extends X509CertificateBuilder { + + @Override + public X509Certificate buildObject(final String namespaceURI, final String localName, final String namespacePrefix) { + return new SecurityX509CertificateImpl(namespaceURI, localName, namespacePrefix); + } + +} diff --git a/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CertificateImpl.java b/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CertificateImpl.java new file mode 100644 index 0000000000..59fbc021d8 --- /dev/null +++ b/src/main/java/org/opensearch/security/opensaml/integration/SecurityX509CertificateImpl.java @@ -0,0 +1,86 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.opensaml.integration; + +import java.lang.ref.Cleaner; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nonnull; + +import net.shibboleth.utilities.java.support.collection.IndexingObjectStore; +import org.opensaml.core.xml.AbstractXMLObject; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.xmlsec.signature.X509Certificate; + +/** + * The class was adapted from {@link org.opensaml.xmlsec.signature.impl.X509CertificateBuilder}. + * The main reason is that it is only one way to set up {@link CleanerFactory} + * together with cleaners daemon thread factory which is required for OpenSearch + */ +public class SecurityX509CertificateImpl extends AbstractXMLObject implements X509Certificate { + + private static final IndexingObjectStore B64_CERT_STORE = new IndexingObjectStore<>(); + + private static final Cleaner CLEANER = CleanerFactory.create(SecurityX509CertificateImpl.class); + + private Cleaner.Cleanable cleanable; + + private String b64CertIndex; + + protected SecurityX509CertificateImpl(final String namespaceURI, final String elementLocalName, final String namespacePrefix) { + super(namespaceURI, elementLocalName, namespacePrefix); + } + + @Override + public String getValue() { + return B64_CERT_STORE.get(b64CertIndex); + } + + @Override + public void setValue(final String newValue) { + // Dump our cached DOM if the new value really is new + final String currentCert = B64_CERT_STORE.get(b64CertIndex); + final String newCert = prepareForAssignment(currentCert, newValue); + + // This is a new value, remove the old one, add the new one + if (!Objects.equals(currentCert, newCert)) { + if (cleanable != null) { + cleanable.clean(); + cleanable = null; + } + b64CertIndex = B64_CERT_STORE.put(newCert); + if (b64CertIndex != null) { + cleanable = CLEANER.register(this, new SecurityX509CertificateImpl.CleanerState(b64CertIndex)); + } + } + } + + @Override + public List getOrderedChildren() { + return Collections.emptyList(); + } + + static class CleanerState implements Runnable { + + private String index; + + public CleanerState(@Nonnull final String idx) { + index = idx; + } + + public void run() { + SecurityX509CertificateImpl.B64_CERT_STORE.remove(index); + } + + } +} diff --git a/src/main/java/org/opensearch/security/opensaml/integration/SecurityXMLObjectProviderInitializer.java b/src/main/java/org/opensearch/security/opensaml/integration/SecurityXMLObjectProviderInitializer.java new file mode 100644 index 0000000000..6cf6d0e6aa --- /dev/null +++ b/src/main/java/org/opensearch/security/opensaml/integration/SecurityXMLObjectProviderInitializer.java @@ -0,0 +1,88 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.opensaml.integration; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import net.shibboleth.utilities.java.support.primitive.StringSupport; +import org.opensaml.core.config.InitializationException; +import org.opensaml.core.xml.config.XMLConfigurationException; +import org.opensaml.core.xml.config.XMLConfigurator; +import org.opensaml.xmlsec.config.impl.XMLObjectProviderInitializer; +import org.opensaml.xmlsec.signature.impl.X509CRLBuilder; +import org.opensaml.xmlsec.signature.impl.X509CertificateBuilder; +import org.w3c.dom.Element; + +/** + * The class extends {@link org.opensaml.xmlsec.config.impl.XMLObjectProviderInitializer} + * which is responsible to map signature configuration from SAML + * .well-known XML to the OpenSAML object model + */ +public class SecurityXMLObjectProviderInitializer extends XMLObjectProviderInitializer { + + protected final static Logger log = LogManager.getLogger(SecurityXMLObjectProviderInitializer.class); + + static final class SecurityObjectProviderXMLConfigurator extends XMLConfigurator { + + public static final String X509_CERTIFICATE_BUILDER_CLASS_NAME = X509CertificateBuilder.class.getCanonicalName(); + + public static final String X509_CRL_BUILDER_CLASS_NAME = X509CRLBuilder.class.getCanonicalName(); + + public SecurityObjectProviderXMLConfigurator() throws XMLConfigurationException { + super(); + } + + @Override + protected Object createClassInstance(Element configuration) throws XMLConfigurationException { + final String className = StringSupport.trimOrNull(configuration.getAttributeNS(null, "className")); + if (X509_CERTIFICATE_BUILDER_CLASS_NAME.equals(className)) { + log.trace("Replace instance of {} with {}", className, SecurityX509CertificateBuilder.class.getCanonicalName()); + return new SecurityX509CertificateBuilder(); + } else if (X509_CRL_BUILDER_CLASS_NAME.equals(className)) { + log.trace("Replace instance of {} with {}", className, SecurityX509CRLBuilder.class.getCanonicalName()); + return new SecurityX509CRLBuilder(); + } else { + return super.createClassInstance(configuration); + } + } + + } + + @Override + public void init() throws InitializationException { + try { + final XMLConfigurator configurator = new SecurityObjectProviderXMLConfigurator(); + for (String resource : getConfigResources()) { + if (resource.startsWith("/")) { + resource = resource.substring(1); + } + log.debug("Loading XMLObject provider configuration from resource '{}'", resource); + try (final InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource)) { + if (is != null) { + configurator.load(is); + } else { + throw new XMLConfigurationException("Resource not found: " + resource); + } + } catch (final IOException e) { + throw new XMLConfigurationException("Error loading resource: " + resource, e); + } + } + } catch (final XMLConfigurationException e) { + log.error("Problem loading configuration resource: {}", e.getMessage()); + throw new InitializationException("Problem loading configuration resource", e); + } + } +} diff --git a/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java b/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java index f051199606..8e0a877a6b 100644 --- a/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java +++ b/src/test/java/com/amazon/dlic/auth/http/saml/MockSamlIdpServer.java @@ -23,6 +23,7 @@ import java.net.URISyntaxException; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -33,6 +34,8 @@ import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; @@ -89,8 +92,8 @@ import org.opensearch.security.test.helper.network.SocketUtils; import net.shibboleth.utilities.java.support.codec.Base64Support; +import net.shibboleth.utilities.java.support.codec.EncodingException; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; -import org.joda.time.DateTime; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.XMLObjectBuilderFactory; import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; @@ -100,7 +103,6 @@ import org.opensaml.messaging.context.MessageContext; import org.opensaml.messaging.decoder.MessageDecodingException; import org.opensaml.messaging.handler.MessageHandlerException; -import org.opensaml.saml.common.SAMLObject; import org.opensaml.saml.common.SAMLVersion; import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext; import org.opensaml.saml.common.messaging.context.SAMLProtocolContext; @@ -350,11 +352,11 @@ public String handleSsoGetRequestBase(HttpRequest request) { HTTPRedirectDeflateDecoder decoder = new HTTPRedirectDeflateDecoder(); decoder.setParserPool(XMLObjectProviderRegistrySupport.getParserPool()); - decoder.setHttpServletRequest(httpServletRequest); + decoder.setHttpServletRequestSupplier(() -> httpServletRequest); decoder.initialize(); decoder.decode(); - MessageContext messageContext = decoder.getMessageContext(); + MessageContext messageContext = decoder.getMessageContext(); if (!(messageContext.getMessage() instanceof AuthnRequest)) { throw new RuntimeException("Expected AuthnRequest; received: " + messageContext.getMessage()); @@ -384,11 +386,11 @@ public void handleSloGetRequestBase(HttpRequest request) { HTTPRedirectDeflateDecoder decoder = new HTTPRedirectDeflateDecoder(); decoder.setParserPool(XMLObjectProviderRegistrySupport.getParserPool()); - decoder.setHttpServletRequest(httpServletRequest); + decoder.setHttpServletRequestSupplier(() -> httpServletRequest); decoder.initialize(); decoder.decode(); - MessageContext messageContext = decoder.getMessageContext(); + MessageContext messageContext = decoder.getMessageContext(); if (!(messageContext.getMessage() instanceof LogoutRequest)) { throw new RuntimeException("Expected LogoutRequest; received: " + messageContext.getMessage()); @@ -410,7 +412,7 @@ public void handleSloGetRequestBase(HttpRequest request) { validationParams.setSignatureTrustEngine(buildSignatureTrustEngine(this.spSignatureCertificate)); securityParametersContext.setSignatureValidationParameters(validationParams); - signatureSecurityHandler.setHttpServletRequest(httpServletRequest); + signatureSecurityHandler.setHttpServletRequestSupplier(() -> httpServletRequest); signatureSecurityHandler.initialize(); signatureSecurityHandler.invoke(messageContext); @@ -434,18 +436,18 @@ private String createSamlAuthResponse(AuthnRequest authnRequest) { response.setVersion(SAMLVersion.VERSION_20); response.setStatus(createStatus(StatusCode.SUCCESS)); - response.setIssueInstant(new DateTime()); + response.setIssueInstant(Instant.now()); Assertion assertion = createSamlElement(Assertion.class); assertion.setID(nextId()); - assertion.setIssueInstant(new DateTime()); + assertion.setIssueInstant(Instant.now()); assertion.setIssuer(createIssuer()); AuthnStatement authnStatement = createSamlElement(AuthnStatement.class); assertion.getAuthnStatements().add(authnStatement); - authnStatement.setAuthnInstant(new DateTime()); + authnStatement.setAuthnInstant(Instant.now()); authnStatement.setSessionIndex(nextId()); authnStatement.setAuthnContext(createAuthnCotext()); @@ -459,7 +461,7 @@ private String createSamlAuthResponse(AuthnRequest authnRequest) { .add( createSubjectConfirmation( "urn:oasis:names:tc:SAML:2.0:cm:bearer", - new DateTime().plusMinutes(1), + Instant.now().plus(1, ChronoUnit.MINUTES), authnRequest.getID(), authnRequest.getAssertionConsumerServiceURL() ) @@ -469,7 +471,7 @@ private String createSamlAuthResponse(AuthnRequest authnRequest) { .add( createSubjectConfirmation( "urn:oasis:names:tc:SAML:2.0:cm:bearer", - new DateTime().plusMinutes(1), + Instant.now().plus(1, ChronoUnit.MINUTES), null, defaultAssertionConsumerService ) @@ -479,8 +481,8 @@ private String createSamlAuthResponse(AuthnRequest authnRequest) { Conditions conditions = createSamlElement(Conditions.class); assertion.setConditions(conditions); - conditions.setNotBefore(new DateTime()); - conditions.setNotOnOrAfter(new DateTime().plusMinutes(1)); + conditions.setNotBefore(Instant.now()); + conditions.setNotOnOrAfter(Instant.now().plus(1, ChronoUnit.MINUTES)); if (authenticateUserRoles != null) { AttributeStatement attributeStatement = createSamlElement(AttributeStatement.class); @@ -520,9 +522,9 @@ private String createSamlAuthResponse(AuthnRequest authnRequest) { String marshalledXml = marshallSamlXml(response); - return Base64Support.encode(marshalledXml.getBytes("UTF-8"), Base64Support.UNCHUNKED); + return Base64Support.encode(marshalledXml.getBytes(StandardCharsets.UTF_8), Base64Support.UNCHUNKED); - } catch (MarshallingException | SignatureException | UnsupportedEncodingException | EncryptionException e) { + } catch (MarshallingException | SignatureException | EncryptionException | EncodingException e) { throw new RuntimeException(e); } } @@ -566,7 +568,7 @@ private NameIDFormat createNameIDFormat(String format) { NameIDFormat nameIdFormat = createSamlElement(NameIDFormat.class); - nameIdFormat.setFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"); + nameIdFormat.setURI("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"); return nameIdFormat; } @@ -586,7 +588,7 @@ private NameID createNameID(String format, String value) { return nameID; } - private SubjectConfirmation createSubjectConfirmation(String method, DateTime notOnOrAfter, String inResponseTo, String recipient) { + private SubjectConfirmation createSubjectConfirmation(String method, Instant notOnOrAfter, String inResponseTo, String recipient) { SubjectConfirmation result = createSamlElement(SubjectConfirmation.class); result.setMethod(method); @@ -610,7 +612,7 @@ private Issuer createIssuer() { private AuthnContext createAuthnCotext() { AuthnContext authnContext = createSamlElement(AuthnContext.class); AuthnContextClassRef authnContextClassRef = createSamlElement(AuthnContextClassRef.class); - authnContextClassRef.setAuthnContextClassRef(AuthnContext.UNSPECIFIED_AUTHN_CTX); + authnContextClassRef.setURI(AuthnContext.UNSPECIFIED_AUTHN_CTX); authnContext.setAuthnContextClassRef(authnContextClassRef); return authnContext; }