Refactor AAAShiroProvider & Co. to be non static
[aaa.git] / aaa-shiro / impl / src / main / java / org / opendaylight / aaa / shiro / realm / MDSALDynamicAuthorizationFilter.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 com.google.common.base.Optional;
11 import com.google.common.util.concurrent.CheckedFuture;
12 import java.util.List;
13 import java.util.Objects;
14 import java.util.concurrent.ExecutionException;
15 import javax.servlet.ServletRequest;
16 import javax.servlet.ServletResponse;
17 import javax.servlet.http.HttpServletRequest;
18 import org.apache.shiro.subject.Subject;
19 import org.apache.shiro.web.filter.authz.AuthorizationFilter;
20 import org.opendaylight.aaa.shiro.web.env.ThreadLocals;
21 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
22 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
23 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
24 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.HttpAuthorization;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.Policies;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.permission.Permissions;
28 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 /**
33  * Provides a dynamic authorization mechanism for restful web services with permission grain
34  * scope.  <code>aaa.yang</code> defines the model for this filtering mechanism.
35  * This model exposes the ability to manipulate policy information for specific paths
36  * based on a tuple of (role, http_permission_list).
37  *
38  * <p>This mechanism will only work when put behind <code>authcBasic</code>.
39  */
40 @SuppressWarnings("checkstyle:AbbreviationAsWordInName")
41 public class MDSALDynamicAuthorizationFilter extends AuthorizationFilter {
42
43     private static final Logger LOG = LoggerFactory.getLogger(MDSALDynamicAuthorizationFilter.class);
44
45     private static final InstanceIdentifier<HttpAuthorization> AUTHZ_CONTAINER_IID =
46             InstanceIdentifier.builder(HttpAuthorization.class).build();
47
48     public static Optional<HttpAuthorization> getHttpAuthzContainer(final DataBroker dataBroker)
49             throws ExecutionException, InterruptedException, ReadFailedException {
50
51         try (ReadOnlyTransaction ro = dataBroker.newReadOnlyTransaction()) {
52             final CheckedFuture<Optional<HttpAuthorization>, ReadFailedException> result =
53                     ro.read(LogicalDatastoreType.CONFIGURATION, AUTHZ_CONTAINER_IID);
54             return result.get();
55         }
56     }
57
58     private final DataBroker dataBroker;
59
60     public MDSALDynamicAuthorizationFilter() {
61         this.dataBroker = Objects.requireNonNull(ThreadLocals.DATABROKER_TL.get());
62     }
63
64     @Override
65     public boolean isAccessAllowed(final ServletRequest request, final ServletResponse response,
66                                    final Object mappedValue) {
67         final Subject subject = getSubject(request, response);
68         final HttpServletRequest httpServletRequest = (HttpServletRequest)request;
69         final String requestURI = httpServletRequest.getRequestURI();
70         LOG.debug("isAccessAllowed for user={} to requestURI={}", subject, requestURI);
71
72         final Optional<HttpAuthorization> authorizationOptional;
73         try {
74             authorizationOptional = getHttpAuthzContainer(dataBroker);
75         } catch(ExecutionException | InterruptedException e) {
76             // Something went completely wrong trying to read the authz container.  Deny access.
77             LOG.debug("Error accessing the Http Authz Container", e);
78             return false;
79         } catch(final ReadFailedException e) {
80             // The MDSAL read attempt failed.  fail-closed to prevent unauthorized access
81             LOG.warn("MDSAL attempt to read Http Authz Container failed, disallowing access", e);
82             return false;
83         }
84
85         if (!authorizationOptional.isPresent()) {
86             // The authorization container does not exist-- hence no authz rules are present
87             // Allow access.
88             LOG.debug("Authorization Container does not exist");
89             return true;
90         }
91
92
93         final HttpAuthorization httpAuthorization = authorizationOptional.get();
94         final Policies policies = httpAuthorization.getPolicies();
95         final List<org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.policies.Policies> policiesList =
96                 policies.getPolicies();
97         if(policiesList.isEmpty()) {
98             // The authorization container exists, but no rules are present.  Allow access.
99             LOG.debug("Exiting successfully early since no authorization rules exist");
100             return true;
101         }
102
103         for (org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.policies.Policies policy :
104                 policiesList) {
105             final String resource = policy.getResource();
106             final boolean pathsMatch = pathsMatch(resource, requestURI);
107             if (pathsMatch) {
108                 LOG.debug("paths match for pattern={} and requestURI={}", resource, requestURI);
109                 final String method = httpServletRequest.getMethod();
110                 LOG.trace("method={}", method);
111                 final List<Permissions> permissions = policy.getPermissions();
112                 for (Permissions permission : permissions) {
113                     final String role = permission.getRole();
114                     LOG.trace("role={}", role);
115                     final List<Permissions.Actions> actions = permission.getActions();
116                     for(Permissions.Actions action : actions) {
117                         LOG.trace("action={}", action.getName());
118                         if(action.getName().equalsIgnoreCase(method)) {
119                             final boolean hasRole = subject.hasRole(role);
120                             LOG.trace("hasRole({})={}", role, hasRole);
121                             if (hasRole) {
122                                 return true;
123                             }
124                         }
125                     }
126                 }
127                 LOG.debug("couldn't authorize the user for access");
128                 return false;
129             }
130         }
131         LOG.debug("successfully authorized the user for access");
132         return true;
133     }
134 }