org.fcrepo.auth.webac.WebACAuthorizingRealm.java Source code

Java tutorial

Introduction

Here is the source code for org.fcrepo.auth.webac.WebACAuthorizingRealm.java

Source

/*
 * Licensed to DuraSpace under one or more contributor license agreements.
 * See the NOTICE file distributed with this work for additional information
 * regarding copyright ownership.
 *
 * DuraSpace licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.fcrepo.auth.webac;

import static org.fcrepo.auth.common.ServletContainerAuthFilter.FEDORA_ADMIN_ROLE;
import static org.fcrepo.auth.common.ServletContainerAuthFilter.FEDORA_USER_ROLE;
import static org.fcrepo.auth.webac.URIConstants.FOAF_AGENT_VALUE;
import static org.fcrepo.auth.webac.URIConstants.WEBAC_AUTHENTICATED_AGENT_VALUE;
import static org.fcrepo.auth.common.HttpHeaderPrincipalProvider.HttpHeaderPrincipal;
import static org.fcrepo.auth.common.DelegateHeaderPrincipalProvider.DelegatedHeaderPrincipal;
import static org.slf4j.LoggerFactory.getLogger;

import java.net.URI;
import java.security.Principal;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;

import org.apache.http.auth.BasicUserPrincipal;
import org.apache.jena.rdf.model.Resource;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.fcrepo.auth.common.ContainerRolesPrincipalProvider.ContainerRolesPrincipal;
import org.fcrepo.http.api.FedoraLdp;
import org.fcrepo.http.commons.api.rdf.HttpResourceConverter;
import org.fcrepo.http.commons.session.HttpSession;
import org.fcrepo.http.commons.session.SessionFactory;
import org.fcrepo.kernel.api.exception.PathNotFoundException;
import org.fcrepo.kernel.api.exception.RepositoryConfigurationException;
import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
import org.fcrepo.kernel.api.identifiers.IdentifierConverter;
import org.fcrepo.kernel.api.models.FedoraResource;
import org.slf4j.Logger;

/**
 * Authorization-only realm that performs authorization checks using WebAC ACLs stored in a Fedora repository. It
 * locates the ACL for the currently requested resource and parses the ACL RDF into a set of {@link WebACPermission}
 * instances.
 *
 * @author peichman
 */
public class WebACAuthorizingRealm extends AuthorizingRealm {

    private static final Logger log = getLogger(WebACAuthorizingRealm.class);

    private static final ContainerRolesPrincipal adminPrincipal = new ContainerRolesPrincipal(FEDORA_ADMIN_ROLE);

    private static final ContainerRolesPrincipal userPrincipal = new ContainerRolesPrincipal(FEDORA_USER_ROLE);

    public static final String URIS_TO_AUTHORIZE = "URIS_TO_AUTHORIZE";

    @Inject
    private SessionFactory sessionFactory;

    @Inject
    private HttpServletRequest request;

    @Inject
    private WebACRolesProvider rolesProvider;

    private HttpSession session;

    private HttpSession session() {
        if (session == null) {
            session = sessionFactory.getSession(request);
        }
        return session;
    }

    private IdentifierConverter<Resource, FedoraResource> idTranslator;

    private IdentifierConverter<Resource, FedoraResource> translator() {
        if (idTranslator == null) {
            idTranslator = new HttpResourceConverter(session(), UriBuilder.fromResource(FedoraLdp.class));
        }

        return idTranslator;
    }

    /**
     * Useful for constructing URLs
     */
    @Context
    private UriInfo uriInfo;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) {
        final SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo();
        boolean isAdmin = false;

        final Collection<DelegatedHeaderPrincipal> delegatePrincipals = principals
                .byType(DelegatedHeaderPrincipal.class);

        // if the user was assigned the "fedoraAdmin" container role, they get the
        // "fedoraAdmin" application role
        if (principals.byType(ContainerRolesPrincipal.class).contains(adminPrincipal)) {
            if (delegatePrincipals.size() > 1) {
                throw new RepositoryConfigurationException("Too many delegates! " + delegatePrincipals);
            } else if (delegatePrincipals.size() < 1) {
                authzInfo.addRole(FEDORA_ADMIN_ROLE);
                return authzInfo;
            }
            isAdmin = true;
            // if Admin is delegating, they are a normal user
            authzInfo.addRole(FEDORA_USER_ROLE);
        } else if (principals.byType(ContainerRolesPrincipal.class).contains(userPrincipal)) {
            authzInfo.addRole(FEDORA_USER_ROLE);
        }

        // for non-admins, we must check the ACL for the requested resource
        @SuppressWarnings("unchecked")
        Set<URI> targetURIs = (Set<URI>) request.getAttribute(URIS_TO_AUTHORIZE);
        if (targetURIs == null) {
            targetURIs = new HashSet<>();
        }
        final Map<URI, Map<String, Collection<String>>> rolesForURI = new HashMap<URI, Map<String, Collection<String>>>();
        final String contextPath = request.getContextPath() + request.getServletPath();
        for (final URI uri : targetURIs) {
            String path = uri.getPath();
            if (path.startsWith(contextPath)) {
                path = path.replaceFirst(contextPath, "");
            }
            log.debug("Getting roles for path {}", path);
            rolesForURI.put(uri, getRolesForPath(path));
        }

        for (final Object o : principals.asList()) {
            log.debug("User has principal with name: {}", ((Principal) o).getName());
        }
        final Principal userPrincipal = principals.oneByType(BasicUserPrincipal.class);
        final Collection<HttpHeaderPrincipal> headerPrincipals = principals.byType(HttpHeaderPrincipal.class);
        // Add permissions for user or delegated user principal
        if (isAdmin && delegatePrincipals.size() == 1) {
            final DelegatedHeaderPrincipal delegatedPrincipal = delegatePrincipals.iterator().next();
            log.debug("Admin user is delegating to {}", delegatedPrincipal);
            addPermissions(authzInfo, rolesForURI, delegatedPrincipal.getName());
            addPermissions(authzInfo, rolesForURI, WEBAC_AUTHENTICATED_AGENT_VALUE);
        } else if (userPrincipal != null) {
            log.debug("Basic user principal username: {}", userPrincipal.getName());
            addPermissions(authzInfo, rolesForURI, userPrincipal.getName());
            addPermissions(authzInfo, rolesForURI, WEBAC_AUTHENTICATED_AGENT_VALUE);
        } else {
            log.debug("No basic user principal found");
        }
        // Add permissions for header principals
        if (headerPrincipals.isEmpty()) {
            log.debug("No header principals found!");
        }
        headerPrincipals.forEach((headerPrincipal) -> {
            addPermissions(authzInfo, rolesForURI, headerPrincipal.getName());
        });

        // Added FOAF_AGENT permissions for both authenticated and unauthenticated users
        addPermissions(authzInfo, rolesForURI, FOAF_AGENT_VALUE);

        return authzInfo;

    }

    private Map<String, Collection<String>> getRolesForPath(final String path) {

        Map<String, Collection<String>> roles = null;
        final FedoraResource fedoraResource = getResourceOrParentFromPath(path);

        if (fedoraResource != null) {
            // check ACL for the request URI and get a mapping of agent => modes
            roles = rolesProvider.getRoles(fedoraResource);
        }
        return roles;
    }

    private void addPermissions(final SimpleAuthorizationInfo authzInfo,
            final Map<URI, Map<String, Collection<String>>> rolesForURI, final String agentName) {
        if (rolesForURI != null) {
            for (final URI uri : rolesForURI.keySet()) {
                log.debug("Adding permissions gathered for URI {}", uri);
                final Map<String, Collection<String>> roles = rolesForURI.get(uri);
                final Collection<String> modesForUser = roles.get(agentName);
                if (modesForUser != null) {
                    // add WebACPermission instance for each mode in the Authorization
                    for (final String mode : modesForUser) {
                        final WebACPermission perm = new WebACPermission(URI.create(mode), uri);
                        authzInfo.addObjectPermission(perm);
                        log.debug("Added permission {}", perm);
                    }
                }
            }
        }
    }

    /**
     * This realm is authorization-only.
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken token)
            throws AuthenticationException {
        return null;
    }

    /**
     * This realm is authorization-only.
     */
    @Override
    public boolean supports(final AuthenticationToken token) {
        return false;
    }

    private FedoraResource getResourceOrParentFromPath(final String path) {
        FedoraResource resource = null;
        log.debug("Attempting to get FedoraResource for {}", path);
        try {
            resource = translator().convert(translator().toDomain(path));
            log.debug("Got FedoraResource for {}", path);
        } catch (final RepositoryRuntimeException e) {
            if (e.getCause() instanceof PathNotFoundException) {
                log.debug("Path {} does not exist", path);
                // go up the path looking for a node that exists
                if (path.length() > 1) {
                    final int lastSlash = path.lastIndexOf("/");
                    final int end = lastSlash > 0 ? lastSlash : lastSlash + 1;
                    resource = getResourceOrParentFromPath(path.substring(0, end));
                }
            }
        }
        return resource;
    }

}