Bump upstreams
[aaa.git] / aaa-shiro / impl / src / test / java / org / opendaylight / aaa / 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 package org.opendaylight.aaa.shiro.realm;
9
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;
18
19 import java.util.List;
20 import java.util.Optional;
21 import java.util.Set;
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;
38
39 /**
40  * Tests the Dynamic Authorization Filter.
41  */
42 @SuppressWarnings("checkstyle:AbbreviationAsWordInName")
43 public class MDSALDynamicAuthorizationFilterTest {
44
45     @Before
46     public void setup() {
47     }
48
49     private static DataBroker mockDataBroker(final Object readData) {
50         final ReadTransaction readOnlyTransaction = mock(ReadTransaction.class);
51
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());
57         } else {
58             doReturn(immediateFluentFuture(Optional.empty())).when(readOnlyTransaction).read(any(), any());
59         }
60
61         final DataBroker mockDataBroker = mock(DataBroker.class);
62         when(mockDataBroker.newReadOnlyTransaction()).thenReturn(readOnlyTransaction);
63         return mockDataBroker;
64     }
65
66     private static MDSALDynamicAuthorizationFilter newFilter(final Subject subject, final DataBroker dataBroker)
67             throws ServletException {
68         ThreadLocals.DATABROKER_TL.set(dataBroker);
69         MDSALDynamicAuthorizationFilter ret;
70         try {
71             ret = new MDSALDynamicAuthorizationFilter() {
72                 @Override
73                 protected Subject getSubject(final ServletRequest request, final ServletResponse servletResponse) {
74                     return subject;
75                 }
76             };
77         } finally {
78             ThreadLocals.DATABROKER_TL.remove();
79         }
80
81         ret.processPathConfig("test-path","test-config");
82         return ret;
83     }
84
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);
88     }
89
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) {
93
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);
106
107         return mockDataBroker(httpAuthorization);
108     }
109
110     @Test
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");
115
116         //
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));
119
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));
123
124         //
125         // Same as above, but with an empty policy list returned.
126
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));
132
133         assertTrue(filter.isAccessAllowed(request, null, null));
134     }
135
136     @Test
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
140         // the Data Store.
141
142         final HttpServletRequest request = mock(HttpServletRequest.class);
143         when(request.getRequestURI()).thenReturn("abc");
144         when(request.getMethod()).thenReturn("Put");
145
146         MDSALDynamicAuthorizationFilter filter = newFilter(mock(Subject.class),
147                 mockDataBroker(new ReadFailedException("Test Fail")));
148
149         // Ensure that if an error occurs while reading MD-SAL that access is denied.
150         assertFalse(filter.isAccessAllowed(request, null, null));
151     }
152
153     @Test
154     public void testBasicAccessWithOneRule() throws Exception {
155
156         //
157         // Test Setup:
158         //
159         // A Rule is added to match /** allowing HTTP PUT for the admin role.
160         // All other Methods are considered unauthorized.
161
162         final Subject subject = mock(Subject.class);
163         final MDSALDynamicAuthorizationFilter filter = newFilter(subject, getTestData());
164
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);
169
170         //
171         // Test Case 1:
172         //
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));
177
178         //
179         // Test Case 2:
180         //
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));
184
185         //
186         // Test Case 3:
187         //
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));
191
192         //
193         // Test Case 4:
194         //
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));
199
200         //
201         // Test Case 5:
202         //
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));
208     }
209
210     @Test
211     public void testSeveralMatchingRules() throws Exception {
212         //
213         // Test Setup:
214         //
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);
245
246         final Subject subject = mock(Subject.class);
247         final MDSALDynamicAuthorizationFilter filter = newFilter(subject, mockDataBroker(httpAuthorization));
248
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);
253
254         //
255         // Test Case 1:
256         //
257         // In the setup, two rules were added.  First, make sure that the first rule is working.
258         assertTrue(filter.isAccessAllowed(request, null, null));
259
260         //
261         // Test Case 2:
262         //
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));
271
272         //
273         // Test Case 3:
274         //
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));
290     }
291
292     @Test
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);
314
315         final Subject subject = mock(Subject.class);
316         final MDSALDynamicAuthorizationFilter filter = newFilter(subject, mockDataBroker(httpAuthorization));
317
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);
323
324         assertFalse(filter.isAccessAllowed(request, null, null));
325         when(request.getMethod()).thenReturn("Get");
326         assertTrue(filter.isAccessAllowed(request, null, null));
327     }
328 }