From 352a3f6c297f3ca3eb7337e4211851a69c7d01f9 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Fri, 6 Jun 2008 00:05:07 +0000 Subject: [PATCH] Added support for secured channels and SecurityContext propagation within messages (INT-117). --- .../.classpath | 17 ++ .../.project | 17 ++ .../build.xml | 8 + .../ivy.xml | 30 ++++ ...yContextAssociatingHandlerInterceptor.java | 59 +++++++ ...yContextPropagatingChannelInterceptor.java | 58 +++++++ .../SecurityEnforcingChannelInterceptor.java | 102 ++++++++++++ .../IntegrationSecurityNamespaceHandler.java | 33 ++++ .../security/config/SecureChannelsParser.java | 56 +++++++ .../security/config/SecuredParser.java | 84 ++++++++++ .../SecurityPropagatingBeanPostProcessor.java | 105 ++++++++++++ ...tingBeanPostProcessorDefinitionHelper.java | 91 +++++++++++ .../META-INF/spring-integration.parsers | 2 + ...extAssociatingHandlerInterceptorTests.java | 147 +++++++++++++++++ ...extPropagatingChannelInterceptorTests.java | 133 ++++++++++++++++ ...urityEnforcingChannelInterceptorTests.java | 150 ++++++++++++++++++ ...serTests-noPropagationByDefaultContext.xml | 22 +++ ...sParserTests-propagateByDefaultContext.xml | 28 ++++ .../config/SecureChannelsParserTests.java | 103 ++++++++++++ .../config/SecuredParserTests-context.xml | 30 ++++ .../security/config/SecuredParserTests.java | 137 ++++++++++++++++ .../config/commonSecurityConfiguration.xml | 30 ++++ .../src/test/resources/log4j.properties | 8 + .../template.mf | 11 ++ 24 files changed, 1461 insertions(+) create mode 100644 org.springframework.integration.security/.classpath create mode 100644 org.springframework.integration.security/.project create mode 100644 org.springframework.integration.security/build.xml create mode 100644 org.springframework.integration.security/ivy.xml create mode 100644 org.springframework.integration.security/src/main/java/org/springframework/integration/security/SecurityContextAssociatingHandlerInterceptor.java create mode 100644 org.springframework.integration.security/src/main/java/org/springframework/integration/security/SecurityContextPropagatingChannelInterceptor.java create mode 100644 org.springframework.integration.security/src/main/java/org/springframework/integration/security/SecurityEnforcingChannelInterceptor.java create mode 100644 org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/IntegrationSecurityNamespaceHandler.java create mode 100644 org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecureChannelsParser.java create mode 100644 org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecuredParser.java create mode 100644 org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecurityPropagatingBeanPostProcessor.java create mode 100644 org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecurityPropagatingBeanPostProcessorDefinitionHelper.java create mode 100644 org.springframework.integration.security/src/main/resources/META-INF/spring-integration.parsers create mode 100644 org.springframework.integration.security/src/test/java/org/springframework/integration/security/SecurityContextAssociatingHandlerInterceptorTests.java create mode 100644 org.springframework.integration.security/src/test/java/org/springframework/integration/security/SecurityContextPropagatingChannelInterceptorTests.java create mode 100644 org.springframework.integration.security/src/test/java/org/springframework/integration/security/SecurityEnforcingChannelInterceptorTests.java create mode 100644 org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecureChannelsParserTests-noPropagationByDefaultContext.xml create mode 100644 org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecureChannelsParserTests-propagateByDefaultContext.xml create mode 100644 org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecureChannelsParserTests.java create mode 100644 org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecuredParserTests-context.xml create mode 100644 org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecuredParserTests.java create mode 100644 org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/commonSecurityConfiguration.xml create mode 100644 org.springframework.integration.security/src/test/resources/log4j.properties create mode 100644 org.springframework.integration.security/template.mf diff --git a/org.springframework.integration.security/.classpath b/org.springframework.integration.security/.classpath new file mode 100644 index 0000000000..b6477a8014 --- /dev/null +++ b/org.springframework.integration.security/.classpath @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/org.springframework.integration.security/.project b/org.springframework.integration.security/.project new file mode 100644 index 0000000000..699b304075 --- /dev/null +++ b/org.springframework.integration.security/.project @@ -0,0 +1,17 @@ + + + org.springframework.integration.security + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/org.springframework.integration.security/build.xml b/org.springframework.integration.security/build.xml new file mode 100644 index 0000000000..f67f70c9f5 --- /dev/null +++ b/org.springframework.integration.security/build.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.springframework.integration.security/ivy.xml b/org.springframework.integration.security/ivy.xml new file mode 100644 index 0000000000..10b8e7fb45 --- /dev/null +++ b/org.springframework.integration.security/ivy.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/SecurityContextAssociatingHandlerInterceptor.java b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/SecurityContextAssociatingHandlerInterceptor.java new file mode 100644 index 0000000000..c4e17d4f62 --- /dev/null +++ b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/SecurityContextAssociatingHandlerInterceptor.java @@ -0,0 +1,59 @@ +/* + * 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; + +import org.springframework.integration.handler.InterceptingMessageHandler; +import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.message.Message; +import org.springframework.security.context.SecurityContext; +import org.springframework.security.context.SecurityContextHolder; + +/** + * Associates the {@link SecurityContext} propagated in the message header + * with the thread executing the handle call to a {@link MessageHandler}. + * + * @author Jonas Partner + */ +public class SecurityContextAssociatingHandlerInterceptor extends InterceptingMessageHandler { + + public SecurityContextAssociatingHandlerInterceptor(MessageHandler target) { + super(target); + } + + + @Override + public Message handle(Message message, MessageHandler target) { + if (message.getHeader().getAttributeNames().contains( + SecurityContextPropagatingChannelInterceptor.SECURITY_CONTEXT_HEADER_ATTRIBUTE)) { + return handleInSecurityContext(message, target); + } + return target.handle(message); + } + + private Message handleInSecurityContext(Message message, MessageHandler target) { + SecurityContext context = (SecurityContext) message.getHeader().getAttribute( + SecurityContextPropagatingChannelInterceptor.SECURITY_CONTEXT_HEADER_ATTRIBUTE); + SecurityContextHolder.setContext(context); + try{ + return target.handle(message); + } + finally { + SecurityContextHolder.clearContext(); + } + } + +} diff --git a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/SecurityContextPropagatingChannelInterceptor.java b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/SecurityContextPropagatingChannelInterceptor.java new file mode 100644 index 0000000000..8f537afd78 --- /dev/null +++ b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/SecurityContextPropagatingChannelInterceptor.java @@ -0,0 +1,58 @@ +/* + * 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; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.channel.interceptor.ChannelInterceptorAdapter; +import org.springframework.integration.message.Message; +import org.springframework.security.context.SecurityContext; +import org.springframework.security.context.SecurityContextHolder; + +/** + * Propagates the {@ link SecurityContext} associated with the current + * thread (if any) by adding it to the header of sent messages. + * + * @author Jonas Partner + */ +public class SecurityContextPropagatingChannelInterceptor extends ChannelInterceptorAdapter { + + public static final String SECURITY_CONTEXT_HEADER_ATTRIBUTE = "SPRING_SECURITY_CONTEXT"; + + + private final Log logger = LogFactory.getLog(this.getClass()); + + + @Override + public boolean preSend(Message message, MessageChannel channel) { + this.setSecurityContextAttribute(message); + return true; + } + + protected void setSecurityContextAttribute(Message message){ + SecurityContext securityContext = SecurityContextHolder.getContext(); + if (securityContext.getAuthentication() != null) { + message.getHeader().setAttribute(SECURITY_CONTEXT_HEADER_ATTRIBUTE, securityContext); + } + else if (logger.isInfoEnabled()) { + logger.info("No security context found"); + } + } + +} diff --git a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/SecurityEnforcingChannelInterceptor.java b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/SecurityEnforcingChannelInterceptor.java new file mode 100644 index 0000000000..1df1ff8c78 --- /dev/null +++ b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/SecurityEnforcingChannelInterceptor.java @@ -0,0 +1,102 @@ +/* + * 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; + +import org.springframework.integration.channel.AbstractMessageChannel; +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.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 on the {@ MessageChannel}. + * + * @author Jonas Partner + */ +public class SecurityEnforcingChannelInterceptor extends ChannelInterceptorAdapter{ + + private final AccessDecisionManager accessDecisionManger; + + private final String channelName; + + private volatile ConfigAttributeDefinition sendSecurityAttributes; + + private volatile ConfigAttributeDefinition receiveSecurityAttributes; + + + public SecurityEnforcingChannelInterceptor(AccessDecisionManager accessDecisionManager, AbstractMessageChannel channelToSecure) { + Assert.notNull(accessDecisionManager, "AccessDecisionManager must not be null"); + Assert.notNull(channelToSecure, "channel to secure must not be null"); + this.accessDecisionManger = accessDecisionManager; + this.channelName = channelToSecure.getName(); + channelToSecure.addInterceptor(this); + } + + + 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 boolean preSend(Message message, MessageChannel channel) { + this.checkSend(channel); + return true; + } + + @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) { + this.accessDecisionManger.decide(SecurityContextHolder.getContext().getAuthentication(), + messageChannel, securityAttributes); + } + } + + @Override + public String toString() { + return getClass().getName() + " for channel '" + this.channelName + "'"; + } + +} diff --git a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/IntegrationSecurityNamespaceHandler.java b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/IntegrationSecurityNamespaceHandler.java new file mode 100644 index 0000000000..0aa9efeb44 --- /dev/null +++ b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/IntegrationSecurityNamespaceHandler.java @@ -0,0 +1,33 @@ +/* + * 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 org.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * Namespace handler for the security namespace. + * + * @author Jonas Partner + */ +public class IntegrationSecurityNamespaceHandler extends NamespaceHandlerSupport { + + public void init() { + registerBeanDefinitionParser("secured", new SecuredParser()); + registerBeanDefinitionParser("secure-channels", new SecureChannelsParser()); + } + +} diff --git a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecureChannelsParser.java b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecureChannelsParser.java new file mode 100644 index 0000000000..5ae74315b9 --- /dev/null +++ b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecureChannelsParser.java @@ -0,0 +1,56 @@ +/* + * 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 org.w3c.dom.Element; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.security.context.SecurityContext; +import org.springframework.util.StringUtils; + +/** + * Interprets the <secure-channels> element which controls default + * {@link SecurityContext} propagation behaviour. + * + * @author Jonas Partner + */ +public class SecureChannelsParser extends AbstractSingleBeanDefinitionParser { + + @Override + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + builder.getBeanDefinition().setAbstract(true); + String propagation = element.getAttribute("propagate"); + boolean propagateByDefault = true; + if (StringUtils.hasText(propagation)) { + propagateByDefault = Boolean.parseBoolean(propagation); + } + if (propagateByDefault) { + SecurityPropagatingBeanPostProcessorDefinitionHelper.setPropagationDefault(true, parserContext); + } + } + + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + return "internal.integration.SecureChannels"; + } + +} diff --git a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecuredParser.java b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecuredParser.java new file mode 100644 index 0000000000..9f67858142 --- /dev/null +++ b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecuredParser.java @@ -0,0 +1,84 @@ +/* + * 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 org.w3c.dom.Element; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.security.SecurityEnforcingChannelInterceptor; +import org.springframework.security.ConfigAttributeDefinition; +import org.springframework.security.context.SecurityContext; +import org.springframework.util.StringUtils; + +/** + * 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. + * + * @author Jonas Partner + */ +public class SecuredParser extends AbstractSingleBeanDefinitionParser { + + @Override + protected boolean shouldGenerateId() { + return false; + } + + @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"); + String propagation = element.getAttribute("propagate"); + String channelName = ((Element)element.getParentNode()).getAttribute("id"); + if (channelName == null) { + parserContext.getReaderContext().error("The secured element requires a channel parent id.", element); + } + builder.getBeanDefinition().setBeanClass(SecurityEnforcingChannelInterceptor.class); + if (!StringUtils.hasText(accessDecisionManager)) { + accessDecisionManager = "accessDecisionManager"; + } + builder.addConstructorArgReference(accessDecisionManager); + builder.addConstructorArgReference(channelName); + if (StringUtils.hasText(sendAccess)) { + ConfigAttributeDefinition sendDefinition = new ConfigAttributeDefinition(sendAccess); + builder.addPropertyValue("sendSecurityAttributes", sendDefinition); + } + if (StringUtils.hasText(receiveAccess)) { + ConfigAttributeDefinition receiveDefinition = new ConfigAttributeDefinition(receiveAccess); + builder.addPropertyValue("receiveSecurityAttributes", receiveDefinition); + } + boolean propagationValue = true; + if (StringUtils.hasText(propagation)) { + propagationValue = Boolean.parseBoolean(propagation); + } + if (propagationValue) { + SecurityPropagatingBeanPostProcessorDefinitionHelper.addToIncludeChannelList(channelName, parserContext); + } + else { + SecurityPropagatingBeanPostProcessorDefinitionHelper.addToExcludeChannelList(channelName, parserContext); + } + } + +} diff --git a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecurityPropagatingBeanPostProcessor.java b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecurityPropagatingBeanPostProcessor.java new file mode 100644 index 0000000000..035d0512b0 --- /dev/null +++ b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecurityPropagatingBeanPostProcessor.java @@ -0,0 +1,105 @@ +/* + * 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.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.Ordered; +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.security.SecurityContextPropagatingChannelInterceptor; + +/** + * Post processes channels applying appropriate propagation behaviour. If + * default propagation is specified with a secure-channels tag, that will + * be applied in the absence of a secured tag for the channel. If the + * secured tag is specified, it will always determine propagation behaviour. + * + * @author Jonas Partner + */ +public class SecurityPropagatingBeanPostProcessor implements BeanPostProcessor, Ordered { + + protected static final String SECURITY_PROPAGATING_BEAN_POST_PROCESSOR_NAME = SecurityPropagatingBeanPostProcessor.class.getName(); + + + private final SecurityContextPropagatingChannelInterceptor interceptor = + new SecurityContextPropagatingChannelInterceptor(); + + private boolean propagateByDefault; + + private final Log logger = LogFactory.getLog(this.getClass()); + + private List channelsToInclude = new ArrayList(); + + private List channelsToExclude = new ArrayList(); + + + public boolean isPropagateByDefault() { + return this.propagateByDefault; + } + + public void setPropagateByDefault(boolean propagateByDefault) { + this.propagateByDefault = propagateByDefault; + } + + public List getChannelsToInclude() { + return this.channelsToInclude; + } + + public void setChannelsToInclude(List channelsToInclude) { + this.channelsToInclude = channelsToInclude; + } + + public List getChannelsToExclude() { + return this.channelsToExclude; + } + + public void setChannelsToExclude(List channelsToExclude) { + this.channelsToExclude = channelsToExclude; + } + + public int getOrder() { + return 0; + } + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (AbstractMessageChannel.class.isAssignableFrom(bean.getClass())) { + AbstractMessageChannel channel = (AbstractMessageChannel) bean; + if(this.channelsToInclude.contains(beanName) || + (this.propagateByDefault && !this.channelsToExclude.contains(beanName))) { + channel.addInterceptor(this.interceptor); + if (logger.isDebugEnabled()) { + logger.debug("Channel '" + beanName + "' will propagate a SecurityContext."); + } + } + else if (logger.isDebugEnabled()) { + logger.debug("Channel '" + beanName + "' is not configured to propagate a SecurityContext."); + } + } + return bean; + } + +} diff --git a/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecurityPropagatingBeanPostProcessorDefinitionHelper.java b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecurityPropagatingBeanPostProcessorDefinitionHelper.java new file mode 100644 index 0000000000..9a5a50d3f3 --- /dev/null +++ b/org.springframework.integration.security/src/main/java/org/springframework/integration/security/config/SecurityPropagatingBeanPostProcessorDefinitionHelper.java @@ -0,0 +1,91 @@ +/* + * 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.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanNameReference; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.context.ApplicationContext; + +/** + * Helper to configure the per {@link ApplicationContext} + * {@link SecurityPropagatingBeanPostProcessor} which determines + * {@link SecurityContext} propagation. + * + * @author Jonas Partner + */ +public class SecurityPropagatingBeanPostProcessorDefinitionHelper { + + private static final String CHANNELS_TO_INCLUDE = "channelsToInclude"; + + private static final String CHANNELS_TO_EXCLUDE = "channelsToExclude"; + + private static final String PROPAGATE_BY_DEFAULT = "propagateByDefault"; + + + public static void setPropagationDefault(boolean valueForPropagationDefault, ParserContext context) { + BeanDefinition beanDefintion = getOrCreateSecurityPropagatingBeanPostProcessor(context); + beanDefintion.getPropertyValues().addPropertyValue(PROPAGATE_BY_DEFAULT, Boolean.valueOf(valueForPropagationDefault)); + } + + @SuppressWarnings("unchecked") + public static void addToExcludeChannelList(String channelName, ParserContext context) { + BeanDefinition beanDefintion = getOrCreateSecurityPropagatingBeanPostProcessor(context); + List channelsToExclude; + if (beanDefintion.getPropertyValues().contains(CHANNELS_TO_EXCLUDE)) { + channelsToExclude = (List) beanDefintion.getPropertyValues().getPropertyValue(CHANNELS_TO_EXCLUDE).getValue(); + } + else { + channelsToExclude = new ArrayList(); + beanDefintion.getPropertyValues().addPropertyValue(CHANNELS_TO_EXCLUDE, channelsToExclude); + } + channelsToExclude.add(channelName); + } + + @SuppressWarnings("unchecked") + public static void addToIncludeChannelList(String channelName, ParserContext context){ + BeanDefinition beanDefintion = getOrCreateSecurityPropagatingBeanPostProcessor(context); + List channelsToExclude; + if (beanDefintion.getPropertyValues().contains(CHANNELS_TO_INCLUDE)) { + channelsToExclude = (List) beanDefintion.getPropertyValues().getPropertyValue(CHANNELS_TO_INCLUDE).getValue(); + } + else { + channelsToExclude = new ArrayList(); + beanDefintion.getPropertyValues().addPropertyValue(CHANNELS_TO_INCLUDE,channelsToExclude); + } + channelsToExclude.add(channelName); + } + + private static BeanDefinition getOrCreateSecurityPropagatingBeanPostProcessor(ParserContext context) { + BeanDefinition beanDefinition = null; + String postProcessorBeanName = SecurityPropagatingBeanPostProcessor.SECURITY_PROPAGATING_BEAN_POST_PROCESSOR_NAME; + if (context.getRegistry().containsBeanDefinition(postProcessorBeanName)) { + beanDefinition = context.getRegistry().getBeanDefinition(postProcessorBeanName); + } + if (beanDefinition == null) { + beanDefinition = new RootBeanDefinition(SecurityPropagatingBeanPostProcessor.class); + context.registerBeanComponent(new BeanComponentDefinition(beanDefinition, postProcessorBeanName)); + } + return beanDefinition; + } + +} diff --git a/org.springframework.integration.security/src/main/resources/META-INF/spring-integration.parsers b/org.springframework.integration.security/src/main/resources/META-INF/spring-integration.parsers new file mode 100644 index 0000000000..2269b399d6 --- /dev/null +++ b/org.springframework.integration.security/src/main/resources/META-INF/spring-integration.parsers @@ -0,0 +1,2 @@ +secured=org.springframework.integration.security.config.SecuredParser +secure-channels=org.springframework.integration.security.config.SecureChannelsParser \ No newline at end of file diff --git a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/SecurityContextAssociatingHandlerInterceptorTests.java b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/SecurityContextAssociatingHandlerInterceptorTests.java new file mode 100644 index 0000000000..1a84ee9a87 --- /dev/null +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/SecurityContextAssociatingHandlerInterceptorTests.java @@ -0,0 +1,147 @@ +/* + * 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.StringMessage; +import org.springframework.security.AccessDeniedException; +import org.springframework.security.Authentication; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.context.SecurityContext; +import org.springframework.security.context.SecurityContextHolder; + +/** + * @author Jonas Partner + */ +public class SecurityContextAssociatingHandlerInterceptorTests { + + @Test + public void testMessageWithSecurityContext() { + final StubSecurityContext securityContext = new StubSecurityContext(); + StringMessage message = new StringMessage("test"); + message.getHeader().setAttribute( + SecurityContextPropagatingChannelInterceptor.SECURITY_CONTEXT_HEADER_ATTRIBUTE, securityContext); + MessageHandler handler = new MessageHandler() { + public Message handle(Message message) { + SecurityContext associatedContext = SecurityContextHolder.getContext(); + assertEquals("Wrong security context", securityContext, associatedContext); + return null; + } + }; + SecurityContextAssociatingHandlerInterceptor associatingInterceptor = + new SecurityContextAssociatingHandlerInterceptor(handler); + associatingInterceptor.handle(message); + assertNull("Security context still present after handler returned", + SecurityContextHolder.getContext().getAuthentication()); + } + + @Test(expected = AccessDeniedException.class) + public void testForSecurityLeakageIfHandlerThrowsException() { + final StubSecurityContext securityContext = new StubSecurityContext(); + StringMessage message = new StringMessage("test"); + message.getHeader().setAttribute( + SecurityContextPropagatingChannelInterceptor.SECURITY_CONTEXT_HEADER_ATTRIBUTE, securityContext); + MessageHandler handler = new MessageHandler() { + public Message handle(Message message) { + SecurityContext associatedContext = SecurityContextHolder.getContext(); + assertEquals("Wrong security context", securityContext, associatedContext); + throw new AccessDeniedException("Not allowed"); + } + }; + SecurityContextAssociatingHandlerInterceptor associatingInterceptor = + new SecurityContextAssociatingHandlerInterceptor(handler); + try { + associatingInterceptor.handle(message); + } + finally { + assertNull("Security context still present after handler threw exception", + SecurityContextHolder.getContext().getAuthentication()); + } + } + + @Test + public void testMessageWithoutSecurityContext() { + final StubSecurityContext securityContext = new StubSecurityContext(); + StringMessage message = new StringMessage("test"); + MessageHandler handler = new MessageHandler() { + public Message handle(Message message) { + SecurityContext associatedContext = SecurityContextHolder.getContext(); + assertNotSame("Wrong security context", securityContext, associatedContext); + return null; + } + }; + SecurityContextAssociatingHandlerInterceptor associatingInterceptor = + new SecurityContextAssociatingHandlerInterceptor(handler); + associatingInterceptor.handle(message); + assertNull("Security context still present after handler returned", + SecurityContextHolder.getContext().getAuthentication()); + } + + + @SuppressWarnings("serial") + private static class StubSecurityContext implements SecurityContext { + + StubAuthentication stubAuthentication = new StubAuthentication(); + + public Authentication getAuthentication() { + return stubAuthentication; + } + + public void setAuthentication(Authentication authentication) { + } + } + + + @SuppressWarnings("serial") + private static class StubAuthentication implements Authentication { + + public GrantedAuthority[] getAuthorities() { + return null; + } + + public Object getCredentials() { + return null; + } + + public Object getDetails() { + return null; + } + + public Object getPrincipal() { + return null; + } + + public boolean isAuthenticated() { + return false; + } + + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + } + + public String getName() { + return null; + } + } + +} diff --git a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/SecurityContextPropagatingChannelInterceptorTests.java b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/SecurityContextPropagatingChannelInterceptorTests.java new file mode 100644 index 0000000000..4f3c9a8713 --- /dev/null +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/SecurityContextPropagatingChannelInterceptorTests.java @@ -0,0 +1,133 @@ +/* + * 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.message.MessageHeader; +import org.springframework.integration.message.StringMessage; +import org.springframework.security.Authentication; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.context.SecurityContext; +import org.springframework.security.context.SecurityContextHolder; + +/** + * @author Jonas Partner + */ +public class SecurityContextPropagatingChannelInterceptorTests { + + private QueueChannel channel; + + private SecurityContextPropagatingChannelInterceptor securityPropogatingChannelInterceptor; + + private StubSecurityContext securityContext; + + + @Before + public void setUp() { + this.channel = new QueueChannel(); + this.securityPropogatingChannelInterceptor = new SecurityContextPropagatingChannelInterceptor(); + this.channel.addInterceptor(securityPropogatingChannelInterceptor); + this.securityContext = new StubSecurityContext(); + } + + @After + public void tearDown(){ + SecurityContextHolder.clearContext(); + } + + + @Test + public void testPropogationWhenSecurityContextExists() { + this.associateContextWithThread(); + StringMessage message = new StringMessage("test"); + this.channel.send(message); + message = (StringMessage) channel.receive(0); + MessageHeader header = message.getHeader(); + assertTrue("No security context attribute found in header.", + header.getAttributeNames().contains(SecurityContextPropagatingChannelInterceptor.SECURITY_CONTEXT_HEADER_ATTRIBUTE)); + SecurityContext contextFromHeader = (SecurityContext) header.getAttribute( + SecurityContextPropagatingChannelInterceptor.SECURITY_CONTEXT_HEADER_ATTRIBUTE); + assertEquals("Incorrect security context in message header.", securityContext, contextFromHeader); + } + + @Test + public void testHeaderNotSetWhenNoSecurityContextExists() { + StringMessage message = new StringMessage("test"); + channel.send(message); + message = (StringMessage) channel.receive(0); + MessageHeader header = message.getHeader(); + assertFalse("Security context header found when no security context existed.", + header.getAttributeNames().contains(SecurityContextPropagatingChannelInterceptor.SECURITY_CONTEXT_HEADER_ATTRIBUTE)); + } + + + private void associateContextWithThread(){ + SecurityContextHolder.setContext(securityContext); + } + + + @SuppressWarnings("serial") + private static class StubSecurityContext implements SecurityContext{ + + private Authentication authentication = new Authentication() { + + public GrantedAuthority[] getAuthorities() { + return null; + } + + public Object getCredentials() { + return null; + } + + public Object getDetails() { + return null; + } + + public Object getPrincipal() { + return null; + } + + public boolean isAuthenticated() { + return false; + } + + public void setAuthenticated(boolean isAuthenticated) + throws IllegalArgumentException { + } + + public String getName() { + return null; + } + }; + + public Authentication getAuthentication() { + return authentication; + } + + public void setAuthentication(Authentication authentication) { + } + } + +} diff --git a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/SecurityEnforcingChannelInterceptorTests.java b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/SecurityEnforcingChannelInterceptorTests.java new file mode 100644 index 0000000000..b26829e313 --- /dev/null +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/SecurityEnforcingChannelInterceptorTests.java @@ -0,0 +1,150 @@ +/* + * 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; + +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.message.StringMessage; +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.InsufficientAuthenticationException; + +/** + * @author Jonas Partner + */ +public class SecurityEnforcingChannelInterceptorTests { + + private QueueChannel channel; + + private SecurityEnforcingChannelInterceptor securityChannelInterceptor; + + + @Before + public void setUp() { + channel = new QueueChannel(); + } + + + @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); + + } + + + 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/SecureChannelsParserTests-noPropagationByDefaultContext.xml b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecureChannelsParserTests-noPropagationByDefaultContext.xml new file mode 100644 index 0000000000..5ec08169cb --- /dev/null +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecureChannelsParserTests-noPropagationByDefaultContext.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecureChannelsParserTests-propagateByDefaultContext.xml b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecureChannelsParserTests-propagateByDefaultContext.xml new file mode 100644 index 0000000000..7cafb33911 --- /dev/null +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecureChannelsParserTests-propagateByDefaultContext.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecureChannelsParserTests.java b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecureChannelsParserTests.java new file mode 100644 index 0000000000..0e0d03d2ca --- /dev/null +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecureChannelsParserTests.java @@ -0,0 +1,103 @@ +/* + * 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 static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.After; +import org.junit.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.StringMessage; +import org.springframework.security.context.SecurityContext; +import org.springframework.security.context.SecurityContextHolder; +import org.springframework.security.context.SecurityContextImpl; +import org.springframework.security.providers.UsernamePasswordAuthenticationToken; + +/** + * @author Jonas Partner + */ +public class SecureChannelsParserTests { + + private ClassPathXmlApplicationContext applicationContext; + + @Autowired + @Qualifier("propagationDefault") + MessageChannel propagationDefault; + + @Autowired + @Qualifier("excludedFromPropagation") + MessageChannel excludedFromPropagation; + + + @After + public void tearDown() { + if (applicationContext != null) { + applicationContext.close(); + } + } + + + @Test + public void testPropagationByDefault() { + loadApplicationContext(this.getClass().getSimpleName() + "-propagateByDefaultContext.xml"); + assertTrue("security context did not propagate by setting message bus level default", + channelPropagatesSecurityContext(propagationDefault)); + } + + @Test + public void testNoPropagationOnExcludedChannel() { + loadApplicationContext(this.getClass().getSimpleName() + "-propagateByDefaultContext.xml"); + assertFalse("security context propagated when channel was explicitly excluded", + channelPropagatesSecurityContext(excludedFromPropagation)); + } + + @Test + public void testNoPropagationWithNoDefaultPropagation() { + loadApplicationContext(this.getClass().getSimpleName() + "-noPropagationByDefaultContext.xml"); + assertFalse("security context propagated when channel default was false and no secured tag present", + channelPropagatesSecurityContext(propagationDefault)); + } + + + private boolean channelPropagatesSecurityContext(MessageChannel channel) { + login("bob", "bobspassword"); + channel.send(new StringMessage("testMessage")); + SecurityContext context = (SecurityContext) + channel.receive(-1).getHeader().getAttribute("SPRING_SECURITY_CONTEXT"); + return context != null; + } + + private void login(String username, String password) { + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password); + SecurityContext context = new SecurityContextImpl(); + context.setAuthentication(authToken); + SecurityContextHolder.setContext(context); + } + + private void loadApplicationContext(String resource) { + this.applicationContext = new ClassPathXmlApplicationContext(resource, this.getClass()); + AutowireCapableBeanFactory beanFactory = this.applicationContext.getAutowireCapableBeanFactory(); + beanFactory.autowireBean(this); + } + +} diff --git a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecuredParserTests-context.xml b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecuredParserTests-context.xml new file mode 100644 index 0000000000..63c8a9a5d4 --- /dev/null +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecuredParserTests-context.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecuredParserTests.java b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecuredParserTests.java new file mode 100644 index 0000000000..a9f0d54b56 --- /dev/null +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/SecuredParserTests.java @@ -0,0 +1,137 @@ +/* + * 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 static org.junit.Assert.assertNotNull; + +import org.junit.After; +import org.junit.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.integration.channel.MessageChannel; +import org.springframework.integration.message.StringMessage; +import org.springframework.security.AccessDeniedException; +import org.springframework.security.context.SecurityContext; +import org.springframework.security.context.SecurityContextHolder; +import org.springframework.security.context.SecurityContextImpl; +import org.springframework.security.providers.AuthenticationProvider; +import org.springframework.security.providers.UsernamePasswordAuthenticationToken; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; + +/** + * @author Jonas Partner + */ +@ContextConfiguration +public class SecuredParserTests extends AbstractJUnit4SpringContextTests{ + + @Autowired + private AuthenticationProvider provider; + + @Autowired + @Qualifier("unsecured") + public MessageChannel unsecuredChannel; + + @Autowired + @Qualifier("adminRequiredForSend") + public MessageChannel adminRequiredForSend; + + @Autowired + @Qualifier("adminRequiredForReceive") + public MessageChannel adminRequiredForReceive; + + @Autowired + @Qualifier("adminRequiredForSendAndReceive") + public MessageChannel adminRequiredForSendAndReceive; + + + @After + public void tearDown() { + SecurityContextHolder.clearContext(); + } + + + @Test + public void testNoSecurityRestrictionsOnChannel() { + unsecuredChannel.send(new StringMessage("testUnsecured")); + assertNotNull("Message not received ", unsecuredChannel.receive(0)); + } + + @Test + public void testAdminRequiredForSendWithAccessGranted() { + login("jimi", "jimispassword"); + adminRequiredForSend.send(new StringMessage("testmessage")); + SecurityContextHolder.clearContext(); + assertNotNull("Message not received", adminRequiredForSend.receive(0)); + } + + @Test(expected=AccessDeniedException.class) + public void testAdminRequiredForSendWithAccessDenied() { + login("bob", "bobspassword"); + adminRequiredForSend.send(new StringMessage("testmessage")); + } + + @Test + public void testAdminRequiredForReceiveWithAccessGranted(){ + adminRequiredForReceive.send(new StringMessage("testmessage")); + login("jimi", "jimispassword"); + assertNotNull("Message not received", adminRequiredForReceive.receive(0)); + } + + @Test(expected=AccessDeniedException.class) + public void testAdminRequiredForReceiveWithAccessDenied() { + adminRequiredForReceive.send(new StringMessage("testmessage")); + login("bob", "bobspassword"); + adminRequiredForReceive.receive(0); + } + + @Test + public void testAdminRequiredForSendAndReceiveWithSendAccessGranted() { + login("jimi", "jimispassword"); + adminRequiredForSendAndReceive.send(new StringMessage("test")); + } + + @Test(expected=AccessDeniedException.class) + public void testAdminRequiredForSendAndReceiveWithSendAccessDenied() { + login("bob", "bobspassword"); + adminRequiredForSendAndReceive.send(new StringMessage("test")); + } + + @Test + public void testAdminRequiredForSendAndReceiveWithReceiveAccessGranted() { + login("jimi", "jimispassword"); + adminRequiredForSendAndReceive.send(new StringMessage("test")); + assertNotNull("Message not received", adminRequiredForSendAndReceive.receive(0)); + } + + @Test(expected=AccessDeniedException.class) + public void testAdminRequiredForSendAndReceiveWithReceiveAccessDenied() { + login("bob","bobspassword"); + adminRequiredForSendAndReceive.receive(0); + } + + + private void login(String username, String password) { + UsernamePasswordAuthenticationToken authToken = (UsernamePasswordAuthenticationToken) + provider.authenticate(new UsernamePasswordAuthenticationToken(username, password)); + SecurityContext context = new SecurityContextImpl(); + context.setAuthentication(authToken); + SecurityContextHolder.setContext(context); + } + +} 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 new file mode 100644 index 0000000000..38dcb8617a --- /dev/null +++ b/org.springframework.integration.security/src/test/java/org/springframework/integration/security/config/commonSecurityConfiguration.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.integration.security/src/test/resources/log4j.properties b/org.springframework.integration.security/src/test/resources/log4j.properties new file mode 100644 index 0000000000..c9354151a2 --- /dev/null +++ b/org.springframework.integration.security/src/test/resources/log4j.properties @@ -0,0 +1,8 @@ +log4j.rootCategory=WARN, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%c{1}: %m%n + +log4j.category.org.springframework.integration.security=DEBUG +log4j.category.org.springframework.integration=INFO diff --git a/org.springframework.integration.security/template.mf b/org.springframework.integration.security/template.mf new file mode 100644 index 0000000000..764bb18c03 --- /dev/null +++ b/org.springframework.integration.security/template.mf @@ -0,0 +1,11 @@ +Bundle-SymbolicName: org.springframework.integration.security +Bundle-Name: Spring Integration Security Support +Bundle-Vendor: SpringSource +Bundle-ManifestVersion: 2 +Import-Template: + org.springframework.*;version="[2.5.4.A, 3.0.0)", + org.springframework.integration.*;version="[1.0.0, 1.0.0]", + org.apache.commons.logging;version="[1.1.1, 2.0.0)" +Unversioned-Imports: + org.w3c.dom +