30ff5daf663001f7862dc96044754bd60439c83c
[aaa.git] / aaa-shiro / impl / src / test / java / org / opendaylight / aaa / impl / shiro / realm / MDSALDynamicAuthorizationFilterTest.java
1 /*
2  * Copyright (c) 2017 Brocade Communications Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.aaa.impl.shiro.realm;
10
11 import static junit.framework.TestCase.assertEquals;
12 import static junit.framework.TestCase.assertFalse;
13 import static junit.framework.TestCase.assertNotNull;
14 import static junit.framework.TestCase.assertTrue;
15 import static org.mockito.Matchers.any;
16 import static org.mockito.Mockito.mock;
17 import static org.mockito.Mockito.when;
18
19 import com.google.common.base.Optional;
20 import com.google.common.collect.Lists;
21 import com.google.common.util.concurrent.CheckedFuture;
22 import com.google.common.util.concurrent.Futures;
23 import java.util.List;
24 import javax.servlet.ServletRequest;
25 import javax.servlet.ServletResponse;
26 import javax.servlet.http.HttpServletRequest;
27 import org.apache.shiro.subject.Subject;
28 import org.junit.Test;
29 import org.opendaylight.aaa.shiro.realm.MDSALDynamicAuthorizationFilter;
30 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
31 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
32 import org.opendaylight.controller.md.sal.common.api.data.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
38 /**
39  * Tests the Dyanmic AuthZ Filter.
40  *
41  * @author Ryan Goulding (ryandgoulding@gmail.com)
42  */
43 public class MDSALDynamicAuthorizationFilterTest {
44
45     // test helper method to generate some cool mdsal data
46     private DataBroker getTestData() throws Exception {
47         return getTestData("/**", "admin", "Default Test AuthZ Rule", Permissions.Actions.Put);
48     }
49
50     // test helper method to generate some cool mdsal data
51     private DataBroker getTestData(final String resource, final String role,
52                                    final String description, final Permissions.Actions actions) throws Exception {
53
54         final List<Permissions.Actions> actionsList = Lists.newArrayList(actions);
55         final Permissions permissions = mock(Permissions.class);
56         when(permissions.getRole()).thenReturn(role);
57         when(permissions.getActions()).thenReturn(actionsList);
58         final List<Permissions> permissionsList = Lists.newArrayList(permissions);
59         final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.policies.Policies innerPolicies =
60                 mock(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.policies.Policies.class);
61         when(innerPolicies.getResource()).thenReturn(resource);
62         when(innerPolicies.getDescription()).thenReturn(description);
63         when(innerPolicies.getPermissions()).thenReturn(permissionsList);
64         final List<org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.policies.Policies> policiesList =
65                 Lists.newArrayList(innerPolicies);
66         final Policies policies = mock(Policies.class);
67         when(policies.getPolicies()).thenReturn(policiesList);
68         final HttpAuthorization httpAuthorization = mock(HttpAuthorization.class);
69         when(httpAuthorization.getPolicies()).thenReturn(policies);
70         final Optional<DataObject> dataObjectOptional = mock(Optional.class);
71         when(dataObjectOptional.get()).thenReturn(httpAuthorization);
72         when(dataObjectOptional.isPresent()).thenReturn(true);
73         final CheckedFuture<Optional<DataObject>, ReadFailedException> cf = mock(CheckedFuture.class);
74         when(cf.get()).thenReturn(dataObjectOptional);
75         final ReadOnlyTransaction rot = mock(ReadOnlyTransaction.class);
76         when(rot.read(any(), any())).thenReturn(cf);
77         final DataBroker dataBroker = mock(DataBroker.class);
78         when(dataBroker.newReadOnlyTransaction()).thenReturn(rot);
79
80         return dataBroker;
81     }
82
83     @Test
84     public void testIsAccessAllowed() throws Exception {
85         //
86         // Test Setup:
87         //
88         // Ensure that the base isAccessAllowed(...) method calls the static helper method.
89         final MDSALDynamicAuthorizationFilter filter = mock(MDSALDynamicAuthorizationFilter.class);
90         when(filter.isAccessAllowed(any(), any(), any(), any())).thenReturn(true);
91         when(filter.isAccessAllowed(any(), any(), any())).thenCallRealMethod();
92         assertTrue(filter.isAccessAllowed(null, null, null));
93         when(filter.isAccessAllowed(any(), any(), any(), any())).thenReturn(false);
94         assertFalse(filter.isAccessAllowed(null, null, null));
95     }
96
97     @Test
98     public void testGetHttpAuthzContainer() throws Exception {
99         //
100         // Test Setup:
101         //
102         // Ensure that data can be extracted appropriately.
103         final DataBroker dataBroker = getTestData();
104         final Optional<HttpAuthorization> httpAuthorizationOptional =
105                 MDSALDynamicAuthorizationFilter.getHttpAuthzContainer(dataBroker);
106
107         assertNotNull(httpAuthorizationOptional);
108         final HttpAuthorization authz = httpAuthorizationOptional.get();
109         assertNotNull(authz);
110         assertEquals(1, authz.getPolicies().getPolicies().size());
111     }
112
113     @Test
114     public void testBasicAccessWithNoRules() throws Exception {
115         //
116         // Test Setup: No rules are added to the HttpAuthorization container.  Open access should be allowed.
117         final Subject subject = mock(Subject.class);
118         final MDSALDynamicAuthorizationFilter filter = new MDSALDynamicAuthorizationFilter() {
119             @Override
120             protected Subject getSubject(final ServletRequest request, final ServletResponse servletResponse) {
121                 return subject;
122             }
123         };
124
125         final HttpServletRequest request = mock(HttpServletRequest.class);
126         when(request.getRequestURI()).thenReturn("abc");
127         when(request.getMethod()).thenReturn("Put");
128         final Optional<DataObject> dataObjectOptional = mock(Optional.class);
129         when(dataObjectOptional.isPresent()).thenReturn(false);
130         final CheckedFuture<Optional<DataObject>, ReadFailedException> cf = mock(CheckedFuture.class);
131         when(cf.get()).thenReturn(dataObjectOptional);
132         final ReadOnlyTransaction rot = mock(ReadOnlyTransaction.class);
133         when(rot.read(any(), any())).thenReturn(cf);
134         final DataBroker dataBroker = mock(DataBroker.class);
135         when(dataBroker.newReadOnlyTransaction()).thenReturn(rot);
136
137         //
138         // Ensure that access is allowed since no data is returned from the MDSAL read.
139         // This is through making sure the Optional is not present.
140         assertTrue(filter.isAccessAllowed(request, null, null, dataBroker));
141
142         //
143         // Same as above, but with an empty policy list returned.
144         final Policies policies = mock(Policies.class);
145         when(policies.getPolicies()).thenReturn(Lists.newArrayList());
146         final HttpAuthorization httpAuthorization = mock(HttpAuthorization.class);
147         when(httpAuthorization.getPolicies()).thenReturn(policies);
148         when(dataObjectOptional.isPresent()).thenReturn(true);
149         when(dataObjectOptional.get()).thenReturn(httpAuthorization);
150         assertTrue(filter.isAccessAllowed(request, null, null, dataBroker));
151     }
152
153     @Test
154     public void testMDSALExceptionDuringRead() throws Exception {
155         //
156         // Test Setup: No rules are added to the HttpAuthorization container.  The MDSAL read
157         // is instructed to return an immediateFailedCheckedFuture, to emulate an error in reading
158         // the Data Store.
159         final Subject subject = mock(Subject.class);
160         final MDSALDynamicAuthorizationFilter filter = new MDSALDynamicAuthorizationFilter() {
161             @Override
162             protected Subject getSubject(final ServletRequest request, final ServletResponse servletResponse) {
163                 return subject;
164             }
165         };
166
167         final HttpServletRequest request = mock(HttpServletRequest.class);
168         when(request.getRequestURI()).thenReturn("abc");
169         when(request.getMethod()).thenReturn("Put");
170
171         final Optional<DataObject> dataObjectOptional = mock(Optional.class);
172         when(dataObjectOptional.isPresent()).thenReturn(false);
173         final CheckedFuture<Optional<DataObject>, ReadFailedException> cf =
174                 Futures.immediateFailedCheckedFuture(new ReadFailedException("Test Fail"));
175         final ReadOnlyTransaction rot = mock(ReadOnlyTransaction.class);
176         when(rot.read(any(), any())).thenReturn(cf);
177         final DataBroker dataBroker = mock(DataBroker.class);
178         when(dataBroker.newReadOnlyTransaction()).thenReturn(rot);
179
180         //
181         // Ensure that if an error occurs while reading MD-SAL that access is denied.
182         assertFalse(filter.isAccessAllowed(request, null, null, dataBroker));
183     }
184
185     @Test
186     public void testBasicAccessWithOneRule() throws Exception {
187
188         //
189         // Test Setup:
190         //
191         // A Rule is added to match /** allowing HTTP PUT for the admin role.
192         // All other Methods are considered unauthorized.
193         final Subject subject = mock(Subject.class);
194         final DataBroker dataBroker = getTestData();
195         final MDSALDynamicAuthorizationFilter filter = new MDSALDynamicAuthorizationFilter() {
196             @Override
197             protected Subject getSubject(final ServletRequest request, final ServletResponse servletResponse) {
198                 return subject;
199             }
200         };
201
202         final HttpServletRequest request = mock(HttpServletRequest.class);
203         when(request.getRequestURI()).thenReturn("abc");
204         when(request.getMethod()).thenReturn("Put");
205         when(subject.hasRole("admin")).thenReturn(true);
206
207         //
208         // Test Case 1:
209         //
210         // Make a PUT HTTP request from a Subject with the admin role.  The request URL does not match,
211         // since "abc" does not start with a "/" character.  Since no rule exists for this particular request,
212         // then access should be allowed.
213         assertTrue(filter.isAccessAllowed(request, null, null, dataBroker));
214
215         //
216         // Test Case 2:
217         //
218         // Repeat of the above against a matching endpoint.  Access should be allowed.
219         when(request.getRequestURI()).thenReturn("/anotherexamplethatshouldwork");
220         assertTrue(filter.isAccessAllowed(request, null, null, dataBroker));
221
222         //
223         // Test Case 3:
224         //
225         // Repeat of the above request against a more complex endpoint.  Access should be allowed.
226         when(request.getRequestURI()).thenReturn("/auth/v1/users");
227         assertTrue(filter.isAccessAllowed(request, null, null, dataBroker));
228
229         //
230         // Test Case 4:
231         //
232         // Negative test case-- ensure that when an unallowed method (POST) is tried with an otherwise
233         // allowable request, that access is denied.
234         when(request.getMethod()).thenReturn("Post");
235         assertFalse(filter.isAccessAllowed(request, null, null, dataBroker));
236
237         //
238         // Test Case 5:
239         //
240         // Negative test case-- ensure that when an unallowed role is tried with an otherwise allowable
241         // request, that acess is denied.
242         when(request.getMethod()).thenReturn("Put");
243         when(subject.hasRole("admin")).thenReturn(false);
244         assertFalse(filter.isAccessAllowed(request, null, null, dataBroker));
245     }
246
247     @Test
248     public void testSeveralMatchingRules() throws Exception {
249         //
250         // Test Setup:
251         //
252         // Create some mock data which has a couple of rules which may/may not match.  This
253         // test ensures the correct application of said rules.
254         final List<Permissions.Actions> actionsList = Lists.newArrayList(Permissions.Actions.Get,
255                 Permissions.Actions.Delete, Permissions.Actions.Patch, Permissions.Actions.Put,
256                 Permissions.Actions.Post);
257         final String role = "admin";
258         final String resource = "/**";
259         final String resource2 = "/specialendpoint/**";
260         final String description = "All encompassing rule";
261         final Permissions permissions = mock(Permissions.class);
262         when(permissions.getRole()).thenReturn(role);
263         when(permissions.getActions()).thenReturn(actionsList);
264         final List<Permissions> permissionsList = Lists.newArrayList(permissions);
265         final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.policies.Policies innerPolicies =
266                 mock(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.policies.Policies.class);
267         when(innerPolicies.getResource()).thenReturn(resource);
268         when(innerPolicies.getDescription()).thenReturn(description);
269         when(innerPolicies.getPermissions()).thenReturn(permissionsList);
270         final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.policies.Policies innerPolicies2 =
271                 mock(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.policies.Policies.class);
272         when(innerPolicies2.getResource()).thenReturn(resource2);
273         final Permissions permissions2 = mock(Permissions.class);
274         when(permissions2.getRole()).thenReturn("dog");
275         when(permissions2.getActions()).thenReturn(actionsList);
276         when(innerPolicies2.getPermissions()).thenReturn(Lists.newArrayList(permissions2));
277         when(innerPolicies2.getDescription()).thenReturn("Specialized Rule");
278         List<org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.policies.Policies> policiesList =
279                 Lists.newArrayList(innerPolicies, innerPolicies2);
280         final Policies policies = mock(Policies.class);
281         when(policies.getPolicies()).thenReturn(policiesList);
282         final HttpAuthorization httpAuthorization = mock(HttpAuthorization.class);
283         when(httpAuthorization.getPolicies()).thenReturn(policies);
284         final Optional<DataObject> dataObjectOptional = mock(Optional.class);
285         when(dataObjectOptional.get()).thenReturn(httpAuthorization);
286         when(dataObjectOptional.isPresent()).thenReturn(true);
287         final CheckedFuture<Optional<DataObject>, ReadFailedException> cf = mock(CheckedFuture.class);
288         when(cf.get()).thenReturn(dataObjectOptional);
289         final ReadOnlyTransaction rot = mock(ReadOnlyTransaction.class);
290         when(rot.read(any(), any())).thenReturn(cf);
291         final DataBroker dataBroker = mock(DataBroker.class);
292         when(dataBroker.newReadOnlyTransaction()).thenReturn(rot);
293
294         final Subject subject = mock(Subject.class);
295         final MDSALDynamicAuthorizationFilter filter = new MDSALDynamicAuthorizationFilter() {
296             @Override
297             protected Subject getSubject(final ServletRequest request, final ServletResponse servletResponse) {
298                 return subject;
299             }
300         };
301
302         final HttpServletRequest request = mock(HttpServletRequest.class);
303         when(request.getRequestURI()).thenReturn("/abc");
304         when(request.getMethod()).thenReturn("Put");
305         when(subject.hasRole("admin")).thenReturn(true);
306
307         //
308         // Test Case 1:
309         //
310         // In the setup, two rules were added.  First, make sure that the first rule is working.
311         assertTrue(filter.isAccessAllowed(request, null, null, dataBroker));
312
313         //
314         // Test Case 2:
315         //
316         // Both rules would technically match the input request URI.  We want to make sure that
317         // order is respected.  We do this by ensuring access is granted (i.e., the first rule is matched).
318         when(request.getRequestURI()).thenReturn("/specialendpoint");
319         assertTrue(filter.isAccessAllowed(request, null, null, dataBroker));
320         when(request.getRequestURI()).thenReturn("/specialendpoint/");
321         assertTrue(filter.isAccessAllowed(request, null, null, dataBroker));
322         when(request.getRequestURI()).thenReturn("/specialendpoint/somewhatextended");
323         assertTrue(filter.isAccessAllowed(request, null, null, dataBroker));
324
325         //
326         // Test Case 3:
327         //
328         // Now reverse the ordering of the rules, and ensure that access is denied (except for
329         // the first non-applicable rule, which should still be allowed).  This is
330         // because the Subject making the request is not granted the "dog" role.
331         policiesList = Lists.newArrayList(innerPolicies2, innerPolicies);
332         when(policies.getPolicies()).thenReturn(policiesList);
333         when(request.getRequestURI()).thenReturn("/abc");
334         assertTrue(filter.isAccessAllowed(request, null, null, dataBroker));
335         when(request.getRequestURI()).thenReturn("/specialendpoint");
336         assertFalse(filter.isAccessAllowed(request, null, null, dataBroker));
337         when(request.getRequestURI()).thenReturn("/specialendpoint/");
338         assertFalse(filter.isAccessAllowed(request, null, null, dataBroker));
339         when(request.getRequestURI()).thenReturn("/specialendpoint/somewhatextended");
340         assertFalse(filter.isAccessAllowed(request, null, null, dataBroker));
341     }
342
343     @Test
344     public void testMultiplePolicies() throws Exception {
345         // admin can do anything
346         final String role = "admin";
347         final String resource = "/**";
348         final String description = "Test description";
349         final List<Permissions.Actions> actionsList = Lists.newArrayList(
350                 Permissions.Actions.Get, Permissions.Actions.Put, Permissions.Actions.Delete,
351                 Permissions.Actions.Patch, Permissions.Actions.Post
352         );
353         final Permissions permissions = mock(Permissions.class);
354         when(permissions.getRole()).thenReturn(role);
355         when(permissions.getActions()).thenReturn(actionsList);
356         final Permissions permissions2 = mock(Permissions.class);
357         when(permissions2.getRole()).thenReturn("user");
358         when(permissions2.getActions()).thenReturn(Lists.newArrayList(Permissions.Actions.Get));
359         final List<Permissions> permissionsList = Lists.newArrayList(permissions, permissions2);
360         final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.policies.Policies innerPolicies =
361                 mock(org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.policies.Policies.class);
362         when(innerPolicies.getResource()).thenReturn(resource);
363         when(innerPolicies.getDescription()).thenReturn(description);
364         when(innerPolicies.getPermissions()).thenReturn(permissionsList);
365         final List<org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.policies.Policies> policiesList =
366                 Lists.newArrayList(innerPolicies);
367         final Policies policies = mock(Policies.class);
368         when(policies.getPolicies()).thenReturn(policiesList);
369         final HttpAuthorization httpAuthorization = mock(HttpAuthorization.class);
370         when(httpAuthorization.getPolicies()).thenReturn(policies);
371         final Optional<DataObject> dataObjectOptional = mock(Optional.class);
372         when(dataObjectOptional.get()).thenReturn(httpAuthorization);
373         when(dataObjectOptional.isPresent()).thenReturn(true);
374         final CheckedFuture<Optional<DataObject>, ReadFailedException> cf = mock(CheckedFuture.class);
375         when(cf.get()).thenReturn(dataObjectOptional);
376         final ReadOnlyTransaction rot = mock(ReadOnlyTransaction.class);
377         when(rot.read(any(), any())).thenReturn(cf);
378         final DataBroker dataBroker = mock(DataBroker.class);
379         when(dataBroker.newReadOnlyTransaction()).thenReturn(rot);
380
381         final Subject subject = mock(Subject.class);
382         final MDSALDynamicAuthorizationFilter filter = new MDSALDynamicAuthorizationFilter() {
383             @Override
384             protected Subject getSubject(final ServletRequest request, final ServletResponse servletResponse) {
385                 return subject;
386             }
387         };
388
389         final HttpServletRequest request = mock(HttpServletRequest.class);
390         when(request.getRequestURI()).thenReturn("/abc");
391         when(request.getMethod()).thenReturn("Put");
392         when(subject.hasRole("admin")).thenReturn(false);
393         when(subject.hasRole("user")).thenReturn(true);
394
395         assertFalse(filter.isAccessAllowed(request, null, null, dataBroker));
396         when(request.getMethod()).thenReturn("Get");
397         assertTrue(filter.isAccessAllowed(request, null, null, dataBroker));
398
399     }
400 }