2 * Copyright (c) 2017 Brocade Communications Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.aaa.shiro.realm;
10 import static junit.framework.TestCase.assertFalse;
11 import static junit.framework.TestCase.assertTrue;
12 import static org.mockito.ArgumentMatchers.any;
13 import static org.mockito.Mockito.doReturn;
14 import static org.mockito.Mockito.mock;
15 import static org.mockito.Mockito.when;
16 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFailedFluentFuture;
17 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture;
19 import java.util.List;
20 import java.util.Optional;
22 import javax.servlet.ServletException;
23 import javax.servlet.ServletRequest;
24 import javax.servlet.ServletResponse;
25 import javax.servlet.http.HttpServletRequest;
26 import org.apache.shiro.subject.Subject;
27 import org.junit.Test;
28 import org.opendaylight.mdsal.binding.api.DataBroker;
29 import org.opendaylight.mdsal.binding.api.ReadTransaction;
30 import org.opendaylight.mdsal.common.api.ReadFailedException;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.HttpAuthorization;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.Policies;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.permission.Permissions;
34 import org.opendaylight.yangtools.yang.binding.DataObject;
35 import org.opendaylight.yangtools.yang.common.Uint32;
38 * Tests the Dynamic Authorization Filter.
40 @SuppressWarnings("checkstyle:AbbreviationAsWordInName")
41 public class MDSALDynamicAuthorizationFilterTest {
43 private static DataBroker mockDataBroker(final Object readData) {
44 final ReadTransaction readOnlyTransaction = mock(ReadTransaction.class);
46 if (readData instanceof DataObject) {
47 doReturn(immediateFluentFuture(Optional.of((DataObject)readData)))
48 .when(readOnlyTransaction).read(any(), any());
49 } else if (readData instanceof Exception) {
50 doReturn(immediateFailedFluentFuture((Exception)readData)).when(readOnlyTransaction).read(any(), any());
52 doReturn(immediateFluentFuture(Optional.empty())).when(readOnlyTransaction).read(any(), any());
55 final DataBroker mockDataBroker = mock(DataBroker.class);
56 when(mockDataBroker.newReadOnlyTransaction()).thenReturn(readOnlyTransaction);
57 return mockDataBroker;
60 private static MDSALDynamicAuthorizationFilter newFilter(final Subject subject, final DataBroker dataBroker)
61 throws ServletException {
62 final var ret = new MDSALDynamicAuthorizationFilter(dataBroker) {
64 protected Subject getSubject(final ServletRequest request, final ServletResponse servletResponse) {
69 ret.processPathConfig("test-path","test-config");
73 // test helper method to generate some cool mdsal data
74 private static DataBroker getTestData() {
75 return getTestData("/**", "admin", "Default Test AuthZ Rule", Permissions.Actions.Put);
78 // test helper method to generate some cool mdsal data
79 private static DataBroker getTestData(final String resource, final String role, final String description,
80 final Permissions.Actions actions) {
82 final Permissions permissions = mock(Permissions.class);
83 when(permissions.getRole()).thenReturn(role);
84 when(permissions.getActions()).thenReturn(Set.of(actions));
85 final var innerPolicies = mock(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214
86 .http.authorization.policies.Policies.class);
87 when(innerPolicies.getResource()).thenReturn(resource);
88 when(innerPolicies.getDescription()).thenReturn(description);
89 when(innerPolicies.getPermissions()).thenReturn(List.of(permissions));
90 final Policies policies = mock(Policies.class);
91 when(policies.getPolicies()).thenReturn(List.of(innerPolicies));
92 final HttpAuthorization httpAuthorization = mock(HttpAuthorization.class);
93 when(httpAuthorization.getPolicies()).thenReturn(policies);
95 return mockDataBroker(httpAuthorization);
99 public void testBasicAccessWithNoRules() throws Exception {
100 final HttpServletRequest request = mock(HttpServletRequest.class);
101 when(request.getRequestURI()).thenReturn("abc");
102 when(request.getMethod()).thenReturn("Put");
105 // Test Setup: No rules are added to the HttpAuthorization container. Open access should be allowed.
106 MDSALDynamicAuthorizationFilter filter = newFilter(mock(Subject.class), mockDataBroker(null));
108 // Ensure that access is allowed since no data is returned from the MDSAL read.
109 // This is through making sure the Optional is not present.
110 assertTrue(filter.isAccessAllowed(request, null, null));
113 // Same as above, but with an empty policy list returned.
115 final Policies policies = mock(Policies.class);
116 when(policies.getPolicies()).thenReturn(List.of());
117 final HttpAuthorization httpAuthorization = mock(HttpAuthorization.class);
118 when(httpAuthorization.getPolicies()).thenReturn(policies);
119 filter = newFilter(mock(Subject.class), mockDataBroker(httpAuthorization));
121 assertTrue(filter.isAccessAllowed(request, null, null));
125 public void testMDSALExceptionDuringRead() throws Exception {
126 // Test Setup: No rules are added to the HttpAuthorization container. The MDSAL read
127 // is instructed to return an immediateFailedFluentFuture, to emulate an error in reading
130 final HttpServletRequest request = mock(HttpServletRequest.class);
131 when(request.getRequestURI()).thenReturn("abc");
132 when(request.getMethod()).thenReturn("Put");
134 MDSALDynamicAuthorizationFilter filter = newFilter(mock(Subject.class),
135 mockDataBroker(new ReadFailedException("Test Fail")));
137 // Ensure that if an error occurs while reading MD-SAL that access is denied.
138 assertFalse(filter.isAccessAllowed(request, null, null));
142 public void testBasicAccessWithOneRule() throws Exception {
147 // A Rule is added to match /** allowing HTTP PUT for the admin role.
148 // All other Methods are considered unauthorized.
150 final Subject subject = mock(Subject.class);
151 final MDSALDynamicAuthorizationFilter filter = newFilter(subject, getTestData());
153 final HttpServletRequest request = mock(HttpServletRequest.class);
154 when(request.getRequestURI()).thenReturn("abc");
155 when(request.getMethod()).thenReturn("Put");
156 when(subject.hasRole("admin")).thenReturn(true);
161 // Make a PUT HTTP request from a Subject with the admin role. The request URL does not match,
162 // since "abc" does not start with a "/" character. Since no rule exists for this particular request,
163 // then access should be allowed.
164 assertTrue(filter.isAccessAllowed(request, null, null));
169 // Repeat of the above against a matching endpoint. Access should be allowed.
170 when(request.getRequestURI()).thenReturn("/anotherexamplethatshouldwork");
171 assertTrue(filter.isAccessAllowed(request, null, null));
176 // Repeat of the above request against a more complex endpoint. Access should be allowed.
177 when(request.getRequestURI()).thenReturn("/auth/v1/users");
178 assertTrue(filter.isAccessAllowed(request, null, null));
183 // Negative test case-- ensure that when an unallowed method (POST) is tried with an otherwise
184 // allowable request, that access is denied.
185 when(request.getMethod()).thenReturn("Post");
186 assertFalse(filter.isAccessAllowed(request, null, null));
191 // Negative test case-- ensure that when an unallowed role is tried with an otherwise allowable
192 // request, that acess is denied.
193 when(request.getMethod()).thenReturn("Put");
194 when(subject.hasRole("admin")).thenReturn(false);
195 assertFalse(filter.isAccessAllowed(request, null, null));
199 public void testSeveralMatchingRules() throws Exception {
203 // Create some mock data which has a couple of rules which may/may not match. This
204 // test ensures the correct application of said rules.
205 final Set<Permissions.Actions> actionsList = Set.of(Permissions.Actions.Get, Permissions.Actions.Delete,
206 Permissions.Actions.Patch, Permissions.Actions.Put, Permissions.Actions.Post);
207 final String role = "admin";
208 final String resource = "/**";
209 final String resource2 = "/specialendpoint/**";
210 final String description = "All encompassing rule";
211 final Permissions permissions = mock(Permissions.class);
212 when(permissions.getRole()).thenReturn(role);
213 when(permissions.getActions()).thenReturn(actionsList);
214 final var innerPolicies = mock(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214
215 .http.authorization.policies.Policies.class);
216 when(innerPolicies.getResource()).thenReturn(resource);
217 when(innerPolicies.getIndex()).thenReturn(Uint32.valueOf(5));
218 when(innerPolicies.getDescription()).thenReturn(description);
219 when(innerPolicies.getPermissions()).thenReturn(List.of(permissions));
220 final var innerPolicies2 = mock(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214
221 .http.authorization.policies.Policies.class);
222 when(innerPolicies2.getResource()).thenReturn(resource2);
223 when(innerPolicies2.getIndex()).thenReturn(Uint32.TEN);
224 final Permissions permissions2 = mock(Permissions.class);
225 when(permissions2.getRole()).thenReturn("dog");
226 when(permissions2.getActions()).thenReturn(actionsList);
227 when(innerPolicies2.getPermissions()).thenReturn(List.of(permissions2));
228 when(innerPolicies2.getDescription()).thenReturn("Specialized Rule");
229 final Policies policies = mock(Policies.class);
230 when(policies.getPolicies()).thenReturn(List.of(innerPolicies, innerPolicies2));
231 final HttpAuthorization httpAuthorization = mock(HttpAuthorization.class);
232 when(httpAuthorization.getPolicies()).thenReturn(policies);
234 final Subject subject = mock(Subject.class);
235 final MDSALDynamicAuthorizationFilter filter = newFilter(subject, mockDataBroker(httpAuthorization));
237 final HttpServletRequest request = mock(HttpServletRequest.class);
238 when(request.getRequestURI()).thenReturn("/abc");
239 when(request.getMethod()).thenReturn("Put");
240 when(subject.hasRole("admin")).thenReturn(true);
245 // In the setup, two rules were added. First, make sure that the first rule is working.
246 assertTrue(filter.isAccessAllowed(request, null, null));
251 // Both rules would technically match the input request URI. We want to make sure that
252 // order is respected. We do this by ensuring access is granted (i.e., the first rule is matched).
253 when(request.getRequestURI()).thenReturn("/specialendpoint");
254 assertTrue(filter.isAccessAllowed(request, null, null));
255 when(request.getRequestURI()).thenReturn("/specialendpoint/");
256 assertTrue(filter.isAccessAllowed(request, null, null));
257 when(request.getRequestURI()).thenReturn("/specialendpoint/somewhatextended");
258 assertTrue(filter.isAccessAllowed(request, null, null));
263 // Now reverse the ordering of the rules, and ensure that access is denied (except for
264 // the first non-applicable rule, which should still be allowed). This is
265 // because the Subject making the request is not granted the "dog" role.
266 when(policies.getPolicies()).thenReturn(List.of(innerPolicies2, innerPolicies));
267 // Modify Index to ensure the innerPolicies2 actually gets
268 // used instead of innerPolicies
269 when(innerPolicies2.getIndex()).thenReturn(Uint32.valueOf(4));
270 when(request.getRequestURI()).thenReturn("/abc");
271 assertTrue(filter.isAccessAllowed(request, null, null));
272 when(request.getRequestURI()).thenReturn("/specialendpoint");
273 assertFalse(filter.isAccessAllowed(request, null, null));
274 when(request.getRequestURI()).thenReturn("/specialendpoint/");
275 assertFalse(filter.isAccessAllowed(request, null, null));
276 when(request.getRequestURI()).thenReturn("/specialendpoint/somewhatextended");
277 assertFalse(filter.isAccessAllowed(request, null, null));
281 public void testMultiplePolicies() throws Exception {
282 // admin can do anything
283 final String role = "admin";
284 final String resource = "/**";
285 final String description = "Test description";
286 final Permissions permissions = mock(Permissions.class);
287 when(permissions.getRole()).thenReturn(role);
288 when(permissions.getActions()).thenReturn(Set.of(Permissions.Actions.Get, Permissions.Actions.Put,
289 Permissions.Actions.Delete, Permissions.Actions.Patch, Permissions.Actions.Post));
290 final Permissions permissions2 = mock(Permissions.class);
291 when(permissions2.getRole()).thenReturn("user");
292 when(permissions2.getActions()).thenReturn(Set.of(Permissions.Actions.Get));
293 final var innerPolicies = mock(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214
294 .http.authorization.policies.Policies.class);
295 when(innerPolicies.getResource()).thenReturn(resource);
296 when(innerPolicies.getDescription()).thenReturn(description);
297 when(innerPolicies.getPermissions()).thenReturn(List.of(permissions, permissions2));
298 final Policies policies = mock(Policies.class);
299 when(policies.getPolicies()).thenReturn(List.of(innerPolicies));
300 final HttpAuthorization httpAuthorization = mock(HttpAuthorization.class);
301 when(httpAuthorization.getPolicies()).thenReturn(policies);
303 final Subject subject = mock(Subject.class);
304 final MDSALDynamicAuthorizationFilter filter = newFilter(subject, mockDataBroker(httpAuthorization));
306 final HttpServletRequest request = mock(HttpServletRequest.class);
307 when(request.getRequestURI()).thenReturn("/abc");
308 when(request.getMethod()).thenReturn("Put");
309 when(subject.hasRole("admin")).thenReturn(false);
310 when(subject.hasRole("user")).thenReturn(true);
312 assertFalse(filter.isAccessAllowed(request, null, null));
313 when(request.getMethod()).thenReturn("Get");
314 assertTrue(filter.isAccessAllowed(request, null, null));