Skip to content

Commit 8ca2446

Browse files
dbuosDaniel Bustamante Ospina
authored and
Daniel Bustamante Ospina
committed
Support Path Variables in Message Expressions
Extract path variables expressed in SimpDestinationMessageMatcher's pattern. Issue: gh-4469
1 parent e60e171 commit 8ca2446

File tree

8 files changed

+160
-15
lines changed

8 files changed

+160
-15
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.messaging.access.expression;
17+
18+
import org.springframework.expression.EvaluationContext;
19+
20+
/**
21+
*
22+
/**
23+
* Allows post processing the {@link EvaluationContext}
24+
*
25+
* <p>
26+
* This API is intentionally kept package scope as it may evolve over time.
27+
* </p>
28+
*
29+
* @author Daniel Bustamante Ospina
30+
* @since 5.1
31+
*/
32+
interface EvaluationContextPostProcessor<I> {
33+
34+
/**
35+
* Allows post processing of the {@link EvaluationContext}. Implementations
36+
* may return a new instance of {@link EvaluationContext} or modify the
37+
* {@link EvaluationContext} that was passed in.
38+
*
39+
* @param context
40+
* the original {@link EvaluationContext}
41+
* @param invocation
42+
* the security invocation object (i.e. Message)
43+
* @return the upated context.
44+
*/
45+
EvaluationContext postProcess(EvaluationContext context, I invocation);
46+
}

messaging/src/main/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactory.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public final class ExpressionBasedMessageSecurityMetadataSourceFactory {
4848
* LinkedHashMap&lt;MessageMatcher&lt;?&gt;,String&gt; matcherToExpression = new LinkedHashMap&lt;MessageMatcher&lt;Object&gt;,String&gt;();
4949
* matcherToExpression.put(new SimDestinationMessageMatcher("/public/**"), "permitAll");
5050
* matcherToExpression.put(new SimDestinationMessageMatcher("/admin/**"), "hasRole('ROLE_ADMIN')");
51+
* matcherToExpression.put(new SimDestinationMessageMatcher("/topics/{name}/**"), "@someBean.customLogic(authentication, #name)");
5152
* matcherToExpression.put(new SimDestinationMessageMatcher("/**"), "authenticated");
5253
*
5354
* MessageSecurityMetadataSource metadataSource = createExpressionMessageMetadataSource(matcherToExpression);
@@ -82,6 +83,7 @@ public static MessageSecurityMetadataSource createExpressionMessageMetadataSourc
8283
* LinkedHashMap&lt;MessageMatcher&lt;?&gt;,String&gt; matcherToExpression = new LinkedHashMap&lt;MessageMatcher&lt;Object&gt;,String&gt;();
8384
* matcherToExpression.put(new SimDestinationMessageMatcher("/public/**"), "permitAll");
8485
* matcherToExpression.put(new SimDestinationMessageMatcher("/admin/**"), "hasRole('ROLE_ADMIN')");
86+
* matcherToExpression.put(new SimDestinationMessageMatcher("/topics/{name}/**"), "@someBean.customLogic(authentication, #name)");
8587
* matcherToExpression.put(new SimDestinationMessageMatcher("/**"), "authenticated");
8688
*
8789
* MessageSecurityMetadataSource metadataSource = createExpressionMessageMetadataSource(matcherToExpression);
@@ -113,7 +115,7 @@ public static MessageSecurityMetadataSource createExpressionMessageMetadataSourc
113115
String rawExpression = entry.getValue();
114116
Expression expression = handler.getExpressionParser().parseExpression(
115117
rawExpression);
116-
ConfigAttribute attribute = new MessageExpressionConfigAttribute(expression);
118+
ConfigAttribute attribute = new MessageExpressionConfigAttribute(expression, matcher);
117119
matcherToAttrs.put(matcher, Arrays.asList(attribute));
118120
}
119121
return new DefaultMessageSecurityMetadataSource(matcherToAttrs);

messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttribute.java

+26-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,32 +15,43 @@
1515
*/
1616
package org.springframework.security.messaging.access.expression;
1717

18+
import org.springframework.expression.EvaluationContext;
1819
import org.springframework.expression.Expression;
1920
import org.springframework.messaging.Message;
2021
import org.springframework.security.access.ConfigAttribute;
22+
import org.springframework.security.messaging.util.matcher.MessageMatcher;
23+
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
2124
import org.springframework.util.Assert;
2225

26+
import java.util.Map;
27+
2328
/**
2429
* Simple expression configuration attribute for use in {@link Message} authorizations.
2530
*
2631
* @since 4.0
2732
* @author Rob Winch
33+
* @author Daniel Bustamante Ospina
2834
*/
2935
@SuppressWarnings("serial")
30-
class MessageExpressionConfigAttribute implements ConfigAttribute {
36+
class MessageExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor<Message<?>> {
3137
private final Expression authorizeExpression;
38+
private final MessageMatcher<?> matcher;
39+
3240

3341
/**
3442
* Creates a new instance
3543
*
3644
* @param authorizeExpression the {@link Expression} to use. Cannot be null
45+
* @param matcher the {@link MessageMatcher} used to match the messages.
3746
*/
38-
public MessageExpressionConfigAttribute(Expression authorizeExpression) {
47+
public MessageExpressionConfigAttribute(Expression authorizeExpression, MessageMatcher<?> matcher) {
3948
Assert.notNull(authorizeExpression, "authorizeExpression cannot be null");
40-
49+
Assert.notNull(matcher, "matcher cannot be null");
4150
this.authorizeExpression = authorizeExpression;
51+
this.matcher = matcher;
4252
}
4353

54+
4455
Expression getAuthorizeExpression() {
4556
return authorizeExpression;
4657
}
@@ -53,4 +64,15 @@ public String getAttribute() {
5364
public String toString() {
5465
return authorizeExpression.getExpressionString();
5566
}
67+
68+
@Override
69+
public EvaluationContext postProcess(EvaluationContext ctx, Message<?> message) {
70+
if (matcher instanceof SimpDestinationMessageMatcher) {
71+
final Map<String, String> variables = ((SimpDestinationMessageMatcher) matcher).extractPathVariables(message);
72+
for (Map.Entry<String, String> entry : variables.entrySet()){
73+
ctx.setVariable(entry.getKey(), entry.getValue());
74+
}
75+
}
76+
return ctx;
77+
}
5678
}

messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionVoter.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@
3535
*
3636
* @since 4.0
3737
* @author Rob Winch
38+
* @author Daniel Bustamante Ospina
3839
*/
3940
public class MessageExpressionVoter<T> implements AccessDecisionVoter<Message<T>> {
4041
private SecurityExpressionHandler<Message<T>> expressionHandler = new DefaultMessageSecurityExpressionHandler<>();
@@ -53,6 +54,7 @@ public int vote(Authentication authentication, Message<T> message,
5354

5455
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
5556
message);
57+
ctx = attr.postProcess(ctx, message);
5658

5759
return ExpressionUtils.evaluateAsBoolean(attr.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
5860
: ACCESS_DENIED;

messaging/src/main/java/org/springframework/security/messaging/util/matcher/SimpDestinationMessageMatcher.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
import org.springframework.util.Assert;
2323
import org.springframework.util.PathMatcher;
2424

25+
import java.util.Collections;
26+
import java.util.Map;
27+
2528
/**
2629
* <p>
2730
* MessageMatcher which compares a pre-defined pattern against the destination of a
@@ -129,6 +132,14 @@ public boolean matches(Message<? extends Object> message) {
129132
return destination != null && matcher.match(pattern, destination);
130133
}
131134

135+
136+
public Map<String, String> extractPathVariables(Message<? extends Object> message){
137+
final String destination = SimpMessageHeaderAccessor.getDestination(message
138+
.getHeaders());
139+
return destination != null ? matcher.extractUriTemplateVariables(pattern, destination)
140+
: Collections.emptyMap();
141+
}
142+
132143
public MessageMatcher<Object> getMessageTypeMatcher() {
133144
return messageTypeMatcher;
134145
}
@@ -175,4 +186,4 @@ public static SimpDestinationMessageMatcher createMessageMatcher(String pattern,
175186
return new SimpDestinationMessageMatcher(pattern, SimpMessageType.MESSAGE,
176187
matcher);
177188
}
178-
}
189+
}

messaging/src/test/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttributeTests.java

+30-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,26 +20,40 @@
2020
import org.junit.runner.RunWith;
2121
import org.mockito.Mock;
2222
import org.mockito.junit.MockitoJUnitRunner;
23+
import org.springframework.expression.EvaluationContext;
2324
import org.springframework.expression.Expression;
25+
import org.springframework.messaging.Message;
26+
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
27+
import org.springframework.messaging.support.MessageBuilder;
28+
import org.springframework.security.messaging.util.matcher.MessageMatcher;
29+
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
2430

2531
import static org.assertj.core.api.Assertions.assertThat;
26-
import static org.mockito.Mockito.when;
32+
import static org.mockito.Mockito.*;
2733

2834
@RunWith(MockitoJUnitRunner.class)
2935
public class MessageExpressionConfigAttributeTests {
3036
@Mock
3137
Expression expression;
3238

39+
@Mock
40+
MessageMatcher<?> matcher;
41+
3342
MessageExpressionConfigAttribute attribute;
3443

3544
@Before
3645
public void setup() {
37-
attribute = new MessageExpressionConfigAttribute(expression);
46+
attribute = new MessageExpressionConfigAttribute(expression, matcher);
3847
}
3948

4049
@Test(expected = IllegalArgumentException.class)
4150
public void constructorNullExpression() {
42-
new MessageExpressionConfigAttribute(null);
51+
new MessageExpressionConfigAttribute(null, matcher);
52+
}
53+
54+
@Test(expected = IllegalArgumentException.class)
55+
public void constructorNullMatcher() {
56+
new MessageExpressionConfigAttribute(expression, null);
4357
}
4458

4559
@Test
@@ -58,4 +72,16 @@ public void toStringUsesExpressionString() {
5872

5973
assertThat(attribute.toString()).isEqualTo(expression.getExpressionString());
6074
}
75+
76+
@Test
77+
public void postProcessContext() {
78+
SimpDestinationMessageMatcher matcher = new SimpDestinationMessageMatcher("/topics/{topic}/**");
79+
Message<?> message = MessageBuilder.withPayload("M").setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER, "/topics/someTopic/sub1").build();
80+
EvaluationContext context = mock(EvaluationContext.class);
81+
82+
attribute = new MessageExpressionConfigAttribute(expression, matcher);
83+
attribute.postProcess(context, message);
84+
85+
verify(context).setVariable("topic", "someTopic");
86+
}
6187
}

messaging/src/test/java/org/springframework/security/messaging/access/expression/MessageExpressionVoterTests.java

+22-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
2727
import org.springframework.security.access.SecurityConfig;
2828
import org.springframework.security.access.expression.SecurityExpressionHandler;
2929
import org.springframework.security.core.Authentication;
30+
import org.springframework.security.messaging.util.matcher.MessageMatcher;
3031

3132
import java.util.Arrays;
3233
import java.util.Collection;
@@ -45,6 +46,8 @@ public class MessageExpressionVoterTests {
4546
@Mock
4647
Expression expression;
4748
@Mock
49+
MessageMatcher<?> matcher;
50+
@Mock
4851
SecurityExpressionHandler<Message> expressionHandler;
4952
@Mock
5053
EvaluationContext evaluationContext;
@@ -54,7 +57,7 @@ public class MessageExpressionVoterTests {
5457
@Before
5558
public void setup() {
5659
attributes = Arrays
57-
.<ConfigAttribute> asList(new MessageExpressionConfigAttribute(expression));
60+
.<ConfigAttribute> asList(new MessageExpressionConfigAttribute(expression, matcher));
5861

5962
voter = new MessageExpressionVoter();
6063
}
@@ -99,7 +102,7 @@ public void supportsSecurityConfigFalse() {
99102

100103
@Test
101104
public void supportsMessageExpressionConfigAttributeTrue() {
102-
assertThat(voter.supports(new MessageExpressionConfigAttribute(expression)))
105+
assertThat(voter.supports(new MessageExpressionConfigAttribute(expression, matcher)))
103106
.isTrue();
104107
}
105108

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

121124
verify(expressionHandler).createEvaluationContext(authentication, message);
122125
}
126+
127+
@Test
128+
public void postProcessEvaluationContext(){
129+
final MessageExpressionConfigAttribute configAttribute = mock(MessageExpressionConfigAttribute.class);
130+
voter.setExpressionHandler(expressionHandler);
131+
when(expressionHandler.createEvaluationContext(authentication, message)).thenReturn(evaluationContext);
132+
when(configAttribute.getAuthorizeExpression()).thenReturn(expression);
133+
attributes = Arrays.<ConfigAttribute> asList(configAttribute);
134+
when(configAttribute.postProcess(evaluationContext, message)).thenReturn(evaluationContext);
135+
when(expression.getValue(any(EvaluationContext.class), eq(Boolean.class)))
136+
.thenReturn(true);
137+
138+
assertThat(voter.vote(authentication, message, attributes)).isEqualTo(
139+
ACCESS_GRANTED);
140+
verify(configAttribute).postProcess(evaluationContext, message);
141+
}
123142
}

messaging/src/test/java/org/springframework/security/messaging/util/matcher/SimpDestinationMessageMatcherTests.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,23 @@ public void matchesNullMessageType() throws Exception {
127127
assertThat(matcher.matches(messageBuilder.build())).isTrue();
128128
}
129129

130+
@Test
131+
public void extractPathVariablesFromDestination() throws Exception {
132+
matcher = new SimpDestinationMessageMatcher("/topics/{topic}/**");
133+
134+
messageBuilder.setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER, "/topics/someTopic/sub1");
135+
messageBuilder.setHeader(SimpMessageHeaderAccessor.MESSAGE_TYPE_HEADER,
136+
SimpMessageType.MESSAGE);
137+
138+
assertThat(matcher.extractPathVariables(messageBuilder.build()).get("topic")).isEqualTo("someTopic");
139+
}
140+
141+
@Test
142+
public void extractedVariablesAreEmptyInNullDestination() throws Exception {
143+
matcher = new SimpDestinationMessageMatcher("/topics/{topic}/**");
144+
assertThat(matcher.extractPathVariables(messageBuilder.build())).isEmpty();
145+
}
146+
130147
@Test
131148
public void typeConstructorParameterIsTransmitted() throws Exception {
132149
matcher = SimpDestinationMessageMatcher.createMessageMatcher("/match",
@@ -139,4 +156,4 @@ public void typeConstructorParameterIsTransmitted() throws Exception {
139156

140157
}
141158

142-
}
159+
}

0 commit comments

Comments
 (0)