invocation) {
+ return new MessageSecurityExpressionRoot(authentication,invocation);
+ }
+}
diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactory.java b/messaging/src/main/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactory.java
new file mode 100644
index 0000000000..2b0047b097
--- /dev/null
+++ b/messaging/src/main/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactory.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-2014 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.security.messaging.access.expression;
+
+import org.springframework.expression.Expression;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.messaging.access.intercept.DefaultMessageSecurityMetadataSource;
+import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource;
+import org.springframework.security.messaging.util.matcher.MessageMatcher;
+import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A class used to create a {@link MessageSecurityMetadataSource} that uses {@link MessageMatcher} mapped to Spring
+ * Expressions.
+ *
+ * @since 4.0
+ * @author Rob Winch
+ */
+public final class ExpressionBasedMessageSecurityMetadataSourceFactory {
+
+ /**
+ * Create a {@link MessageSecurityMetadataSource} that uses {@link MessageMatcher} mapped to Spring
+ * Expressions. Each entry is considered in order and only the first match is used.
+ *
+ * For example:
+ *
+ *
+ * LinkedHashMap matcherToExpression = new LinkedHashMap();
+ * matcherToExpression.put(new SimDestinationMessageMatcher("/public/**"), "permitAll");
+ * matcherToExpression.put(new SimDestinationMessageMatcher("/admin/**"), "hasRole('ROLE_ADMIN')");
+ * matcherToExpression.put(new SimDestinationMessageMatcher("/**"), "authenticated");
+ *
+ * MessageSecurityMetadataSource metadataSource = createExpressionMessageMetadataSource(matcherToExpression);
+ *
+ *
+ *
+ * If our destination is "/public/hello", it would match on "/public/**" and on "/**". However, only "/public/**"
+ * would be used since it is the first entry. That means that a destination of "/public/hello" will be mapped to
+ * "permitAll".
+ *
+ *
+ *
+ * For a complete listing of expressions see {@link MessageSecurityExpressionRoot}
+ *
+ *
+ * @param matcherToExpression an ordered mapping of {@link MessageMatcher} to Strings that are turned into an
+ * Expression using {@link DefaultMessageSecurityExpressionHandler#getExpressionParser()}
+ * @return the {@link MessageSecurityMetadataSource} to use. Cannot be null.
+ */
+ public static MessageSecurityMetadataSource createExpressionMessageMetadataSource(LinkedHashMap,String> matcherToExpression) {
+ DefaultMessageSecurityExpressionHandler handler = new DefaultMessageSecurityExpressionHandler();
+
+ LinkedHashMap, Collection> matcherToAttrs = new LinkedHashMap, Collection>();
+
+ for(Map.Entry,String> entry : matcherToExpression.entrySet()) {
+ MessageMatcher> matcher = entry.getKey();
+ String rawExpression = entry.getValue();
+ Expression expression = handler.getExpressionParser().parseExpression(rawExpression);
+ ConfigAttribute attribute = new MessageExpressionConfigAttribute(expression);
+ matcherToAttrs.put(matcher, Arrays.asList(attribute));
+ }
+ return new DefaultMessageSecurityMetadataSource(matcherToAttrs);
+ }
+
+ private ExpressionBasedMessageSecurityMetadataSourceFactory() {}
+}
\ No newline at end of file
diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttribute.java b/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttribute.java
new file mode 100644
index 0000000000..b80dc50107
--- /dev/null
+++ b/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttribute.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2014 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.security.messaging.access.expression;
+
+import org.springframework.expression.Expression;
+import org.springframework.messaging.Message;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.util.Assert;
+
+/**
+ * Simple expression configuration attribute for use in {@link Message} authorizations.
+ *
+ * @since 4.0
+ * @author Rob Winch
+ */
+class MessageExpressionConfigAttribute implements ConfigAttribute {
+ private final Expression authorizeExpression;
+
+ /**
+ * Creates a new instance
+ *
+ * @param authorizeExpression the {@link Expression} to use. Cannot be null
+ */
+ public MessageExpressionConfigAttribute(Expression authorizeExpression) {
+ Assert.notNull(authorizeExpression, "authorizeExpression cannot be null");
+
+ this.authorizeExpression = authorizeExpression;
+ }
+
+ Expression getAuthorizeExpression() {
+ return authorizeExpression;
+ }
+
+ public String getAttribute() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return authorizeExpression.getExpressionString();
+ }
+}
diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionVoter.java b/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionVoter.java
new file mode 100644
index 0000000000..3079e1f2fb
--- /dev/null
+++ b/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionVoter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2014 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.security.messaging.access.expression;
+
+import org.springframework.expression.EvaluationContext;
+import org.springframework.messaging.Message;
+import org.springframework.security.access.AccessDecisionVoter;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.access.expression.ExpressionUtils;
+import org.springframework.security.access.expression.SecurityExpressionHandler;
+import org.springframework.security.core.Authentication;
+import org.springframework.util.Assert;
+
+import java.util.Collection;
+
+/**
+ * Voter which handles {@link Message} authorisation decisions. If a {@link MessageExpressionConfigAttribute} is found,
+ * then its expression is evaluated. If true, {@code ACCESS_GRANTED} is returned. If false, {@code ACCESS_DENIED} is
+ * returned. If no {@code MessageExpressionConfigAttribute} is found, then {@code ACCESS_ABSTAIN} is returned.
+ *
+ * @since 4.0
+ * @author Rob Winch
+ */
+public class MessageExpressionVoter implements AccessDecisionVoter> {
+ private SecurityExpressionHandler> expressionHandler = new DefaultMessageSecurityExpressionHandler();
+
+ public int vote(Authentication authentication, Message message, Collection attributes) {
+ assert authentication != null;
+ assert message != null;
+ assert attributes != null;
+
+ MessageExpressionConfigAttribute attr = findConfigAttribute(attributes);
+
+ if (attr == null) {
+ return ACCESS_ABSTAIN;
+ }
+
+ EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, message);
+
+ return ExpressionUtils.evaluateAsBoolean(attr.getAuthorizeExpression(), ctx) ?
+ ACCESS_GRANTED : ACCESS_DENIED;
+ }
+
+ private MessageExpressionConfigAttribute findConfigAttribute(Collection attributes) {
+ for (ConfigAttribute attribute : attributes) {
+ if (attribute instanceof MessageExpressionConfigAttribute) {
+ return (MessageExpressionConfigAttribute)attribute;
+ }
+ }
+ return null;
+ }
+
+ public boolean supports(ConfigAttribute attribute) {
+ return attribute instanceof MessageExpressionConfigAttribute;
+ }
+
+ public boolean supports(Class> clazz) {
+ return Message.class.isAssignableFrom(clazz);
+ }
+
+ public void setExpressionHandler(SecurityExpressionHandler> expressionHandler) {
+ Assert.notNull(expressionHandler, "expressionHandler cannot be null");
+ this.expressionHandler = expressionHandler;
+ }
+}
diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java b/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java
new file mode 100644
index 0000000000..ba9a15ec80
--- /dev/null
+++ b/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2002-2014 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.security.messaging.access.expression;
+
+import org.springframework.messaging.Message;
+import org.springframework.security.access.expression.SecurityExpressionRoot;
+import org.springframework.security.core.Authentication;
+
+/**
+ * The {@link SecurityExpressionRoot} used for {@link Message} expressions.
+ *
+ * @since 4.0
+ * @author Rob Winch
+ */
+final class MessageSecurityExpressionRoot extends SecurityExpressionRoot {
+
+ public MessageSecurityExpressionRoot(Authentication authentication, Message message) {
+ super(authentication);
+ }
+}
diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptor.java b/messaging/src/main/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptor.java
new file mode 100644
index 0000000000..c0dd5eacdf
--- /dev/null
+++ b/messaging/src/main/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptor.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2002-2014 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.security.messaging.access.intercept;
+
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.support.ChannelInterceptor;
+import org.springframework.security.access.SecurityMetadataSource;
+import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
+import org.springframework.security.access.intercept.InterceptorStatusToken;
+import org.springframework.security.messaging.access.expression.ExpressionBasedMessageSecurityMetadataSourceFactory;
+import org.springframework.util.Assert;
+
+/**
+ * Performs security handling of Message resources via a ChannelInterceptor implementation.
+ *
+ * The SecurityMetadataSource required by this security interceptor is of type {@link
+ * MessageSecurityMetadataSource}.
+ *
+ *
+ * Refer to {@link AbstractSecurityInterceptor} for details on the workflow.
+ *
+ *
+ * @see 4.0
+ * @author Rob Winch
+ */
+public final class ChannelSecurityInterceptor extends AbstractSecurityInterceptor implements ChannelInterceptor {
+
+ private final MessageSecurityMetadataSource metadataSource;
+
+ /**
+ * Creates a new instance
+ *
+ * @param metadataSource the MessageSecurityMetadataSource to use. Cannot be null.
+ *
+ * @see DefaultMessageSecurityMetadataSource
+ * @see ExpressionBasedMessageSecurityMetadataSourceFactory
+ */
+ public ChannelSecurityInterceptor(MessageSecurityMetadataSource metadataSource) {
+ Assert.notNull(metadataSource, "metadataSource cannot be null");
+ this.metadataSource = metadataSource;
+ }
+
+ @Override
+ public Class> getSecureObjectClass() {
+ return Message.class;
+ }
+
+ @Override
+ public SecurityMetadataSource obtainSecurityMetadataSource() {
+ return metadataSource;
+ }
+
+ @Override
+ public Message> preSend(Message> message, MessageChannel channel) {
+ InterceptorStatusToken token = beforeInvocation(message);
+ return token == null ? message : new TokenMessage(message,token);
+ }
+
+ @Override
+ public void postSend(Message> message, MessageChannel channel, boolean sent) {
+ if(!(message instanceof TokenMessage)) {
+ // TODO What if other classes return another instance too?
+ return;
+ }
+ InterceptorStatusToken token = ((TokenMessage)message).getToken();
+ afterInvocation(token, null);
+ }
+
+ @Override
+ public void afterSendCompletion(Message> message, MessageChannel channel, boolean sent, Exception ex) {
+ if(!(message instanceof TokenMessage)) {
+ // TODO What if other classes return another instance too?
+ return;
+ }
+ InterceptorStatusToken token = ((TokenMessage)message).getToken();
+ finallyInvocation(token);
+ }
+
+ public boolean preReceive(MessageChannel channel) {
+ return true;
+ }
+
+ @Override
+ public Message> postReceive(Message> message, MessageChannel channel) {
+ return message;
+ }
+
+ @Override
+ public void afterReceiveCompletion(Message> message, MessageChannel channel, Exception ex) {
+ }
+
+ static final class TokenMessage implements Message {
+ private final Message delegate;
+ private final InterceptorStatusToken token;
+
+ TokenMessage(Message delegate, InterceptorStatusToken token) {
+ this.delegate = delegate;
+ this.token = token;
+ }
+
+ public InterceptorStatusToken getToken() {
+ return token;
+ }
+
+ @Override
+ public MessageHeaders getHeaders() {
+ return delegate.getHeaders();
+ }
+
+ @Override
+ public Object getPayload() {
+ return delegate.getPayload();
+ }
+ }
+}
diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSource.java b/messaging/src/main/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSource.java
new file mode 100644
index 0000000000..fa0b3f0a88
--- /dev/null
+++ b/messaging/src/main/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSource.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2014 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.security.messaging.access.intercept;
+
+import org.springframework.messaging.Message;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.messaging.access.expression.ExpressionBasedMessageSecurityMetadataSourceFactory;
+import org.springframework.security.messaging.util.matcher.MessageMatcher;
+
+import java.util.*;
+
+/**
+ * A default implementation of {@link MessageSecurityMetadataSource} that looks up the {@link ConfigAttribute} instances
+ * using a {@link MessageMatcher}.
+ *
+ *
+ * Each entry is considered in order. The first entry that matches, the corresponding {@code Collection}
+ * is returned.
+ *
+ *
+ * @see ChannelSecurityInterceptor
+ * @see ExpressionBasedMessageSecurityMetadataSourceFactory
+ *
+ * @since 4.0
+ * @author Rob Winch
+ */
+public final class DefaultMessageSecurityMetadataSource implements MessageSecurityMetadataSource {
+ private final Map,Collection> messageMap;
+
+ public DefaultMessageSecurityMetadataSource(LinkedHashMap, Collection> messageMap) {
+ this.messageMap = messageMap;
+ }
+
+ @Override
+ public Collection getAttributes(Object object) throws IllegalArgumentException {
+ final Message message = (Message) object;
+ for (Map.Entry, Collection> entry : messageMap.entrySet()) {
+ if (entry.getKey().matches(message)) {
+ return entry.getValue();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Collection getAllConfigAttributes() {
+ Set allAttributes = new HashSet();
+
+ for (Collection entry : messageMap.values()) {
+ allAttributes.addAll(entry);
+ }
+
+ return allAttributes;
+ }
+
+ @Override
+ public boolean supports(Class> clazz) {
+ return Message.class.isAssignableFrom(clazz);
+ }
+}
diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/intercept/MessageSecurityMetadataSource.java b/messaging/src/main/java/org/springframework/security/messaging/access/intercept/MessageSecurityMetadataSource.java
new file mode 100644
index 0000000000..9f54b1deb9
--- /dev/null
+++ b/messaging/src/main/java/org/springframework/security/messaging/access/intercept/MessageSecurityMetadataSource.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2002-2014 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.security.messaging.access.intercept;
+
+import org.springframework.messaging.Message;
+import org.springframework.security.access.SecurityMetadataSource;
+
+/**
+ * A {@link SecurityMetadataSource} that is used for securing {@link Message}
+ *
+ * @see ChannelSecurityInterceptor
+ * @see DefaultMessageSecurityMetadataSource
+ *
+ * @since 4.0
+ * @author Rob Winch
+ */
+public interface MessageSecurityMetadataSource extends SecurityMetadataSource {
+}
diff --git a/messaging/src/main/java/org/springframework/security/messaging/context/SecurityContextChannelInterceptor.java b/messaging/src/main/java/org/springframework/security/messaging/context/SecurityContextChannelInterceptor.java
new file mode 100644
index 0000000000..f53fd56e48
--- /dev/null
+++ b/messaging/src/main/java/org/springframework/security/messaging/context/SecurityContextChannelInterceptor.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2002-2014 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.security.messaging.context;
+
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
+import org.springframework.messaging.support.ChannelInterceptorAdapter;
+import org.springframework.messaging.support.ExecutorChannelInterceptor;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.util.Assert;
+
+/**
+ *
+ * Creates a {@link ExecutorChannelInterceptor} that will obtain the {@link Authentication} from the specified
+ * {@link Message#getHeaders()}.
+ *
+ *
+ * @since 4.0
+ * @author Rob Winch
+ */
+public final class SecurityContextChannelInterceptor extends ChannelInterceptorAdapter implements ExecutorChannelInterceptor {
+ private final String authenticationHeaderName;
+
+ /**
+ * Creates a new instance using the header of the name {@link SimpMessageHeaderAccessor#USER_HEADER}.
+ */
+ public SecurityContextChannelInterceptor() {
+ this(SimpMessageHeaderAccessor.USER_HEADER);
+ }
+
+ /**
+ * Creates a new instance that uses the specified header to obtain the {@link Authentication}.
+ *
+ * @param authenticationHeaderName the header name to obtain the {@link Authentication}. Cannot be null.
+ */
+ public SecurityContextChannelInterceptor(String authenticationHeaderName) {
+ Assert.notNull(authenticationHeaderName, "authenticationHeaderName cannot be null");
+ this.authenticationHeaderName = authenticationHeaderName;
+ }
+ @Override
+ public Message> preSend(Message> message, MessageChannel channel) {
+ setup(message);
+ return message;
+ }
+
+ @Override
+ public void afterSendCompletion(Message> message, MessageChannel channel, boolean sent, Exception ex) {
+ cleanup();
+ }
+
+ @Override
+ public Message> beforeHandle(Message> message, MessageChannel channel, MessageHandler handler) {
+ setup(message);
+ return message;
+ }
+
+ @Override
+ public void afterMessageHandled(Message> message, MessageChannel channel, MessageHandler handler, Exception ex) {
+ cleanup();
+ }
+
+ private void setup(Message> message) {
+ Object user = message.getHeaders().get(authenticationHeaderName);
+ if(!(user instanceof Authentication)) {
+ return;
+ }
+ Authentication authentication = (Authentication) user;
+ SecurityContext context = SecurityContextHolder.createEmptyContext();
+ context.setAuthentication(authentication);
+ SecurityContextHolder.setContext(context);
+ }
+
+ private void cleanup() {
+ SecurityContextHolder.clearContext();
+ }
+}
diff --git a/messaging/src/main/java/org/springframework/security/messaging/util/matcher/MessageMatcher.java b/messaging/src/main/java/org/springframework/security/messaging/util/matcher/MessageMatcher.java
new file mode 100644
index 0000000000..8a70934fb6
--- /dev/null
+++ b/messaging/src/main/java/org/springframework/security/messaging/util/matcher/MessageMatcher.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2014 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.security.messaging.util.matcher;
+
+import org.springframework.messaging.Message;
+
+/**
+ * API for determining if a {@link Message} should be matched on.
+ *
+ * @since 4.0
+ * @author Rob Winch
+ */
+public interface MessageMatcher {
+
+ /**
+ * Returns true if the {@link Message} matches, else false
+ * @param message the {@link Message} to match on
+ * @return true if the {@link Message} matches, else false
+ */
+ boolean matches(Message extends T> message);
+
+ /**
+ * Matches every {@link Message}
+ */
+ MessageMatcher ANY_MESSAGE = new MessageMatcher() {
+ public boolean matches(Message extends Object> message) {
+ return true;
+ }
+ };
+}
diff --git a/messaging/src/main/java/org/springframework/security/messaging/util/matcher/SimpDestinationMessageMatcher.java b/messaging/src/main/java/org/springframework/security/messaging/util/matcher/SimpDestinationMessageMatcher.java
new file mode 100644
index 0000000000..84ba4d3592
--- /dev/null
+++ b/messaging/src/main/java/org/springframework/security/messaging/util/matcher/SimpDestinationMessageMatcher.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002-2014 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.security.messaging.util.matcher;
+
+import org.springframework.messaging.Message;
+import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.Assert;
+
+/**
+ *
+ * MessageMatcher which compares a pre-defined ant-style pattern against the destination of a {@link Message}.
+ *
+ *
+ * The mapping matches destinations using the following rules:
+ *
+ *
+ * ? matches one character
+ * * matches zero or more characters
+ * ** matches zero or more 'directories' in a path
+ *
+ *
+ * Some examples:
+ *
+ *
+ * {@code com/t?st.jsp} - matches {@code com/test} but also
+ * {@code com/tast} or {@code com/txst}
+ * {@code com/*suffix} - matches all files ending in {@code suffix} in the {@code com} directory
+ * {@code com/**/test} - matches all destinations ending with {@code test} underneath the {@code com} path
+ *
+ *
+ * @author Rob Winch
+ */
+public final class SimpDestinationMessageMatcher implements MessageMatcher {
+ private final AntPathMatcher matcher = new AntPathMatcher();
+ private final String pattern;
+
+ public SimpDestinationMessageMatcher(String pattern) {
+ Assert.notNull(pattern, "pattern cannot be null");
+ this.pattern = pattern;
+ }
+
+ @Override
+ public boolean matches(Message extends Object> message) {
+ String destination = SimpMessageHeaderAccessor.getDestination(message.getHeaders());
+ return destination != null && matcher.match(pattern, destination);
+ }
+}
\ No newline at end of file
diff --git a/messaging/src/test/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactoryTests.java b/messaging/src/test/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactoryTests.java
new file mode 100644
index 0000000000..aacdaa56dc
--- /dev/null
+++ b/messaging/src/test/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactoryTests.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2002-2014 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.security.messaging.access.expression;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.powermock.api.mockito.PowerMockito.when;
+import static org.springframework.security.messaging.access.expression.ExpressionBasedMessageSecurityMetadataSourceFactory.*;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.messaging.Message;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource;
+import org.springframework.security.messaging.util.matcher.MessageMatcher;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ExpressionBasedMessageSecurityMetadataSourceFactoryTests {
+ @Mock
+ MessageMatcher matcher1;
+ @Mock
+ MessageMatcher matcher2;
+ @Mock
+ Message message;
+ @Mock
+ Authentication authentication;
+
+ String expression1;
+
+ String expression2;
+
+ LinkedHashMap,String> matcherToExpression;
+
+ MessageSecurityMetadataSource source;
+
+ MessageSecurityExpressionRoot rootObject;
+
+ @Before
+ public void setup() {
+ expression1 = "permitAll";
+ expression2 = "denyAll";
+ matcherToExpression = new LinkedHashMap, String>();
+ matcherToExpression.put(matcher1, expression1);
+ matcherToExpression.put(matcher2, expression2);
+
+ source = createExpressionMessageMetadataSource(matcherToExpression);
+ rootObject = new MessageSecurityExpressionRoot(authentication, message);
+ }
+
+ @Test
+ public void createExpressionMessageMetadataSourceNoMatch() {
+
+ Collection attrs = source.getAttributes(message);
+
+ assertThat(attrs).isNull();
+ }
+
+ @Test
+ public void createExpressionMessageMetadataSourceMatchFirst() {
+ when(matcher1.matches(message)).thenReturn(true);
+
+ Collection attrs = source.getAttributes(message);
+
+ assertThat(attrs.size()).isEqualTo(1);
+ ConfigAttribute attr = attrs.iterator().next();
+ assertThat(attr).isInstanceOf(MessageExpressionConfigAttribute.class);
+ assertThat(((MessageExpressionConfigAttribute)attr).getAuthorizeExpression().getValue(rootObject)).isEqualTo(true);
+ }
+
+ @Test
+ public void createExpressionMessageMetadataSourceMatchSecond() {
+ when(matcher2.matches(message)).thenReturn(true);
+
+ Collection attrs = source.getAttributes(message);
+
+ assertThat(attrs.size()).isEqualTo(1);
+ ConfigAttribute attr = attrs.iterator().next();
+ assertThat(attr).isInstanceOf(MessageExpressionConfigAttribute.class);
+ assertThat(((MessageExpressionConfigAttribute)attr).getAuthorizeExpression().getValue(rootObject)).isEqualTo(false);
+ }
+}
\ No newline at end of file
diff --git a/messaging/src/test/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttributeTests.java b/messaging/src/test/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttributeTests.java
new file mode 100644
index 0000000000..0185cfcc98
--- /dev/null
+++ b/messaging/src/test/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttributeTests.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2014 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.security.messaging.access.expression;
+
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.expression.Expression;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class MessageExpressionConfigAttributeTests {
+ @Mock
+ Expression expression;
+
+ MessageExpressionConfigAttribute attribute;
+
+ @Before
+ public void setup() {
+ attribute = new MessageExpressionConfigAttribute(expression);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void constructorNullExpression() {
+ new MessageExpressionConfigAttribute(null);
+ }
+
+ @Test
+ public void getAuthorizeExpression() {
+ assertThat(attribute.getAuthorizeExpression()).isSameAs(expression);
+ }
+
+ @Test
+ public void getAttribute() {
+ assertThat(attribute.getAttribute()).isNull();
+ }
+
+ @Test
+ public void toStringUsesExpressionString() {
+ when(expression.getExpressionString()).thenReturn("toString");
+
+ assertThat(attribute.toString()).isEqualTo(expression.getExpressionString());
+ }
+}
\ No newline at end of file
diff --git a/messaging/src/test/java/org/springframework/security/messaging/access/expression/MessageExpressionVoterTests.java b/messaging/src/test/java/org/springframework/security/messaging/access/expression/MessageExpressionVoterTests.java
new file mode 100644
index 0000000000..12052c09b2
--- /dev/null
+++ b/messaging/src/test/java/org/springframework/security/messaging/access/expression/MessageExpressionVoterTests.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2002-2014 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.security.messaging.access.expression;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.messaging.Message;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.access.SecurityConfig;
+import org.springframework.security.access.expression.SecurityExpressionHandler;
+import org.springframework.security.core.Authentication;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+import static org.springframework.security.access.AccessDecisionVoter.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class MessageExpressionVoterTests {
+ @Mock
+ Authentication authentication;
+ @Mock
+ Message message;
+ Collection attributes;
+ @Mock
+ Expression expression;
+ @Mock
+ SecurityExpressionHandler expressionHandler;
+ @Mock
+ EvaluationContext evaluationContext;
+
+ MessageExpressionVoter voter;
+
+ @Before
+ public void setup() {
+ attributes = Arrays.asList(new MessageExpressionConfigAttribute(expression));
+
+ voter = new MessageExpressionVoter();
+ }
+
+ @Test
+ public void voteGranted() {
+ when(expression.getValue(any(EvaluationContext.class),eq(Boolean.class))).thenReturn(true);
+ assertThat(voter.vote(authentication, message, attributes)).isEqualTo(ACCESS_GRANTED);
+ }
+
+ @Test
+ public void voteDenied() {
+ when(expression.getValue(any(EvaluationContext.class),eq(Boolean.class))).thenReturn(false);
+ assertThat(voter.vote(authentication, message, attributes)).isEqualTo(ACCESS_DENIED);
+ }
+
+ @Test
+ public void voteAbstain() {
+ attributes = Arrays.asList(new SecurityConfig("ROLE_USER"));
+ assertThat(voter.vote(authentication, message, attributes)).isEqualTo(ACCESS_ABSTAIN);
+ }
+
+ @Test
+ public void supportsObjectClassFalse() {
+ assertThat(voter.supports(Object.class)).isFalse();
+ }
+
+ @Test
+ public void supportsMessageClassTrue() {
+ assertThat(voter.supports(Message.class)).isTrue();
+ }
+
+ @Test
+ public void supportsSecurityConfigFalse() {
+ assertThat(voter.supports(new SecurityConfig("ROLE_USER"))).isFalse();
+ }
+
+ @Test
+ public void supportsMessageExpressionConfigAttributeTrue() {
+ assertThat(voter.supports(new MessageExpressionConfigAttribute(expression))).isTrue();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void setExpressionHandlerNull() {
+ voter.setExpressionHandler(null);
+ }
+
+ @Test
+ public void customExpressionHandler() {
+ voter.setExpressionHandler(expressionHandler);
+ when(expressionHandler.createEvaluationContext(authentication, message)).thenReturn(evaluationContext);
+ when(expression.getValue(evaluationContext, Boolean.class)).thenReturn(true);
+
+ assertThat(voter.vote(authentication, message, attributes)).isEqualTo(ACCESS_GRANTED);
+
+ verify(expressionHandler).createEvaluationContext(authentication, message);
+ }
+}
\ No newline at end of file
diff --git a/messaging/src/test/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptorTests.java b/messaging/src/test/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptorTests.java
new file mode 100644
index 0000000000..fd335623a1
--- /dev/null
+++ b/messaging/src/test/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptorTests.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2002-2014 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.security.messaging.access.intercept;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.security.access.AccessDecisionManager;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.access.SecurityConfig;
+import org.springframework.security.access.intercept.InterceptorStatusToken;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ChannelSecurityInterceptorTests {
+ @Mock
+ Message message;
+ @Mock
+ MessageChannel channel;
+ @Mock
+ MessageSecurityMetadataSource source;
+ @Mock
+ AccessDecisionManager accessDecisionManager;
+ List attrs;
+
+ ChannelSecurityInterceptor interceptor;
+
+ @Before
+ public void setup() {
+ attrs = Arrays.asList(new SecurityConfig("ROLE_USER"));
+ interceptor = new ChannelSecurityInterceptor(source);
+ interceptor.setAccessDecisionManager(accessDecisionManager);
+
+ SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_USER"));
+ }
+
+ @After
+ public void cleanup() {
+ SecurityContextHolder.clearContext();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void constructorMessageSecurityMetadataSourceNull() {
+ new ChannelSecurityInterceptor(null);
+ }
+
+ @Test
+ public void getSecureObjectClass() throws Exception {
+ assertThat(interceptor.getSecureObjectClass()).isEqualTo(Message.class);
+ }
+
+ @Test
+ public void obtainSecurityMetadataSource() throws Exception {
+ assertThat(interceptor.obtainSecurityMetadataSource()).isEqualTo(source);
+ }
+
+ @Test
+ public void preSendNullAttributes() throws Exception {
+ assertThat(interceptor.preSend(message, channel)).isSameAs(message);
+ }
+
+ @Test
+ public void preSendGrant() throws Exception {
+ when(source.getAttributes(message)).thenReturn(attrs);
+
+ Message> result = interceptor.preSend(message, channel);
+
+ assertThat(result).isInstanceOf(ChannelSecurityInterceptor.TokenMessage.class);
+ ChannelSecurityInterceptor.TokenMessage tm = (ChannelSecurityInterceptor.TokenMessage) result;
+ assertThat(tm.getHeaders()).isSameAs(message.getHeaders());
+ assertThat(tm.getPayload()).isSameAs(message.getPayload());
+ assertThat(tm.getToken()).isNotNull();
+ }
+
+ @Test(expected = AccessDeniedException.class)
+ public void preSendDeny() throws Exception {
+ when(source.getAttributes(message)).thenReturn(attrs);
+ doThrow(new AccessDeniedException("")).when(accessDecisionManager).decide(any(Authentication.class), eq(message), eq(attrs));
+
+ interceptor.preSend(message, channel);
+ }
+
+ @Test
+ public void postSendNotTokenMessageNoExceptionThrown() throws Exception {
+ interceptor.postSend(message, channel, true);
+ }
+
+ @Test
+ public void postSendTokenMessage() throws Exception {
+ InterceptorStatusToken token = new InterceptorStatusToken(SecurityContextHolder.createEmptyContext(),true,attrs,message);
+ ChannelSecurityInterceptor.TokenMessage tokenMessage = new ChannelSecurityInterceptor.TokenMessage(message, token);
+
+ interceptor.postSend(tokenMessage, channel, true);
+
+ assertThat(SecurityContextHolder.getContext()).isSameAs(token.getSecurityContext());
+ }
+
+ @Test
+ public void afterSendCompletionNotTokenMessageNoExceptionThrown() throws Exception {
+ interceptor.afterSendCompletion(message, channel, true, null);
+ }
+
+ @Test
+ public void afterSendCompletionTokenMessage() throws Exception {
+ InterceptorStatusToken token = new InterceptorStatusToken(SecurityContextHolder.createEmptyContext(),true,attrs,message);
+ ChannelSecurityInterceptor.TokenMessage tokenMessage = new ChannelSecurityInterceptor.TokenMessage(message, token);
+
+ interceptor.afterSendCompletion(tokenMessage, channel, true, null);
+
+ assertThat(SecurityContextHolder.getContext()).isSameAs(token.getSecurityContext());
+ }
+
+ @Test
+ public void preReceive() throws Exception {
+ assertThat(interceptor.preReceive(channel)).isTrue();;
+ }
+
+ @Test
+ public void postReceive() throws Exception {
+ assertThat(interceptor.postReceive(message, channel)).isSameAs(message);
+ }
+
+ @Test
+ public void afterReceiveCompletionNullExceptionNoExceptionThrown() throws Exception {
+ interceptor.afterReceiveCompletion(message, channel, null);
+ }
+}
\ No newline at end of file
diff --git a/messaging/src/test/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSourceTests.java b/messaging/src/test/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSourceTests.java
new file mode 100644
index 0000000000..3ce85fca12
--- /dev/null
+++ b/messaging/src/test/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSourceTests.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2002-2014 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.security.messaging.access.intercept;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.messaging.Message;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.access.SecurityConfig;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.messaging.util.matcher.MessageMatcher;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.powermock.api.mockito.PowerMockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DefaultMessageSecurityMetadataSourceTests {
+ @Mock
+ MessageMatcher matcher1;
+ @Mock
+ MessageMatcher matcher2;
+ @Mock
+ Message message;
+ @Mock
+ Authentication authentication;
+
+ SecurityConfig config1;
+
+ SecurityConfig config2;
+
+ LinkedHashMap,Collection> messageMap;
+
+ MessageSecurityMetadataSource source;
+
+ @Before
+ public void setup() {
+ messageMap = new LinkedHashMap, Collection>();
+ messageMap.put(matcher1, Arrays.asList(config1));
+ messageMap.put(matcher2, Arrays.asList(config2));
+
+ source = new DefaultMessageSecurityMetadataSource(messageMap);
+ }
+
+ @Test
+ public void getAttributesNull() {
+ assertThat(source.getAttributes(message)).isNull();
+ }
+
+ @Test
+ public void getAttributesFirst() {
+ when(matcher1.matches(message)).thenReturn(true);
+
+ assertThat(source.getAttributes(message)).containsOnly(config1);
+ }
+
+ @Test
+ public void getAttributesSecond() {
+ when(matcher1.matches(message)).thenReturn(true);
+
+ assertThat(source.getAttributes(message)).containsOnly(config2);
+ }
+
+ @Test
+ public void getAllConfigAttributes() {
+ assertThat(source.getAllConfigAttributes()).containsOnly(config1,config2);
+ }
+
+ @Test
+ public void supportsFalse() {
+ assertThat(source.supports(Object.class)).isFalse();
+ }
+
+ @Test
+ public void supportsTrue() {
+ assertThat(source.supports(Message.class)).isTrue();
+ }
+}
\ No newline at end of file
diff --git a/messaging/src/test/java/org/springframework/security/messaging/context/SecurityContextChannelInterceptorTests.java b/messaging/src/test/java/org/springframework/security/messaging/context/SecurityContextChannelInterceptorTests.java
new file mode 100644
index 0000000000..60c3ab9ccf
--- /dev/null
+++ b/messaging/src/test/java/org/springframework/security/messaging/context/SecurityContextChannelInterceptorTests.java
@@ -0,0 +1,149 @@
+package org.springframework.security.messaging.context;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.messaging.context.SecurityContextChannelInterceptor;
+
+import java.security.Principal;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.springframework.security.core.context.SecurityContextHolder.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SecurityContextChannelInterceptorTests {
+ @Mock
+ MessageChannel channel;
+ @Mock
+ MessageHandler handler;
+ @Mock
+ Principal principal;
+
+ MessageBuilder messageBuilder;
+
+ Authentication authentication;
+
+ SecurityContextChannelInterceptor interceptor;
+
+ @Before
+ public void setup() {
+ authentication = new TestingAuthenticationToken("user","pass", "ROLE_USER");
+ messageBuilder = MessageBuilder.withPayload("payload");
+
+ interceptor = new SecurityContextChannelInterceptor();
+ }
+
+ @After
+ public void cleanup() {
+ clearContext();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void constructorNullHeader() {
+ new SecurityContextChannelInterceptor(null);
+ }
+
+ @Test
+ public void preSendCustomHeader() throws Exception {
+ String headerName = "header";
+ interceptor = new SecurityContextChannelInterceptor(headerName);
+ messageBuilder.setHeader(headerName, authentication);
+
+ interceptor.preSend(messageBuilder.build(), channel);
+
+ assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(authentication);
+ }
+
+ @Test
+ public void preSendUserSet() throws Exception {
+ messageBuilder.setHeader(SimpMessageHeaderAccessor.USER_HEADER, authentication);
+
+ interceptor.preSend(messageBuilder.build(), channel);
+
+ assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(authentication);
+ }
+
+ @Test
+ public void preSendUserNotAuthentication() throws Exception {
+ messageBuilder.setHeader(SimpMessageHeaderAccessor.USER_HEADER, principal);
+
+ interceptor.preSend(messageBuilder.build(), channel);
+
+ assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
+ }
+
+ @Test
+ public void preSendUserNotSet() throws Exception {
+ interceptor.preSend(messageBuilder.build(), channel);
+
+ assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
+ }
+
+ @Test
+ public void afterSendCompletion() throws Exception {
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+
+ interceptor.afterSendCompletion(messageBuilder.build(), channel, true, null);
+
+ assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
+ }
+
+ @Test
+ public void afterSendCompletionNullAuthentication() throws Exception {
+ interceptor.afterSendCompletion(messageBuilder.build(), channel, true, null);
+
+ assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
+ }
+
+ @Test
+ public void beforeHandleUserSet() throws Exception {
+ messageBuilder.setHeader(SimpMessageHeaderAccessor.USER_HEADER, authentication);
+
+ interceptor.beforeHandle(messageBuilder.build(), channel, handler);
+
+ assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(authentication);
+ }
+
+ @Test
+ public void beforeHandleUserNotAuthentication() throws Exception {
+ messageBuilder.setHeader(SimpMessageHeaderAccessor.USER_HEADER, principal);
+
+ interceptor.beforeHandle(messageBuilder.build(), channel, handler);
+
+ assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
+ }
+
+ @Test
+ public void beforeHandleUserNotSet() throws Exception {
+ interceptor.beforeHandle(messageBuilder.build(), channel, handler);
+
+ assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
+ }
+
+
+ @Test
+ public void afterMessageHandledUserNotSet() throws Exception {
+ interceptor.afterMessageHandled(messageBuilder.build(), channel, handler, null);
+
+ assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
+ }
+
+ @Test
+ public void afterMessageHandled() throws Exception {
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+
+ interceptor.afterMessageHandled(messageBuilder.build(), channel, handler, null);
+
+ assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
+ }
+}
\ No newline at end of file
diff --git a/messaging/src/test/java/org/springframework/security/messaging/util/matcher/SimpDestinationMessageMatcherTests.java b/messaging/src/test/java/org/springframework/security/messaging/util/matcher/SimpDestinationMessageMatcherTests.java
new file mode 100644
index 0000000000..b364e9417b
--- /dev/null
+++ b/messaging/src/test/java/org/springframework/security/messaging/util/matcher/SimpDestinationMessageMatcherTests.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2014 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.security.messaging.util.matcher;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
+import org.springframework.messaging.support.MessageBuilder;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+
+public class SimpDestinationMessageMatcherTests {
+ MessageBuilder messageBuilder;
+
+ SimpDestinationMessageMatcher matcher;
+
+ @Before
+ public void setup() {
+ messageBuilder = MessageBuilder.withPayload("M");
+ matcher = new SimpDestinationMessageMatcher("/**");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void constructorPatternNull() {
+ new SimpDestinationMessageMatcher(null);
+ }
+
+ @Test
+ public void matchesDoesNotMatchNullDestination() throws Exception {
+ assertThat(matcher.matches(messageBuilder.build())).isFalse();
+ }
+
+ @Test
+ public void matchesAllWithDestination() throws Exception {
+ messageBuilder.setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER,"/destination/1");
+
+ assertThat(matcher.matches(messageBuilder.build())).isTrue();
+ }
+
+ @Test
+ public void matchesSpecificWithDestination() throws Exception {
+ matcher = new SimpDestinationMessageMatcher("/destination/1");
+
+ messageBuilder.setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER,"/destination/1");
+
+ assertThat(matcher.matches(messageBuilder.build())).isTrue();
+ }
+
+ @Test
+ public void matchesFalseWithDestination() throws Exception {
+ matcher = new SimpDestinationMessageMatcher("/nomatch");
+
+ messageBuilder.setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER,"/destination/1");
+
+ assertThat(matcher.matches(messageBuilder.build())).isFalse();
+ }
+}
\ No newline at end of file
diff --git a/samples/messages-jc/pom.xml b/samples/messages-jc/pom.xml
index f309eb4246..6ba16f53cd 100644
--- a/samples/messages-jc/pom.xml
+++ b/samples/messages-jc/pom.xml
@@ -88,7 +88,7 @@
org.springframework.data
spring-data-jpa
- 1.3.4.RELEASE
+ 1.7.0.M1
compile
diff --git a/settings.gradle b/settings.gradle
index 13ca32b1c0..5de7cc3904 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -11,6 +11,7 @@ def String[] modules = [
'taglibs',
'aspects',
'crypto',
+ 'messaging',
'test'
]