Skip to content

Commit 492bde7

Browse files
committed
Merge branch '6.0.x'
Closes gh-12924
2 parents 13f707a + 97b53f0 commit 492bde7

File tree

2 files changed

+154
-46
lines changed

2 files changed

+154
-46
lines changed

messaging/src/main/java/org/springframework/security/messaging/access/intercept/MessageMatcherDelegatingAuthorizationManager.java

Lines changed: 26 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.List;
21+
import java.util.Map;
2122
import java.util.function.Supplier;
2223

2324
import org.apache.commons.logging.Log;
@@ -37,6 +38,7 @@
3738
import org.springframework.util.AntPathMatcher;
3839
import org.springframework.util.Assert;
3940
import org.springframework.util.PathMatcher;
41+
import org.springframework.util.function.SingletonSupplier;
4042

4143
public final class MessageMatcherDelegatingAuthorizationManager implements AuthorizationManager<Message<?>> {
4244

@@ -87,6 +89,10 @@ private MessageAuthorizationContext<?> authorizationContext(MessageMatcher<?> ma
8789
SimpDestinationMessageMatcher simp = (SimpDestinationMessageMatcher) matcher;
8890
return new MessageAuthorizationContext<>(message, simp.extractPathVariables(message));
8991
}
92+
if (matcher instanceof Builder.LazySimpDestinationMessageMatcher) {
93+
Builder.LazySimpDestinationMessageMatcher path = (Builder.LazySimpDestinationMessageMatcher) matcher;
94+
return new MessageAuthorizationContext<>(message, path.extractPathVariables(message));
95+
}
9096
return new MessageAuthorizationContext<>(message);
9197
}
9298

@@ -192,8 +198,7 @@ public Builder.Constraint simpSubscribeDestMatchers(String... patterns) {
192198
private Builder.Constraint simpDestMatchers(SimpMessageType type, String... patterns) {
193199
List<MessageMatcher<?>> matchers = new ArrayList<>(patterns.length);
194200
for (String pattern : patterns) {
195-
Supplier<MessageMatcher<Object>> supplier = new Builder.PathMatcherMessageMatcherBuilder(pattern, type);
196-
MessageMatcher<?> matcher = new Builder.SupplierMessageMatcher(supplier);
201+
MessageMatcher<Object> matcher = new LazySimpDestinationMessageMatcher(pattern, type);
197202
matchers.add(matcher);
198203
}
199204
return new Builder.Constraint(matchers);
@@ -375,58 +380,33 @@ public Builder access(AuthorizationManager<MessageAuthorizationContext<?>> autho
375380

376381
}
377382

378-
private static final class SupplierMessageMatcher implements MessageMatcher<Object> {
379-
380-
private final Supplier<MessageMatcher<Object>> supplier;
383+
private final class LazySimpDestinationMessageMatcher implements MessageMatcher<Object> {
381384

382-
private volatile MessageMatcher<Object> delegate;
385+
private final Supplier<SimpDestinationMessageMatcher> delegate;
383386

384-
SupplierMessageMatcher(Supplier<MessageMatcher<Object>> supplier) {
385-
this.supplier = supplier;
387+
private LazySimpDestinationMessageMatcher(String pattern, SimpMessageType type) {
388+
this.delegate = SingletonSupplier.of(() -> {
389+
PathMatcher pathMatcher = Builder.this.pathMatcher.get();
390+
if (type == null) {
391+
return new SimpDestinationMessageMatcher(pattern, pathMatcher);
392+
}
393+
if (SimpMessageType.MESSAGE == type) {
394+
return SimpDestinationMessageMatcher.createMessageMatcher(pattern, pathMatcher);
395+
}
396+
if (SimpMessageType.SUBSCRIBE == type) {
397+
return SimpDestinationMessageMatcher.createSubscribeMatcher(pattern, pathMatcher);
398+
}
399+
throw new IllegalStateException(type + " is not supported since it does not have a destination");
400+
});
386401
}
387402

388403
@Override
389404
public boolean matches(Message<?> message) {
390-
if (this.delegate == null) {
391-
synchronized (this.supplier) {
392-
if (this.delegate == null) {
393-
this.delegate = this.supplier.get();
394-
}
395-
}
396-
}
397-
return this.delegate.matches(message);
398-
}
399-
400-
}
401-
402-
private final class PathMatcherMessageMatcherBuilder implements Supplier<MessageMatcher<Object>> {
403-
404-
private final String pattern;
405-
406-
private final SimpMessageType type;
407-
408-
private PathMatcherMessageMatcherBuilder(String pattern, SimpMessageType type) {
409-
this.pattern = pattern;
410-
this.type = type;
411-
}
412-
413-
private PathMatcher resolvePathMatcher() {
414-
return Builder.this.pathMatcher.get();
405+
return this.delegate.get().matches(message);
415406
}
416407

417-
@Override
418-
public MessageMatcher<Object> get() {
419-
PathMatcher pathMatcher = resolvePathMatcher();
420-
if (this.type == null) {
421-
return new SimpDestinationMessageMatcher(this.pattern, pathMatcher);
422-
}
423-
if (SimpMessageType.MESSAGE == this.type) {
424-
return SimpDestinationMessageMatcher.createMessageMatcher(this.pattern, pathMatcher);
425-
}
426-
if (SimpMessageType.SUBSCRIBE == this.type) {
427-
return SimpDestinationMessageMatcher.createSubscribeMatcher(this.pattern, pathMatcher);
428-
}
429-
throw new IllegalStateException(this.type + " is not supported since it does not have a destination");
408+
Map<String, String> extractPathVariables(Message<?> message) {
409+
return this.delegate.get().extractPathVariables(message);
430410
}
431411

432412
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Copyright 2002-2023 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+
* https://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+
17+
package org.springframework.security.messaging.access.intercept;
18+
19+
import java.util.Map;
20+
import java.util.function.Supplier;
21+
22+
import org.junit.jupiter.api.Test;
23+
24+
import org.springframework.messaging.Message;
25+
import org.springframework.messaging.MessageHeaders;
26+
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
27+
import org.springframework.messaging.simp.SimpMessageType;
28+
import org.springframework.messaging.support.GenericMessage;
29+
import org.springframework.security.authentication.TestingAuthenticationToken;
30+
import org.springframework.security.authorization.AuthorizationDecision;
31+
import org.springframework.security.authorization.AuthorizationManager;
32+
import org.springframework.security.core.Authentication;
33+
34+
import static org.assertj.core.api.Assertions.assertThat;
35+
import static org.mockito.Mockito.mock;
36+
37+
/**
38+
* Tests for {@link MessageMatcherDelegatingAuthorizationManager}
39+
*/
40+
public final class MessageMatcherDelegatingAuthorizationManagerTests {
41+
42+
@Test
43+
void checkWhenPermitAllThenPermits() {
44+
AuthorizationManager<Message<?>> authorizationManager = builder().anyMessage().permitAll().build();
45+
Message<?> message = new GenericMessage<>(new Object());
46+
assertThat(authorizationManager.check(mock(Supplier.class), message).isGranted()).isTrue();
47+
}
48+
49+
@Test
50+
void checkWhenAnyMessageHasRoleThenRequires() {
51+
AuthorizationManager<Message<?>> authorizationManager = builder().anyMessage().hasRole("USER").build();
52+
Message<?> message = new GenericMessage<>(new Object());
53+
Authentication user = new TestingAuthenticationToken("user", "password", "ROLE_USER");
54+
assertThat(authorizationManager.check(() -> user, message).isGranted()).isTrue();
55+
Authentication admin = new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
56+
assertThat(authorizationManager.check(() -> admin, message).isGranted()).isFalse();
57+
}
58+
59+
@Test
60+
void checkWhenSimpDestinationMatchesThenUses() {
61+
AuthorizationManager<Message<?>> authorizationManager = builder().simpDestMatchers("destination").permitAll()
62+
.anyMessage().denyAll().build();
63+
MessageHeaders headers = new MessageHeaders(
64+
Map.of(SimpMessageHeaderAccessor.DESTINATION_HEADER, "destination"));
65+
Message<?> message = new GenericMessage<>(new Object(), headers);
66+
assertThat(authorizationManager.check(mock(Supplier.class), message).isGranted()).isTrue();
67+
}
68+
69+
@Test
70+
void checkWhenNullDestinationHeaderMatchesThenUses() {
71+
AuthorizationManager<Message<?>> authorizationManager = builder().nullDestMatcher().permitAll().anyMessage()
72+
.denyAll().build();
73+
Message<?> message = new GenericMessage<>(new Object());
74+
assertThat(authorizationManager.check(mock(Supplier.class), message).isGranted()).isTrue();
75+
MessageHeaders headers = new MessageHeaders(
76+
Map.of(SimpMessageHeaderAccessor.DESTINATION_HEADER, "destination"));
77+
message = new GenericMessage<>(new Object(), headers);
78+
assertThat(authorizationManager.check(mock(Supplier.class), message).isGranted()).isFalse();
79+
}
80+
81+
@Test
82+
void checkWhenSimpTypeMatchesThenUses() {
83+
AuthorizationManager<Message<?>> authorizationManager = builder().simpTypeMatchers(SimpMessageType.CONNECT)
84+
.permitAll().anyMessage().denyAll().build();
85+
MessageHeaders headers = new MessageHeaders(
86+
Map.of(SimpMessageHeaderAccessor.MESSAGE_TYPE_HEADER, SimpMessageType.CONNECT));
87+
Message<?> message = new GenericMessage<>(new Object(), headers);
88+
assertThat(authorizationManager.check(mock(Supplier.class), message).isGranted()).isTrue();
89+
}
90+
91+
// gh-12540
92+
@Test
93+
void checkWhenSimpDestinationMatchesThenVariablesExtracted() {
94+
AuthorizationManager<Message<?>> authorizationManager = builder().simpDestMatchers("destination/{id}")
95+
.access(variable("id").isEqualTo("3")).anyMessage().denyAll().build();
96+
MessageHeaders headers = new MessageHeaders(
97+
Map.of(SimpMessageHeaderAccessor.DESTINATION_HEADER, "destination/3"));
98+
Message<?> message = new GenericMessage<>(new Object(), headers);
99+
assertThat(authorizationManager.check(mock(Supplier.class), message).isGranted()).isTrue();
100+
}
101+
102+
private MessageMatcherDelegatingAuthorizationManager.Builder builder() {
103+
return MessageMatcherDelegatingAuthorizationManager.builder();
104+
}
105+
106+
private Builder variable(String name) {
107+
return new Builder(name);
108+
109+
}
110+
111+
private static final class Builder {
112+
113+
private final String name;
114+
115+
private Builder(String name) {
116+
this.name = name;
117+
}
118+
119+
AuthorizationManager<MessageAuthorizationContext<?>> isEqualTo(String value) {
120+
return (authentication, object) -> {
121+
String extracted = object.getVariables().get(this.name);
122+
return new AuthorizationDecision(value.equals(extracted));
123+
};
124+
}
125+
126+
}
127+
128+
}

0 commit comments

Comments
 (0)