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 com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.collect.Iterables;
14 import com.google.common.util.concurrent.Futures;
15 import com.google.common.util.concurrent.ListenableFuture;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Comparator;
19 import java.util.List;
20 import java.util.Optional;
21 import java.util.concurrent.ExecutionException;
22 import javax.servlet.Filter;
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.apache.shiro.web.filter.authz.AuthorizationFilter;
28 import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener;
29 import org.opendaylight.mdsal.binding.api.DataBroker;
30 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
31 import org.opendaylight.mdsal.binding.api.DataTreeModification;
32 import org.opendaylight.mdsal.binding.api.ReadTransaction;
33 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.HttpAuthorization;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.policies.Policies;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.permission.Permissions;
37 import org.opendaylight.yangtools.concepts.ListenerRegistration;
38 import org.opendaylight.yangtools.concepts.Registration;
39 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
44 * Provides a dynamic authorization mechanism for restful web services with permission grain
45 * scope. <code>aaa.yang</code> defines the model for this filtering mechanism.
46 * This model exposes the ability to manipulate policy information for specific paths
47 * based on a tuple of (role, http_permission_list).
49 * <p>This mechanism will only work when put behind <code>authcBasic</code>.
51 @SuppressWarnings("checkstyle:AbbreviationAsWordInName")
52 public class MDSALDynamicAuthorizationFilter extends AuthorizationFilter
53 implements ClusteredDataTreeChangeListener<HttpAuthorization> {
55 private static final Logger LOG = LoggerFactory.getLogger(MDSALDynamicAuthorizationFilter.class);
57 private static final DataTreeIdentifier<HttpAuthorization> AUTHZ_CONTAINER = DataTreeIdentifier.create(
58 LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(HttpAuthorization.class));
60 private static final ThreadLocal<DataBroker> DATABROKER_TL = new ThreadLocal<>();
62 private final DataBroker dataBroker;
64 private ListenerRegistration<?> reg;
65 private volatile ListenableFuture<Optional<HttpAuthorization>> authContainer;
67 public MDSALDynamicAuthorizationFilter() {
68 this(verifyNotNull(DATABROKER_TL.get(), "MDSALDynamicAuthorizationFilter loading not prepared"));
71 public MDSALDynamicAuthorizationFilter(final DataBroker dataBroker) {
72 this.dataBroker = requireNonNull(dataBroker);
75 public static Registration prepareForLoad(final DataBroker dataBroker) {
76 DATABROKER_TL.set(requireNonNull(dataBroker));
77 return DATABROKER_TL::remove;
81 public Filter processPathConfig(final String path, final String config) {
82 try (ReadTransaction tx = dataBroker.newReadOnlyTransaction()) {
83 authContainer = tx.read(AUTHZ_CONTAINER.getDatastoreType(), AUTHZ_CONTAINER.getRootIdentifier());
85 reg = dataBroker.registerDataTreeChangeListener(AUTHZ_CONTAINER, this);
86 return super.processPathConfig(path, config);
90 public void destroy() {
99 public void onDataTreeChanged(final Collection<DataTreeModification<HttpAuthorization>> changes) {
100 final HttpAuthorization newVal = Iterables.getLast(changes).getRootNode().getDataAfter();
101 LOG.debug("Updating authorization information to {}", newVal);
102 authContainer = Futures.immediateFuture(Optional.ofNullable(newVal));
106 public boolean isAccessAllowed(final ServletRequest request, final ServletResponse response,
107 final Object mappedValue) {
108 if (!(request instanceof HttpServletRequest httpServletRequest)) {
109 throw new IllegalArgumentException("Expected HttpServletRequest, received " + request);
112 final Subject subject = getSubject(request, response);
113 final String requestURI = httpServletRequest.getRequestURI();
114 LOG.debug("isAccessAllowed for user={} to requestURI={}", subject, requestURI);
116 final Optional<HttpAuthorization> authorizationOptional;
118 authorizationOptional = authContainer.get();
119 } catch (ExecutionException | InterruptedException e) {
120 // Something went completely wrong trying to read the authz container. Deny access.
121 LOG.warn("MDSAL attempt to read Http Authz Container failed, disallowing access", e);
125 if (!authorizationOptional.isPresent()) {
126 // The authorization container does not exist-- hence no authz rules are present
128 LOG.debug("Authorization Container does not exist");
132 final HttpAuthorization httpAuthorization = authorizationOptional.get();
133 final var policies = httpAuthorization.getPolicies();
134 List<Policies> policiesList = policies != null ? policies.getPolicies() : null;
135 if (policiesList == null || policiesList.isEmpty()) {
136 // The authorization container exists, but no rules are present. Allow access.
137 LOG.debug("Exiting successfully early since no authorization rules exist");
141 // Sort the Policies list based on index
142 policiesList = new ArrayList<>(policiesList);
143 policiesList.sort(Comparator.comparing(Policies::getIndex));
145 for (Policies policy : policiesList) {
146 final String resource = policy.getResource();
147 final boolean pathsMatch = pathsMatch(resource, requestURI);
149 LOG.debug("paths match for pattern={} and requestURI={}", resource, requestURI);
150 final String method = httpServletRequest.getMethod();
151 LOG.trace("method={}", method);
152 for (Permissions permission : policy.getPermissions()) {
153 final String role = permission.getRole();
154 LOG.trace("role={}", role);
155 for (Permissions.Actions action : permission.getActions()) {
156 LOG.trace("action={}", action.getName());
157 if (action.getName().equalsIgnoreCase(method)) {
158 final boolean hasRole = subject.hasRole(role);
159 LOG.trace("hasRole({})={}", role, hasRole);
166 LOG.debug("couldn't authorize the user for access");
170 LOG.debug("successfully authorized the user for access");