Skip to content

Commit bd94348

Browse files
parikshitduttajzheaux
authored andcommitted
Add authorization events
Closes gh-9288
1 parent a43677d commit bd94348

File tree

7 files changed

+297
-2
lines changed

7 files changed

+297
-2
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2002-2021 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.authorization;
18+
19+
/**
20+
* @author Parikshit Dutta
21+
* @since 5.5
22+
*/
23+
public interface AuthorizationEventPublisher {
24+
25+
void publishAuthorizationSuccess(AuthorizationDecision authorizationDecision);
26+
27+
void publishAuthorizationFailure(AuthorizationDecision authorizationDecision);
28+
29+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2002-2021 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.authorization;
18+
19+
import org.springframework.context.ApplicationEventPublisher;
20+
import org.springframework.context.ApplicationEventPublisherAware;
21+
import org.springframework.security.authorization.event.AuthorizationFailureEvent;
22+
import org.springframework.security.authorization.event.AuthorizationSuccessEvent;
23+
24+
/**
25+
* Default implementation of {@link AuthorizationEventPublisher}
26+
*
27+
* @author Parikshit Dutta
28+
* @since 5.5
29+
*/
30+
public class DefaultAuthorizationEventPublisher implements AuthorizationEventPublisher, ApplicationEventPublisherAware {
31+
32+
private ApplicationEventPublisher applicationEventPublisher;
33+
34+
public DefaultAuthorizationEventPublisher() {
35+
this(null);
36+
}
37+
38+
public DefaultAuthorizationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
39+
this.applicationEventPublisher = applicationEventPublisher;
40+
}
41+
42+
@Override
43+
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
44+
this.applicationEventPublisher = applicationEventPublisher;
45+
}
46+
47+
@Override
48+
public void publishAuthorizationSuccess(AuthorizationDecision authorizationDecision) {
49+
if (this.applicationEventPublisher != null) {
50+
this.applicationEventPublisher.publishEvent(new AuthorizationSuccessEvent(authorizationDecision));
51+
}
52+
}
53+
54+
@Override
55+
public void publishAuthorizationFailure(AuthorizationDecision authorizationDecision) {
56+
if (this.applicationEventPublisher != null) {
57+
this.applicationEventPublisher.publishEvent(new AuthorizationFailureEvent(authorizationDecision));
58+
}
59+
}
60+
61+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2002-2021 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.authorization.event;
18+
19+
import org.springframework.context.ApplicationEvent;
20+
import org.springframework.security.authorization.AuthorizationDecision;
21+
22+
/**
23+
* An {@link ApplicationEvent} which indicates failed authorization.
24+
*
25+
* @author Parikshit Dutta
26+
* @since 5.5
27+
*/
28+
public class AuthorizationFailureEvent extends ApplicationEvent {
29+
30+
public AuthorizationFailureEvent(AuthorizationDecision authorizationDecision) {
31+
super(authorizationDecision);
32+
}
33+
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2002-2021 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.authorization.event;
18+
19+
import org.springframework.context.ApplicationEvent;
20+
import org.springframework.security.authorization.AuthorizationDecision;
21+
22+
/**
23+
* An {@link ApplicationEvent} which indicates successful authorization.
24+
*
25+
* @author Parikshit Dutta
26+
* @since 5.5
27+
*/
28+
public class AuthorizationSuccessEvent extends ApplicationEvent {
29+
30+
public AuthorizationSuccessEvent(AuthorizationDecision authorizationDecision) {
31+
super(authorizationDecision);
32+
}
33+
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2002-2021 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.authorization;
18+
19+
import org.junit.jupiter.api.BeforeEach;
20+
import org.junit.jupiter.api.Test;
21+
22+
import org.springframework.context.ApplicationEventPublisher;
23+
import org.springframework.security.authorization.event.AuthorizationFailureEvent;
24+
import org.springframework.security.authorization.event.AuthorizationSuccessEvent;
25+
26+
import static org.mockito.ArgumentMatchers.any;
27+
import static org.mockito.ArgumentMatchers.isA;
28+
import static org.mockito.Mockito.mock;
29+
import static org.mockito.Mockito.never;
30+
import static org.mockito.Mockito.verify;
31+
32+
/**
33+
* Tests for {@link DefaultAuthorizationEventPublisher}
34+
*
35+
* @author Parikshit Dutta
36+
*/
37+
public class DefaultAuthorizationEventPublisherTests {
38+
39+
ApplicationEventPublisher applicationEventPublisher;
40+
41+
DefaultAuthorizationEventPublisher authorizationEventPublisher;
42+
43+
@BeforeEach
44+
public void init() {
45+
this.applicationEventPublisher = mock(ApplicationEventPublisher.class);
46+
this.authorizationEventPublisher = new DefaultAuthorizationEventPublisher();
47+
this.authorizationEventPublisher.setApplicationEventPublisher(this.applicationEventPublisher);
48+
}
49+
50+
@Test
51+
public void testAuthenticationSuccessIsPublished() {
52+
this.authorizationEventPublisher.publishAuthorizationSuccess(mock(AuthorizationDecision.class));
53+
verify(this.applicationEventPublisher).publishEvent(isA(AuthorizationSuccessEvent.class));
54+
}
55+
56+
@Test
57+
public void testAuthenticationFailureIsPublished() {
58+
this.authorizationEventPublisher.publishAuthorizationFailure(mock(AuthorizationDecision.class));
59+
verify(this.applicationEventPublisher).publishEvent(isA(AuthorizationFailureEvent.class));
60+
}
61+
62+
@Test
63+
public void testNullPublisherNotInvoked() {
64+
this.authorizationEventPublisher.setApplicationEventPublisher(null);
65+
this.authorizationEventPublisher.publishAuthorizationSuccess(mock(AuthorizationDecision.class));
66+
this.authorizationEventPublisher.publishAuthorizationFailure(mock(AuthorizationDecision.class));
67+
verify(this.applicationEventPublisher, never()).publishEvent(any());
68+
}
69+
70+
}

web/src/main/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManager.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import org.springframework.core.log.LogMessage;
3030
import org.springframework.security.authorization.AuthorizationDecision;
31+
import org.springframework.security.authorization.AuthorizationEventPublisher;
3132
import org.springframework.security.authorization.AuthorizationManager;
3233
import org.springframework.security.core.Authentication;
3334
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -39,6 +40,7 @@
3940
* {@link AuthorizationManager} based on a {@link RequestMatcher} evaluation.
4041
*
4142
* @author Evgeniy Cheban
43+
* @author Parikshit Dutta
4244
* @since 5.5
4345
*/
4446
public final class RequestMatcherDelegatingAuthorizationManager implements AuthorizationManager<HttpServletRequest> {
@@ -47,6 +49,8 @@ public final class RequestMatcherDelegatingAuthorizationManager implements Autho
4749

4850
private final Map<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> mappings;
4951

52+
private AuthorizationEventPublisher authorizationEventPublisher;
53+
5054
private RequestMatcherDelegatingAuthorizationManager(
5155
Map<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> mappings) {
5256
Assert.notEmpty(mappings, "mappings cannot be empty");
@@ -77,14 +81,36 @@ public AuthorizationDecision check(Supplier<Authentication> authentication, Http
7781
if (this.logger.isTraceEnabled()) {
7882
this.logger.trace(LogMessage.format("Checking authorization on %s using %s", request, manager));
7983
}
80-
return manager.check(authentication,
84+
AuthorizationDecision authorizationDecision = manager.check(authentication,
8185
new RequestAuthorizationContext(request, matchResult.getVariables()));
86+
publishAuthorizationEvent(authorizationDecision);
87+
return authorizationDecision;
8288
}
8389
}
8490
this.logger.trace("Abstaining since did not find matching RequestMatcher");
8591
return null;
8692
}
8793

94+
private void publishAuthorizationEvent(AuthorizationDecision authorizationDecision) {
95+
if (this.authorizationEventPublisher != null) {
96+
if (authorizationDecision.isGranted()) {
97+
this.authorizationEventPublisher.publishAuthorizationSuccess(authorizationDecision);
98+
}
99+
else {
100+
this.authorizationEventPublisher.publishAuthorizationFailure(authorizationDecision);
101+
}
102+
}
103+
}
104+
105+
/**
106+
* Set implementation of an {@link AuthorizationEventPublisher}
107+
* @param authorizationEventPublisher
108+
*/
109+
public void setAuthorizationEventPublisher(AuthorizationEventPublisher authorizationEventPublisher) {
110+
Assert.notNull(authorizationEventPublisher, "AuthorizationEventPublisher cannot be null");
111+
this.authorizationEventPublisher = authorizationEventPublisher;
112+
}
113+
88114
/**
89115
* Creates a builder for {@link RequestMatcherDelegatingAuthorizationManager}.
90116
* @return the new {@link Builder} instance

web/src/test/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManagerTests.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 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.
@@ -24,17 +24,21 @@
2424
import org.springframework.security.authentication.TestingAuthenticationToken;
2525
import org.springframework.security.authorization.AuthorityAuthorizationManager;
2626
import org.springframework.security.authorization.AuthorizationDecision;
27+
import org.springframework.security.authorization.AuthorizationEventPublisher;
2728
import org.springframework.security.core.Authentication;
2829
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
2930
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
3031

3132
import static org.assertj.core.api.Assertions.assertThat;
3233
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
34+
import static org.mockito.Mockito.mock;
35+
import static org.mockito.Mockito.verify;
3336

3437
/**
3538
* Tests for {@link RequestMatcherDelegatingAuthorizationManager}.
3639
*
3740
* @author Evgeniy Cheban
41+
* @author Parikshit Dutta
3842
*/
3943
public class RequestMatcherDelegatingAuthorizationManagerTests {
4044

@@ -98,6 +102,7 @@ public void checkWhenMultipleMappingsConfiguredWithConsumerThenDelegatesMatching
98102
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
99103

100104
AuthorizationDecision grant = manager.check(authentication, new MockHttpServletRequest(null, "/grant"));
105+
101106
assertThat(grant).isNotNull();
102107
assertThat(grant.isGranted()).isTrue();
103108

@@ -121,4 +126,40 @@ public void addWhenMappingsConsumerNullThenException() {
121126
.withMessage("mappingsConsumer cannot be null");
122127
}
123128

129+
@Test
130+
public void testAuthorizationEventPublisherIsNotNull() {
131+
RequestMatcherDelegatingAuthorizationManager manager = RequestMatcherDelegatingAuthorizationManager.builder()
132+
.add(new MvcRequestMatcher(null, "/grant"), (a, o) -> new AuthorizationDecision(true)).build();
133+
assertThatIllegalArgumentException().isThrownBy(() -> manager.setAuthorizationEventPublisher(null))
134+
.withMessage("AuthorizationEventPublisher cannot be null");
135+
}
136+
137+
@Test
138+
public void testAuthorizationSuccessEventWhenAuthorizationGranted() {
139+
RequestMatcherDelegatingAuthorizationManager manager = RequestMatcherDelegatingAuthorizationManager.builder()
140+
.add(new MvcRequestMatcher(null, "/grant"), (a, o) -> new AuthorizationDecision(true)).build();
141+
142+
AuthorizationEventPublisher authorizationEventPublisher = mock(AuthorizationEventPublisher.class);
143+
manager.setAuthorizationEventPublisher(authorizationEventPublisher);
144+
145+
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
146+
147+
AuthorizationDecision grant = manager.check(authentication, new MockHttpServletRequest(null, "/grant"));
148+
verify(authorizationEventPublisher).publishAuthorizationSuccess(grant);
149+
}
150+
151+
@Test
152+
public void testAuthorizationFailureEventWhenAuthorizationNotGranted() {
153+
RequestMatcherDelegatingAuthorizationManager manager = RequestMatcherDelegatingAuthorizationManager.builder()
154+
.add(new MvcRequestMatcher(null, "/deny"), (a, o) -> new AuthorizationDecision(false)).build();
155+
156+
AuthorizationEventPublisher authorizationEventPublisher = mock(AuthorizationEventPublisher.class);
157+
manager.setAuthorizationEventPublisher(authorizationEventPublisher);
158+
159+
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
160+
161+
AuthorizationDecision grant = manager.check(authentication, new MockHttpServletRequest(null, "/deny"));
162+
verify(authorizationEventPublisher).publishAuthorizationFailure(grant);
163+
}
164+
124165
}

0 commit comments

Comments
 (0)