Move Web Access API
Issue gh-17847
This commit is contained in:
@@ -12,6 +12,7 @@ dependencies {
|
||||
api 'io.micrometer:micrometer-observation'
|
||||
|
||||
optional project(':spring-security-messaging')
|
||||
optional project(':spring-security-web')
|
||||
optional 'org.springframework:spring-websocket'
|
||||
optional 'com.fasterxml.jackson.core:jackson-databind'
|
||||
optional 'io.micrometer:context-propagation'
|
||||
@@ -22,6 +23,9 @@ dependencies {
|
||||
optional 'org.springframework:spring-tx'
|
||||
optional 'org.jetbrains.kotlinx:kotlinx-coroutines-reactor'
|
||||
|
||||
provided 'jakarta.servlet:jakarta.servlet-api'
|
||||
|
||||
testImplementation project(path : ':spring-security-web', configuration : 'tests')
|
||||
testImplementation 'commons-collections:commons-collections'
|
||||
testImplementation 'io.projectreactor:reactor-test'
|
||||
testImplementation "org.assertj:assertj-core"
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.web.access;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.ServletContextAware;
|
||||
|
||||
/**
|
||||
* Allows users to determine whether they have privileges for a given web URI.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Luke Taylor
|
||||
* @since 3.0
|
||||
* @deprecated Use {@link AuthorizationManagerWebInvocationPrivilegeEvaluator} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class DefaultWebInvocationPrivilegeEvaluator implements WebInvocationPrivilegeEvaluator, ServletContextAware {
|
||||
|
||||
protected static final Log logger = LogFactory.getLog(DefaultWebInvocationPrivilegeEvaluator.class);
|
||||
|
||||
private final AbstractSecurityInterceptor securityInterceptor;
|
||||
|
||||
private @Nullable ServletContext servletContext;
|
||||
|
||||
public DefaultWebInvocationPrivilegeEvaluator(AbstractSecurityInterceptor securityInterceptor) {
|
||||
Assert.notNull(securityInterceptor, "SecurityInterceptor cannot be null");
|
||||
Assert.isTrue(FilterInvocation.class.equals(securityInterceptor.getSecureObjectClass()),
|
||||
"AbstractSecurityInterceptor does not support FilterInvocations");
|
||||
Assert.notNull(securityInterceptor.getAccessDecisionManager(),
|
||||
"AbstractSecurityInterceptor must provide a non-null AccessDecisionManager");
|
||||
this.securityInterceptor = securityInterceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the user represented by the supplied <tt>Authentication</tt>
|
||||
* object is allowed to invoke the supplied URI.
|
||||
* @param uri the URI excluding the context path (a default context path setting will
|
||||
* be used)
|
||||
*/
|
||||
@Override
|
||||
public boolean isAllowed(String uri, @Nullable Authentication authentication) {
|
||||
return isAllowed(null, uri, null, authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the user represented by the supplied <tt>Authentication</tt>
|
||||
* object is allowed to invoke the supplied URI, with the given .
|
||||
* <p>
|
||||
* Note the default implementation of <tt>FilterInvocationSecurityMetadataSource</tt>
|
||||
* disregards the <code>contextPath</code> when evaluating which secure object
|
||||
* metadata applies to a given request URI, so generally the <code>contextPath</code>
|
||||
* is unimportant unless you are using a custom
|
||||
* <code>FilterInvocationSecurityMetadataSource</code>.
|
||||
* @param uri the URI excluding the context path
|
||||
* @param contextPath the context path (may be null, in which case a default value
|
||||
* will be used).
|
||||
* @param method the HTTP method (or null, for any method)
|
||||
* @param authentication the <tt>Authentication</tt> instance whose authorities should
|
||||
* be used in evaluation whether access should be granted.
|
||||
* @return true if access is allowed, false if denied
|
||||
*/
|
||||
@Override
|
||||
public boolean isAllowed(@Nullable String contextPath, String uri, @Nullable String method,
|
||||
@Nullable Authentication authentication) {
|
||||
Assert.notNull(uri, "uri parameter is required");
|
||||
FilterInvocation filterInvocation = new FilterInvocation(contextPath, uri, method, this.servletContext);
|
||||
Collection<ConfigAttribute> attributes = this.securityInterceptor.obtainSecurityMetadataSource()
|
||||
.getAttributes(filterInvocation);
|
||||
if (attributes == null) {
|
||||
return (!this.securityInterceptor.isRejectPublicInvocations());
|
||||
}
|
||||
if (authentication == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
this.securityInterceptor.getAccessDecisionManager().decide(authentication, filterInvocation, attributes);
|
||||
return true;
|
||||
}
|
||||
catch (AccessDeniedException ex) {
|
||||
logger.debug(LogMessage.format("%s denied for %s", filterInvocation, authentication), ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServletContext(ServletContext servletContext) {
|
||||
this.servletContext = servletContext;
|
||||
}
|
||||
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.web.access.channel;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||
import org.springframework.security.web.PortMapper;
|
||||
import org.springframework.security.web.PortMapperImpl;
|
||||
import org.springframework.security.web.RedirectStrategy;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @deprecated please use
|
||||
* {@link org.springframework.security.web.transport.HttpsRedirectFilter} and its
|
||||
* associated {@link PortMapper}
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class AbstractRetryEntryPoint implements ChannelEntryPoint {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private PortMapper portMapper = new PortMapperImpl();
|
||||
|
||||
/**
|
||||
* The scheme ("http://" or "https://")
|
||||
*/
|
||||
private final String scheme;
|
||||
|
||||
/**
|
||||
* The standard port for the scheme (80 for http, 443 for https)
|
||||
*/
|
||||
private final int standardPort;
|
||||
|
||||
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
|
||||
|
||||
public AbstractRetryEntryPoint(String scheme, int standardPort) {
|
||||
this.scheme = scheme;
|
||||
this.standardPort = standardPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
String queryString = request.getQueryString();
|
||||
String redirectUrl = request.getRequestURI() + ((queryString != null) ? ("?" + queryString) : "");
|
||||
Integer currentPort = this.portMapper.getServerPort(request);
|
||||
Integer redirectPort = getMappedPort(currentPort);
|
||||
if (redirectPort != null) {
|
||||
boolean includePort = redirectPort != this.standardPort;
|
||||
String port = (includePort) ? (":" + redirectPort) : "";
|
||||
redirectUrl = this.scheme + request.getServerName() + port + redirectUrl;
|
||||
}
|
||||
this.logger.debug(LogMessage.format("Redirecting to: %s", redirectUrl));
|
||||
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
|
||||
}
|
||||
|
||||
protected abstract @Nullable Integer getMappedPort(Integer mapFromPort);
|
||||
|
||||
protected final PortMapper getPortMapper() {
|
||||
return this.portMapper;
|
||||
}
|
||||
|
||||
public void setPortMapper(PortMapper portMapper) {
|
||||
Assert.notNull(portMapper, "portMapper cannot be null");
|
||||
this.portMapper = portMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the strategy to be used for redirecting to the required channel URL. A
|
||||
* {@code DefaultRedirectStrategy} instance will be used if not set.
|
||||
* @param redirectStrategy the strategy instance to which the URL will be passed.
|
||||
*/
|
||||
public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
|
||||
Assert.notNull(redirectStrategy, "redirectStrategy cannot be null");
|
||||
this.redirectStrategy = redirectStrategy;
|
||||
}
|
||||
|
||||
protected final RedirectStrategy getRedirectStrategy() {
|
||||
return this.redirectStrategy;
|
||||
}
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.channel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
* Decides whether a web channel provides sufficient security.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @deprecated no replacement is planned, though consider using a custom
|
||||
* {@link RequestMatcher} for any sophisticated decision-making
|
||||
*/
|
||||
@Deprecated
|
||||
public interface ChannelDecisionManager {
|
||||
|
||||
/**
|
||||
* Decided whether the presented {@link FilterInvocation} provides the appropriate
|
||||
* level of channel security based on the requested list of <tt>ConfigAttribute</tt>s.
|
||||
*
|
||||
*/
|
||||
void decide(FilterInvocation invocation, Collection<ConfigAttribute> config) throws IOException, ServletException;
|
||||
|
||||
/**
|
||||
* Indicates whether this <code>ChannelDecisionManager</code> is able to process the
|
||||
* passed <code>ConfigAttribute</code>.
|
||||
* <p>
|
||||
* This allows the <code>ChannelProcessingFilter</code> to check every configuration
|
||||
* attribute can be consumed by the configured <code>ChannelDecisionManager</code>.
|
||||
* </p>
|
||||
* @param attribute a configuration attribute that has been configured against the
|
||||
* <code>ChannelProcessingFilter</code>
|
||||
* @return true if this <code>ChannelDecisionManager</code> can support the passed
|
||||
* configuration attribute
|
||||
*/
|
||||
boolean supports(ConfigAttribute attribute);
|
||||
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.channel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import org.jspecify.annotations.NullUnmarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ChannelDecisionManager}.
|
||||
* <p>
|
||||
* Iterates through each configured {@link ChannelProcessor}. If a
|
||||
* <code>ChannelProcessor</code> has any issue with the security of the request, it should
|
||||
* cause a redirect, exception or whatever other action is appropriate for the
|
||||
* <code>ChannelProcessor</code> implementation.
|
||||
* <p>
|
||||
* Once any response is committed (ie a redirect is written to the response object), the
|
||||
* <code>ChannelDecisionManagerImpl</code> will not iterate through any further
|
||||
* <code>ChannelProcessor</code>s.
|
||||
* <p>
|
||||
* The attribute "ANY_CHANNEL" if applied to a particular URL, the iteration through the
|
||||
* channel processors will be skipped (see SEC-494, SEC-335).
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @deprecated no replacement is planned, though consider using a custom
|
||||
* {@link RequestMatcher} for any sophisticated decision-making
|
||||
*/
|
||||
@Deprecated
|
||||
@NullUnmarked
|
||||
public class ChannelDecisionManagerImpl implements ChannelDecisionManager, InitializingBean {
|
||||
|
||||
public static final String ANY_CHANNEL = "ANY_CHANNEL";
|
||||
|
||||
private List<ChannelProcessor> channelProcessors;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.notEmpty(this.channelProcessors, "A list of ChannelProcessors is required");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config)
|
||||
throws IOException, ServletException {
|
||||
for (ConfigAttribute attribute : config) {
|
||||
if (ANY_CHANNEL.equals(attribute.getAttribute())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (ChannelProcessor processor : this.channelProcessors) {
|
||||
processor.decide(invocation, config);
|
||||
if (invocation.getResponse().isCommitted()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected @Nullable List<ChannelProcessor> getChannelProcessors() {
|
||||
return this.channelProcessors;
|
||||
}
|
||||
|
||||
@SuppressWarnings("cast")
|
||||
public void setChannelProcessors(List<?> channelProcessors) {
|
||||
Assert.notEmpty(channelProcessors, "A list of ChannelProcessors is required");
|
||||
this.channelProcessors = new ArrayList<>(channelProcessors.size());
|
||||
for (Object currentObject : channelProcessors) {
|
||||
Assert.isInstanceOf(ChannelProcessor.class, currentObject, () -> "ChannelProcessor "
|
||||
+ currentObject.getClass().getName() + " must implement ChannelProcessor");
|
||||
this.channelProcessors.add((ChannelProcessor) currentObject);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(ConfigAttribute attribute) {
|
||||
if (ANY_CHANNEL.equals(attribute.getAttribute())) {
|
||||
return true;
|
||||
}
|
||||
for (ChannelProcessor processor : this.channelProcessors) {
|
||||
if (processor.supports(attribute)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.channel;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.web.PortMapper;
|
||||
|
||||
/**
|
||||
* May be used by a {@link ChannelProcessor} to launch a web channel.
|
||||
*
|
||||
* <p>
|
||||
* <code>ChannelProcessor</code>s can elect to launch a new web channel directly, or they
|
||||
* can delegate to another class. The <code>ChannelEntryPoint</code> is a pluggable
|
||||
* interface to assist <code>ChannelProcessor</code>s in performing this delegation.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @deprecated please use
|
||||
* {@link org.springframework.security.web.transport.HttpsRedirectFilter} and its
|
||||
* associated {@link PortMapper}
|
||||
*/
|
||||
@Deprecated
|
||||
public interface ChannelEntryPoint {
|
||||
|
||||
/**
|
||||
* Commences a secure channel.
|
||||
* <p>
|
||||
* Implementations should modify the headers on the <code>ServletResponse</code> as
|
||||
* necessary to commence the user agent using the implementation's supported channel
|
||||
* type.
|
||||
* @param request that a <code>ChannelProcessor</code> has rejected
|
||||
* @param response so that the user agent can begin using a new channel
|
||||
*
|
||||
*/
|
||||
void commence(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;
|
||||
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.channel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
/**
|
||||
* Ensures a web request is delivered over the required channel.
|
||||
* <p>
|
||||
* Internally uses a {@link FilterInvocation} to represent the request, allowing a
|
||||
* {@code FilterInvocationSecurityMetadataSource} to be used to lookup the attributes
|
||||
* which apply.
|
||||
* <p>
|
||||
* Delegates the actual channel security decisions and necessary actions to the configured
|
||||
* {@link ChannelDecisionManager}. If a response is committed by the
|
||||
* {@code ChannelDecisionManager}, the filter chain will not proceed.
|
||||
* <p>
|
||||
* The most common usage is to ensure that a request takes place over HTTPS, where the
|
||||
* {@link ChannelDecisionManagerImpl} is configured with a {@link SecureChannelProcessor}
|
||||
* and an {@link InsecureChannelProcessor}. A typical configuration would be
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* <bean id="channelProcessingFilter" class="org.springframework.security.web.access.channel.ChannelProcessingFilter">
|
||||
* <property name="channelDecisionManager" ref="channelDecisionManager"/>
|
||||
* <property name="securityMetadataSource">
|
||||
* <security:filter-security-metadata-source request-matcher="regex">
|
||||
* <security:intercept-url pattern="\A/secure/.*\Z" access="REQUIRES_SECURE_CHANNEL"/>
|
||||
* <security:intercept-url pattern="\A/login.jsp.*\Z" access="REQUIRES_SECURE_CHANNEL"/>
|
||||
* <security:intercept-url pattern="\A/.*\Z" access="ANY_CHANNEL"/>
|
||||
* </security:filter-security-metadata-source>
|
||||
* </property>
|
||||
* </bean>
|
||||
*
|
||||
* <bean id="channelDecisionManager" class="org.springframework.security.web.access.channel.ChannelDecisionManagerImpl">
|
||||
* <property name="channelProcessors">
|
||||
* <list>
|
||||
* <ref bean="secureChannelProcessor"/>
|
||||
* <ref bean="insecureChannelProcessor"/>
|
||||
* </list>
|
||||
* </property>
|
||||
* </bean>
|
||||
*
|
||||
* <bean id="secureChannelProcessor"
|
||||
* class="org.springframework.security.web.access.channel.SecureChannelProcessor"/>
|
||||
* <bean id="insecureChannelProcessor"
|
||||
* class="org.springframework.security.web.access.channel.InsecureChannelProcessor"/>
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* which would force the login form and any access to the {@code /secure} path to be made
|
||||
* over HTTPS.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @deprecated see {@link org.springframework.security.web.transport.HttpsRedirectFilter}
|
||||
*/
|
||||
@Deprecated
|
||||
public class ChannelProcessingFilter extends GenericFilterBean {
|
||||
|
||||
@SuppressWarnings("NullAway.Init")
|
||||
private ChannelDecisionManager channelDecisionManager;
|
||||
|
||||
@SuppressWarnings("NullAway.Init")
|
||||
private FilterInvocationSecurityMetadataSource securityMetadataSource;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.notNull(this.securityMetadataSource, "securityMetadataSource must be specified");
|
||||
Assert.notNull(this.channelDecisionManager, "channelDecisionManager must be specified");
|
||||
Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAllConfigAttributes();
|
||||
if (attributes == null) {
|
||||
this.logger.warn("Could not validate configuration attributes as the "
|
||||
+ "FilterInvocationSecurityMetadataSource did not return any attributes");
|
||||
return;
|
||||
}
|
||||
Set<ConfigAttribute> unsupportedAttributes = getUnsupportedAttributes(attributes);
|
||||
Assert.isTrue(unsupportedAttributes.isEmpty(),
|
||||
() -> "Unsupported configuration attributes: " + unsupportedAttributes);
|
||||
this.logger.info("Validated configuration attributes");
|
||||
}
|
||||
|
||||
private Set<ConfigAttribute> getUnsupportedAttributes(Collection<ConfigAttribute> attrDefs) {
|
||||
Set<ConfigAttribute> unsupportedAttributes = new HashSet<>();
|
||||
for (ConfigAttribute attr : attrDefs) {
|
||||
if (!this.channelDecisionManager.supports(attr)) {
|
||||
unsupportedAttributes.add(attr);
|
||||
}
|
||||
}
|
||||
return unsupportedAttributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) req;
|
||||
HttpServletResponse response = (HttpServletResponse) res;
|
||||
FilterInvocation filterInvocation = new FilterInvocation(request, response, chain);
|
||||
Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(filterInvocation);
|
||||
if (attributes != null) {
|
||||
this.logger.debug(LogMessage.format("Request: %s; ConfigAttributes: %s", filterInvocation, attributes));
|
||||
this.channelDecisionManager.decide(filterInvocation, attributes);
|
||||
@Nullable HttpServletResponse channelResponse = filterInvocation.getResponse();
|
||||
Assert.notNull(channelResponse, "HttpServletResponse is required");
|
||||
if (channelResponse.isCommitted()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
protected @Nullable ChannelDecisionManager getChannelDecisionManager() {
|
||||
return this.channelDecisionManager;
|
||||
}
|
||||
|
||||
protected FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
|
||||
return this.securityMetadataSource;
|
||||
}
|
||||
|
||||
public void setChannelDecisionManager(ChannelDecisionManager channelDecisionManager) {
|
||||
this.channelDecisionManager = channelDecisionManager;
|
||||
}
|
||||
|
||||
public void setSecurityMetadataSource(
|
||||
FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource) {
|
||||
this.securityMetadataSource = filterInvocationSecurityMetadataSource;
|
||||
}
|
||||
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.channel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
* Decides whether a web channel meets a specific security condition.
|
||||
* <p>
|
||||
* <code>ChannelProcessor</code> implementations are iterated by the
|
||||
* {@link ChannelDecisionManagerImpl}.
|
||||
* <p>
|
||||
* If an implementation has an issue with the channel security, they should take action
|
||||
* themselves. The callers of the implementation do not take any action.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @deprecated no replacement is planned, though consider using a custom
|
||||
* {@link RequestMatcher} for any sophisticated decision-making
|
||||
*/
|
||||
@Deprecated
|
||||
public interface ChannelProcessor {
|
||||
|
||||
/**
|
||||
* Decided whether the presented {@link FilterInvocation} provides the appropriate
|
||||
* level of channel security based on the requested list of <tt>ConfigAttribute</tt>s.
|
||||
*/
|
||||
void decide(FilterInvocation invocation, Collection<ConfigAttribute> config) throws IOException, ServletException;
|
||||
|
||||
/**
|
||||
* Indicates whether this <code>ChannelProcessor</code> is able to process the passed
|
||||
* <code>ConfigAttribute</code>.
|
||||
* <p>
|
||||
* This allows the <code>ChannelProcessingFilter</code> to check every configuration
|
||||
* attribute can be consumed by the configured <code>ChannelDecisionManager</code>.
|
||||
* @param attribute a configuration attribute that has been configured against the
|
||||
* <tt>ChannelProcessingFilter</tt>.
|
||||
* @return true if this <code>ChannelProcessor</code> can support the passed
|
||||
* configuration attribute
|
||||
*/
|
||||
boolean supports(ConfigAttribute attribute);
|
||||
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.channel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Ensures channel security is inactive by review of
|
||||
* <code>HttpServletRequest.isSecure()</code> responses.
|
||||
* <p>
|
||||
* The class responds to one case-sensitive keyword, {@link #getInsecureKeyword}. If this
|
||||
* keyword is detected, <code>HttpServletRequest.isSecure()</code> is used to determine
|
||||
* the channel security offered. If channel security is present, the configured
|
||||
* <code>ChannelEntryPoint</code> is called. By default the entry point is
|
||||
* {@link RetryWithHttpEntryPoint}.
|
||||
* <p>
|
||||
* The default <code>insecureKeyword</code> is <code>REQUIRES_INSECURE_CHANNEL</code>.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @deprecated no replacement is planned, though consider using a custom
|
||||
* {@link RequestMatcher} for any sophisticated decision-making
|
||||
*/
|
||||
@Deprecated
|
||||
public class InsecureChannelProcessor implements InitializingBean, ChannelProcessor {
|
||||
|
||||
private ChannelEntryPoint entryPoint = new RetryWithHttpEntryPoint();
|
||||
|
||||
private String insecureKeyword = "REQUIRES_INSECURE_CHANNEL";
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.hasLength(this.insecureKeyword, "insecureKeyword required");
|
||||
Assert.notNull(this.entryPoint, "entryPoint required");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config)
|
||||
throws IOException, ServletException {
|
||||
Assert.isTrue(invocation != null && config != null, "Nulls cannot be provided");
|
||||
for (ConfigAttribute attribute : config) {
|
||||
if (supports(attribute)) {
|
||||
if (invocation.getHttpRequest().isSecure()) {
|
||||
@Nullable HttpServletResponse response = invocation.getResponse();
|
||||
Assert.notNull(response, "HttpServletResponse required");
|
||||
this.entryPoint.commence(invocation.getRequest(), response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ChannelEntryPoint getEntryPoint() {
|
||||
return this.entryPoint;
|
||||
}
|
||||
|
||||
public String getInsecureKeyword() {
|
||||
return this.insecureKeyword;
|
||||
}
|
||||
|
||||
public void setEntryPoint(ChannelEntryPoint entryPoint) {
|
||||
this.entryPoint = entryPoint;
|
||||
}
|
||||
|
||||
public void setInsecureKeyword(String secureKeyword) {
|
||||
this.insecureKeyword = secureKeyword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(ConfigAttribute attribute) {
|
||||
return (attribute != null) && (attribute.getAttribute() != null)
|
||||
&& attribute.getAttribute().equals(getInsecureKeyword());
|
||||
}
|
||||
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.channel;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.web.PortMapper;
|
||||
|
||||
/**
|
||||
* Commences an insecure channel by retrying the original request using HTTP.
|
||||
* <p>
|
||||
* This entry point should suffice in most circumstances. However, it is not intended to
|
||||
* properly handle HTTP POSTs or other usage where a standard redirect would cause an
|
||||
* issue.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @deprecated please use
|
||||
* {@link org.springframework.security.web.transport.HttpsRedirectFilter} and its
|
||||
* associated {@link PortMapper}
|
||||
*/
|
||||
@Deprecated(since = "6.5")
|
||||
public class RetryWithHttpEntryPoint extends AbstractRetryEntryPoint {
|
||||
|
||||
public RetryWithHttpEntryPoint() {
|
||||
super("http://", 80);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Integer getMappedPort(Integer mapFromPort) {
|
||||
return getPortMapper().lookupHttpPort(mapFromPort);
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.channel;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.web.PortMapper;
|
||||
|
||||
/**
|
||||
* Commences a secure channel by retrying the original request using HTTPS.
|
||||
* <p>
|
||||
* This entry point should suffice in most circumstances. However, it is not intended to
|
||||
* properly handle HTTP POSTs or other usage where a standard redirect would cause an
|
||||
* issue.
|
||||
* </p>
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @deprecated please use
|
||||
* {@link org.springframework.security.web.transport.HttpsRedirectFilter} and its
|
||||
* associated {@link PortMapper}
|
||||
*/
|
||||
@Deprecated(since = "6.5")
|
||||
public class RetryWithHttpsEntryPoint extends AbstractRetryEntryPoint {
|
||||
|
||||
public RetryWithHttpsEntryPoint() {
|
||||
super("https://", 443);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Integer getMappedPort(Integer mapFromPort) {
|
||||
return getPortMapper().lookupHttpsPort(mapFromPort);
|
||||
}
|
||||
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.channel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Ensures channel security is active by review of
|
||||
* <code>HttpServletRequest.isSecure()</code> responses.
|
||||
* <p>
|
||||
* The class responds to one case-sensitive keyword, {@link #getSecureKeyword}. If this
|
||||
* keyword is detected, <code>HttpServletRequest.isSecure()</code> is used to determine
|
||||
* the channel security offered. If channel security is not present, the configured
|
||||
* <code>ChannelEntryPoint</code> is called. By default the entry point is
|
||||
* {@link RetryWithHttpsEntryPoint}.
|
||||
* <p>
|
||||
* The default <code>secureKeyword</code> is <code>REQUIRES_SECURE_CHANNEL</code>.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @deprecated no replacement is planned, though consider using a custom
|
||||
* {@link RequestMatcher} for any sophisticated decision-making
|
||||
*/
|
||||
@Deprecated
|
||||
public class SecureChannelProcessor implements InitializingBean, ChannelProcessor {
|
||||
|
||||
private ChannelEntryPoint entryPoint = new RetryWithHttpsEntryPoint();
|
||||
|
||||
private String secureKeyword = "REQUIRES_SECURE_CHANNEL";
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.hasLength(this.secureKeyword, "secureKeyword required");
|
||||
Assert.notNull(this.entryPoint, "entryPoint required");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config)
|
||||
throws IOException, ServletException {
|
||||
Assert.isTrue((invocation != null) && (config != null), "Nulls cannot be provided");
|
||||
for (ConfigAttribute attribute : config) {
|
||||
if (supports(attribute)) {
|
||||
if (!invocation.getHttpRequest().isSecure()) {
|
||||
HttpServletResponse response = invocation.getResponse();
|
||||
Assert.notNull(response, "HttpServletResponse is required");
|
||||
this.entryPoint.commence(invocation.getRequest(), response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ChannelEntryPoint getEntryPoint() {
|
||||
return this.entryPoint;
|
||||
}
|
||||
|
||||
public String getSecureKeyword() {
|
||||
return this.secureKeyword;
|
||||
}
|
||||
|
||||
public void setEntryPoint(ChannelEntryPoint entryPoint) {
|
||||
this.entryPoint = entryPoint;
|
||||
}
|
||||
|
||||
public void setSecureKeyword(String secureKeyword) {
|
||||
this.secureKeyword = secureKeyword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(ConfigAttribute attribute) {
|
||||
return (attribute != null) && (attribute.getAttribute() != null)
|
||||
&& attribute.getAttribute().equals(getSecureKeyword());
|
||||
}
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Classes that ensure web requests are received over required transport channels.
|
||||
* <p>
|
||||
* Most commonly used to enforce that requests are submitted over HTTP or HTTPS.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.security.web.access.channel;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.web.access.expression;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.access.expression.AbstractSecurityExpressionHandler;
|
||||
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||
import org.springframework.security.access.expression.SecurityExpressionOperations;
|
||||
import org.springframework.security.authentication.AuthenticationTrustResolver;
|
||||
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
|
||||
import org.springframework.security.authorization.AuthorizationManagerFactory;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @author Eddú Meléndez
|
||||
* @author Steve Riesenberg
|
||||
* @since 3.0
|
||||
*/
|
||||
public class DefaultWebSecurityExpressionHandler extends AbstractSecurityExpressionHandler<FilterInvocation>
|
||||
implements SecurityExpressionHandler<FilterInvocation> {
|
||||
|
||||
private static final String DEFAULT_ROLE_PREFIX = "ROLE_";
|
||||
|
||||
private String defaultRolePrefix = DEFAULT_ROLE_PREFIX;
|
||||
|
||||
@Override
|
||||
protected SecurityExpressionOperations createSecurityExpressionRoot(@Nullable Authentication authentication,
|
||||
FilterInvocation fi) {
|
||||
FilterInvocationExpressionRoot root = new FilterInvocationExpressionRoot(() -> authentication, fi);
|
||||
root.setAuthorizationManagerFactory(getAuthorizationManagerFactory());
|
||||
root.setPermissionEvaluator(getPermissionEvaluator());
|
||||
if (!DEFAULT_ROLE_PREFIX.equals(this.defaultRolePrefix)) {
|
||||
// Ensure SecurityExpressionRoot can strip the custom role prefix
|
||||
root.setDefaultRolePrefix(this.defaultRolePrefix);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationTrustResolver} to be used. The default is
|
||||
* {@link AuthenticationTrustResolverImpl}.
|
||||
* @param trustResolver the {@link AuthenticationTrustResolver} to use. Cannot be
|
||||
* null.
|
||||
* @deprecated Use
|
||||
* {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
|
||||
*/
|
||||
@Deprecated(since = "7.0")
|
||||
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
|
||||
getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets the default prefix to be added to
|
||||
* {@link org.springframework.security.access.expression.SecurityExpressionRoot#hasAnyRole(String...)}
|
||||
* or
|
||||
* {@link org.springframework.security.access.expression.SecurityExpressionRoot#hasRole(String)}.
|
||||
* For example, if hasRole("ADMIN") or hasRole("ROLE_ADMIN") is passed in, then the
|
||||
* role ROLE_ADMIN will be used when the defaultRolePrefix is "ROLE_" (default).
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If null or empty, then no default role prefix is used.
|
||||
* </p>
|
||||
* @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_".
|
||||
* @deprecated Use
|
||||
* {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
|
||||
*/
|
||||
@Deprecated(since = "7.0")
|
||||
public void setDefaultRolePrefix(@Nullable String defaultRolePrefix) {
|
||||
if (defaultRolePrefix == null) {
|
||||
defaultRolePrefix = "";
|
||||
}
|
||||
getDefaultAuthorizationManagerFactory().setRolePrefix(defaultRolePrefix);
|
||||
this.defaultRolePrefix = defaultRolePrefix;
|
||||
}
|
||||
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.web.access.expression;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.ParseException;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.annotation.SecurityAnnotationScanner;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Expression-based {@code FilterInvocationSecurityMetadataSource}.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @author Eddú Meléndez
|
||||
* @since 3.0
|
||||
* @deprecated In modern Spring Security APIs, each API manages its own configuration
|
||||
* context. As such there is no direct replacement for this interface. In the case of
|
||||
* method security, please see {@link SecurityAnnotationScanner} and
|
||||
* {@link AuthorizationManager}. In the case of channel security, please see
|
||||
* {@code HttpsRedirectFilter}. In the case of web security, please see
|
||||
* {@link AuthorizationManager}.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class ExpressionBasedFilterInvocationSecurityMetadataSource
|
||||
extends DefaultFilterInvocationSecurityMetadataSource {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ExpressionBasedFilterInvocationSecurityMetadataSource.class);
|
||||
|
||||
public ExpressionBasedFilterInvocationSecurityMetadataSource(
|
||||
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap,
|
||||
SecurityExpressionHandler<FilterInvocation> expressionHandler) {
|
||||
super(processMap(requestMap, expressionHandler.getExpressionParser()));
|
||||
Assert.notNull(expressionHandler, "A non-null SecurityExpressionHandler is required");
|
||||
}
|
||||
|
||||
private static LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> processMap(
|
||||
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap, ExpressionParser parser) {
|
||||
Assert.notNull(parser, "SecurityExpressionHandler returned a null parser object");
|
||||
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> processed = new LinkedHashMap<>(requestMap);
|
||||
requestMap.forEach((request, value) -> process(parser, request, value, processed::put));
|
||||
return processed;
|
||||
}
|
||||
|
||||
private static void process(ExpressionParser parser, RequestMatcher request, Collection<ConfigAttribute> value,
|
||||
BiConsumer<RequestMatcher, Collection<ConfigAttribute>> consumer) {
|
||||
String expression = getExpression(request, value);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(LogMessage.format("Adding web access control expression [%s] for %s", expression, request));
|
||||
}
|
||||
AbstractVariableEvaluationContextPostProcessor postProcessor = createPostProcessor(request);
|
||||
ArrayList<ConfigAttribute> processed = new ArrayList<>(1);
|
||||
try {
|
||||
processed.add(new WebExpressionConfigAttribute(parser.parseExpression(expression), postProcessor));
|
||||
}
|
||||
catch (ParseException ex) {
|
||||
throw new IllegalArgumentException("Failed to parse expression '" + expression + "'");
|
||||
}
|
||||
consumer.accept(request, processed);
|
||||
}
|
||||
|
||||
private static String getExpression(RequestMatcher request, Collection<ConfigAttribute> value) {
|
||||
Assert.isTrue(value.size() == 1, () -> "Expected a single expression attribute for " + request);
|
||||
return value.toArray(new ConfigAttribute[1])[0].getAttribute();
|
||||
}
|
||||
|
||||
private static AbstractVariableEvaluationContextPostProcessor createPostProcessor(RequestMatcher request) {
|
||||
return new RequestVariablesExtractorEvaluationContextPostProcessor(request);
|
||||
}
|
||||
|
||||
static class RequestVariablesExtractorEvaluationContextPostProcessor
|
||||
extends AbstractVariableEvaluationContextPostProcessor {
|
||||
|
||||
private final RequestMatcher matcher;
|
||||
|
||||
RequestVariablesExtractorEvaluationContextPostProcessor(RequestMatcher matcher) {
|
||||
this.matcher = matcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
Map<String, String> extractVariables(HttpServletRequest request) {
|
||||
return this.matcher.matcher(request).getVariables();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.web.access.expression;
|
||||
|
||||
import org.jspecify.annotations.NullUnmarked;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
|
||||
/**
|
||||
* Simple expression configuration attribute for use in web request authorizations.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @since 3.0
|
||||
* @deprecated In modern Spring Security APIs, each API manages its own configuration
|
||||
* context. As such there is no direct replacement for this interface. Please see
|
||||
* {@link AuthorizationManager}.
|
||||
*/
|
||||
@Deprecated
|
||||
@NullUnmarked
|
||||
class WebExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor<FilterInvocation> {
|
||||
|
||||
private final Expression authorizeExpression;
|
||||
|
||||
private final EvaluationContextPostProcessor<FilterInvocation> postProcessor;
|
||||
|
||||
WebExpressionConfigAttribute(Expression authorizeExpression,
|
||||
EvaluationContextPostProcessor<FilterInvocation> postProcessor) {
|
||||
this.authorizeExpression = authorizeExpression;
|
||||
this.postProcessor = postProcessor;
|
||||
}
|
||||
|
||||
Expression getAuthorizeExpression() {
|
||||
return this.authorizeExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EvaluationContext postProcess(EvaluationContext context, FilterInvocation fi) {
|
||||
return (this.postProcessor != null) ? this.postProcessor.postProcess(context, fi) : context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.authorizeExpression.getExpressionString();
|
||||
}
|
||||
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.web.access.expression;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.security.access.AccessDecisionVoter;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.expression.ExpressionUtils;
|
||||
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Voter which handles web authorisation decisions.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @since 3.0
|
||||
* @deprecated Use {@link WebExpressionAuthorizationManager} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class WebExpressionVoter implements AccessDecisionVoter<FilterInvocation> {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();
|
||||
|
||||
@Override
|
||||
public int vote(Authentication authentication, FilterInvocation filterInvocation,
|
||||
Collection<ConfigAttribute> attributes) {
|
||||
Assert.notNull(authentication, "authentication must not be null");
|
||||
Assert.notNull(filterInvocation, "filterInvocation must not be null");
|
||||
Assert.notNull(attributes, "attributes must not be null");
|
||||
WebExpressionConfigAttribute webExpressionConfigAttribute = findConfigAttribute(attributes);
|
||||
if (webExpressionConfigAttribute == null) {
|
||||
this.logger
|
||||
.trace("Abstained since did not find a config attribute of instance WebExpressionConfigAttribute");
|
||||
return ACCESS_ABSTAIN;
|
||||
}
|
||||
EvaluationContext ctx = webExpressionConfigAttribute.postProcess(
|
||||
this.expressionHandler.createEvaluationContext(authentication, filterInvocation), filterInvocation);
|
||||
boolean granted = ExpressionUtils.evaluateAsBoolean(webExpressionConfigAttribute.getAuthorizeExpression(), ctx);
|
||||
if (granted) {
|
||||
return ACCESS_GRANTED;
|
||||
}
|
||||
this.logger.trace("Voted to deny authorization");
|
||||
return ACCESS_DENIED;
|
||||
}
|
||||
|
||||
private @Nullable WebExpressionConfigAttribute findConfigAttribute(Collection<ConfigAttribute> attributes) {
|
||||
for (ConfigAttribute attribute : attributes) {
|
||||
if (attribute instanceof WebExpressionConfigAttribute) {
|
||||
return (WebExpressionConfigAttribute) attribute;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(ConfigAttribute attribute) {
|
||||
return attribute instanceof WebExpressionConfigAttribute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return FilterInvocation.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
public void setExpressionHandler(SecurityExpressionHandler<FilterInvocation> expressionHandler) {
|
||||
this.expressionHandler = expressionHandler;
|
||||
}
|
||||
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.intercept;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.annotation.SecurityAnnotationScanner;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
* Default implementation of <tt>FilterInvocationDefinitionSource</tt>.
|
||||
* <p>
|
||||
* Stores an ordered map of {@link RequestMatcher}s to <tt>ConfigAttribute</tt>
|
||||
* collections and provides matching of {@code FilterInvocation}s against the items stored
|
||||
* in the map.
|
||||
* <p>
|
||||
* The order of the {@link RequestMatcher}s in the map is very important. The <b>first</b>
|
||||
* one which matches the request will be used. Later matchers in the map will not be
|
||||
* invoked if a match has already been found. Accordingly, the most specific matchers
|
||||
* should be registered first, with the most general matches registered last.
|
||||
* <p>
|
||||
* The most common method creating an instance is using the Spring Security namespace. For
|
||||
* example, the {@code pattern} and {@code access} attributes of the
|
||||
* {@code <intercept-url>} elements defined as children of the {@code <http>} element are
|
||||
* combined to build the instance used by the {@code FilterSecurityInterceptor}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Luke Taylor
|
||||
* @deprecated In modern Spring Security APIs, each API manages its own configuration
|
||||
* context. As such there is no direct replacement for this interface. In the case of
|
||||
* method security, please see {@link SecurityAnnotationScanner} and
|
||||
* {@link AuthorizationManager}. In the case of channel security, please see
|
||||
* {@code HttpsRedirectFilter}. In the case of web security, please see
|
||||
* {@link AuthorizationManager}.
|
||||
*/
|
||||
@Deprecated
|
||||
public class DefaultFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final Map<RequestMatcher, Collection<ConfigAttribute>> requestMap;
|
||||
|
||||
/**
|
||||
* Sets the internal request map from the supplied map. The key elements should be of
|
||||
* type {@link RequestMatcher}, which. The path stored in the key will depend on the
|
||||
* type of the supplied UrlMatcher.
|
||||
* @param requestMap order-preserving map of request definitions to attribute lists
|
||||
*/
|
||||
public DefaultFilterInvocationSecurityMetadataSource(
|
||||
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap) {
|
||||
this.requestMap = requestMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ConfigAttribute> getAllConfigAttributes() {
|
||||
Set<ConfigAttribute> allAttributes = new HashSet<>();
|
||||
this.requestMap.values().forEach(allAttributes::addAll);
|
||||
return allAttributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ConfigAttribute> getAttributes(Object object) {
|
||||
final HttpServletRequest request = getHttpServletRequest(object);
|
||||
int count = 0;
|
||||
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : this.requestMap.entrySet()) {
|
||||
if (entry.getKey().matches(request)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
else {
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace(LogMessage.format("Did not match request to %s - %s (%d/%d)", entry.getKey(),
|
||||
entry.getValue(), ++count, this.requestMap.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return FilterInvocation.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
private HttpServletRequest getHttpServletRequest(Object object) {
|
||||
if (object instanceof FilterInvocation invocation) {
|
||||
return invocation.getHttpRequest();
|
||||
}
|
||||
if (object instanceof HttpServletRequest request) {
|
||||
return request;
|
||||
}
|
||||
throw new IllegalArgumentException("object must be of type FilterInvocation or HttpServletRequest");
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.intercept;
|
||||
|
||||
import org.springframework.security.access.SecurityMetadataSource;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.annotation.SecurityAnnotationScanner;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
|
||||
/**
|
||||
* Marker interface for <code>SecurityMetadataSource</code> implementations that are
|
||||
* designed to perform lookups keyed on {@link FilterInvocation}s.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @deprecated In modern Spring Security APIs, each API manages its own configuration
|
||||
* context. As such there is no direct replacement for this interface. In the case of
|
||||
* method security, please see {@link SecurityAnnotationScanner} and
|
||||
* {@link AuthorizationManager}. In the case of channel security, please see
|
||||
* {@code HttpsRedirectFilter}. In the case of web security, please see
|
||||
* {@link AuthorizationManager}.
|
||||
*/
|
||||
@Deprecated
|
||||
public interface FilterInvocationSecurityMetadataSource extends SecurityMetadataSource {
|
||||
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.intercept;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.FilterConfig;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.access.SecurityMetadataSource;
|
||||
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
|
||||
import org.springframework.security.access.intercept.InterceptorStatusToken;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
|
||||
/**
|
||||
* Performs security handling of HTTP resources via a filter implementation.
|
||||
* <p>
|
||||
* The <code>SecurityMetadataSource</code> required by this security interceptor is of
|
||||
* type {@link FilterInvocationSecurityMetadataSource}.
|
||||
* <p>
|
||||
* Refer to {@link AbstractSecurityInterceptor} for details on the workflow.
|
||||
* </p>
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Rob Winch
|
||||
* @deprecated Use {@link AuthorizationFilter} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
|
||||
|
||||
private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
|
||||
|
||||
private @Nullable FilterInvocationSecurityMetadataSource securityMetadataSource;
|
||||
|
||||
private boolean observeOncePerRequest = false;
|
||||
|
||||
/**
|
||||
* Not used (we rely on IoC container lifecycle services instead)
|
||||
* @param arg0 ignored
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public void init(FilterConfig arg0) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Not used (we rely on IoC container lifecycle services instead)
|
||||
*/
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that is actually called by the filter chain. Simply delegates to the
|
||||
* {@link #invoke(FilterInvocation)} method.
|
||||
* @param request the servlet request
|
||||
* @param response the servlet response
|
||||
* @param chain the filter chain
|
||||
* @throws IOException if the filter chain fails
|
||||
* @throws ServletException if the filter chain fails
|
||||
*/
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
invoke(new FilterInvocation(request, response, chain));
|
||||
}
|
||||
|
||||
public @Nullable FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
|
||||
return this.securityMetadataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SecurityMetadataSource obtainSecurityMetadataSource() {
|
||||
return this.securityMetadataSource;
|
||||
}
|
||||
|
||||
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
|
||||
this.securityMetadataSource = newSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getSecureObjectClass() {
|
||||
return FilterInvocation.class;
|
||||
}
|
||||
|
||||
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
|
||||
if (isApplied(filterInvocation) && this.observeOncePerRequest) {
|
||||
// filter already applied to this request and user wants us to observe
|
||||
// once-per-request handling, so don't re-do security checking
|
||||
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
|
||||
return;
|
||||
}
|
||||
// first time this request being called, so perform security checking
|
||||
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
|
||||
filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
|
||||
}
|
||||
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
|
||||
try {
|
||||
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
|
||||
}
|
||||
finally {
|
||||
super.finallyInvocation(token);
|
||||
}
|
||||
super.afterInvocation(token, null);
|
||||
}
|
||||
|
||||
private boolean isApplied(FilterInvocation filterInvocation) {
|
||||
return (filterInvocation.getRequest() != null)
|
||||
&& (filterInvocation.getRequest().getAttribute(FILTER_APPLIED) != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether once-per-request handling will be observed. By default this is
|
||||
* <code>true</code>, meaning the <code>FilterSecurityInterceptor</code> will only
|
||||
* execute once-per-request. Sometimes users may wish it to execute more than once per
|
||||
* request, such as when JSP forwards are being used and filter security is desired on
|
||||
* each included fragment of the HTTP request.
|
||||
* @return <code>true</code> (the default) if once-per-request is honoured, otherwise
|
||||
* <code>false</code> if <code>FilterSecurityInterceptor</code> will enforce
|
||||
* authorizations for each and every fragment of the HTTP request.
|
||||
*/
|
||||
public boolean isObserveOncePerRequest() {
|
||||
return this.observeOncePerRequest;
|
||||
}
|
||||
|
||||
public void setObserveOncePerRequest(boolean observeOncePerRequest) {
|
||||
this.observeOncePerRequest = observeOncePerRequest;
|
||||
}
|
||||
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.web.access;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.BDDMockito;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.security.access.AccessDecisionManager;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.intercept.RunAsManager;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
|
||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||
|
||||
/**
|
||||
* Tests
|
||||
* {@link org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
*/
|
||||
public class DefaultWebInvocationPrivilegeEvaluatorTests {
|
||||
|
||||
private AccessDecisionManager adm;
|
||||
|
||||
private FilterInvocationSecurityMetadataSource ods;
|
||||
|
||||
private RunAsManager ram;
|
||||
|
||||
private FilterSecurityInterceptor interceptor;
|
||||
|
||||
@BeforeEach
|
||||
public final void setUp() {
|
||||
this.interceptor = new FilterSecurityInterceptor();
|
||||
this.ods = Mockito.mock(FilterInvocationSecurityMetadataSource.class);
|
||||
this.adm = Mockito.mock(AccessDecisionManager.class);
|
||||
this.ram = Mockito.mock(RunAsManager.class);
|
||||
this.interceptor.setAuthenticationManager(Mockito.mock(AuthenticationManager.class));
|
||||
this.interceptor.setSecurityMetadataSource(this.ods);
|
||||
this.interceptor.setAccessDecisionManager(this.adm);
|
||||
this.interceptor.setRunAsManager(this.ram);
|
||||
this.interceptor.setApplicationEventPublisher(Mockito.mock(ApplicationEventPublisher.class));
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void permitsAccessIfNoMatchingAttributesAndPublicInvocationsAllowed() {
|
||||
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
|
||||
BDDMockito.given(this.ods.getAttributes(ArgumentMatchers.any())).willReturn(null);
|
||||
Assertions.assertThat(wipe.isAllowed("/context", "/foo/index.jsp", "GET", Mockito.mock(Authentication.class)))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deniesAccessIfNoMatchingAttributesAndPublicInvocationsNotAllowed() {
|
||||
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
|
||||
BDDMockito.given(this.ods.getAttributes(ArgumentMatchers.any())).willReturn(null);
|
||||
this.interceptor.setRejectPublicInvocations(true);
|
||||
Assertions.assertThat(wipe.isAllowed("/context", "/foo/index.jsp", "GET", Mockito.mock(Authentication.class)))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deniesAccessIfAuthenticationIsNull() {
|
||||
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
|
||||
Assertions.assertThat(wipe.isAllowed("/foo/index.jsp", null)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowsAccessIfAccessDecisionManagerDoes() {
|
||||
Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX");
|
||||
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
|
||||
Assertions.assertThat(wipe.isAllowed("/foo/index.jsp", token)).isTrue();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void deniesAccessIfAccessDecisionManagerDoes() {
|
||||
Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX");
|
||||
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
|
||||
BDDMockito.willThrow(new AccessDeniedException(""))
|
||||
.given(this.adm)
|
||||
.decide(ArgumentMatchers.any(Authentication.class), ArgumentMatchers.any(), ArgumentMatchers.anyList());
|
||||
Assertions.assertThat(wipe.isAllowed("/foo/index.jsp", token)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAllowedWhenServletContextIsSetThenPassedFilterInvocationHasServletContext() {
|
||||
Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX");
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
ArgumentCaptor<FilterInvocation> filterInvocationArgumentCaptor = ArgumentCaptor
|
||||
.forClass(FilterInvocation.class);
|
||||
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
|
||||
wipe.setServletContext(servletContext);
|
||||
wipe.isAllowed("/foo/index.jsp", token);
|
||||
Mockito.verify(this.adm)
|
||||
.decide(ArgumentMatchers.eq(token), filterInvocationArgumentCaptor.capture(), ArgumentMatchers.any());
|
||||
Assertions.assertThat(filterInvocationArgumentCaptor.getValue().getRequest().getServletContext()).isNotNull();
|
||||
}
|
||||
|
||||
}
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.channel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests {@link ChannelDecisionManagerImpl}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class ChannelDecisionManagerImplTests {
|
||||
|
||||
@Test
|
||||
public void testCannotSetEmptyChannelProcessorsList() throws Exception {
|
||||
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> {
|
||||
cdm.setChannelProcessors(new Vector());
|
||||
cdm.afterPropertiesSet();
|
||||
}).withMessage("A list of ChannelProcessors is required");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCannotSetIncorrectObjectTypesIntoChannelProcessorsList() {
|
||||
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
|
||||
List list = new Vector();
|
||||
list.add("THIS IS NOT A CHANNELPROCESSOR");
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> cdm.setChannelProcessors(list));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCannotSetNullChannelProcessorsList() throws Exception {
|
||||
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> {
|
||||
cdm.setChannelProcessors(null);
|
||||
cdm.afterPropertiesSet();
|
||||
}).withMessage("A list of ChannelProcessors is required");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecideIsOperational() throws Exception {
|
||||
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
|
||||
MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
|
||||
MockChannelProcessor cpAbc = new MockChannelProcessor("abc", true);
|
||||
List list = new Vector();
|
||||
list.add(cpXyz);
|
||||
list.add(cpAbc);
|
||||
cdm.setChannelProcessors(list);
|
||||
cdm.afterPropertiesSet();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
|
||||
List<ConfigAttribute> cad = SecurityConfig.createList("xyz");
|
||||
cdm.decide(fi, cad);
|
||||
Assertions.assertThat(fi.getResponse().isCommitted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAnyChannelAttributeCausesProcessorsToBeSkipped() throws Exception {
|
||||
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
|
||||
MockChannelProcessor cpAbc = new MockChannelProcessor("abc", true);
|
||||
List list = new Vector();
|
||||
list.add(cpAbc);
|
||||
cdm.setChannelProcessors(list);
|
||||
cdm.afterPropertiesSet();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
|
||||
cdm.decide(fi, SecurityConfig.createList(new String[] { "abc", "ANY_CHANNEL" }));
|
||||
Assertions.assertThat(fi.getResponse().isCommitted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecideIteratesAllProcessorsIfNoneCommitAResponse() throws Exception {
|
||||
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
|
||||
MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
|
||||
MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false);
|
||||
List list = new Vector();
|
||||
list.add(cpXyz);
|
||||
list.add(cpAbc);
|
||||
cdm.setChannelProcessors(list);
|
||||
cdm.afterPropertiesSet();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
|
||||
cdm.decide(fi, SecurityConfig.createList("SOME_ATTRIBUTE_NO_PROCESSORS_SUPPORT"));
|
||||
Assertions.assertThat(fi.getResponse().isCommitted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelegatesSupports() throws Exception {
|
||||
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
|
||||
MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
|
||||
MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false);
|
||||
List list = new Vector();
|
||||
list.add(cpXyz);
|
||||
list.add(cpAbc);
|
||||
cdm.setChannelProcessors(list);
|
||||
cdm.afterPropertiesSet();
|
||||
assertThat(cdm.supports(new SecurityConfig("xyz"))).isTrue();
|
||||
assertThat(cdm.supports(new SecurityConfig("abc"))).isTrue();
|
||||
assertThat(cdm.supports(new SecurityConfig("UNSUPPORTED"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGettersSetters() {
|
||||
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
|
||||
assertThat(cdm.getChannelProcessors()).isNull();
|
||||
MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
|
||||
MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false);
|
||||
List list = new Vector();
|
||||
list.add(cpXyz);
|
||||
list.add(cpAbc);
|
||||
cdm.setChannelProcessors(list);
|
||||
assertThat(cdm.getChannelProcessors()).isEqualTo(list);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartupFailsWithEmptyChannelProcessorsList() throws Exception {
|
||||
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
|
||||
assertThatIllegalArgumentException().isThrownBy(cdm::afterPropertiesSet)
|
||||
.withMessage("A list of ChannelProcessors is required");
|
||||
}
|
||||
|
||||
private class MockChannelProcessor implements ChannelProcessor {
|
||||
|
||||
private String configAttribute;
|
||||
|
||||
private boolean failIfCalled;
|
||||
|
||||
MockChannelProcessor(String configAttribute, boolean failIfCalled) {
|
||||
this.configAttribute = configAttribute;
|
||||
this.failIfCalled = failIfCalled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config) throws IOException {
|
||||
Iterator iter = config.iterator();
|
||||
if (this.failIfCalled) {
|
||||
fail("Should not have called this channel processor: " + this.configAttribute);
|
||||
}
|
||||
while (iter.hasNext()) {
|
||||
ConfigAttribute attr = (ConfigAttribute) iter.next();
|
||||
if (attr.getAttribute().equals(this.configAttribute)) {
|
||||
invocation.getHttpResponse().sendRedirect("/redirected");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(ConfigAttribute attribute) {
|
||||
return attribute.getAttribute().equals(this.configAttribute);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.channel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
|
||||
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests {@link ChannelProcessingFilter}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
*/
|
||||
public class ChannelProcessingFilterTests {
|
||||
|
||||
@Test
|
||||
public void testDetectsMissingChannelDecisionManager() {
|
||||
ChannelProcessingFilter filter = new ChannelProcessingFilter();
|
||||
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "MOCK");
|
||||
filter.setSecurityMetadataSource(fids);
|
||||
assertThatIllegalArgumentException().isThrownBy(filter::afterPropertiesSet);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetectsMissingFilterInvocationSecurityMetadataSource() {
|
||||
ChannelProcessingFilter filter = new ChannelProcessingFilter();
|
||||
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "MOCK"));
|
||||
assertThatIllegalArgumentException().isThrownBy(filter::afterPropertiesSet);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetectsSupportedConfigAttribute() {
|
||||
ChannelProcessingFilter filter = new ChannelProcessingFilter();
|
||||
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "SUPPORTS_MOCK_ONLY"));
|
||||
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true,
|
||||
"SUPPORTS_MOCK_ONLY");
|
||||
filter.setSecurityMetadataSource(fids);
|
||||
filter.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetectsUnsupportedConfigAttribute() {
|
||||
ChannelProcessingFilter filter = new ChannelProcessingFilter();
|
||||
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "SUPPORTS_MOCK_ONLY"));
|
||||
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true,
|
||||
"SUPPORTS_MOCK_ONLY", "INVALID_ATTRIBUTE");
|
||||
filter.setSecurityMetadataSource(fids);
|
||||
assertThatIllegalArgumentException().isThrownBy(filter::afterPropertiesSet);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoFilterWhenManagerDoesCommitResponse() throws Exception {
|
||||
ChannelProcessingFilter filter = new ChannelProcessingFilter();
|
||||
filter.setChannelDecisionManager(new MockChannelDecisionManager(true, "SOME_ATTRIBUTE"));
|
||||
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "SOME_ATTRIBUTE");
|
||||
filter.setSecurityMetadataSource(fids);
|
||||
MockHttpServletRequest request = TestMockHttpServletRequests.get("/path").build();
|
||||
request.setQueryString("info=now");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
filter.doFilter(request, response, mock(FilterChain.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoFilterWhenManagerDoesNotCommitResponse() throws Exception {
|
||||
ChannelProcessingFilter filter = new ChannelProcessingFilter();
|
||||
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "SOME_ATTRIBUTE"));
|
||||
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "SOME_ATTRIBUTE");
|
||||
filter.setSecurityMetadataSource(fids);
|
||||
MockHttpServletRequest request = TestMockHttpServletRequests.get("/path").build();
|
||||
request.setQueryString("info=now");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
filter.doFilter(request, response, mock(FilterChain.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoFilterWhenNullConfigAttributeReturned() throws Exception {
|
||||
ChannelProcessingFilter filter = new ChannelProcessingFilter();
|
||||
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "NOT_USED"));
|
||||
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "NOT_USED");
|
||||
filter.setSecurityMetadataSource(fids);
|
||||
MockHttpServletRequest request = TestMockHttpServletRequests.get("/PATH_NOT_MATCHING_CONFIG_ATTRIBUTE").build();
|
||||
request.setQueryString("info=now");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
filter.doFilter(request, response, mock(FilterChain.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetterSetters() {
|
||||
ChannelProcessingFilter filter = new ChannelProcessingFilter();
|
||||
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "MOCK"));
|
||||
assertThat(filter.getChannelDecisionManager() != null).isTrue();
|
||||
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", false, "MOCK");
|
||||
filter.setSecurityMetadataSource(fids);
|
||||
assertThat(filter.getSecurityMetadataSource()).isSameAs(fids);
|
||||
filter.afterPropertiesSet();
|
||||
}
|
||||
|
||||
private class MockChannelDecisionManager implements ChannelDecisionManager {
|
||||
|
||||
private String supportAttribute;
|
||||
|
||||
private boolean commitAResponse;
|
||||
|
||||
MockChannelDecisionManager(boolean commitAResponse, String supportAttribute) {
|
||||
this.commitAResponse = commitAResponse;
|
||||
this.supportAttribute = supportAttribute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config) throws IOException {
|
||||
if (this.commitAResponse) {
|
||||
invocation.getHttpResponse().sendRedirect("/redirected");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(ConfigAttribute attribute) {
|
||||
return attribute.getAttribute().equals(this.supportAttribute);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class MockFilterInvocationDefinitionMap implements FilterInvocationSecurityMetadataSource {
|
||||
|
||||
private Collection<ConfigAttribute> toReturn;
|
||||
|
||||
private String servletPath;
|
||||
|
||||
private boolean provideIterator;
|
||||
|
||||
MockFilterInvocationDefinitionMap(String servletPath, boolean provideIterator, String... toReturn) {
|
||||
this.servletPath = servletPath;
|
||||
this.toReturn = SecurityConfig.createList(toReturn);
|
||||
this.provideIterator = provideIterator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
|
||||
FilterInvocation fi = (FilterInvocation) object;
|
||||
if (this.servletPath.equals(fi.getHttpRequest().getServletPath())) {
|
||||
return this.toReturn;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ConfigAttribute> getAllConfigAttributes() {
|
||||
if (!this.provideIterator) {
|
||||
return null;
|
||||
}
|
||||
return this.toReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.channel;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests {@link InsecureChannelProcessor}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
*/
|
||||
public class InsecureChannelProcessorTests {
|
||||
|
||||
@Test
|
||||
public void testDecideDetectsAcceptableChannel() throws Exception {
|
||||
MockHttpServletRequest request = TestMockHttpServletRequests.get("http://localhost:8080")
|
||||
.requestUri("/bigapp", "/servlet", null)
|
||||
.queryString("info=true")
|
||||
.build();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
|
||||
InsecureChannelProcessor processor = new InsecureChannelProcessor();
|
||||
processor.decide(fi, SecurityConfig.createList("SOME_IGNORED_ATTRIBUTE", "REQUIRES_INSECURE_CHANNEL"));
|
||||
Assertions.assertThat(fi.getResponse().isCommitted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecideDetectsUnacceptableChannel() throws Exception {
|
||||
MockHttpServletRequest request = TestMockHttpServletRequests.get("https://localhost:8443")
|
||||
.requestUri("/bigapp", "/servlet", null)
|
||||
.queryString("info=true")
|
||||
.build();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
|
||||
InsecureChannelProcessor processor = new InsecureChannelProcessor();
|
||||
processor.decide(fi,
|
||||
SecurityConfig.createList(new String[] { "SOME_IGNORED_ATTRIBUTE", "REQUIRES_INSECURE_CHANNEL" }));
|
||||
Assertions.assertThat(fi.getResponse().isCommitted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecideRejectsNulls() throws Exception {
|
||||
InsecureChannelProcessor processor = new InsecureChannelProcessor();
|
||||
processor.afterPropertiesSet();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> processor.decide(null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGettersSetters() {
|
||||
InsecureChannelProcessor processor = new InsecureChannelProcessor();
|
||||
assertThat(processor.getInsecureKeyword()).isEqualTo("REQUIRES_INSECURE_CHANNEL");
|
||||
processor.setInsecureKeyword("X");
|
||||
assertThat(processor.getInsecureKeyword()).isEqualTo("X");
|
||||
assertThat(processor.getEntryPoint() != null).isTrue();
|
||||
processor.setEntryPoint(null);
|
||||
assertThat(processor.getEntryPoint() == null).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingEntryPoint() throws Exception {
|
||||
InsecureChannelProcessor processor = new InsecureChannelProcessor();
|
||||
processor.setEntryPoint(null);
|
||||
assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet)
|
||||
.withMessage("entryPoint required");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingSecureChannelKeyword() throws Exception {
|
||||
InsecureChannelProcessor processor = new InsecureChannelProcessor();
|
||||
processor.setInsecureKeyword(null);
|
||||
assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet)
|
||||
.withMessage("insecureKeyword required");
|
||||
processor.setInsecureKeyword("");
|
||||
assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet)
|
||||
.withMessage("insecureKeyword required");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupports() {
|
||||
InsecureChannelProcessor processor = new InsecureChannelProcessor();
|
||||
assertThat(processor.supports(new SecurityConfig("REQUIRES_INSECURE_CHANNEL"))).isTrue();
|
||||
assertThat(processor.supports(null)).isFalse();
|
||||
assertThat(processor.supports(new SecurityConfig("NOT_SUPPORTED"))).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.channel;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.web.PortMapper;
|
||||
import org.springframework.security.web.PortMapperImpl;
|
||||
import org.springframework.security.web.RedirectStrategy;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests {@link RetryWithHttpEntryPoint}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
*/
|
||||
public class RetryWithHttpEntryPointTests {
|
||||
|
||||
@Test
|
||||
public void testDetectsMissingPortMapper() {
|
||||
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> ep.setPortMapper(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGettersSetters() {
|
||||
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
|
||||
PortMapper portMapper = mock(PortMapper.class);
|
||||
RedirectStrategy redirector = mock(RedirectStrategy.class);
|
||||
ep.setPortMapper(portMapper);
|
||||
ep.setRedirectStrategy(redirector);
|
||||
assertThat(ep.getPortMapper()).isSameAs(portMapper);
|
||||
assertThat(ep.getRedirectStrategy()).isSameAs(redirector);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalOperation() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html");
|
||||
request.setQueryString("open=true");
|
||||
request.setScheme("https");
|
||||
request.setServerName("localhost");
|
||||
request.setServerPort(443);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
|
||||
ep.setPortMapper(new PortMapperImpl());
|
||||
ep.commence(request, response);
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/bigWebApp/hello/pathInfo.html?open=true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalOperationWithNullQueryString() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello");
|
||||
request.setScheme("https");
|
||||
request.setServerName("localhost");
|
||||
request.setServerPort(443);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
|
||||
ep.setPortMapper(new PortMapperImpl());
|
||||
ep.commence(request, response);
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/bigWebApp/hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOperationWhenTargetPortIsUnknown() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp");
|
||||
request.setQueryString("open=true");
|
||||
request.setScheme("https");
|
||||
request.setServerName("www.example.com");
|
||||
request.setServerPort(8768);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
|
||||
ep.setPortMapper(new PortMapperImpl());
|
||||
ep.commence(request, response);
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp?open=true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOperationWithNonStandardPort() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html");
|
||||
request.setQueryString("open=true");
|
||||
request.setScheme("https");
|
||||
request.setServerName("localhost");
|
||||
request.setServerPort(9999);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
PortMapperImpl portMapper = new PortMapperImpl();
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("8888", "9999");
|
||||
portMapper.setPortMappings(map);
|
||||
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
|
||||
ep.setPortMapper(portMapper);
|
||||
ep.commence(request, response);
|
||||
assertThat(response.getRedirectedUrl())
|
||||
.isEqualTo("http://localhost:8888/bigWebApp/hello/pathInfo.html?open=true");
|
||||
}
|
||||
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.channel;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.web.PortMapperImpl;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests {@link RetryWithHttpsEntryPoint}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
*/
|
||||
public class RetryWithHttpsEntryPointTests {
|
||||
|
||||
@Test
|
||||
public void testDetectsMissingPortMapper() {
|
||||
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> ep.setPortMapper(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGettersSetters() {
|
||||
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
|
||||
ep.setPortMapper(new PortMapperImpl());
|
||||
assertThat(ep.getPortMapper() != null).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalOperation() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html");
|
||||
request.setQueryString("open=true");
|
||||
request.setScheme("http");
|
||||
request.setServerName("www.example.com");
|
||||
request.setServerPort(80);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
|
||||
ep.setPortMapper(new PortMapperImpl());
|
||||
ep.commence(request, response);
|
||||
assertThat(response.getRedirectedUrl())
|
||||
.isEqualTo("https://www.example.com/bigWebApp/hello/pathInfo.html?open=true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalOperationWithNullQueryString() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello");
|
||||
request.setScheme("http");
|
||||
request.setServerName("www.example.com");
|
||||
request.setServerPort(80);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
|
||||
ep.setPortMapper(new PortMapperImpl());
|
||||
ep.commence(request, response);
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo("https://www.example.com/bigWebApp/hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOperationWhenTargetPortIsUnknown() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp");
|
||||
request.setQueryString("open=true");
|
||||
request.setScheme("http");
|
||||
request.setServerName("www.example.com");
|
||||
request.setServerPort(8768);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
|
||||
ep.setPortMapper(new PortMapperImpl());
|
||||
ep.commence(request, response);
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp?open=true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOperationWithNonStandardPort() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html");
|
||||
request.setQueryString("open=true");
|
||||
request.setScheme("http");
|
||||
request.setServerName("www.example.com");
|
||||
request.setServerPort(8888);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
PortMapperImpl portMapper = new PortMapperImpl();
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("8888", "9999");
|
||||
portMapper.setPortMappings(map);
|
||||
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
|
||||
ep.setPortMapper(portMapper);
|
||||
ep.commence(request, response);
|
||||
assertThat(response.getRedirectedUrl())
|
||||
.isEqualTo("https://www.example.com:9999/bigWebApp/hello/pathInfo.html?open=true");
|
||||
}
|
||||
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.channel;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests {@link SecureChannelProcessor}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
*/
|
||||
public class SecureChannelProcessorTests {
|
||||
|
||||
@Test
|
||||
public void testDecideDetectsAcceptableChannel() throws Exception {
|
||||
MockHttpServletRequest request = TestMockHttpServletRequests.get("https://localhost:8443")
|
||||
.requestUri("/bigapp", "/servlet", null)
|
||||
.queryString("info=true")
|
||||
.build();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
|
||||
SecureChannelProcessor processor = new SecureChannelProcessor();
|
||||
processor.decide(fi, SecurityConfig.createList("SOME_IGNORED_ATTRIBUTE", "REQUIRES_SECURE_CHANNEL"));
|
||||
Assertions.assertThat(fi.getResponse().isCommitted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecideDetectsUnacceptableChannel() throws Exception {
|
||||
MockHttpServletRequest request = TestMockHttpServletRequests.get("http://localhost:8080")
|
||||
.requestUri("/bigapp", "/servlet", null)
|
||||
.queryString("info=true")
|
||||
.build();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
|
||||
SecureChannelProcessor processor = new SecureChannelProcessor();
|
||||
processor.decide(fi,
|
||||
SecurityConfig.createList(new String[] { "SOME_IGNORED_ATTRIBUTE", "REQUIRES_SECURE_CHANNEL" }));
|
||||
Assertions.assertThat(fi.getResponse().isCommitted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecideRejectsNulls() throws Exception {
|
||||
SecureChannelProcessor processor = new SecureChannelProcessor();
|
||||
processor.afterPropertiesSet();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> processor.decide(null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGettersSetters() {
|
||||
SecureChannelProcessor processor = new SecureChannelProcessor();
|
||||
assertThat(processor.getSecureKeyword()).isEqualTo("REQUIRES_SECURE_CHANNEL");
|
||||
processor.setSecureKeyword("X");
|
||||
assertThat(processor.getSecureKeyword()).isEqualTo("X");
|
||||
assertThat(processor.getEntryPoint() != null).isTrue();
|
||||
processor.setEntryPoint(null);
|
||||
assertThat(processor.getEntryPoint() == null).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingEntryPoint() throws Exception {
|
||||
SecureChannelProcessor processor = new SecureChannelProcessor();
|
||||
processor.setEntryPoint(null);
|
||||
assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet)
|
||||
.withMessage("entryPoint required");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingSecureChannelKeyword() throws Exception {
|
||||
SecureChannelProcessor processor = new SecureChannelProcessor();
|
||||
processor.setSecureKeyword(null);
|
||||
assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet)
|
||||
.withMessage("secureKeyword required");
|
||||
processor.setSecureKeyword("");
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> processor.afterPropertiesSet())
|
||||
.withMessage("secureKeyword required");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupports() {
|
||||
SecureChannelProcessor processor = new SecureChannelProcessor();
|
||||
assertThat(processor.supports(new SecurityConfig("REQUIRES_SECURE_CHANNEL"))).isTrue();
|
||||
assertThat(processor.supports(null)).isFalse();
|
||||
assertThat(processor.supports(new SecurityConfig("NOT_SUPPORTED"))).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.web.access.expression;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.authentication.AuthenticationTrustResolver;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class DefaultWebSecurityExpressionHandlerTests {
|
||||
|
||||
@Mock
|
||||
private AuthenticationTrustResolver trustResolver;
|
||||
|
||||
@Mock
|
||||
private Authentication authentication;
|
||||
|
||||
@Mock
|
||||
private FilterInvocation invocation;
|
||||
|
||||
private DefaultWebSecurityExpressionHandler handler;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.handler = new DefaultWebSecurityExpressionHandler();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expressionPropertiesAreResolvedAgainstAppContextBeans() {
|
||||
StaticApplicationContext appContext = new StaticApplicationContext();
|
||||
RootBeanDefinition bean = new RootBeanDefinition(SecurityConfig.class);
|
||||
bean.getConstructorArgumentValues().addGenericArgumentValue("ROLE_A");
|
||||
appContext.registerBeanDefinition("role", bean);
|
||||
this.handler.setApplicationContext(appContext);
|
||||
EvaluationContext ctx = this.handler.createEvaluationContext(mock(Authentication.class),
|
||||
mock(FilterInvocation.class));
|
||||
ExpressionParser parser = this.handler.getExpressionParser();
|
||||
assertThat(parser.parseExpression("@role.getAttribute() == 'ROLE_A'").getValue(ctx, Boolean.class)).isTrue();
|
||||
assertThat(parser.parseExpression("@role.attribute == 'ROLE_A'").getValue(ctx, Boolean.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTrustResolverNull() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.handler.setTrustResolver(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createEvaluationContextCustomTrustResolver() {
|
||||
this.handler.setTrustResolver(this.trustResolver);
|
||||
Expression expression = this.handler.getExpressionParser().parseExpression("anonymous");
|
||||
EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.invocation);
|
||||
assertThat(expression.getValue(context, Boolean.class)).isFalse();
|
||||
verify(this.trustResolver).isAnonymous(this.authentication);
|
||||
}
|
||||
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.web.access.expression;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
*/
|
||||
public class ExpressionBasedFilterInvocationSecurityMetadataSourceTests {
|
||||
|
||||
@Test
|
||||
public void expectedAttributeIsReturned() {
|
||||
final String expression = "hasRole('X')";
|
||||
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
|
||||
requestMap.put(AnyRequestMatcher.INSTANCE, SecurityConfig.createList(expression));
|
||||
ExpressionBasedFilterInvocationSecurityMetadataSource mds = new ExpressionBasedFilterInvocationSecurityMetadataSource(
|
||||
requestMap, new DefaultWebSecurityExpressionHandler());
|
||||
assertThat(mds.getAllConfigAttributes()).hasSize(1);
|
||||
Collection<ConfigAttribute> attrs = mds.getAttributes(new FilterInvocation("/path", "GET"));
|
||||
assertThat(attrs).hasSize(1);
|
||||
WebExpressionConfigAttribute attribute = (WebExpressionConfigAttribute) attrs.toArray()[0];
|
||||
assertThat(attribute.getAttribute()).isNull();
|
||||
assertThat(attribute.getAuthorizeExpression().getExpressionString()).isEqualTo(expression);
|
||||
assertThat(attribute.toString()).isEqualTo(expression);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidExpressionIsRejected() {
|
||||
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
|
||||
requestMap.put(AnyRequestMatcher.INSTANCE, SecurityConfig.createList("hasRole('X'"));
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new ExpressionBasedFilterInvocationSecurityMetadataSource(requestMap,
|
||||
new DefaultWebSecurityExpressionHandler()));
|
||||
}
|
||||
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2004-present 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.web.access.expression;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.security.access.AccessDecisionVoter;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
public class WebExpressionVoterTests {
|
||||
|
||||
private Authentication user = new TestingAuthenticationToken("user", "pass", "X");
|
||||
|
||||
@Test
|
||||
public void supportsWebConfigAttributeAndFilterInvocation() {
|
||||
WebExpressionVoter voter = new WebExpressionVoter();
|
||||
assertThat(voter.supports(
|
||||
new WebExpressionConfigAttribute(mock(Expression.class), mock(EvaluationContextPostProcessor.class))))
|
||||
.isTrue();
|
||||
assertThat(voter.supports(FilterInvocation.class)).isTrue();
|
||||
assertThat(voter.supports(MethodInvocation.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void abstainsIfNoAttributeFound() {
|
||||
WebExpressionVoter voter = new WebExpressionVoter();
|
||||
assertThat(
|
||||
voter.vote(this.user, new FilterInvocation("/path", "GET"), SecurityConfig.createList("A", "B", "C")))
|
||||
.isEqualTo(AccessDecisionVoter.ACCESS_ABSTAIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void grantsAccessIfExpressionIsTrueDeniesIfFalse() {
|
||||
WebExpressionVoter voter = new WebExpressionVoter();
|
||||
Expression ex = mock(Expression.class);
|
||||
EvaluationContextPostProcessor postProcessor = mock(EvaluationContextPostProcessor.class);
|
||||
given(postProcessor.postProcess(any(EvaluationContext.class), any(FilterInvocation.class)))
|
||||
.willAnswer((invocation) -> invocation.getArgument(0));
|
||||
WebExpressionConfigAttribute weca = new WebExpressionConfigAttribute(ex, postProcessor);
|
||||
EvaluationContext ctx = mock(EvaluationContext.class);
|
||||
SecurityExpressionHandler eh = mock(SecurityExpressionHandler.class);
|
||||
FilterInvocation fi = new FilterInvocation("/path", "GET");
|
||||
voter.setExpressionHandler(eh);
|
||||
given(eh.createEvaluationContext(this.user, fi)).willReturn(ctx);
|
||||
given(ex.getValue(ctx, Boolean.class)).willReturn(Boolean.TRUE, Boolean.FALSE);
|
||||
ArrayList attributes = new ArrayList();
|
||||
attributes.addAll(SecurityConfig.createList("A", "B", "C"));
|
||||
attributes.add(weca);
|
||||
assertThat(voter.vote(this.user, fi, attributes)).isEqualTo(AccessDecisionVoter.ACCESS_GRANTED);
|
||||
// Second time false
|
||||
assertThat(voter.vote(this.user, fi, attributes)).isEqualTo(AccessDecisionVoter.ACCESS_DENIED);
|
||||
}
|
||||
|
||||
// SEC-2507
|
||||
@Test
|
||||
public void supportFilterInvocationSubClass() {
|
||||
WebExpressionVoter voter = new WebExpressionVoter();
|
||||
assertThat(voter.supports(FilterInvocationChild.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportFilterInvocation() {
|
||||
WebExpressionVoter voter = new WebExpressionVoter();
|
||||
assertThat(voter.supports(FilterInvocation.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsObjectIsFalse() {
|
||||
WebExpressionVoter voter = new WebExpressionVoter();
|
||||
assertThat(voter.supports(Object.class)).isFalse();
|
||||
}
|
||||
|
||||
private static class FilterInvocationChild extends FilterInvocation {
|
||||
|
||||
FilterInvocationChild(ServletRequest request, ServletResponse response, FilterChain chain) {
|
||||
super(request, response, chain);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.intercept;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
|
||||
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests {@link DefaultFilterInvocationSecurityMetadataSource}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
*/
|
||||
public class DefaultFilterInvocationSecurityMetadataSourceTests {
|
||||
|
||||
private DefaultFilterInvocationSecurityMetadataSource fids;
|
||||
|
||||
private Collection<ConfigAttribute> def = SecurityConfig.createList("ROLE_ONE");
|
||||
|
||||
private void createFids(String pattern, HttpMethod method) {
|
||||
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
|
||||
requestMap.put(PathPatternRequestMatcher.pathPattern(method, pattern), this.def);
|
||||
this.fids = new DefaultFilterInvocationSecurityMetadataSource(requestMap);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lookupNotRequiringExactMatchSucceedsIfNotMatching() {
|
||||
createFids("/secure/super/**", null);
|
||||
FilterInvocation fi = createFilterInvocation("/secure/super/somefile.html", null, null, "GET");
|
||||
assertThat(this.fids.getAttributes(fi)).isEqualTo(this.def);
|
||||
}
|
||||
|
||||
/**
|
||||
* SEC-501. Note that as of 2.0, lower case comparisons are the default for this
|
||||
* class.
|
||||
*/
|
||||
@Test
|
||||
public void lookupNotRequiringExactMatchSucceedsIfSecureUrlPathContainsUpperCase() {
|
||||
createFids("/secure/super/**", null);
|
||||
FilterInvocation fi = createFilterInvocation("/secure", "/super/somefile.html", null, "GET");
|
||||
Collection<ConfigAttribute> response = this.fids.getAttributes(fi);
|
||||
assertThat(response).isEqualTo(this.def);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lookupRequiringExactMatchIsSuccessful() {
|
||||
createFids("/SeCurE/super/**", null);
|
||||
FilterInvocation fi = createFilterInvocation("/SeCurE/super/somefile.html", null, null, "GET");
|
||||
Collection<ConfigAttribute> response = this.fids.getAttributes(fi);
|
||||
assertThat(response).isEqualTo(this.def);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lookupRequiringExactMatchWithAdditionalSlashesIsSuccessful() {
|
||||
createFids("/someAdminPage.html**", null);
|
||||
FilterInvocation fi = createFilterInvocation("/someAdminPage.html", null, "a=/test", "GET");
|
||||
Collection<ConfigAttribute> response = this.fids.getAttributes(fi);
|
||||
assertThat(response); // see SEC-161 (it should truncate after ?
|
||||
// sign).isEqualTo(def)
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpMethodLookupSucceeds() {
|
||||
createFids("/somepage**", HttpMethod.GET);
|
||||
FilterInvocation fi = createFilterInvocation("/somepage", null, null, "GET");
|
||||
Collection<ConfigAttribute> attrs = this.fids.getAttributes(fi);
|
||||
assertThat(attrs).isEqualTo(this.def);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generalMatchIsUsedIfNoMethodSpecificMatchExists() {
|
||||
createFids("/somepage**", null);
|
||||
FilterInvocation fi = createFilterInvocation("/somepage", null, null, "GET");
|
||||
Collection<ConfigAttribute> attrs = this.fids.getAttributes(fi);
|
||||
assertThat(attrs).isEqualTo(this.def);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWithDifferentHttpMethodDoesntMatch() {
|
||||
createFids("/somepage**", HttpMethod.GET);
|
||||
FilterInvocation fi = createFilterInvocation("/somepage", null, null, "POST");
|
||||
Collection<ConfigAttribute> attrs = this.fids.getAttributes(fi);
|
||||
assertThat(attrs).isEmpty();
|
||||
}
|
||||
|
||||
// SEC-1236
|
||||
@Test
|
||||
public void mixingPatternsWithAndWithoutHttpMethodsIsSupported() {
|
||||
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
|
||||
Collection<ConfigAttribute> userAttrs = SecurityConfig.createList("A");
|
||||
requestMap.put(PathPatternRequestMatcher.pathPattern("/user/**"), userAttrs);
|
||||
requestMap.put(PathPatternRequestMatcher.pathPattern(HttpMethod.GET, "/teller/**"),
|
||||
SecurityConfig.createList("B"));
|
||||
this.fids = new DefaultFilterInvocationSecurityMetadataSource(requestMap);
|
||||
FilterInvocation fi = createFilterInvocation("/user", null, null, "GET");
|
||||
Collection<ConfigAttribute> attrs = this.fids.getAttributes(fi);
|
||||
assertThat(attrs).isEqualTo(userAttrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check fixes for SEC-321
|
||||
*/
|
||||
@Test
|
||||
public void extraQuestionMarkStillMatches() {
|
||||
createFids("/someAdminPage.html*", null);
|
||||
FilterInvocation fi = createFilterInvocation("/someAdminPage.html", null, null, "GET");
|
||||
Collection<ConfigAttribute> response = this.fids.getAttributes(fi);
|
||||
assertThat(response).isEqualTo(this.def);
|
||||
fi = createFilterInvocation("/someAdminPage.html", null, "?", "GET");
|
||||
response = this.fids.getAttributes(fi);
|
||||
assertThat(response).isEqualTo(this.def);
|
||||
}
|
||||
|
||||
private FilterInvocation createFilterInvocation(String servletPath, String pathInfo, String queryString,
|
||||
String method) {
|
||||
MockHttpServletRequest request = TestMockHttpServletRequests.request(method)
|
||||
.requestUri(null, servletPath, pathInfo)
|
||||
.queryString(queryString)
|
||||
.build();
|
||||
return new FilterInvocation(request, new MockHttpServletResponse(), mock(FilterChain.class));
|
||||
}
|
||||
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* 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.web.access.intercept;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.access.AccessDecisionManager;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.access.event.AuthorizedEvent;
|
||||
import org.springframework.security.access.intercept.AfterInvocationManager;
|
||||
import org.springframework.security.access.intercept.RunAsManager;
|
||||
import org.springframework.security.access.intercept.RunAsUserToken;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyCollection;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.willThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* Tests {@link FilterSecurityInterceptor}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Luke Taylor
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class FilterSecurityInterceptorTests {
|
||||
|
||||
private AuthenticationManager am;
|
||||
|
||||
private AccessDecisionManager adm;
|
||||
|
||||
private FilterInvocationSecurityMetadataSource ods;
|
||||
|
||||
private RunAsManager ram;
|
||||
|
||||
private FilterSecurityInterceptor interceptor;
|
||||
|
||||
private ApplicationEventPublisher publisher;
|
||||
|
||||
@BeforeEach
|
||||
public final void setUp() {
|
||||
this.interceptor = new FilterSecurityInterceptor();
|
||||
this.am = mock(AuthenticationManager.class);
|
||||
this.ods = mock(FilterInvocationSecurityMetadataSource.class);
|
||||
this.adm = mock(AccessDecisionManager.class);
|
||||
this.ram = mock(RunAsManager.class);
|
||||
this.publisher = mock(ApplicationEventPublisher.class);
|
||||
this.interceptor.setAuthenticationManager(this.am);
|
||||
this.interceptor.setSecurityMetadataSource(this.ods);
|
||||
this.interceptor.setAccessDecisionManager(this.adm);
|
||||
this.interceptor.setRunAsManager(this.ram);
|
||||
this.interceptor.setApplicationEventPublisher(this.publisher);
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnsuresAccessDecisionManagerSupportsFilterInvocationClass() throws Exception {
|
||||
given(this.adm.supports(FilterInvocation.class)).willReturn(true);
|
||||
assertThatIllegalArgumentException().isThrownBy(this.interceptor::afterPropertiesSet);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnsuresRunAsManagerSupportsFilterInvocationClass() throws Exception {
|
||||
given(this.adm.supports(FilterInvocation.class)).willReturn(false);
|
||||
assertThatIllegalArgumentException().isThrownBy(this.interceptor::afterPropertiesSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* We just test invocation works in a success event. There is no need to test access
|
||||
* denied events as the abstract parent enforces that logic, which is extensively
|
||||
* tested separately.
|
||||
*/
|
||||
@Test
|
||||
public void testSuccessfulInvocation() throws Throwable {
|
||||
// Setup a Context
|
||||
Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED");
|
||||
SecurityContextHolder.getContext().setAuthentication(token);
|
||||
FilterInvocation fi = createinvocation();
|
||||
given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK"));
|
||||
this.interceptor.invoke(fi);
|
||||
// SEC-1697
|
||||
verify(this.publisher, never()).publishEvent(any(AuthorizedEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void afterInvocationIsNotInvokedIfExceptionThrown() throws Exception {
|
||||
Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED");
|
||||
SecurityContextHolder.getContext().setAuthentication(token);
|
||||
FilterInvocation fi = createinvocation();
|
||||
FilterChain chain = fi.getChain();
|
||||
willThrow(new RuntimeException()).given(chain)
|
||||
.doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK"));
|
||||
AfterInvocationManager aim = mock(AfterInvocationManager.class);
|
||||
this.interceptor.setAfterInvocationManager(aim);
|
||||
assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> this.interceptor.invoke(fi));
|
||||
verifyNoMoreInteractions(aim);
|
||||
}
|
||||
|
||||
// SEC-1967
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void finallyInvocationIsInvokedIfExceptionThrown() throws Exception {
|
||||
SecurityContext ctx = SecurityContextHolder.getContext();
|
||||
Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED");
|
||||
token.setAuthenticated(true);
|
||||
ctx.setAuthentication(token);
|
||||
RunAsManager runAsManager = mock(RunAsManager.class);
|
||||
given(runAsManager.buildRunAs(eq(token), any(), anyCollection()))
|
||||
.willReturn(new RunAsUserToken("key", "someone", "creds", token.getAuthorities(), token.getClass()));
|
||||
this.interceptor.setRunAsManager(runAsManager);
|
||||
FilterInvocation fi = createinvocation();
|
||||
FilterChain chain = fi.getChain();
|
||||
willThrow(new RuntimeException()).given(chain)
|
||||
.doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK"));
|
||||
AfterInvocationManager aim = mock(AfterInvocationManager.class);
|
||||
this.interceptor.setAfterInvocationManager(aim);
|
||||
assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> this.interceptor.invoke(fi));
|
||||
// Check we've changed back
|
||||
assertThat(SecurityContextHolder.getContext()).isSameAs(ctx);
|
||||
assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(token);
|
||||
}
|
||||
|
||||
@Test
|
||||
// gh-4997
|
||||
public void doFilterWhenObserveOncePerRequestThenAttributeNotSet() throws Exception {
|
||||
this.interceptor.setObserveOncePerRequest(false);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
this.interceptor.doFilter(request, response, new MockFilterChain());
|
||||
assertThat(request.getAttributeNames().hasMoreElements()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenObserveOncePerRequestFalseAndInvokedTwiceThenObserveTwice() throws Throwable {
|
||||
Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED");
|
||||
SecurityContextHolder.getContext().setAuthentication(token);
|
||||
FilterInvocation fi = createinvocation();
|
||||
given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK"));
|
||||
this.interceptor.invoke(fi);
|
||||
this.interceptor.invoke(fi);
|
||||
verify(this.adm, times(2)).decide(any(), any(), any());
|
||||
}
|
||||
|
||||
private FilterInvocation createinvocation() {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockHttpServletRequest request = TestMockHttpServletRequests.get("/secure/page.html").build();
|
||||
FilterChain chain = mock(FilterChain.class);
|
||||
FilterInvocation fi = new FilterInvocation(request, response, chain);
|
||||
return fi;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user