c662fa08c458b893a047c442a18f2c7f3a2cdef5
[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.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;
36
37 /**
38  * Tests the Dynamic Authorization Filter.
39  */
40 @SuppressWarnings("checkstyle:AbbreviationAsWordInName")
41 public class MDSALDynamicAuthorizationFilterTest {
42
43     private static DataBroker mockDataBroker(final Object readData) {
44         final ReadTransaction readOnlyTransaction = mock(ReadTransaction.class);
45
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());
51         } else {
52             doReturn(immediateFluentFuture(Optional.empty())).when(readOnlyTransaction).read(any(), any());
53         }
54
55         final DataBroker mockDataBroker = mock(DataBroker.class);
56         when(mockDataBroker.newReadOnlyTransaction()).thenReturn(readOnlyTransaction);
57         return mockDataBroker;
58     }
59
60     private static MDSALDynamicAuthorizationFilter newFilter(final Subject subject, final DataBroker dataBroker)
61             throws ServletException {
62         final var ret = new MDSALDynamicAuthorizationFilter(dataBroker) {
63             @Override
64             protected Subject getSubject(final ServletRequest request, final ServletResponse servletResponse) {
65                 return subject;
66             }
67         };
68
69         ret.processPathConfig("test-path","test-config");
70         return ret;
71     }
72
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);
76     }
77
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) {
81
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);
94
95         return mockDataBroker(httpAuthorization);
96     }
97
98     @Test
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");
103
104         //
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));
107
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));
111
112         //
113         // Same as above, but with an empty policy list returned.
114
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));
120
121         assertTrue(filter.isAccessAllowed(request, null, null));
122     }
123
124     @Test
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
128         // the Data Store.
129
130         final HttpServletRequest request = mock(HttpServletRequest.class);
131         when(request.getRequestURI()).thenReturn("abc");
132         when(request.getMethod()).thenReturn("Put");
133
134         MDSALDynamicAuthorizationFilter filter = newFilter(mock(Subject.class),
135                 mockDataBroker(new ReadFailedException("Test Fail")));
136
137         // Ensure that if an error occurs while reading MD-SAL that access is denied.
138         assertFalse(filter.isAccessAllowed(request, null, null));
139     }
140
141     @Test
142     public void testBasicAccessWithOneRule() throws Exception {
143
144         //
145         // Test Setup:
146         //
147         // A Rule is added to match /** allowing HTTP PUT for the admin role.
148         // All other Methods are considered unauthorized.
149
150         final Subject subject = mock(Subject.class);
151         final MDSALDynamicAuthorizationFilter filter = newFilter(subject, getTestData());
152
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);
157
158         //
159         // Test Case 1:
160         //
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));
165
166         //
167         // Test Case 2:
168         //
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));
172
173         //
174         // Test Case 3:
175         //
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));
179
180         //
181         // Test Case 4:
182         //
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));
187
188         //
189         // Test Case 5:
190         //
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));
196     }
197
198     @Test
199     public void testSeveralMatchingRules() throws Exception {
200         //
201         // Test Setup:
202         //
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);
233
234         final Subject subject = mock(Subject.class);
235         final MDSALDynamicAuthorizationFilter filter = newFilter(subject, mockDataBroker(httpAuthorization));
236
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);
241
242         //
243         // Test Case 1:
244         //
245         // In the setup, two rules were added.  First, make sure that the first rule is working.
246         assertTrue(filter.isAccessAllowed(request, null, null));
247
248         //
249         // Test Case 2:
250         //
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));
259
260         //
261         // Test Case 3:
262         //
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));
278     }
279
280     @Test
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);
302
303         final Subject subject = mock(Subject.class);
304         final MDSALDynamicAuthorizationFilter filter = newFilter(subject, mockDataBroker(httpAuthorization));
305
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);
311
312         assertFalse(filter.isAccessAllowed(request, null, null));
313         when(request.getMethod()).thenReturn("Get");
314         assertTrue(filter.isAccessAllowed(request, null, null));
315     }
316 }