Support Path Variables in Message Expressions
Extract path variables expressed in SimpDestinationMessageMatcher's pattern. Issue: gh-4469
This commit is contained in:
parent
fcd8a38f0b
commit
f97ac4daa6
|
@ -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);
|
||||
}
|
|
@ -48,6 +48,7 @@ public final class ExpressionBasedMessageSecurityMetadataSourceFactory {
|
|||
* LinkedHashMap<MessageMatcher<?>,String> matcherToExpression = new LinkedHashMap<MessageMatcher<Object>,String>();
|
||||
* 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);
|
||||
|
@ -82,6 +83,7 @@ public final class ExpressionBasedMessageSecurityMetadataSourceFactory {
|
|||
* LinkedHashMap<MessageMatcher<?>,String> matcherToExpression = new LinkedHashMap<MessageMatcher<Object>,String>();
|
||||
* 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);
|
||||
|
@ -113,7 +115,7 @@ public final class ExpressionBasedMessageSecurityMetadataSourceFactory {
|
|||
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);
|
||||
|
|
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -53,4 +64,15 @@ class MessageExpressionConfigAttribute implements ConfigAttribute {
|
|||
public String toString() {
|
||||
return authorizeExpression.getExpressionString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EvaluationContext postProcess(EvaluationContext ctx, Message<?> message) {
|
||||
if (matcher instanceof SimpDestinationMessageMatcher) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -35,6 +35,7 @@ import java.util.Collection;
|
|||
*
|
||||
* @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<>();
|
||||
|
@ -53,6 +54,7 @@ public class MessageExpressionVoter<T> implements AccessDecisionVoter<Message<T>
|
|||
|
||||
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
|
||||
message);
|
||||
ctx = attr.postProcess(ctx, message);
|
||||
|
||||
return ExpressionUtils.evaluateAsBoolean(attr.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
|
||||
: ACCESS_DENIED;
|
||||
|
|
|
@ -22,6 +22,9 @@ import org.springframework.util.AntPathMatcher;
|
|||
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
|
||||
|
@ -129,6 +132,14 @@ public final class SimpDestinationMessageMatcher implements MessageMatcher<Objec
|
|||
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;
|
||||
}
|
||||
|
@ -175,4 +186,4 @@ public final class SimpDestinationMessageMatcher implements MessageMatcher<Objec
|
|||
return new SimpDestinationMessageMatcher(pattern, SimpMessageType.MESSAGE,
|
||||
matcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -20,26 +20,40 @@ import org.junit.Test;
|
|||
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
|
||||
|
@ -58,4 +72,16 @@ public class MessageExpressionConfigAttributeTests {
|
|||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -27,6 +27,7 @@ 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 org.springframework.security.messaging.util.matcher.MessageMatcher;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -45,6 +46,8 @@ public class MessageExpressionVoterTests {
|
|||
@Mock
|
||||
Expression expression;
|
||||
@Mock
|
||||
MessageMatcher<?> matcher;
|
||||
@Mock
|
||||
SecurityExpressionHandler<Message> expressionHandler;
|
||||
@Mock
|
||||
EvaluationContext evaluationContext;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -99,7 +102,7 @@ public class MessageExpressionVoterTests {
|
|||
|
||||
@Test
|
||||
public void supportsMessageExpressionConfigAttributeTrue() {
|
||||
assertThat(voter.supports(new MessageExpressionConfigAttribute(expression)))
|
||||
assertThat(voter.supports(new MessageExpressionConfigAttribute(expression, matcher)))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
|
@ -120,4 +123,20 @@ public class MessageExpressionVoterTests {
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,6 +127,23 @@ public class SimpDestinationMessageMatcherTests {
|
|||
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",
|
||||
|
@ -139,4 +156,4 @@ public class SimpDestinationMessageMatcherTests {
|
|||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue