7 changed files with 229 additions and 89 deletions
@ -0,0 +1,146 @@
@@ -0,0 +1,146 @@
|
||||
/* |
||||
* Copyright 2002-2020 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.security.saml2.core; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.concurrent.atomic.AtomicBoolean; |
||||
import java.util.function.Consumer; |
||||
import javax.xml.XMLConstants; |
||||
|
||||
import net.shibboleth.utilities.java.support.xml.BasicParserPool; |
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.opensaml.core.config.ConfigurationService; |
||||
import org.opensaml.core.config.InitializationService; |
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistry; |
||||
|
||||
import org.springframework.security.saml2.Saml2Exception; |
||||
|
||||
import static java.lang.Boolean.FALSE; |
||||
import static java.lang.Boolean.TRUE; |
||||
import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.setParserPool; |
||||
|
||||
/** |
||||
* An initialization service for initializing OpenSAML. Each Spring Security OpenSAML-based component invokes |
||||
* the {@link #initialize()} method at static initialization time. |
||||
* |
||||
* {@link #initialize()} is idempotent and may be safely called in custom classes that need OpenSAML to be |
||||
* initialized in order to function correctly. It's recommended that you call this {@link #initialize()} method |
||||
* when using Spring Security and OpenSAML instead of OpenSAML's {@link InitializationService#initialize()}. |
||||
* |
||||
* The primary purpose of {@link #initialize()} is to prepare OpenSAML's {@link XMLObjectProviderRegistry} |
||||
* with some reasonable defaults. Any changes that Spring Security makes to the registry happen in this method. |
||||
* |
||||
* To override those defaults, call {@link #requireInitialize(Consumer)} and change the registry: |
||||
* |
||||
* <pre> |
||||
* static { |
||||
* OpenSamlInitializationService.requireInitialize(registry -> { |
||||
* registry.setParserPool(...); |
||||
* registry.getBuilderFactory().registerBuilder(...); |
||||
* }); |
||||
* } |
||||
* </pre> |
||||
* |
||||
* {@link #requireInitialize(Consumer)} may only be called once per application. |
||||
* |
||||
* If the application already initialized OpenSAML before {@link #requireInitialize(Consumer)} was called, |
||||
* then the configuration changes will not be applied and an exception will be thrown. The reason for this is to |
||||
* alert you to the fact that there are likely some initialization ordering problems in your application that |
||||
* would otherwise lead to an unpredictable state. |
||||
* |
||||
* If you must change the registry's configuration in multiple places in your application, you are expected |
||||
* to handle the initialization ordering issues yourself instead of trying to call {@link #requireInitialize(Consumer)} |
||||
* multiple times. |
||||
* |
||||
* @author Josh Cummings |
||||
* @since 5.4 |
||||
*/ |
||||
public class OpenSamlInitializationService { |
||||
private static final Log log = LogFactory.getLog(OpenSamlInitializationService.class); |
||||
private static final AtomicBoolean initialized = new AtomicBoolean(false); |
||||
|
||||
/** |
||||
* Ready OpenSAML for use and configure it with reasonable defaults. |
||||
* |
||||
* Initialization is guaranteed to happen only once per application. This method will passively return |
||||
* {@code false} if initialization already took place earlier in the application. |
||||
* |
||||
* @return whether or not initialization was performed. The first thread to initialize OpenSAML will |
||||
* return {@code true} while the rest will return {@code false}. |
||||
* @throws Saml2Exception if OpenSAML failed to initialize |
||||
*/ |
||||
public static boolean initialize() { |
||||
return initialize(registry -> {}); |
||||
} |
||||
|
||||
/** |
||||
* Ready OpenSAML for use, configure it with reasonable defaults, and modify the {@link XMLObjectProviderRegistry} |
||||
* using the provided {@link Consumer}. |
||||
* |
||||
* Initialization is guaranteed to happen only once per application. This method will throw an exception |
||||
* if initialization already took place earlier in the application. |
||||
* |
||||
* @param registryConsumer the {@link Consumer} to further configure the {@link XMLObjectProviderRegistry} |
||||
* @throws Saml2Exception if initialization already happened previously or if OpenSAML failed to initialize |
||||
*/ |
||||
public static void requireInitialize(Consumer<XMLObjectProviderRegistry> registryConsumer) { |
||||
if (!initialize(registryConsumer)) { |
||||
throw new Saml2Exception("OpenSAML was already initialized previously"); |
||||
} |
||||
} |
||||
|
||||
private static boolean initialize(Consumer<XMLObjectProviderRegistry> registryConsumer) { |
||||
if (initialized.compareAndSet(false, true)) { |
||||
log.trace("Initializing OpenSAML"); |
||||
|
||||
try { |
||||
InitializationService.initialize(); |
||||
} catch (Exception e) { |
||||
throw new Saml2Exception(e); |
||||
} |
||||
|
||||
BasicParserPool parserPool = new BasicParserPool(); |
||||
parserPool.setMaxPoolSize(50); |
||||
|
||||
Map<String, Boolean> parserBuilderFeatures = new HashMap<>(); |
||||
parserBuilderFeatures.put("http://apache.org/xml/features/disallow-doctype-decl", TRUE); |
||||
parserBuilderFeatures.put(XMLConstants.FEATURE_SECURE_PROCESSING, TRUE); |
||||
parserBuilderFeatures.put("http://xml.org/sax/features/external-general-entities", FALSE); |
||||
parserBuilderFeatures.put("http://apache.org/xml/features/validation/schema/normalized-value", FALSE); |
||||
parserBuilderFeatures.put("http://xml.org/sax/features/external-parameter-entities", FALSE); |
||||
parserBuilderFeatures.put("http://apache.org/xml/features/dom/defer-node-expansion", FALSE); |
||||
parserPool.setBuilderFeatures(parserBuilderFeatures); |
||||
|
||||
try { |
||||
parserPool.initialize(); |
||||
} catch (Exception e) { |
||||
throw new Saml2Exception(e); |
||||
} |
||||
setParserPool(parserPool); |
||||
|
||||
registryConsumer.accept(ConfigurationService.get(XMLObjectProviderRegistry.class)); |
||||
|
||||
log.debug("Initialized OpenSAML"); |
||||
return true; |
||||
} else { |
||||
log.debug("Refused to re-initialize OpenSAML"); |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
/* |
||||
* Copyright 2002-2020 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.security.saml2.core; |
||||
|
||||
import org.junit.Test; |
||||
import org.opensaml.core.config.ConfigurationService; |
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistry; |
||||
|
||||
import org.springframework.security.saml2.Saml2Exception; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatCode; |
||||
|
||||
/** |
||||
* Tests for {@link OpenSamlInitializationService} |
||||
* |
||||
* @author Josh Cummings |
||||
*/ |
||||
public class OpenSamlInitializationServiceTests { |
||||
|
||||
@Test |
||||
public void initializeWhenInvokedMultipleTimesThenInitializesOnce() { |
||||
OpenSamlInitializationService.initialize(); |
||||
XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class); |
||||
assertThat(registry.getParserPool()).isNotNull(); |
||||
registry.setParserPool(null); |
||||
OpenSamlInitializationService.initialize(); |
||||
assertThat(registry.getParserPool()).isNull(); |
||||
assertThatCode(() -> OpenSamlInitializationService.requireInitialize(r -> {})) |
||||
.isInstanceOf(Saml2Exception.class) |
||||
.hasMessageContaining("OpenSAML was already initialized previously"); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue