Skip to content

Commit c004add

Browse files
added support for authorization events in DelegatingAuthorizationManager
1 parent d948528 commit c004add

File tree

7 files changed

+297
-3
lines changed

7 files changed

+297
-3
lines changed
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+
}
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+
}
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+
}
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.Before;
20+
import org.junit.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+
@Before
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+
}

Diff for: web/src/main/java/org/springframework/security/web/access/intercept/DelegatingAuthorizationManager.java

+28-2
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.
@@ -27,6 +27,7 @@
2727

2828
import org.springframework.core.log.LogMessage;
2929
import org.springframework.security.authorization.AuthorizationDecision;
30+
import org.springframework.security.authorization.AuthorizationEventPublisher;
3031
import org.springframework.security.authorization.AuthorizationManager;
3132
import org.springframework.security.core.Authentication;
3233
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -38,6 +39,7 @@
3839
* {@link AuthorizationManager} based on a {@link RequestMatcher} evaluation.
3940
*
4041
* @author Evgeniy Cheban
42+
* @author Parikshit Dutta
4143
* @since 5.5
4244
*/
4345
public final class DelegatingAuthorizationManager implements AuthorizationManager<HttpServletRequest> {
@@ -46,6 +48,8 @@ public final class DelegatingAuthorizationManager implements AuthorizationManage
4648

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

51+
private AuthorizationEventPublisher authorizationEventPublisher;
52+
4953
private DelegatingAuthorizationManager(
5054
Map<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> mappings) {
5155
Assert.notEmpty(mappings, "mappings cannot be empty");
@@ -76,14 +80,36 @@ public AuthorizationDecision check(Supplier<Authentication> authentication, Http
7680
if (this.logger.isTraceEnabled()) {
7781
this.logger.trace(LogMessage.format("Checking authorization on %s using %s", request, manager));
7882
}
79-
return manager.check(authentication,
83+
AuthorizationDecision authorizationDecision = manager.check(authentication,
8084
new RequestAuthorizationContext(request, matchResult.getVariables()));
85+
publishAuthorizationEvent(authorizationDecision);
86+
return authorizationDecision;
8187
}
8288
}
8389
this.logger.trace("Abstaining since did not find matching RequestMatcher");
8490
return null;
8591
}
8692

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

Diff for: web/src/test/java/org/springframework/security/web/access/intercept/DelegatingAuthorizationManagerTests.java

+41-1
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.
@@ -23,16 +23,20 @@
2323
import org.springframework.mock.web.MockHttpServletRequest;
2424
import org.springframework.security.authentication.TestingAuthenticationToken;
2525
import org.springframework.security.authorization.AuthorizationDecision;
26+
import org.springframework.security.authorization.AuthorizationEventPublisher;
2627
import org.springframework.security.core.Authentication;
2728
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
2829

2930
import static org.assertj.core.api.Assertions.assertThat;
3031
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
32+
import static org.mockito.Mockito.mock;
33+
import static org.mockito.Mockito.verify;
3134

3235
/**
3336
* Tests for {@link DelegatingAuthorizationManager}.
3437
*
3538
* @author Evgeniy Cheban
39+
* @author Parikshit Dutta
3640
*/
3741
public class DelegatingAuthorizationManagerTests {
3842

@@ -81,4 +85,40 @@ public void checkWhenMultipleMappingsConfiguredThenDelegatesMatchingManager() {
8185
assertThat(abstain).isNull();
8286
}
8387

88+
@Test
89+
public void testAuthorizationEventPublisherIsNotNull() {
90+
DelegatingAuthorizationManager manager = DelegatingAuthorizationManager.builder()
91+
.add(new MvcRequestMatcher(null, "/grant"), (a, o) -> new AuthorizationDecision(true)).build();
92+
assertThatIllegalArgumentException().isThrownBy(() -> manager.setAuthorizationEventPublisher(null))
93+
.withMessage("AuthorizationEventPublisher cannot be null");
94+
}
95+
96+
@Test
97+
public void testAuthorizationSuccessEventWhenAuthorizationGranted() {
98+
DelegatingAuthorizationManager manager = DelegatingAuthorizationManager.builder()
99+
.add(new MvcRequestMatcher(null, "/grant"), (a, o) -> new AuthorizationDecision(true)).build();
100+
101+
AuthorizationEventPublisher authorizationEventPublisher = mock(AuthorizationEventPublisher.class);
102+
manager.setAuthorizationEventPublisher(authorizationEventPublisher);
103+
104+
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
105+
106+
AuthorizationDecision grant = manager.check(authentication, new MockHttpServletRequest(null, "/grant"));
107+
verify(authorizationEventPublisher).publishAuthorizationSuccess(grant);
108+
}
109+
110+
@Test
111+
public void testAuthorizationFailureEventWhenAuthorizationNotGranted() {
112+
DelegatingAuthorizationManager manager = DelegatingAuthorizationManager.builder()
113+
.add(new MvcRequestMatcher(null, "/deny"), (a, o) -> new AuthorizationDecision(false)).build();
114+
115+
AuthorizationEventPublisher authorizationEventPublisher = mock(AuthorizationEventPublisher.class);
116+
manager.setAuthorizationEventPublisher(authorizationEventPublisher);
117+
118+
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
119+
120+
AuthorizationDecision grant = manager.check(authentication, new MockHttpServletRequest(null, "/deny"));
121+
verify(authorizationEventPublisher).publishAuthorizationFailure(grant);
122+
}
123+
84124
}

0 commit comments

Comments
 (0)