diff --git a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/channel/ChannelAccessPolicy.java b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/channel/ChannelAccessPolicy.java new file mode 100644 index 0000000000..96c72f01cf --- /dev/null +++ b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/channel/ChannelAccessPolicy.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2008 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 + * + * http://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.integration.security.channel; + +import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Creates the {@link ConfigAttributeDefinition}s for secured channel + * send and receive operations based on simple String values. + */ +public class ChannelAccessPolicy { + + private final ConfigAttributeDefinition configAttributeDefinitionForSend; + + private final ConfigAttributeDefinition configAttributeDefinitionForReceive; + + + /** + * Create an access policy instance. The provided 'sendAccess' and 'receiveAccess' + * values may be a single String or a comma-delimited list of values. All whitespace + * will be trimmed. A null value indicates that the policy does not + * apply for either send or receive access type. At most one of the values may be null. + */ + public ChannelAccessPolicy(String sendAccess, String receiveAccess) { + Assert.isTrue(sendAccess != null || receiveAccess != null, + "At least one of 'sendAccess' and 'receiveAccess' must not be null."); + String[] sendValues = StringUtils.trimArrayElements( + StringUtils.commaDelimitedListToStringArray(sendAccess)); + String[] receiveValues = StringUtils.trimArrayElements( + StringUtils.commaDelimitedListToStringArray(receiveAccess)); + this.configAttributeDefinitionForSend = (sendValues.length > 0) + ? new ConfigAttributeDefinition(sendValues) : null; + this.configAttributeDefinitionForReceive = (receiveValues.length > 0) + ? new ConfigAttributeDefinition(receiveValues) : null; + } + + + public ConfigAttributeDefinition getConfigAttributeDefinitionForSend() { + return this.configAttributeDefinitionForSend; + } + + public ConfigAttributeDefinition getConfigAttributeDefinitionForReceive() { + return this.configAttributeDefinitionForReceive; + } + +} diff --git a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/channel/ChannelInvocation.java b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/channel/ChannelInvocation.java new file mode 100644 index 0000000000..98cc6dadbf --- /dev/null +++ b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/channel/ChannelInvocation.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2008 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 + * + * http://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.integration.security.channel; + +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.Message; +import org.springframework.util.Assert; + +/** + * Secured object for {@link ChannelSecurityInterceptor}. Maintains a reference + * to the original {@link MethodInvocation} instance and provides convenient + * access to the secured {@link MessageChannel}. If the intercepted invocation + * is a send operation, the {@link Message} is also available. + * + * @author Mark Fisher + */ +public class ChannelInvocation { + + private final MessageChannel channel; + + private final Message message; + + private final MethodInvocation methodInvocation; + + + /** + * @param methodInvocation the intercepted MethodInvocation instance + */ + public ChannelInvocation(MethodInvocation methodInvocation) { + Assert.notNull(methodInvocation, "MethodInvocation must not be null"); + Assert.isAssignable(MessageChannel.class, methodInvocation.getThis().getClass(), + "MethodInvocation must be on a MessageChannel"); + this.channel = (MessageChannel) methodInvocation.getThis(); + if (methodInvocation.getMethod().getName().equals("send")) { + if (methodInvocation.getArguments().length < 1 || !(methodInvocation.getArguments()[0] instanceof Message)) { + throw new IllegalStateException("expected a Message as the first parameter of the channel's send method"); + } + this.message = (Message) methodInvocation.getArguments()[0]; + } + else { + this.message = null; + } + this.methodInvocation = methodInvocation; + } + + + public MessageChannel getChannel() { + return this.channel; + } + + public Message getMessage() { + return this.message; + } + + public MethodInvocation getMethodInvocation() { + return this.methodInvocation; + } + + public boolean isSend() { + return "send".equals(this.methodInvocation.getMethod().getName()); + } + + public boolean isReceive() { + return "receive".equals(this.methodInvocation.getMethod().getName()); + } + +} diff --git a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/channel/ChannelInvocationDefinitionSource.java b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/channel/ChannelInvocationDefinitionSource.java new file mode 100644 index 0000000000..603774e524 --- /dev/null +++ b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/channel/ChannelInvocationDefinitionSource.java @@ -0,0 +1,100 @@ +/* + * Copyright 2002-2008 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 + * + * http://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.integration.security.channel; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import org.springframework.integration.channel.MessageChannel; +import org.springframework.security.ConfigAttribute; +import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.security.intercept.ObjectDefinitionSource; +import org.springframework.util.Assert; + +/** + * The {@link ObjectDefinitionSource} implementation for secured {@link MessageChannel}s. + * + * @author Mark Fisher + */ +public class ChannelInvocationDefinitionSource implements ObjectDefinitionSource { + + private final Map patternMappings = + new LinkedHashMap(); + + + public void addPatternMapping(Pattern pattern, ChannelAccessPolicy accessPolicy) { + this.patternMappings.put(pattern, accessPolicy); + } + + public Set getPatterns() { + return this.patternMappings.keySet(); + } + + @SuppressWarnings("unchecked") + public boolean supports(Class clazz) { + return ChannelInvocation.class.isAssignableFrom(clazz); + } + + @SuppressWarnings("unchecked") + public ConfigAttributeDefinition getAttributes(Object object) throws IllegalArgumentException { + Assert.isAssignable(ChannelInvocation.class, object.getClass()); + ChannelInvocation invocation = (ChannelInvocation) object; + String channelName = invocation.getChannel().getName(); + List attributes = new ArrayList(); + for (Map.Entry mapping : this.patternMappings.entrySet()) { + Pattern pattern = mapping.getKey(); + ChannelAccessPolicy accessPolicy = mapping.getValue(); + if (pattern.matcher(channelName).matches()) { + if (invocation.isSend()) { + ConfigAttributeDefinition definition = accessPolicy.getConfigAttributeDefinitionForSend(); + if (definition != null) { + attributes.addAll(definition.getConfigAttributes()); + } + } + else if (invocation.isReceive()) { + ConfigAttributeDefinition definition = accessPolicy.getConfigAttributeDefinitionForReceive(); + if (definition != null) { + attributes.addAll(definition.getConfigAttributes()); + } + } + } + } + return new ConfigAttributeDefinition(attributes); + } + + public Collection getConfigAttributeDefinitions() { + Set definitions = new HashSet(); + for (ChannelAccessPolicy accessPolicy : this.patternMappings.values()) { + ConfigAttributeDefinition sendDefinition = accessPolicy.getConfigAttributeDefinitionForSend(); + if (sendDefinition != null) { + definitions.add(sendDefinition); + } + ConfigAttributeDefinition receiveDefinition = accessPolicy.getConfigAttributeDefinitionForReceive(); + if (receiveDefinition != null) { + definitions.add(receiveDefinition); + } + } + return definitions; + } + +} diff --git a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/channel/ChannelSecurityInterceptor.java b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/channel/ChannelSecurityInterceptor.java new file mode 100644 index 0000000000..3d6728917c --- /dev/null +++ b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/channel/ChannelSecurityInterceptor.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2008 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 + * + * http://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.integration.security.channel; + +import java.lang.reflect.Method; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.security.intercept.AbstractSecurityInterceptor; +import org.springframework.security.intercept.InterceptorStatusToken; +import org.springframework.security.intercept.ObjectDefinitionSource; +import org.springframework.util.Assert; + +/** + * An AOP interceptor that enforces authorization for MessageChannel send and/or receive calls. + * + * @author Mark Fisher + */ +public class ChannelSecurityInterceptor extends AbstractSecurityInterceptor implements MethodInterceptor { + + private final ChannelInvocationDefinitionSource objectDefinitionSource; + + + public ChannelSecurityInterceptor(ChannelInvocationDefinitionSource objectDefinitionSource) { + Assert.notNull(objectDefinitionSource, "objectDefinitionSource must not be null"); + this.objectDefinitionSource = objectDefinitionSource; + } + + + @Override + public Class getSecureObjectClass() { + return ChannelInvocation.class; + } + + @Override + public ObjectDefinitionSource obtainObjectDefinitionSource() { + return this.objectDefinitionSource; + } + + public Object invoke(MethodInvocation invocation) throws Throwable { + Method method = invocation.getMethod(); + if (method.getName().equals("send") || method.getName().equals("receive")) { + return this.invokeWithAuthorizationCheck(invocation); + } + return invocation.proceed(); + } + + private Object invokeWithAuthorizationCheck(MethodInvocation methodInvocation) throws Throwable { + Object returnValue = null; + InterceptorStatusToken token = super.beforeInvocation(new ChannelInvocation(methodInvocation)); + try { + returnValue = methodInvocation.proceed(); + } + finally { + returnValue = super.afterInvocation(token, returnValue); + } + return returnValue; + } + +} diff --git a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/channel/SecurityEnforcingChannelInterceptor.java b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/channel/SecurityEnforcingChannelInterceptor.java deleted file mode 100644 index eb84abb4ae..0000000000 --- a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/channel/SecurityEnforcingChannelInterceptor.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2002-2008 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 - * - * http://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.integration.security.channel; - -import org.springframework.integration.channel.MessageChannel; -import org.springframework.integration.channel.interceptor.ChannelInterceptorAdapter; -import org.springframework.integration.message.Message; -import org.springframework.security.AccessDecisionManager; -import org.springframework.security.Authentication; -import org.springframework.security.AuthenticationCredentialsNotFoundException; -import org.springframework.security.ConfigAttributeDefinition; -import org.springframework.security.context.SecurityContextHolder; -import org.springframework.util.Assert; - -/** - * Delegates to the provided instance of {@link AccessDecisionManager} to - * enforce the security on the send and receive calls of the {@link MessageChannel}. - * - * @author Jonas Partner - * @author Mark Fisher - */ -public class SecurityEnforcingChannelInterceptor extends ChannelInterceptorAdapter { - - private final AccessDecisionManager accessDecisionManger; - - private volatile ConfigAttributeDefinition sendSecurityAttributes; - - private volatile ConfigAttributeDefinition receiveSecurityAttributes; - - - public SecurityEnforcingChannelInterceptor(AccessDecisionManager accessDecisionManager) { - Assert.notNull(accessDecisionManager, "AccessDecisionManager must not be null"); - this.accessDecisionManger = accessDecisionManager; - } - - - public ConfigAttributeDefinition getSendSecurityAttributes() { - return this.sendSecurityAttributes; - } - - public void setSendSecurityAttributes(ConfigAttributeDefinition sendSecurityAttributes) { - this.sendSecurityAttributes = sendSecurityAttributes; - } - - public ConfigAttributeDefinition getReceiveSecurityAttributes() { - return this.receiveSecurityAttributes; - } - - public void setReceiveSecurityAttributes(ConfigAttributeDefinition receiveSecurityAttributes) { - this.receiveSecurityAttributes = receiveSecurityAttributes; - } - - @Override - public Message preSend(Message message, MessageChannel channel) { - this.checkSend(channel); - return message; - } - - @Override - public boolean preReceive(MessageChannel channel) { - this.checkReceive(channel); - return super.preReceive(channel); - } - - private void checkSend(MessageChannel channel) { - this.checkPermission(channel, this.sendSecurityAttributes); - } - - private void checkReceive(MessageChannel channel) { - this.checkPermission(channel, this.receiveSecurityAttributes); - } - - private void checkPermission(MessageChannel messageChannel, ConfigAttributeDefinition securityAttributes) { - if (securityAttributes != null) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null) { - throw new AuthenticationCredentialsNotFoundException( - "No Authentication object available. Consider enabling the SecurityPropagatingBeanPostProcessor."); - } - this.accessDecisionManger.decide(authentication, messageChannel, securityAttributes); - } - } - -} diff --git a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/ChannelSecurityInterceptorBeanPostProcessor.java b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/ChannelSecurityInterceptorBeanPostProcessor.java new file mode 100644 index 0000000000..a7f636817a --- /dev/null +++ b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/ChannelSecurityInterceptorBeanPostProcessor.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2008 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 + * + * http://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.integration.security.config; + +import java.util.Set; +import java.util.regex.Pattern; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.security.channel.ChannelInvocationDefinitionSource; +import org.springframework.integration.security.channel.ChannelSecurityInterceptor; +import org.springframework.util.Assert; + +/** + * A {@link BeanPostProcessor} that proxies {@link MessageChannel}s to apply a {@link ChannelSecurityInterceptor}. + * + * @author Mark Fisher + */ +public class ChannelSecurityInterceptorBeanPostProcessor implements BeanPostProcessor { + + private final ChannelSecurityInterceptor interceptor; + + + public ChannelSecurityInterceptorBeanPostProcessor(ChannelSecurityInterceptor interceptor) { + Assert.notNull(interceptor, "interceptor must not be null"); + this.interceptor = interceptor; + } + + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof MessageChannel && shouldProxy((MessageChannel) bean, + (ChannelInvocationDefinitionSource) this.interceptor.obtainObjectDefinitionSource())) { + ProxyFactory proxyFactory = new ProxyFactory(bean); + proxyFactory.addAdvisor(new DefaultPointcutAdvisor(this.interceptor)); + return proxyFactory.getProxy(); + } + return bean; + } + + private boolean shouldProxy(MessageChannel channel, ChannelInvocationDefinitionSource definitionSource) { + Assert.notNull(channel.getName(), "channel name must not be null"); + Set patterns = ((ChannelInvocationDefinitionSource) this.interceptor.obtainObjectDefinitionSource()).getPatterns(); + for (Pattern pattern : patterns) { + if (pattern.matcher(channel.getName()).matches()) { + return true; + } + } + return false; + } + +} diff --git a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecuredChannelsParser.java b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecuredChannelsParser.java index 9ad93250b2..74ae346d31 100644 --- a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecuredChannelsParser.java +++ b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecuredChannelsParser.java @@ -16,34 +16,37 @@ package org.springframework.integration.security.config; -import java.util.ArrayList; import java.util.List; +import java.util.regex.Pattern; + +import org.w3c.dom.Element; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.security.ChannelInterceptorRegisteringBeanPostProcessor; -import org.springframework.integration.security.channel.SecurityEnforcingChannelInterceptor; -import org.springframework.security.ConfigAttributeDefinition; -import org.springframework.security.context.SecurityContext; +import org.springframework.integration.ConfigurationException; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.config.IntegrationNamespaceUtils; +import org.springframework.integration.security.channel.ChannelAccessPolicy; +import org.springframework.integration.security.channel.ChannelInvocationDefinitionSource; +import org.springframework.integration.security.channel.ChannelSecurityInterceptor; import org.springframework.util.StringUtils; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; +import org.springframework.util.xml.DomUtils; /** - * Determines {@link SecurityContext} propagation behaviour for the parent - * element channel, and creates a {@link SecurityEnforcingChannelInterceptor} to - * control send and receive access if send-access and/or receive-access is - * specified. + * Creates a {@link ChannelSecurityInterceptor} to control send and receive access, + * and creates a {@link ChannelSecurityInterceptorBeanPostProcessor} to apply the + * interceptor to {@link MessageChannel}s whose names match the specified patterns. * * @author Jonas Partner + * @author Mark Fisher */ public class SecuredChannelsParser extends AbstractSingleBeanDefinitionParser { - public SecuredChannelsParser() { - super(); + @Override + protected Class getBeanClass(Element element) { + return ChannelSecurityInterceptorBeanPostProcessor.class; } @Override @@ -51,61 +54,33 @@ public class SecuredChannelsParser extends AbstractSingleBeanDefinitionParser { return true; } - @Override - protected boolean shouldGenerateIdAsFallback() { - return true; - } - @Override protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { - String receiveAccess = element.getAttribute("receive-access"); - String sendAccess = element.getAttribute("send-access"); - String accessDecisionManager = element.getAttribute("access-decision-manager"); - - BeanDefinition interceptorBeanDefinition = createSecurityEnforcingChannelInterceptor(accessDecisionManager, - sendAccess, receiveAccess); - - List patternList = processPatterns(element.getElementsByTagNameNS(element.getNamespaceURI(), - "channel-name-pattern")); - - builder.getBeanDefinition().setBeanClass(ChannelInterceptorRegisteringBeanPostProcessor.class); - builder.getBeanDefinition().getConstructorArgumentValues().addGenericArgumentValue( - new ValueHolder(interceptorBeanDefinition)); - builder.getBeanDefinition().getConstructorArgumentValues() - .addGenericArgumentValue(new ValueHolder(patternList)); - + ChannelInvocationDefinitionSource objectDefinitionSource = this.parseObjectDefinitionSource(element); + BeanDefinitionBuilder interceptorBuilder = BeanDefinitionBuilder.genericBeanDefinition(ChannelSecurityInterceptor.class); + interceptorBuilder.addConstructorArgValue(objectDefinitionSource); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(interceptorBuilder, element, "authentication-manager"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(interceptorBuilder, element, "access-decision-manager"); + String interceptorBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName( + interceptorBuilder.getBeanDefinition(), parserContext.getRegistry()); + builder.addConstructorArgReference(interceptorBeanName); } - protected List processPatterns(NodeList patternList) { - List patterns = new ArrayList(); - for (int i = 0; i < patternList.getLength(); i++) { - Element patternElement = (Element) patternList.item(i); - patterns.add(patternElement.getTextContent()); - } - return patterns; - } - protected BeanDefinition createSecurityEnforcingChannelInterceptor(String accessDecisionManager, String sendAccess, - String receiveAccess) { - if (!StringUtils.hasText(accessDecisionManager)) { - accessDecisionManager = "accessDecisionManager"; + @SuppressWarnings("unchecked") + private ChannelInvocationDefinitionSource parseObjectDefinitionSource(Element element) { + ChannelInvocationDefinitionSource objectDefinitionSource = new ChannelInvocationDefinitionSource(); + List accessPolicyElements = (List) DomUtils.getChildElementsByTagName(element, "access-policy"); + for (Element accessPolicyElement : accessPolicyElements) { + Pattern pattern = Pattern.compile(accessPolicyElement.getAttribute("pattern")); + String sendAccess = accessPolicyElement.getAttribute("send-access"); + String receiveAccess = accessPolicyElement.getAttribute("receive-access"); + if (!StringUtils.hasText(sendAccess) && !StringUtils.hasText(receiveAccess)) { + throw new ConfigurationException("At least one of 'send-access' or 'receive-access' must be provided."); + } + objectDefinitionSource.addPatternMapping(pattern, new ChannelAccessPolicy(sendAccess, receiveAccess)); } - BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder - .genericBeanDefinition(SecurityEnforcingChannelInterceptor.class); - beanDefinitionBuilder.addConstructorArgReference(accessDecisionManager); - - if (StringUtils.hasText(sendAccess)) { - ConfigAttributeDefinition sendDefinition = new ConfigAttributeDefinition(StringUtils.tokenizeToStringArray( - sendAccess, ",")); - beanDefinitionBuilder.addPropertyValue("sendSecurityAttributes", sendDefinition); - } - if (StringUtils.hasText(receiveAccess)) { - ConfigAttributeDefinition receiveDefinition = new ConfigAttributeDefinition(StringUtils - .tokenizeToStringArray(receiveAccess, ",")); - beanDefinitionBuilder.addPropertyValue("receiveSecurityAttributes", receiveDefinition); - } - return beanDefinitionBuilder.getBeanDefinition(); - + return objectDefinitionSource; } } diff --git a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/spring-integration-security-1.0.xsd b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/spring-integration-security-1.0.xsd index b15fb1f8f0..3fe6054ec9 100644 --- a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/spring-integration-security-1.0.xsd +++ b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/spring-integration-security-1.0.xsd @@ -1,10 +1,9 @@ - - @@ -18,14 +17,24 @@ - + - - + + + + + Defines the security access policy for send and/or receive invocations based on a Message Channel name pattern. + + + + + + + diff --git a/org.springframework.integration.security/src/main/resources/META-INF/spring.handlers b/org.springframework.integration.security/src/main/resources/META-INF/spring.handlers index 340dbcfb29..9ee913c503 100644 --- a/org.springframework.integration.security/src/main/resources/META-INF/spring.handlers +++ b/org.springframework.integration.security/src/main/resources/META-INF/spring.handlers @@ -1 +1 @@ -http\://www.springframework.org/schema/integration-security=org.springframework.integration.security.config.IntegrationSecurityNamespaceHandler \ No newline at end of file +http\://www.springframework.org/schema/integration/security=org.springframework.integration.security.config.IntegrationSecurityNamespaceHandler \ No newline at end of file diff --git a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/channel/ChannelAdapterSecurityIntegrationTests-context.xml b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/channel/ChannelAdapterSecurityIntegrationTests-context.xml index 03cce3b527..35a983afb8 100644 --- a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/channel/ChannelAdapterSecurityIntegrationTests-context.xml +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/channel/ChannelAdapterSecurityIntegrationTests-context.xml @@ -1,26 +1,30 @@ + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-2.0.xsd + http://www.springframework.org/schema/security + http://www.springframework.org/schema/security/spring-security-2.0.xsd + http://www.springframework.org/schema/integration + http://www.springframework.org/schema/integration/spring-integration-1.0.xsd + http://www.springframework.org/schema/integration/security + http://www.springframework.org/schema/integration/spring-integration-security-1.0.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context-2.5.xsd"> - - secured.* + + - + diff --git a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/channel/ChannelSecurityInterceptorBeanPostProcessorTests.java b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/channel/ChannelSecurityInterceptorBeanPostProcessorTests.java new file mode 100644 index 0000000000..b6f867b30c --- /dev/null +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/channel/ChannelSecurityInterceptorBeanPostProcessorTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2008 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 + * + * http://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.integration.security.channel; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.regex.Pattern; + +import org.junit.Test; + +import org.springframework.aop.support.AopUtils; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.security.config.ChannelSecurityInterceptorBeanPostProcessor; + +/** + * @author Mark Fisher + */ +public class ChannelSecurityInterceptorBeanPostProcessorTests { + + @Test + public void securedChannelIsProxied() { + ChannelInvocationDefinitionSource objectDefinitionSource = new ChannelInvocationDefinitionSource(); + objectDefinitionSource.addPatternMapping(Pattern.compile("secured.*"), new ChannelAccessPolicy("ROLE_ADMIN", null)); + ChannelSecurityInterceptor interceptor = new ChannelSecurityInterceptor(objectDefinitionSource); + ChannelSecurityInterceptorBeanPostProcessor postProcessor = new ChannelSecurityInterceptorBeanPostProcessor(interceptor); + QueueChannel securedChannel = new QueueChannel(); + securedChannel.setBeanName("securedChannel"); + MessageChannel postProcessedChannel = (MessageChannel) postProcessor.postProcessAfterInitialization(securedChannel, "securedChannel"); + assertTrue(AopUtils.isAopProxy(postProcessedChannel)); + } + + @Test + public void nonsecuredChannelIsNotProxied() { + ChannelInvocationDefinitionSource objectDefinitionSource = new ChannelInvocationDefinitionSource(); + objectDefinitionSource.addPatternMapping(Pattern.compile("secured.*"), new ChannelAccessPolicy("ROLE_ADMIN", null)); + ChannelSecurityInterceptor interceptor = new ChannelSecurityInterceptor(objectDefinitionSource); + ChannelSecurityInterceptorBeanPostProcessor postProcessor = new ChannelSecurityInterceptorBeanPostProcessor(interceptor); + QueueChannel channel = new QueueChannel(); + channel.setBeanName("testChannel"); + MessageChannel postProcessedChannel = (MessageChannel) postProcessor.postProcessAfterInitialization(channel, "testChannel"); + assertFalse(AopUtils.isAopProxy(postProcessedChannel)); + } + +} diff --git a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/channel/ChannelSecurityInterceptorTests.java b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/channel/ChannelSecurityInterceptorTests.java new file mode 100644 index 0000000000..41b3f10e97 --- /dev/null +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/channel/ChannelSecurityInterceptorTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2008 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 + * + * http://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.integration.security.channel; + +import java.util.Collections; +import java.util.regex.Pattern; + +import org.junit.After; +import org.junit.Test; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.message.StringMessage; +import org.springframework.integration.security.SecurityTestUtil; +import org.springframework.security.AccessDeniedException; +import org.springframework.security.AuthenticationException; +import org.springframework.security.MockAuthenticationManager; +import org.springframework.security.context.SecurityContext; +import org.springframework.security.context.SecurityContextHolder; +import org.springframework.security.vote.AffirmativeBased; +import org.springframework.security.vote.RoleVoter; + +/** + * @author Mark Fisher + */ +public class ChannelSecurityInterceptorTests { + + @After + public void clearSecurityContext() { + SecurityContextHolder.clearContext(); + } + + @Test(expected = AuthenticationException.class) + public void securedSendWithoutAuthentication() throws Exception { + MessageChannel channel = getSecuredChannel("ROLE_ADMIN"); + channel.send(new StringMessage("test")); + } + + @Test(expected = AccessDeniedException.class) + public void securedSendWithoutRole() throws Exception { + MessageChannel channel = getSecuredChannel("ROLE_ADMIN"); + SecurityContext context = SecurityTestUtil.createContext("test", "pwd", "ROLE_USER"); + SecurityContextHolder.setContext(context); + channel.send(new StringMessage("test")); + } + + @Test + public void securedSendWithRole() throws Exception { + MessageChannel channel = getSecuredChannel("ROLE_ADMIN"); + SecurityContext context = SecurityTestUtil.createContext("test", "pwd", "ROLE_ADMIN"); + SecurityContextHolder.setContext(context); + channel.send(new StringMessage("test")); + } + + + private static MessageChannel getSecuredChannel(String role) throws Exception { + QueueChannel channel = new QueueChannel(); + channel.setBeanName("securedChannel"); + ProxyFactory proxyFactory = new ProxyFactory(channel); + proxyFactory.addAdvice(createInterceptor(role)); + return (MessageChannel) proxyFactory.getProxy(); + } + + private static ChannelSecurityInterceptor createInterceptor(String role) throws Exception { + ChannelInvocationDefinitionSource objectDefinitionSource = new ChannelInvocationDefinitionSource(); + objectDefinitionSource.addPatternMapping(Pattern.compile("secured.*"), new ChannelAccessPolicy(role, null)); + ChannelSecurityInterceptor interceptor = new ChannelSecurityInterceptor(objectDefinitionSource); + AffirmativeBased accessDecisionManager = new AffirmativeBased(); + accessDecisionManager.setDecisionVoters(Collections.singletonList(new RoleVoter())); + accessDecisionManager.afterPropertiesSet(); + interceptor.setAccessDecisionManager(accessDecisionManager); + interceptor.setAuthenticationManager(new MockAuthenticationManager(true)); + interceptor.afterPropertiesSet(); + return interceptor; + } + +} diff --git a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/channel/SecurityEnforcingChannelInterceptorTests.java b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/channel/SecurityEnforcingChannelInterceptorTests.java deleted file mode 100644 index 48a3c94f4a..0000000000 --- a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/channel/SecurityEnforcingChannelInterceptorTests.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2002-2008 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 - * - * http://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.integration.security.channel; - -import static org.junit.Assert.assertEquals; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.message.StringMessage; -import org.springframework.integration.security.channel.SecurityEnforcingChannelInterceptor; -import org.springframework.security.AccessDecisionManager; -import org.springframework.security.AccessDeniedException; -import org.springframework.security.Authentication; -import org.springframework.security.ConfigAttribute; -import org.springframework.security.ConfigAttributeDefinition; -import org.springframework.security.GrantedAuthorityImpl; -import org.springframework.security.InsufficientAuthenticationException; -import org.springframework.security.context.SecurityContextHolder; -import org.springframework.security.providers.TestingAuthenticationToken; - -/** - * @author Jonas Partner - */ -public class SecurityEnforcingChannelInterceptorTests { - - private QueueChannel channel; - - private SecurityEnforcingChannelInterceptor securityChannelInterceptor; - - - @Before - public void setUp() { - channel = new QueueChannel(); - SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken( - "stub", "passwd", new GrantedAuthorityImpl[] {})); - } - - @After - public void clearSecurityContext(){ - SecurityContextHolder.clearContext(); - } - - @Test(expected = AccessDeniedException.class) - public void testSendSecuredAndAccessDenied() { - try { - Runnable decision = new Runnable() { - public void run() { - throw new AccessDeniedException("nope"); - } - }; - this.registerInterceptor(new ConfigurableAccessDecisionManager(decision)); - this.securityChannelInterceptor.setSendSecurityAttributes(new ConfigAttributeDefinition("ROLE_ADMIN")); - this.channel.send(new StringMessage("test")); - } - finally { - assertEquals("Wrong message count after refused send.", 0, channel.clear().size()); - } - } - - @Test - public void testUnsecuredSend() { - this.registerInterceptor(new ConfigurableAccessDecisionManager(null)); - this.channel.send(new StringMessage("test")); - assertEquals("Wrong message count after send.", 1,channel.clear().size()); - } - - @Test - public void testSendSecuredAndAllowed() { - Runnable decision = new Runnable() { - public void run() { - } - }; - this.registerInterceptor(new ConfigurableAccessDecisionManager(decision)); - this.securityChannelInterceptor.setSendSecurityAttributes(new ConfigAttributeDefinition("ROLE_ADMIN")); - this.channel.send(new StringMessage("test")); - assertEquals("Wrong message count after send", 1, channel.clear().size()); - } - - @Test - public void testReceiveSecuredAndAllowed() { - Runnable decision = new Runnable() { - public void run() { - } - }; - this.registerInterceptor(new ConfigurableAccessDecisionManager(decision)); - this.securityChannelInterceptor.setReceiveSecurityAttributes(new ConfigAttributeDefinition("ROLE_ADMIN")); - this.channel.receive(0); - } - - @Test(expected = AccessDeniedException.class) - public void testReceiveSecuredAndAccessDenied() { - Runnable decision = new Runnable() { - public void run() { - throw new AccessDeniedException("nope"); - } - }; - this.registerInterceptor(new ConfigurableAccessDecisionManager(decision)); - this.securityChannelInterceptor.setReceiveSecurityAttributes(new ConfigAttributeDefinition("ROLE_ADMIN")); - this.channel.receive(0); - } - - @Test - public void testReceiveUnsecured() { - Runnable decision = new Runnable() { - public void run() { - throw new AccessDeniedException("nope"); - } - }; - this.registerInterceptor(new ConfigurableAccessDecisionManager(decision)); - this.channel.receive(0); - } - - - private void registerInterceptor(AccessDecisionManager accessDecisionManager) { - securityChannelInterceptor = new SecurityEnforcingChannelInterceptor(accessDecisionManager); - channel.addInterceptor(securityChannelInterceptor); - } - - - private static class ConfigurableAccessDecisionManager implements AccessDecisionManager { - - private Runnable decisionRunner; - - public ConfigurableAccessDecisionManager(Runnable decision) { - this.decisionRunner = decision; - } - - public void decide(Authentication authentication, Object object, ConfigAttributeDefinition config) - throws AccessDeniedException, InsufficientAuthenticationException { - this.decisionRunner.run(); - } - - public boolean supports(ConfigAttribute attribute) { - return true; - } - - @SuppressWarnings("unchecked") - public boolean supports(Class clazz) { - return true; - } - } - -} diff --git a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecuredChannelsParserTests-context.xml b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecuredChannelsParserTests-context.xml index ab678e83c0..ece1a49cdb 100644 --- a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecuredChannelsParserTests-context.xml +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecuredChannelsParserTests-context.xml @@ -1,36 +1,29 @@ + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-2.0.xsd + http://www.springframework.org/schema/security + http://www.springframework.org/schema/security/spring-security-2.0.xsd + http://www.springframework.org/schema/integration + http://www.springframework.org/schema/integration/spring-integration-1.0.xsd + http://www.springframework.org/schema/integration/security + http://www.springframework.org/schema/integration/spring-integration-security-1.0.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context-2.5.xsd"> - - adminRequiredForSend + + + + + + - - adminOrUserRequiredForSend - - - - adminRequiredForReceive - - - - adminOrUserRequiredForReceive - - - - adminForSendAndReceive - - - \ No newline at end of file + diff --git a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecuredChannelsParserTests.java b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecuredChannelsParserTests.java index 39db3112db..ee28a85dc0 100644 --- a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecuredChannelsParserTests.java +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecuredChannelsParserTests.java @@ -16,25 +16,42 @@ package org.springframework.integration.security.config; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; import org.junit.Before; import org.junit.Test; +import org.springframework.aop.Advisor; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.DirectFieldAccessor; import org.springframework.integration.channel.AbstractPollableChannel; import org.springframework.integration.channel.ChannelInterceptor; +import org.springframework.integration.channel.MessageChannel; import org.springframework.integration.message.Message; import org.springframework.integration.message.selector.MessageSelector; -import org.springframework.integration.security.channel.SecurityEnforcingChannelInterceptor; -import org.springframework.security.SecurityConfig; +import org.springframework.integration.security.channel.ChannelAccessPolicy; +import org.springframework.integration.security.channel.ChannelInvocationDefinitionSource; +import org.springframework.integration.security.channel.ChannelSecurityInterceptor; +import org.springframework.security.ConfigAttribute; +import org.springframework.security.ConfigAttributeDefinition; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; /** * @author Jonas Partner + * @author Mark Fisher */ @ContextConfiguration public class SecuredChannelsParserTests extends AbstractJUnit4SpringContextTests { @@ -48,67 +65,125 @@ public class SecuredChannelsParserTests extends AbstractJUnit4SpringContextTests @Test public void testAdminRequiredForSend() { - applicationContext.getAutowireCapableBeanFactory().applyBeanPostProcessorsAfterInitialization(messageChannel, - "adminRequiredForSend"); - assertEquals("Wrong count of interceptors ", 1, messageChannel.interceptors.size()); - SecurityEnforcingChannelInterceptor interceptor = (SecurityEnforcingChannelInterceptor) messageChannel.interceptors - .get(0); - assertTrue("ROLE_ADMIN not found as send attribute", interceptor.getSendSecurityAttributes().contains( - new SecurityConfig("ROLE_ADMIN"))); - assertNull("Receive security attribute were not null", interceptor.getReceiveSecurityAttributes()); + String beanName = "adminRequiredForSend"; + messageChannel.setBeanName(beanName); + MessageChannel proxy = (MessageChannel) applicationContext.getAutowireCapableBeanFactory() + .applyBeanPostProcessorsAfterInitialization(messageChannel, beanName); + assertTrue("Channel was not proxied", AopUtils.isAopProxy(proxy)); + Advisor[] advisors = ((Advised) proxy).getAdvisors(); + assertEquals("Wrong number of interceptors", 1, advisors.length); + ChannelSecurityInterceptor interceptor = (ChannelSecurityInterceptor) advisors[0].getAdvice(); + ChannelAccessPolicy policy = this.retrievePolicyForPatternString(beanName, interceptor); + assertNotNull("Pattern '" + beanName + "' is not included in mappings", policy); + ConfigAttributeDefinition sendDefinition = policy.getConfigAttributeDefinitionForSend(); + ConfigAttributeDefinition receiveDefinition = policy.getConfigAttributeDefinitionForReceive(); + assertTrue("ROLE_ADMIN not found as send attribute", this.getRolesFromDefintion(sendDefinition).contains("ROLE_ADMIN")); + assertNull("Policy applies to receive", receiveDefinition); } @Test public void testAdminOrUserRequiredForSend() { - applicationContext.getAutowireCapableBeanFactory().applyBeanPostProcessorsAfterInitialization(messageChannel, - "adminOrUserRequiredForSend"); - assertEquals("Wrong count of interceptors ", 1, messageChannel.interceptors.size()); - SecurityEnforcingChannelInterceptor interceptor = (SecurityEnforcingChannelInterceptor) messageChannel.interceptors - .get(0); - assertTrue("ROLE_ADMIN not found as send attribute", interceptor.getSendSecurityAttributes().contains( - new SecurityConfig("ROLE_ADMIN"))); - assertTrue("ROLE_USER not found as send attribute", interceptor.getSendSecurityAttributes().contains( - new SecurityConfig("ROLE_USER"))); - assertNull("Receive security attribute were not null", interceptor.getReceiveSecurityAttributes()); + String beanName = "adminOrUserRequiredForSend"; + messageChannel.setBeanName(beanName); + MessageChannel proxy = (MessageChannel) applicationContext.getAutowireCapableBeanFactory() + .applyBeanPostProcessorsAfterInitialization(messageChannel, beanName); + assertTrue("Channel was not proxied", AopUtils.isAopProxy(proxy)); + Advisor[] advisors = ((Advised) proxy).getAdvisors(); + assertEquals("Wrong number of interceptors", 1, advisors.length); + ChannelSecurityInterceptor interceptor = (ChannelSecurityInterceptor) advisors[0].getAdvice(); + ChannelAccessPolicy policy = this.retrievePolicyForPatternString(beanName, interceptor); + assertNotNull("Pattern '" + beanName + "' is not included in mappings", policy); + ConfigAttributeDefinition sendDefinition = policy.getConfigAttributeDefinitionForSend(); + ConfigAttributeDefinition receiveDefinition = policy.getConfigAttributeDefinitionForReceive(); + Collection sendRoles = this.getRolesFromDefintion(sendDefinition); + assertTrue("ROLE_ADMIN not found as send attribute", sendRoles.contains("ROLE_ADMIN")); + assertTrue("ROLE_USER not found as send attribute", sendRoles.contains("ROLE_USER")); + assertNull("Policy applies to receive", receiveDefinition); } @Test public void testAdminRequiredForReceive() { - applicationContext.getAutowireCapableBeanFactory().applyBeanPostProcessorsAfterInitialization(messageChannel, - "adminRequiredForReceive"); - assertEquals("Wrong count of interceptors ", 1, messageChannel.interceptors.size()); - SecurityEnforcingChannelInterceptor interceptor = (SecurityEnforcingChannelInterceptor) messageChannel.interceptors - .get(0); - assertTrue("ROLE_ADMIN not found as receive attribute", interceptor.getReceiveSecurityAttributes().contains( - new SecurityConfig("ROLE_ADMIN"))); - assertNull("Send security attribute were not null", interceptor.getSendSecurityAttributes()); + String beanName = "adminRequiredForReceive"; + messageChannel.setBeanName(beanName); + MessageChannel proxy = (MessageChannel) applicationContext.getAutowireCapableBeanFactory() + .applyBeanPostProcessorsAfterInitialization(messageChannel, beanName); + assertTrue("Channel was not proxied", AopUtils.isAopProxy(proxy)); + Advisor[] advisors = ((Advised) proxy).getAdvisors(); + assertEquals("Wrong number of interceptors", 1, advisors.length); + ChannelSecurityInterceptor interceptor = (ChannelSecurityInterceptor) advisors[0].getAdvice(); + ChannelAccessPolicy policy = this.retrievePolicyForPatternString(beanName, interceptor); + assertNotNull("Pattern '" + beanName + "' is not included in mappings", policy); + ConfigAttributeDefinition sendDefinition = policy.getConfigAttributeDefinitionForSend(); + ConfigAttributeDefinition receiveDefinition = policy.getConfigAttributeDefinitionForReceive(); + Collection receiveRoles = this.getRolesFromDefintion(receiveDefinition); + assertTrue("ROLE_ADMIN not found as receive attribute", receiveRoles.contains("ROLE_ADMIN")); + assertNull("Policy applies to send", sendDefinition); } @Test public void testAdminOrUserRequiredForReceive() { - applicationContext.getAutowireCapableBeanFactory().applyBeanPostProcessorsAfterInitialization(messageChannel, - "adminOrUserRequiredForReceive"); - assertEquals("Wrong count of interceptors ", 1, messageChannel.interceptors.size()); - SecurityEnforcingChannelInterceptor interceptor = (SecurityEnforcingChannelInterceptor) messageChannel.interceptors - .get(0); - assertTrue("ROLE_ADMIN not found as receive attribute", interceptor.getReceiveSecurityAttributes().contains( - new SecurityConfig("ROLE_ADMIN"))); - assertTrue("ROLE_USER not found as receive attribute", interceptor.getReceiveSecurityAttributes().contains( - new SecurityConfig("ROLE_USER"))); - assertNull("Send security attribute were not null", interceptor.getSendSecurityAttributes()); + String beanName = "adminOrUserRequiredForReceive"; + messageChannel.setBeanName(beanName); + MessageChannel proxy = (MessageChannel) applicationContext.getAutowireCapableBeanFactory() + .applyBeanPostProcessorsAfterInitialization(messageChannel, beanName); + assertTrue("Channel was not proxied", AopUtils.isAopProxy(proxy)); + Advisor[] advisors = ((Advised) proxy).getAdvisors(); + assertEquals("Wrong number of interceptors", 1, advisors.length); + ChannelSecurityInterceptor interceptor = (ChannelSecurityInterceptor) advisors[0].getAdvice(); + ChannelAccessPolicy policy = this.retrievePolicyForPatternString(beanName, interceptor); + assertNotNull("Pattern '" + beanName + "' is not included in mappings", policy); + ConfigAttributeDefinition sendDefinition = policy.getConfigAttributeDefinitionForSend(); + ConfigAttributeDefinition receiveDefinition = policy.getConfigAttributeDefinitionForReceive(); + Collection receiveRoles = this.getRolesFromDefintion(receiveDefinition); + assertTrue("ROLE_ADMIN not found as receive attribute", receiveRoles.contains("ROLE_ADMIN")); + assertTrue("ROLE_USER not found as receive attribute", receiveRoles.contains("ROLE_USER")); + assertNull("Policy applies to send", sendDefinition); } @Test public void testAdminRequiredForSendAndReceive() { - applicationContext.getAutowireCapableBeanFactory().applyBeanPostProcessorsAfterInitialization(messageChannel, - "adminForSendAndReceive"); - assertEquals("Wrong count of interceptors ", 1, messageChannel.interceptors.size()); - SecurityEnforcingChannelInterceptor interceptor = (SecurityEnforcingChannelInterceptor) messageChannel.interceptors - .get(0); - assertTrue("ROLE_ADMIN not found as receive attribute", interceptor.getReceiveSecurityAttributes().contains( - new SecurityConfig("ROLE_ADMIN"))); - assertTrue("ROLE_USER not found as send attribute", interceptor.getSendSecurityAttributes().contains( - new SecurityConfig("ROLE_ADMIN"))); + String beanName = "adminRequiredForSendAndReceive"; + messageChannel.setBeanName(beanName); + MessageChannel proxy = (MessageChannel) applicationContext.getAutowireCapableBeanFactory() + .applyBeanPostProcessorsAfterInitialization(messageChannel, beanName); + assertTrue("Channel was not proxied", AopUtils.isAopProxy(proxy)); + Advisor[] advisors = ((Advised) proxy).getAdvisors(); + assertEquals("Wrong number of interceptors", 1, advisors.length); + ChannelSecurityInterceptor interceptor = (ChannelSecurityInterceptor) advisors[0].getAdvice(); + ChannelAccessPolicy policy = this.retrievePolicyForPatternString(beanName, interceptor); + assertNotNull("Pattern '" + beanName + "' is not included in mappings", policy); + ConfigAttributeDefinition sendDefinition = policy.getConfigAttributeDefinitionForSend(); + ConfigAttributeDefinition receiveDefinition = policy.getConfigAttributeDefinitionForReceive(); + assertNotNull("Pattern does not apply to 'send'", sendDefinition); + assertNotNull("Pattern does not apply to 'receive'", receiveDefinition); + Collection sendRoles = this.getRolesFromDefintion(sendDefinition); + Collection receiveRoles = this.getRolesFromDefintion(receiveDefinition); + assertTrue("ROLE_ADMIN not found in send attributes", sendRoles.contains("ROLE_ADMIN")); + assertTrue("ROLE_ADMIN not found in receive attributes", receiveRoles.contains("ROLE_ADMIN")); + } + + + @SuppressWarnings("unchecked") + private ChannelAccessPolicy retrievePolicyForPatternString(String patternString, ChannelSecurityInterceptor interceptor) { + DirectFieldAccessor accessor = new DirectFieldAccessor((ChannelInvocationDefinitionSource) interceptor.obtainObjectDefinitionSource()); + Map policies = (Map) accessor.getPropertyValue("patternMappings"); + for (Map.Entry entry : policies.entrySet()) { + if (entry.getKey().pattern().equals(patternString)) { + return entry.getValue(); + } + } + return null; + } + + @SuppressWarnings("unchecked") + private Collection getRolesFromDefintion(ConfigAttributeDefinition definition) { + Set roles = new HashSet(); + Collection configAttributes = definition.getConfigAttributes(); + for (Object next : configAttributes) { + ConfigAttribute attribute = (ConfigAttribute) next; + roles.add(attribute.getAttribute()); + } + return roles; } diff --git a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecurityPropagatingChannelsParserTests-noPropagationByDefaultContext.xml b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecurityPropagatingChannelsParserTests-noPropagationByDefaultContext.xml index 05b56af744..7e69006344 100644 --- a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecurityPropagatingChannelsParserTests-noPropagationByDefaultContext.xml +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecurityPropagatingChannelsParserTests-noPropagationByDefaultContext.xml @@ -1,6 +1,6 @@ diff --git a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/commonSecurityConfiguration.xml b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/commonSecurityConfiguration.xml index 38dcb8617a..af53461114 100644 --- a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/commonSecurityConfiguration.xml +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/commonSecurityConfiguration.xml @@ -11,6 +11,8 @@ + + diff --git a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/endpoint/EndpointSecurityIntegrationTests-context.xml b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/endpoint/EndpointSecurityIntegrationTests-context.xml index 94a4a54f0d..ba6b88dbcc 100644 --- a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/endpoint/EndpointSecurityIntegrationTests-context.xml +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/endpoint/EndpointSecurityIntegrationTests-context.xml @@ -1,6 +1,6 @@