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.Before;
28 import org.junit.Test;
29 import org.opendaylight.aaa.shiro.web.env.ThreadLocals;
30 import org.opendaylight.mdsal.binding.api.DataBroker;
31 import org.opendaylight.mdsal.binding.api.ReadTransaction;
32 import org.opendaylight.mdsal.common.api.ReadFailedException;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.HttpAuthorization;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.Policies;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.permission.Permissions;
36 import org.opendaylight.yangtools.yang.binding.DataObject;
37 import org.opendaylight.yangtools.yang.common.Uint32;
40 * Tests the Dynamic Authorization Filter.
42 @SuppressWarnings("checkstyle:AbbreviationAsWordInName")
43 public class MDSALDynamicAuthorizationFilterTest {
49 private static DataBroker mockDataBroker(final Object readData) {
50 final ReadTransaction readOnlyTransaction = mock(ReadTransaction.class);
52 if (readData instanceof DataObject) {
53 doReturn(immediateFluentFuture(Optional.of((DataObject)readData)))
54 .when(readOnlyTransaction).read(any(), any());
55 } else if (readData instanceof Exception) {
56 doReturn(immediateFailedFluentFuture((Exception)readData)).when(readOnlyTransaction).read(any(), any());
58 doReturn(immediateFluentFuture(Optional.empty())).when(readOnlyTransaction).read(any(), any());
61 final DataBroker mockDataBroker = mock(DataBroker.class);
62 when(mockDataBroker.newReadOnlyTransaction()).thenReturn(readOnlyTransaction);
63 return mockDataBroker;
66 private static MDSALDynamicAuthorizationFilter newFilter(final Subject subject, final DataBroker dataBroker)
67 throws ServletException {
68 ThreadLocals.DATABROKER_TL.set(dataBroker);
69 MDSALDynamicAuthorizationFilter ret;
71 ret = new MDSALDynamicAuthorizationFilter() {
73 protected Subject getSubject(final ServletRequest request, final ServletResponse servletResponse) {
78 ThreadLocals.DATABROKER_TL.remove();
81 ret.processPathConfig("test-path","test-config");
85 // test helper method to generate some cool mdsal data
86 private static DataBroker getTestData() {
87 return getTestData("/**", "admin", "Default Test AuthZ Rule", Permissions.Actions.Put);
90 // test helper method to generate some cool mdsal data
91 private static DataBroker getTestData(final String resource, final String role, final String description,
92 final Permissions.Actions actions) {
94 final Permissions permissions = mock(Permissions.class);
95 when(permissions.getRole()).thenReturn(role);
96 when(permissions.getActions()).thenReturn(Set.of(actions));
97 final var innerPolicies = mock(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214
98 .http.authorization.policies.Policies.class);
99 when(innerPolicies.getResource()).thenReturn(resource);
100 when(innerPolicies.getDescription()).thenReturn(description);
101 when(innerPolicies.getPermissions()).thenReturn(List.of(permissions));
102 final Policies policies = mock(Policies.class);
103 when(policies.getPolicies()).thenReturn(List.of(innerPolicies));
104 final HttpAuthorization httpAuthorization = mock(HttpAuthorization.class);
105 when(httpAuthorization.getPolicies()).thenReturn(policies);
107 return mockDataBroker(httpAuthorization);
111 public void testBasicAccessWithNoRules() throws Exception {
112 final HttpServletRequest request = mock(HttpServletRequest.class);
113 when(request.getRequestURI()).thenReturn("abc");
114 when(request.getMethod()).thenReturn("Put");
117 // Test Setup: No rules are added to the HttpAuthorization container. Open access should be allowed.
118 MDSALDynamicAuthorizationFilter filter = newFilter(mock(Subject.class), mockDataBroker(null));
120 // Ensure that access is allowed since no data is returned from the MDSAL read.
121 // This is through making sure the Optional is not present.
122 assertTrue(filter.isAccessAllowed(request, null, null));
125 // Same as above, but with an empty policy list returned.
127 final Policies policies = mock(Policies.class);
128 when(policies.getPolicies()).thenReturn(List.of());
129 final HttpAuthorization httpAuthorization = mock(HttpAuthorization.class);
130 when(httpAuthorization.getPolicies()).thenReturn(policies);
131 filter = newFilter(mock(Subject.class), mockDataBroker(httpAuthorization));
133 assertTrue(filter.isAccessAllowed(request, null, null));
137 public void testMDSALExceptionDuringRead() throws Exception {
138 // Test Setup: No rules are added to the HttpAuthorization container. The MDSAL read
139 // is instructed to return an immediateFailedFluentFuture, to emulate an error in reading
142 final HttpServletRequest request = mock(HttpServletRequest.class);
143 when(request.getRequestURI()).thenReturn("abc");
144 when(request.getMethod()).thenReturn("Put");
146 MDSALDynamicAuthorizationFilter filter = newFilter(mock(Subject.class),
147 mockDataBroker(new ReadFailedException("Test Fail")));
149 // Ensure that if an error occurs while reading MD-SAL that access is denied.
150 assertFalse(filter.isAccessAllowed(request, null, null));
154 public void testBasicAccessWithOneRule() throws Exception {
159 // A Rule is added to match /** allowing HTTP PUT for the admin role.
160 // All other Methods are considered unauthorized.
162 final Subject subject = mock(Subject.class);
163 final MDSALDynamicAuthorizationFilter filter = newFilter(subject, getTestData());
165 final HttpServletRequest request = mock(HttpServletRequest.class);
166 when(request.getRequestURI()).thenReturn("abc");
167 when(request.getMethod()).thenReturn("Put");
168 when(subject.hasRole("admin")).thenReturn(true);
173 // Make a PUT HTTP request from a Subject with the admin role. The request URL does not match,
174 // since "abc" does not start with a "/" character. Since no rule exists for this particular request,
175 // then access should be allowed.
176 assertTrue(filter.isAccessAllowed(request, null, null));
181 // Repeat of the above against a matching endpoint. Access should be allowed.
182 when(request.getRequestURI()).thenReturn("/anotherexamplethatshouldwork");
183 assertTrue(filter.isAccessAllowed(request, null, null));
188 // Repeat of the above request against a more complex endpoint. Access should be allowed.
189 when(request.getRequestURI()).thenReturn("/auth/v1/users");
190 assertTrue(filter.isAccessAllowed(request, null, null));
195 // Negative test case-- ensure that when an unallowed method (POST) is tried with an otherwise
196 // allowable request, that access is denied.
197 when(request.getMethod()).thenReturn("Post");
198 assertFalse(filter.isAccessAllowed(request, null, null));
203 // Negative test case-- ensure that when an unallowed role is tried with an otherwise allowable
204 // request, that acess is denied.
205 when(request.getMethod()).thenReturn("Put");
206 when(subject.hasRole("admin")).thenReturn(false);
207 assertFalse(filter.isAccessAllowed(request, null, null));
211 public void testSeveralMatchingRules() throws Exception {
215 // Create some mock data which has a couple of rules which may/may not match. This
216 // test ensures the correct application of said rules.
217 final Set<Permissions.Actions> actionsList = Set.of(Permissions.Actions.Get, Permissions.Actions.Delete,
218 Permissions.Actions.Patch, Permissions.Actions.Put, Permissions.Actions.Post);
219 final String role = "admin";
220 final String resource = "/**";
221 final String resource2 = "/specialendpoint/**";
222 final String description = "All encompassing rule";
223 final Permissions permissions = mock(Permissions.class);
224 when(permissions.getRole()).thenReturn(role);
225 when(permissions.getActions()).thenReturn(actionsList);
226 final var innerPolicies = mock(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214
227 .http.authorization.policies.Policies.class);
228 when(innerPolicies.getResource()).thenReturn(resource);
229 when(innerPolicies.getIndex()).thenReturn(Uint32.valueOf(5));
230 when(innerPolicies.getDescription()).thenReturn(description);
231 when(innerPolicies.getPermissions()).thenReturn(List.of(permissions));
232 final var innerPolicies2 = mock(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214
233 .http.authorization.policies.Policies.class);
234 when(innerPolicies2.getResource()).thenReturn(resource2);
235 when(innerPolicies2.getIndex()).thenReturn(Uint32.TEN);
236 final Permissions permissions2 = mock(Permissions.class);
237 when(permissions2.getRole()).thenReturn("dog");
238 when(permissions2.getActions()).thenReturn(actionsList);
239 when(innerPolicies2.getPermissions()).thenReturn(List.of(permissions2));
240 when(innerPolicies2.getDescription()).thenReturn("Specialized Rule");
241 final Policies policies = mock(Policies.class);
242 when(policies.getPolicies()).thenReturn(List.of(innerPolicies, innerPolicies2));
243 final HttpAuthorization httpAuthorization = mock(HttpAuthorization.class);
244 when(httpAuthorization.getPolicies()).thenReturn(policies);
246 final Subject subject = mock(Subject.class);
247 final MDSALDynamicAuthorizationFilter filter = newFilter(subject, mockDataBroker(httpAuthorization));
249 final HttpServletRequest request = mock(HttpServletRequest.class);
250 when(request.getRequestURI()).thenReturn("/abc");
251 when(request.getMethod()).thenReturn("Put");
252 when(subject.hasRole("admin")).thenReturn(true);
257 // In the setup, two rules were added. First, make sure that the first rule is working.
258 assertTrue(filter.isAccessAllowed(request, null, null));
263 // Both rules would technically match the input request URI. We want to make sure that
264 // order is respected. We do this by ensuring access is granted (i.e., the first rule is matched).
265 when(request.getRequestURI()).thenReturn("/specialendpoint");
266 assertTrue(filter.isAccessAllowed(request, null, null));
267 when(request.getRequestURI()).thenReturn("/specialendpoint/");
268 assertTrue(filter.isAccessAllowed(request, null, null));
269 when(request.getRequestURI()).thenReturn("/specialendpoint/somewhatextended");
270 assertTrue(filter.isAccessAllowed(request, null, null));
275 // Now reverse the ordering of the rules, and ensure that access is denied (except for
276 // the first non-applicable rule, which should still be allowed). This is
277 // because the Subject making the request is not granted the "dog" role.
278 when(policies.getPolicies()).thenReturn(List.of(innerPolicies2, innerPolicies));
279 // Modify Index to ensure the innerPolicies2 actually gets
280 // used instead of innerPolicies
281 when(innerPolicies2.getIndex()).thenReturn(Uint32.valueOf(4));
282 when(request.getRequestURI()).thenReturn("/abc");
283 assertTrue(filter.isAccessAllowed(request, null, null));
284 when(request.getRequestURI()).thenReturn("/specialendpoint");
285 assertFalse(filter.isAccessAllowed(request, null, null));
286 when(request.getRequestURI()).thenReturn("/specialendpoint/");
287 assertFalse(filter.isAccessAllowed(request, null, null));
288 when(request.getRequestURI()).thenReturn("/specialendpoint/somewhatextended");
289 assertFalse(filter.isAccessAllowed(request, null, null));
293 public void testMultiplePolicies() throws Exception {
294 // admin can do anything
295 final String role = "admin";
296 final String resource = "/**";
297 final String description = "Test description";
298 final Permissions permissions = mock(Permissions.class);
299 when(permissions.getRole()).thenReturn(role);
300 when(permissions.getActions()).thenReturn(Set.of(Permissions.Actions.Get, Permissions.Actions.Put,
301 Permissions.Actions.Delete, Permissions.Actions.Patch, Permissions.Actions.Post));
302 final Permissions permissions2 = mock(Permissions.class);
303 when(permissions2.getRole()).thenReturn("user");
304 when(permissions2.getActions()).thenReturn(Set.of(Permissions.Actions.Get));
305 final var innerPolicies = mock(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214
306 .http.authorization.policies.Policies.class);
307 when(innerPolicies.getResource()).thenReturn(resource);
308 when(innerPolicies.getDescription()).thenReturn(description);
309 when(innerPolicies.getPermissions()).thenReturn(List.of(permissions, permissions2));
310 final Policies policies = mock(Policies.class);
311 when(policies.getPolicies()).thenReturn(List.of(innerPolicies));
312 final HttpAuthorization httpAuthorization = mock(HttpAuthorization.class);
313 when(httpAuthorization.getPolicies()).thenReturn(policies);
315 final Subject subject = mock(Subject.class);
316 final MDSALDynamicAuthorizationFilter filter = newFilter(subject, mockDataBroker(httpAuthorization));
318 final HttpServletRequest request = mock(HttpServletRequest.class);
319 when(request.getRequestURI()).thenReturn("/abc");
320 when(request.getMethod()).thenReturn("Put");
321 when(subject.hasRole("admin")).thenReturn(false);
322 when(subject.hasRole("user")).thenReturn(true);
324 assertFalse(filter.isAccessAllowed(request, null, null));
325 when(request.getMethod()).thenReturn("Get");
326 assertTrue(filter.isAccessAllowed(request, null, null));