Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Path Variables in Message Expressions #6110

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2002-2018 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;

/**
*
/**
* Allows post processing the {@link EvaluationContext}
*
* <p>
* This API is intentionally kept package scope as it may evolve over time.
* </p>
*
* @author Daniel Bustamante Ospina
* @since 5.1
*/
interface EvaluationContextPostProcessor<I> {

/**
* Allows post processing of the {@link EvaluationContext}. Implementations
* may return a new instance of {@link EvaluationContext} or modify the
* {@link EvaluationContext} that was passed in.
*
* @param context
* the original {@link EvaluationContext}
* @param invocation
* the security invocation object (i.e. Message)
* @return the upated context.
*/
EvaluationContext postProcess(EvaluationContext context, I invocation);
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public final class ExpressionBasedMessageSecurityMetadataSourceFactory {
* LinkedHashMap&lt;MessageMatcher&lt;?&gt;,String&gt; matcherToExpression = new LinkedHashMap&lt;MessageMatcher&lt;Object&gt;,String&gt;();
* matcherToExpression.put(new SimDestinationMessageMatcher("/public/**"), "permitAll");
* matcherToExpression.put(new SimDestinationMessageMatcher("/admin/**"), "hasRole('ROLE_ADMIN')");
* matcherToExpression.put(new SimDestinationMessageMatcher("/topics/{name}/**"), "@someBean.customLogic(authentication, #name)");
* matcherToExpression.put(new SimDestinationMessageMatcher("/**"), "authenticated");
*
* MessageSecurityMetadataSource metadataSource = createExpressionMessageMetadataSource(matcherToExpression);
Expand Down Expand Up @@ -82,6 +83,7 @@ public static MessageSecurityMetadataSource createExpressionMessageMetadataSourc
* LinkedHashMap&lt;MessageMatcher&lt;?&gt;,String&gt; matcherToExpression = new LinkedHashMap&lt;MessageMatcher&lt;Object&gt;,String&gt;();
* matcherToExpression.put(new SimDestinationMessageMatcher("/public/**"), "permitAll");
* matcherToExpression.put(new SimDestinationMessageMatcher("/admin/**"), "hasRole('ROLE_ADMIN')");
* matcherToExpression.put(new SimDestinationMessageMatcher("/topics/{name}/**"), "@someBean.customLogic(authentication, #name)");
* matcherToExpression.put(new SimDestinationMessageMatcher("/**"), "authenticated");
*
* MessageSecurityMetadataSource metadataSource = createExpressionMessageMetadataSource(matcherToExpression);
Expand Down Expand Up @@ -113,7 +115,7 @@ public static MessageSecurityMetadataSource createExpressionMessageMetadataSourc
String rawExpression = entry.getValue();
Expression expression = handler.getExpressionParser().parseExpression(
rawExpression);
ConfigAttribute attribute = new MessageExpressionConfigAttribute(expression);
ConfigAttribute attribute = new MessageExpressionConfigAttribute(expression, matcher);
matcherToAttrs.put(matcher, Arrays.asList(attribute));
}
return new DefaultMessageSecurityMetadataSource(matcherToAttrs);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
Expand All @@ -15,32 +15,43 @@
*/
package org.springframework.security.messaging.access.expression;

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.messaging.util.matcher.MessageMatcher;
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
import org.springframework.util.Assert;

import java.util.Map;

/**
* Simple expression configuration attribute for use in {@link Message} authorizations.
*
* @since 4.0
* @author Rob Winch
* @author Daniel Bustamante Ospina
*/
@SuppressWarnings("serial")
class MessageExpressionConfigAttribute implements ConfigAttribute {
class MessageExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor<Message<?>> {
private final Expression authorizeExpression;
private final MessageMatcher<?> matcher;


/**
* Creates a new instance
*
* @param authorizeExpression the {@link Expression} to use. Cannot be null
* @param matcher the {@link MessageMatcher} used to match the messages.
*/
public MessageExpressionConfigAttribute(Expression authorizeExpression) {
public MessageExpressionConfigAttribute(Expression authorizeExpression, MessageMatcher<?> matcher) {
Assert.notNull(authorizeExpression, "authorizeExpression cannot be null");

Assert.notNull(matcher, "matcher cannot be null");
this.authorizeExpression = authorizeExpression;
this.matcher = matcher;
}


Expression getAuthorizeExpression() {
return authorizeExpression;
}
Expand All @@ -53,4 +64,15 @@ public String getAttribute() {
public String toString() {
return authorizeExpression.getExpressionString();
}

@Override
public EvaluationContext postProcess(EvaluationContext ctx, Message<?> message) {
if (matcher instanceof SimpDestinationMessageMatcher) {
dbuos marked this conversation as resolved.
Show resolved Hide resolved
final Map<String, String> variables = ((SimpDestinationMessageMatcher) matcher).extractPathVariables(message);
for (Map.Entry<String, String> entry : variables.entrySet()){
ctx.setVariable(entry.getKey(), entry.getValue());
}
}
return ctx;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
Expand Down Expand Up @@ -35,6 +35,7 @@
*
* @since 4.0
* @author Rob Winch
* @author Daniel Bustamante Ospina
*/
public class MessageExpressionVoter<T> implements AccessDecisionVoter<Message<T>> {
private SecurityExpressionHandler<Message<T>> expressionHandler = new DefaultMessageSecurityExpressionHandler<>();
Expand All @@ -53,6 +54,7 @@ public int vote(Authentication authentication, Message<T> message,

EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
message);
ctx = attr.postProcess(ctx, message);

return ExpressionUtils.evaluateAsBoolean(attr.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
: ACCESS_DENIED;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;

import java.util.Collections;
import java.util.Map;

/**
* <p>
* MessageMatcher which compares a pre-defined pattern against the destination of a
Expand Down Expand Up @@ -129,6 +132,14 @@ public boolean matches(Message<? extends Object> message) {
return destination != null && matcher.match(pattern, destination);
}


public Map<String, String> extractPathVariables(Message<? extends Object> message){
final String destination = SimpMessageHeaderAccessor.getDestination(message
.getHeaders());
return destination != null ? matcher.extractUriTemplateVariables(pattern, destination)
: Collections.emptyMap();
}

public MessageMatcher<Object> getMessageTypeMatcher() {
return messageTypeMatcher;
}
Expand Down Expand Up @@ -175,4 +186,4 @@ public static SimpDestinationMessageMatcher createMessageMatcher(String pattern,
return new SimpDestinationMessageMatcher(pattern, SimpMessageType.MESSAGE,
matcher);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
Expand All @@ -20,26 +20,40 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.messaging.Message;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.security.messaging.util.matcher.MessageMatcher;
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class MessageExpressionConfigAttributeTests {
@Mock
Expression expression;

@Mock
MessageMatcher<?> matcher;

MessageExpressionConfigAttribute attribute;

@Before
public void setup() {
attribute = new MessageExpressionConfigAttribute(expression);
attribute = new MessageExpressionConfigAttribute(expression, matcher);
}

@Test(expected = IllegalArgumentException.class)
public void constructorNullExpression() {
new MessageExpressionConfigAttribute(null);
new MessageExpressionConfigAttribute(null, matcher);
}

@Test(expected = IllegalArgumentException.class)
public void constructorNullMatcher() {
new MessageExpressionConfigAttribute(expression, null);
}

@Test
Expand All @@ -58,4 +72,16 @@ public void toStringUsesExpressionString() {

assertThat(attribute.toString()).isEqualTo(expression.getExpressionString());
}

@Test
public void postProcessContext() {
SimpDestinationMessageMatcher matcher = new SimpDestinationMessageMatcher("/topics/{topic}/**");
Message<?> message = MessageBuilder.withPayload("M").setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER, "/topics/someTopic/sub1").build();
EvaluationContext context = mock(EvaluationContext.class);

attribute = new MessageExpressionConfigAttribute(expression, matcher);
attribute.postProcess(context, message);

verify(context).setVariable("topic", "someTopic");
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
Expand Down Expand Up @@ -27,6 +27,7 @@
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.core.Authentication;
import org.springframework.security.messaging.util.matcher.MessageMatcher;

import java.util.Arrays;
import java.util.Collection;
Expand All @@ -45,6 +46,8 @@ public class MessageExpressionVoterTests {
@Mock
Expression expression;
@Mock
MessageMatcher<?> matcher;
@Mock
SecurityExpressionHandler<Message> expressionHandler;
@Mock
EvaluationContext evaluationContext;
Expand All @@ -54,7 +57,7 @@ public class MessageExpressionVoterTests {
@Before
public void setup() {
attributes = Arrays
.<ConfigAttribute> asList(new MessageExpressionConfigAttribute(expression));
.<ConfigAttribute> asList(new MessageExpressionConfigAttribute(expression, matcher));

voter = new MessageExpressionVoter();
}
Expand Down Expand Up @@ -99,7 +102,7 @@ public void supportsSecurityConfigFalse() {

@Test
public void supportsMessageExpressionConfigAttributeTrue() {
assertThat(voter.supports(new MessageExpressionConfigAttribute(expression)))
assertThat(voter.supports(new MessageExpressionConfigAttribute(expression, matcher)))
.isTrue();
}

Expand All @@ -120,4 +123,20 @@ public void customExpressionHandler() {

verify(expressionHandler).createEvaluationContext(authentication, message);
}

@Test
public void postProcessEvaluationContext(){
final MessageExpressionConfigAttribute configAttribute = mock(MessageExpressionConfigAttribute.class);
voter.setExpressionHandler(expressionHandler);
when(expressionHandler.createEvaluationContext(authentication, message)).thenReturn(evaluationContext);
when(configAttribute.getAuthorizeExpression()).thenReturn(expression);
attributes = Arrays.<ConfigAttribute> asList(configAttribute);
when(configAttribute.postProcess(evaluationContext, message)).thenReturn(evaluationContext);
when(expression.getValue(any(EvaluationContext.class), eq(Boolean.class)))
.thenReturn(true);

assertThat(voter.vote(authentication, message, attributes)).isEqualTo(
ACCESS_GRANTED);
verify(configAttribute).postProcess(evaluationContext, message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,23 @@ public void matchesNullMessageType() throws Exception {
assertThat(matcher.matches(messageBuilder.build())).isTrue();
}

@Test
public void extractPathVariablesFromDestination() throws Exception {
matcher = new SimpDestinationMessageMatcher("/topics/{topic}/**");

messageBuilder.setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER, "/topics/someTopic/sub1");
messageBuilder.setHeader(SimpMessageHeaderAccessor.MESSAGE_TYPE_HEADER,
SimpMessageType.MESSAGE);

assertThat(matcher.extractPathVariables(messageBuilder.build()).get("topic")).isEqualTo("someTopic");
}

@Test
public void extractedVariablesAreEmptyInNullDestination() throws Exception {
matcher = new SimpDestinationMessageMatcher("/topics/{topic}/**");
assertThat(matcher.extractPathVariables(messageBuilder.build())).isEmpty();
}

@Test
public void typeConstructorParameterIsTransmitted() throws Exception {
matcher = SimpDestinationMessageMatcher.createMessageMatcher("/match",
Expand All @@ -139,4 +156,4 @@ public void typeConstructorParameterIsTransmitted() throws Exception {

}

}
}