/* * Copyright (c) 2017 Brocade Communications Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.aaa.shiro.realm; import com.google.common.base.Optional; import com.google.common.util.concurrent.CheckedFuture; import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutionException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authz.AuthorizationFilter; import org.opendaylight.aaa.shiro.web.env.ThreadLocals; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.HttpAuthorization; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.Policies; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.permission.Permissions; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides a dynamic authorization mechanism for restful web services with permission grain * scope. aaa.yang defines the model for this filtering mechanism. * This model exposes the ability to manipulate policy information for specific paths * based on a tuple of (role, http_permission_list). * *

This mechanism will only work when put behind authcBasic. */ @SuppressWarnings("checkstyle:AbbreviationAsWordInName") public class MDSALDynamicAuthorizationFilter extends AuthorizationFilter { private static final Logger LOG = LoggerFactory.getLogger(MDSALDynamicAuthorizationFilter.class); private static final InstanceIdentifier AUTHZ_CONTAINER_IID = InstanceIdentifier.builder(HttpAuthorization.class).build(); public static Optional getHttpAuthzContainer(final DataBroker dataBroker) throws ExecutionException, InterruptedException, ReadFailedException { try (ReadOnlyTransaction ro = dataBroker.newReadOnlyTransaction()) { final CheckedFuture, ReadFailedException> result = ro.read(LogicalDatastoreType.CONFIGURATION, AUTHZ_CONTAINER_IID); return result.get(); } } private final DataBroker dataBroker; public MDSALDynamicAuthorizationFilter() { this.dataBroker = Objects.requireNonNull(ThreadLocals.DATABROKER_TL.get()); } @Override public boolean isAccessAllowed(final ServletRequest request, final ServletResponse response, final Object mappedValue) { final Subject subject = getSubject(request, response); final HttpServletRequest httpServletRequest = (HttpServletRequest)request; final String requestURI = httpServletRequest.getRequestURI(); LOG.debug("isAccessAllowed for user={} to requestURI={}", subject, requestURI); final Optional authorizationOptional; try { authorizationOptional = getHttpAuthzContainer(dataBroker); } catch(ExecutionException | InterruptedException e) { // Something went completely wrong trying to read the authz container. Deny access. LOG.debug("Error accessing the Http Authz Container", e); return false; } catch(final ReadFailedException e) { // The MDSAL read attempt failed. fail-closed to prevent unauthorized access LOG.warn("MDSAL attempt to read Http Authz Container failed, disallowing access", e); return false; } if (!authorizationOptional.isPresent()) { // The authorization container does not exist-- hence no authz rules are present // Allow access. LOG.debug("Authorization Container does not exist"); return true; } final HttpAuthorization httpAuthorization = authorizationOptional.get(); final Policies policies = httpAuthorization.getPolicies(); final List policiesList = policies.getPolicies(); if(policiesList.isEmpty()) { // The authorization container exists, but no rules are present. Allow access. LOG.debug("Exiting successfully early since no authorization rules exist"); return true; } for (org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.http.authorization.policies.Policies policy : policiesList) { final String resource = policy.getResource(); final boolean pathsMatch = pathsMatch(resource, requestURI); if (pathsMatch) { LOG.debug("paths match for pattern={} and requestURI={}", resource, requestURI); final String method = httpServletRequest.getMethod(); LOG.trace("method={}", method); final List permissions = policy.getPermissions(); for (Permissions permission : permissions) { final String role = permission.getRole(); LOG.trace("role={}", role); final List actions = permission.getActions(); for(Permissions.Actions action : actions) { LOG.trace("action={}", action.getName()); if(action.getName().equalsIgnoreCase(method)) { final boolean hasRole = subject.hasRole(role); LOG.trace("hasRole({})={}", role, hasRole); if (hasRole) { return true; } } } } LOG.debug("couldn't authorize the user for access"); return false; } } LOG.debug("successfully authorized the user for access"); return true; } }