001/*
002 * Licensed to DuraSpace under one or more contributor license agreements.
003 * See the NOTICE file distributed with this work for additional information
004 * regarding copyright ownership.
005 *
006 * DuraSpace licenses this file to you under the Apache License,
007 * Version 2.0 (the "License"); you may not use this file except in
008 * compliance with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.fcrepo.auth.webac;
019
020import static org.fcrepo.auth.common.ServletContainerAuthFilter.FEDORA_ADMIN_ROLE;
021import static org.fcrepo.auth.common.ServletContainerAuthFilter.FEDORA_USER_ROLE;
022import static org.fcrepo.auth.webac.URIConstants.FOAF_AGENT_VALUE;
023import static org.fcrepo.auth.webac.URIConstants.WEBAC_AUTHENTICATED_AGENT_VALUE;
024import static org.fcrepo.auth.common.HttpHeaderPrincipalProvider.HttpHeaderPrincipal;
025import static org.fcrepo.auth.common.DelegateHeaderPrincipalProvider.DelegatedHeaderPrincipal;
026import static org.slf4j.LoggerFactory.getLogger;
027
028import java.net.URI;
029import java.security.Principal;
030import java.util.Collection;
031import java.util.Map;
032import javax.inject.Inject;
033import javax.jcr.Node;
034import javax.jcr.PathNotFoundException;
035import javax.servlet.http.HttpServletRequest;
036import javax.ws.rs.core.Context;
037import javax.ws.rs.core.UriBuilder;
038import javax.ws.rs.core.UriInfo;
039
040import org.apache.http.auth.BasicUserPrincipal;
041import org.apache.jena.rdf.model.Resource;
042import org.apache.shiro.authc.AuthenticationException;
043import org.apache.shiro.authc.AuthenticationInfo;
044import org.apache.shiro.authc.AuthenticationToken;
045import org.apache.shiro.authz.AuthorizationInfo;
046import org.apache.shiro.authz.SimpleAuthorizationInfo;
047import org.apache.shiro.realm.AuthorizingRealm;
048import org.apache.shiro.subject.PrincipalCollection;
049import org.fcrepo.auth.common.ContainerRolesPrincipalProvider.ContainerRolesPrincipal;
050import org.fcrepo.http.api.FedoraLdp;
051import org.fcrepo.http.commons.api.rdf.HttpResourceConverter;
052import org.fcrepo.http.commons.session.HttpSession;
053import org.fcrepo.http.commons.session.SessionFactory;
054import org.fcrepo.kernel.api.exception.RepositoryConfigurationException;
055import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
056import org.fcrepo.kernel.api.identifiers.IdentifierConverter;
057import org.fcrepo.kernel.api.models.FedoraResource;
058import org.fcrepo.kernel.modeshape.FedoraResourceImpl;
059import org.slf4j.Logger;
060/**
061 * Authorization-only realm that performs authorization checks using WebAC ACLs stored in a Fedora repository. It
062 * locates the ACL for the currently requested resource and parses the ACL RDF into a set of {@link WebACPermission}
063 * instances.
064 *
065 * @author peichman
066 */
067public class WebACAuthorizingRealm extends AuthorizingRealm {
068
069    private static final Logger log = getLogger(WebACAuthorizingRealm.class);
070
071    private static final ContainerRolesPrincipal adminPrincipal = new ContainerRolesPrincipal(FEDORA_ADMIN_ROLE);
072
073    private static final ContainerRolesPrincipal userPrincipal = new ContainerRolesPrincipal(FEDORA_USER_ROLE);
074
075    @Inject
076    private SessionFactory sessionFactory;
077
078    @Inject
079    private HttpServletRequest request;
080
081    @Inject
082    private WebACRolesProvider rolesProvider;
083
084    private HttpSession session;
085
086    private HttpSession session() {
087        if (session == null) {
088            session = sessionFactory.getSession(request);
089        }
090        return session;
091    }
092
093    private IdentifierConverter<Resource, FedoraResource> idTranslator;
094
095    private IdentifierConverter<Resource, FedoraResource> translator() {
096        if (idTranslator == null) {
097            idTranslator = new HttpResourceConverter(session(), UriBuilder.fromResource(FedoraLdp.class));
098        }
099
100        return idTranslator;
101    }
102
103    /**
104     * Useful for constructing URLs
105     */
106    @Context
107    private UriInfo uriInfo;
108
109
110    @Override
111    protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) {
112        final SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo();
113        final Map<String, Collection<String>> roles;
114        boolean isAdmin = false;
115
116        final Collection<DelegatedHeaderPrincipal> delegatePrincipals =
117                principals.byType(DelegatedHeaderPrincipal.class);
118
119        // if the user was assigned the "fedoraAdmin" container role, they get the
120        // "fedoraAdmin" application role
121        if (principals.byType(ContainerRolesPrincipal.class).contains(adminPrincipal)) {
122            if (delegatePrincipals.size() > 1) {
123                throw new RepositoryConfigurationException("Too many delegates! " + delegatePrincipals);
124            } else if (delegatePrincipals.size() < 1) {
125                authzInfo.addRole(FEDORA_ADMIN_ROLE);
126                return authzInfo;
127            }
128            isAdmin = true;
129            // if Admin is delegating, they are a normal user
130            authzInfo.addRole(FEDORA_USER_ROLE);
131        } else if (principals.byType(ContainerRolesPrincipal.class).contains(userPrincipal)) {
132            authzInfo.addRole(FEDORA_USER_ROLE);
133        }
134
135        // for non-admins, we must check the ACL for the requested resource
136        roles = getRolesForPath();
137
138        for (Object o : principals.asList()) {
139            log.debug("User has principal with name: {}", ((Principal) o).getName());
140        }
141        final Principal userPrincipal = principals.oneByType(BasicUserPrincipal.class);
142        final Collection<HttpHeaderPrincipal> headerPrincipals = principals.byType(HttpHeaderPrincipal.class);
143        // Add permissions for user or delegated user principal
144        if (isAdmin && delegatePrincipals.size() == 1) {
145            final DelegatedHeaderPrincipal delegatedPrincipal = delegatePrincipals.iterator().next();
146            log.debug("Admin user is delegating to {}", delegatedPrincipal);
147            addPermissions(authzInfo, roles, delegatedPrincipal.getName());
148            addPermissions(authzInfo, roles, WEBAC_AUTHENTICATED_AGENT_VALUE);
149        } else if (userPrincipal != null) {
150            log.debug("Basic user principal username: {}", userPrincipal.getName());
151            addPermissions(authzInfo, roles, userPrincipal.getName());
152            addPermissions(authzInfo, roles, WEBAC_AUTHENTICATED_AGENT_VALUE);
153        } else {
154            log.debug("No basic user principal found");
155        }
156        // Add permissions for header principals
157        if (headerPrincipals.isEmpty()) {
158            log.debug("No header principals found!");
159        }
160        headerPrincipals.forEach((headerPrincipal) -> {
161            addPermissions(authzInfo, roles, headerPrincipal.getName());
162        });
163
164        // Added FOAF_AGENT permissions for both authenticated and unauthenticated users
165        addPermissions(authzInfo, roles, FOAF_AGENT_VALUE);
166
167        return authzInfo;
168
169    }
170
171    private Map<String, Collection<String>> getRolesForPath() {
172        Map<String, Collection<String>> roles = null;
173        final FedoraResource fedoraResource = getResourceOrParentFromPath(request.getPathInfo());
174
175        if (fedoraResource != null) {
176            final Node node = ((FedoraResourceImpl) fedoraResource).getNode();
177
178            // check ACL for the request URI and get a mapping of agent => modes
179            roles = rolesProvider.getRoles(node);
180        }
181        return roles;
182    }
183
184    private void addPermissions(final SimpleAuthorizationInfo authzInfo, final Map<String, Collection<String>> roles,
185            final String agentName) {
186        if (roles != null) {
187            final Collection<String> modesForUser = roles.get(agentName);
188                if (modesForUser != null) {
189                    // add WebACPermission instance for each mode in the Authorization
190                    final URI fullRequestURI = URI.create(request.getRequestURL().toString());
191                    for (String mode : modesForUser) {
192                        final WebACPermission perm = new WebACPermission(URI.create(mode), fullRequestURI);
193                        authzInfo.addObjectPermission(perm);
194                        log.debug("Added permission {}", perm);
195                    }
196                }
197        }
198    }
199
200    /**
201     * This realm is authorization-only.
202     */
203    @Override
204    protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken token)
205            throws AuthenticationException {
206        return null;
207    }
208
209    /**
210     * This realm is authorization-only.
211     */
212    @Override
213    public boolean supports(final AuthenticationToken token) {
214        return false;
215    }
216
217    private FedoraResource getResourceOrParentFromPath(final String path) {
218        FedoraResource resource = null;
219        log.debug("Attempting to get FedoraResource for {}", path);
220        try {
221            resource = translator().convert(translator().toDomain(path));
222            log.debug("Got FedoraResource for {}", path);
223        } catch (RepositoryRuntimeException e) {
224            if (e.getCause() instanceof PathNotFoundException) {
225                log.debug("Path {} does not exist", path);
226                // go up the path looking for a node that exists
227                if (path.length() > 1) {
228                    final int lastSlash = path.lastIndexOf("/");
229                    final int end = lastSlash > 0 ? lastSlash : lastSlash + 1;
230                    resource = getResourceOrParentFromPath(path.substring(0, end));
231                }
232            }
233        }
234        return resource;
235    }
236
237}