SEC-628: Added port-mappings element to allow use of a PortMapper.
This commit is contained in:
parent
60b7e2d4f2
commit
9e21c48fce
|
@ -43,4 +43,5 @@ public abstract class BeanIds {
|
||||||
public static final String METHOD_DEFINITION_ATTRIBUTES = "_methodDefinitionAttributes";
|
public static final String METHOD_DEFINITION_ATTRIBUTES = "_methodDefinitionAttributes";
|
||||||
public static final String EMBEDDED_APACHE_DS = "_apacheDirectoryServerContainer";
|
public static final String EMBEDDED_APACHE_DS = "_apacheDirectoryServerContainer";
|
||||||
public static final String CONTEXT_SOURCE = "_securityContextSource";
|
public static final String CONTEXT_SOURCE = "_securityContextSource";
|
||||||
|
public static final String PORT_MAPPER = "_portMapper";
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,4 +27,6 @@ abstract class Elements {
|
||||||
public static final String ANNOTATION_DRIVEN = "annotation-driven";
|
public static final String ANNOTATION_DRIVEN = "annotation-driven";
|
||||||
public static final String PASSWORD_ENCODER = "password-encoder";
|
public static final String PASSWORD_ENCODER = "password-encoder";
|
||||||
public static final String SALT_SOURCE = "salt-source";
|
public static final String SALT_SOURCE = "salt-source";
|
||||||
|
public static final String PORT_MAPPINGS = "port-mappings";
|
||||||
|
public static final String PORT_MAPPING = "port-mapping";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.springframework.security.config;
|
package org.springframework.security.config;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
@ -12,6 +11,7 @@ import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||||
|
import org.springframework.beans.factory.support.ManagedList;
|
||||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||||
import org.springframework.beans.factory.xml.ParserContext;
|
import org.springframework.beans.factory.xml.ParserContext;
|
||||||
import org.springframework.security.ConfigAttributeDefinition;
|
import org.springframework.security.ConfigAttributeDefinition;
|
||||||
|
@ -27,6 +27,8 @@ import org.springframework.security.securechannel.ChannelDecisionManagerImpl;
|
||||||
import org.springframework.security.securechannel.ChannelProcessingFilter;
|
import org.springframework.security.securechannel.ChannelProcessingFilter;
|
||||||
import org.springframework.security.securechannel.InsecureChannelProcessor;
|
import org.springframework.security.securechannel.InsecureChannelProcessor;
|
||||||
import org.springframework.security.securechannel.SecureChannelProcessor;
|
import org.springframework.security.securechannel.SecureChannelProcessor;
|
||||||
|
import org.springframework.security.securechannel.RetryWithHttpEntryPoint;
|
||||||
|
import org.springframework.security.securechannel.RetryWithHttpsEntryPoint;
|
||||||
import org.springframework.security.ui.ExceptionTranslationFilter;
|
import org.springframework.security.ui.ExceptionTranslationFilter;
|
||||||
import org.springframework.security.util.FilterChainProxy;
|
import org.springframework.security.util.FilterChainProxy;
|
||||||
import org.springframework.security.util.RegexUrlPathMatcher;
|
import org.springframework.security.util.RegexUrlPathMatcher;
|
||||||
|
@ -76,9 +78,16 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
static final String ATT_ACCESS_MGR = "access-decision-manager";
|
static final String ATT_ACCESS_MGR = "access-decision-manager";
|
||||||
|
|
||||||
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
||||||
|
BeanDefinitionRegistry registry = parserContext.getRegistry();
|
||||||
RootBeanDefinition filterChainProxy = new RootBeanDefinition(FilterChainProxy.class);
|
RootBeanDefinition filterChainProxy = new RootBeanDefinition(FilterChainProxy.class);
|
||||||
RootBeanDefinition httpScif = new RootBeanDefinition(HttpSessionContextIntegrationFilter.class);
|
RootBeanDefinition httpScif = new RootBeanDefinition(HttpSessionContextIntegrationFilter.class);
|
||||||
|
|
||||||
|
BeanDefinition portMapper = new PortMappingsBeanDefinitionParser().parse(
|
||||||
|
DomUtils.getChildElementByTagName(element, Elements.PORT_MAPPINGS), parserContext);
|
||||||
|
registry.registerBeanDefinition(BeanIds.PORT_MAPPER, portMapper);
|
||||||
|
|
||||||
|
RuntimeBeanReference portMapperRef = new RuntimeBeanReference(BeanIds.PORT_MAPPER);
|
||||||
|
|
||||||
String createSession = element.getAttribute(ATT_CREATE_SESSION);
|
String createSession = element.getAttribute(ATT_CREATE_SESSION);
|
||||||
if (OPT_CREATE_SESSION_ALWAYS.equals(createSession)) {
|
if (OPT_CREATE_SESSION_ALWAYS.equals(createSession)) {
|
||||||
httpScif.getPropertyValues().addPropertyValue("allowSessionCreation", Boolean.TRUE);
|
httpScif.getPropertyValues().addPropertyValue("allowSessionCreation", Boolean.TRUE);
|
||||||
|
@ -157,8 +166,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
parseInterceptUrls(DomUtils.getChildElementsByTagName(element, "intercept-url"),
|
parseInterceptUrls(DomUtils.getChildElementsByTagName(element, "intercept-url"),
|
||||||
filterChainMap, interceptorFilterInvDefSource, channelFilterInvDefSource, parserContext);
|
filterChainMap, interceptorFilterInvDefSource, channelFilterInvDefSource, parserContext);
|
||||||
|
|
||||||
BeanDefinitionRegistry registry = parserContext.getRegistry();
|
|
||||||
|
|
||||||
// Check if we need to register the channel processing beans
|
// Check if we need to register the channel processing beans
|
||||||
if (((AbstractFilterInvocationDefinitionSource)channelFilterInvDefSource).getMapSize() > 0) {
|
if (((AbstractFilterInvocationDefinitionSource)channelFilterInvDefSource).getMapSize() > 0) {
|
||||||
// At least one channel requirement has been specified
|
// At least one channel requirement has been specified
|
||||||
|
@ -169,9 +176,17 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
channelFilter.getPropertyValues().addPropertyValue("filterInvocationDefinitionSource",
|
channelFilter.getPropertyValues().addPropertyValue("filterInvocationDefinitionSource",
|
||||||
channelFilterInvDefSource);
|
channelFilterInvDefSource);
|
||||||
RootBeanDefinition channelDecisionManager = new RootBeanDefinition(ChannelDecisionManagerImpl.class);
|
RootBeanDefinition channelDecisionManager = new RootBeanDefinition(ChannelDecisionManagerImpl.class);
|
||||||
List channelProcessors = new ArrayList(2);
|
ManagedList channelProcessors = new ManagedList(2);
|
||||||
channelProcessors.add(new SecureChannelProcessor());
|
RootBeanDefinition secureChannelProcessor = new RootBeanDefinition(SecureChannelProcessor.class);
|
||||||
channelProcessors.add(new InsecureChannelProcessor());
|
RootBeanDefinition retryWithHttp = new RootBeanDefinition(RetryWithHttpEntryPoint.class);
|
||||||
|
RootBeanDefinition retryWithHttps = new RootBeanDefinition(RetryWithHttpsEntryPoint.class);
|
||||||
|
retryWithHttp.getPropertyValues().addPropertyValue("portMapper", portMapperRef);
|
||||||
|
retryWithHttps.getPropertyValues().addPropertyValue("portMapper", portMapperRef);
|
||||||
|
secureChannelProcessor.getPropertyValues().addPropertyValue("entryPoint", retryWithHttps);
|
||||||
|
RootBeanDefinition inSecureChannelProcessor = new RootBeanDefinition(InsecureChannelProcessor.class);
|
||||||
|
inSecureChannelProcessor.getPropertyValues().addPropertyValue("entryPoint", retryWithHttp);
|
||||||
|
channelProcessors.add(secureChannelProcessor);
|
||||||
|
channelProcessors.add(inSecureChannelProcessor);
|
||||||
channelDecisionManager.getPropertyValues().addPropertyValue("channelProcessors", channelProcessors);
|
channelDecisionManager.getPropertyValues().addPropertyValue("channelProcessors", channelProcessors);
|
||||||
|
|
||||||
registry.registerBeanDefinition(BeanIds.CHANNEL_PROCESSING_FILTER, channelFilter);
|
registry.registerBeanDefinition(BeanIds.CHANNEL_PROCESSING_FILTER, channelFilter);
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package org.springframework.security.config;
|
||||||
|
|
||||||
|
import org.springframework.security.util.PortMapperImpl;
|
||||||
|
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||||
|
import org.springframework.beans.factory.xml.ParserContext;
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
|
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.xml.DomUtils;
|
||||||
|
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a port-mappings element, producing a single {@link org.springframework.security.util.PortMapperImpl}
|
||||||
|
* bean.
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class PortMappingsBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
|
public static final String ATT_HTTP_PORT = "http";
|
||||||
|
public static final String ATT_HTTPS_PORT = "https";
|
||||||
|
|
||||||
|
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
||||||
|
BeanDefinition portMapper = new RootBeanDefinition(PortMapperImpl.class);
|
||||||
|
|
||||||
|
if (element != null) {
|
||||||
|
List mappingElts = DomUtils.getChildElementsByTagName(element, Elements.PORT_MAPPING);
|
||||||
|
Assert.notEmpty(mappingElts, "No port-mapping child elements!");
|
||||||
|
Map mappings = new HashMap();
|
||||||
|
|
||||||
|
Iterator iterator = mappingElts.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Element elt = (Element) iterator.next();
|
||||||
|
String httpPort = elt.getAttribute(ATT_HTTP_PORT);
|
||||||
|
String httpsPort = elt.getAttribute(ATT_HTTPS_PORT);
|
||||||
|
Assert.notNull(httpPort, "No http port supplied in mapping");
|
||||||
|
Assert.notNull(httpsPort, "No https port supplied in mapping");
|
||||||
|
|
||||||
|
mappings.put(httpPort, httpsPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
portMapper.getPropertyValues().addPropertyValue("portMappings", mappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return portMapper;
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,9 +23,10 @@ import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concrete implementation of {@link PortMapper} that obtains HTTP:HTTPS pairs from the application context.<P>By
|
* Concrete implementation of {@link PortMapper} that obtains HTTP:HTTPS pairs from the application context.
|
||||||
* default the implementation will assume 80:443 and 8080:8443 are HTTP:HTTPS pairs respectively. If different pairs
|
* <p>
|
||||||
* are required, use {@link #setPortMappings(Map)}.</p>
|
* By default the implementation will assume 80:443 and 8080:8443 are HTTP:HTTPS pairs respectively. If different pairs
|
||||||
|
* are required, use {@link #setPortMappings(Map)}.
|
||||||
*
|
*
|
||||||
* @author Ben Alex
|
* @author Ben Alex
|
||||||
* @author colin sampaleanu
|
* @author colin sampaleanu
|
||||||
|
@ -75,10 +76,15 @@ public class PortMapperImpl implements PortMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Set to override the default HTTP port to HTTPS port mappings of 80:443, and 8080:8443.</p>
|
* Set to override the default HTTP port to HTTPS port mappings of 80:443, and 8080:8443.
|
||||||
* In a Spring XML ApplicationContext, a definition would look something like this:<pre>
|
* In a Spring XML ApplicationContext, a definition would look something like this:
|
||||||
* <property name="portMappings"> <map> <entry key="80"><value>443</value></entry>
|
* <pre>
|
||||||
* <entry key="8080"><value>8443</value></entry> </map> </property></pre>
|
* <property name="portMappings">
|
||||||
|
* <map>
|
||||||
|
* <entry key="80"><value>443</value></entry>
|
||||||
|
* <entry key="8080"><value>8443</value></entry>
|
||||||
|
* </map>
|
||||||
|
* </property></pre>
|
||||||
*
|
*
|
||||||
* @param newMappings A Map consisting of String keys and String values, where for each entry the key is the string
|
* @param newMappings A Map consisting of String keys and String values, where for each entry the key is the string
|
||||||
* representation of an integer HTTP port number, and the value is the string representation of the
|
* representation of an integer HTTP port number, and the value is the string representation of the
|
||||||
|
|
|
@ -97,7 +97,7 @@ annotation-driven.attlist &=
|
||||||
|
|
||||||
http =
|
http =
|
||||||
## Container element for HTTP security configuration
|
## Container element for HTTP security configuration
|
||||||
element http {http.attlist, (intercept-url+ & form-login? & http-basic? & logout? & concurrent-session-control? & remember-me? & anonymous?) }
|
element http {http.attlist, (intercept-url+ & form-login? & http-basic? & logout? & concurrent-session-control? & remember-me? & anonymous? & port-mappings) }
|
||||||
http.attlist &=
|
http.attlist &=
|
||||||
## Automatically registers a login form, BASIC authentication, anonymous authentication, logout services, remember-me and servlet-api-integration. If set to "true", all of these capabilities are added (although you can still customize the configuration of each by providing the respective element). If unspecified, defaults to "false".
|
## Automatically registers a login form, BASIC authentication, anonymous authentication, logout services, remember-me and servlet-api-integration. If set to "true", all of these capabilities are added (although you can still customize the configuration of each by providing the respective element). If unspecified, defaults to "false".
|
||||||
attribute auto-config {"true" | "false" }?
|
attribute auto-config {"true" | "false" }?
|
||||||
|
@ -239,6 +239,18 @@ user.attlist &=
|
||||||
## One of more authorities granted to the user. Separate authorities with a comma (but no space). For example, "ROLE_USER,ROLE_ADMINISTRATOR"
|
## One of more authorities granted to the user. Separate authorities with a comma (but no space). For example, "ROLE_USER,ROLE_ADMINISTRATOR"
|
||||||
attribute authorities {xsd:string}
|
attribute authorities {xsd:string}
|
||||||
|
|
||||||
|
port-mappings =
|
||||||
|
## Defines the list of mappings between http and https ports for use in redirects
|
||||||
|
element port-mappings {port-mappings.attlist, port-mapping+}
|
||||||
|
|
||||||
|
port-mappings.attlist &= empty
|
||||||
|
|
||||||
|
port-mapping =
|
||||||
|
element port-mapping {http-port, https-port}
|
||||||
|
|
||||||
|
http-port = attribute http {xsd:integer}
|
||||||
|
|
||||||
|
https-port = attribute https {xsd:integer}
|
||||||
|
|
||||||
jdbc-user-service =
|
jdbc-user-service =
|
||||||
## Causes creation of a JDBC-based UserDetailsService.
|
## Causes creation of a JDBC-based UserDetailsService.
|
||||||
|
|
|
@ -258,6 +258,7 @@
|
||||||
<xs:element ref="security:concurrent-session-control"/>
|
<xs:element ref="security:concurrent-session-control"/>
|
||||||
<xs:element ref="security:remember-me"/>
|
<xs:element ref="security:remember-me"/>
|
||||||
<xs:element ref="security:anonymous"/>
|
<xs:element ref="security:anonymous"/>
|
||||||
|
<xs:element ref="security:port-mappings"/>
|
||||||
</xs:choice>
|
</xs:choice>
|
||||||
<xs:attributeGroup ref="security:http.attlist"/>
|
<xs:attributeGroup ref="security:http.attlist"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
@ -585,6 +586,28 @@
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
</xs:attributeGroup>
|
</xs:attributeGroup>
|
||||||
|
<xs:element name="port-mappings">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Defines the list of mappings between http and https ports for use in redirects</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element maxOccurs="unbounded" ref="security:port-mapping"/>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="port-mapping">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:attributeGroup ref="security:http-port"/>
|
||||||
|
<xs:attributeGroup ref="security:https-port"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:attributeGroup name="http-port">
|
||||||
|
<xs:attribute name="http" use="required" type="xs:integer"/>
|
||||||
|
</xs:attributeGroup>
|
||||||
|
<xs:attributeGroup name="https-port">
|
||||||
|
<xs:attribute name="https" use="required" type="xs:integer"/>
|
||||||
|
</xs:attributeGroup>
|
||||||
<xs:element name="jdbc-user-service">
|
<xs:element name="jdbc-user-service">
|
||||||
<xs:annotation>
|
<xs:annotation>
|
||||||
<xs:documentation>Causes creation of a JDBC-based UserDetailsService.</xs:documentation>
|
<xs:documentation>Causes creation of a JDBC-based UserDetailsService.</xs:documentation>
|
||||||
|
|
|
@ -4,6 +4,10 @@ import org.springframework.security.concurrent.ConcurrentSessionFilter;
|
||||||
import org.springframework.security.context.HttpSessionContextIntegrationFilter;
|
import org.springframework.security.context.HttpSessionContextIntegrationFilter;
|
||||||
import org.springframework.security.intercept.web.FilterSecurityInterceptor;
|
import org.springframework.security.intercept.web.FilterSecurityInterceptor;
|
||||||
import org.springframework.security.securechannel.ChannelProcessingFilter;
|
import org.springframework.security.securechannel.ChannelProcessingFilter;
|
||||||
|
import org.springframework.security.securechannel.ChannelDecisionManager;
|
||||||
|
import org.springframework.security.securechannel.ChannelDecisionManagerImpl;
|
||||||
|
import org.springframework.security.securechannel.SecureChannelProcessor;
|
||||||
|
import org.springframework.security.securechannel.RetryWithHttpsEntryPoint;
|
||||||
import org.springframework.security.ui.ExceptionTranslationFilter;
|
import org.springframework.security.ui.ExceptionTranslationFilter;
|
||||||
import org.springframework.security.ui.basicauth.BasicProcessingFilter;
|
import org.springframework.security.ui.basicauth.BasicProcessingFilter;
|
||||||
import org.springframework.security.ui.logout.LogoutFilter;
|
import org.springframework.security.ui.logout.LogoutFilter;
|
||||||
|
@ -11,9 +15,11 @@ import org.springframework.security.ui.rememberme.RememberMeProcessingFilter;
|
||||||
import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
|
import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
|
||||||
import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter;
|
import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter;
|
||||||
import org.springframework.security.util.FilterChainProxy;
|
import org.springframework.security.util.FilterChainProxy;
|
||||||
|
import org.springframework.security.util.PortMapperImpl;
|
||||||
import org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter;
|
import org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter;
|
||||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
@ -79,5 +85,14 @@ public class HttpSecurityBeanDefinitionParserTests {
|
||||||
assertTrue(filters.next() instanceof RememberMeProcessingFilter);
|
assertTrue(filters.next() instanceof RememberMeProcessingFilter);
|
||||||
assertTrue(filters.next() instanceof ExceptionTranslationFilter);
|
assertTrue(filters.next() instanceof ExceptionTranslationFilter);
|
||||||
assertTrue(filters.next() instanceof FilterSecurityInterceptor);
|
assertTrue(filters.next() instanceof FilterSecurityInterceptor);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void portMappingsAreParsedCorrectly() throws Exception {
|
||||||
|
PortMapperImpl pm = (PortMapperImpl) appContext.getBean(BeanIds.PORT_MAPPER);
|
||||||
|
assertEquals(1, pm.getTranslatedPortMappings().size());
|
||||||
|
assertEquals(Integer.valueOf(9080), pm.lookupHttpPort(9443));
|
||||||
|
assertEquals(Integer.valueOf(9443), pm.lookupHttpsPort(9080));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,10 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc
|
||||||
<concurrent-session-control max-sessions="1"/>
|
<concurrent-session-control max-sessions="1"/>
|
||||||
|
|
||||||
<remember-me key="doesntmatter" token-repository="tokenRepo"/>
|
<remember-me key="doesntmatter" token-repository="tokenRepo"/>
|
||||||
|
|
||||||
|
<port-mappings>
|
||||||
|
<port-mapping http="9080" https="9443"/>
|
||||||
|
</port-mappings>
|
||||||
</http>
|
</http>
|
||||||
|
|
||||||
<authentication-provider>
|
<authentication-provider>
|
||||||
|
|
Loading…
Reference in New Issue