001/*
002 * The contents of this file are subject to the license and copyright
003 * detailed in the LICENSE and NOTICE files at the root of the source
004 * tree.
005 */
006package org.fcrepo.auth.webac;
007
008import static org.fcrepo.auth.common.DelegateHeaderPrincipalProvider.DelegatedHeaderPrincipal;
009import static org.fcrepo.auth.common.HttpHeaderPrincipalProvider.HttpHeaderPrincipal;
010import static org.fcrepo.auth.common.ServletContainerAuthFilter.FEDORA_ADMIN_ROLE;
011import static org.fcrepo.auth.common.ServletContainerAuthFilter.FEDORA_USER_ROLE;
012import static org.fcrepo.auth.webac.URIConstants.FOAF_AGENT_VALUE;
013import static org.fcrepo.auth.webac.URIConstants.WEBAC_AUTHENTICATED_AGENT_VALUE;
014import static org.fcrepo.auth.webac.WebACFilter.getBaseUri;
015import static org.fcrepo.auth.webac.WebACFilter.identifierConverter;
016import static org.fcrepo.http.commons.session.TransactionConstants.ATOMIC_ID_HEADER;
017import static org.fcrepo.kernel.api.FedoraTypes.FCR_TX;
018import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_ID_PREFIX;
019import static org.slf4j.LoggerFactory.getLogger;
020
021import java.net.URI;
022import java.security.Principal;
023import java.util.Collection;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.Map;
027import java.util.Set;
028
029import javax.inject.Inject;
030import javax.servlet.http.HttpServletRequest;
031
032import org.fcrepo.auth.common.ContainerRolesPrincipalProvider.ContainerRolesPrincipal;
033import org.fcrepo.config.FedoraPropsConfig;
034import org.fcrepo.http.commons.session.TransactionProvider;
035import org.fcrepo.kernel.api.ContainmentIndex;
036import org.fcrepo.kernel.api.ReadOnlyTransaction;
037import org.fcrepo.kernel.api.Transaction;
038import org.fcrepo.kernel.api.TransactionManager;
039import org.fcrepo.kernel.api.exception.PathNotFoundException;
040import org.fcrepo.kernel.api.exception.RepositoryConfigurationException;
041import org.fcrepo.kernel.api.identifiers.FedoraId;
042import org.fcrepo.kernel.api.models.FedoraResource;
043import org.fcrepo.kernel.api.models.ResourceFactory;
044
045import org.apache.http.auth.BasicUserPrincipal;
046import org.apache.shiro.authc.AuthenticationException;
047import org.apache.shiro.authc.AuthenticationInfo;
048import org.apache.shiro.authc.AuthenticationToken;
049import org.apache.shiro.authz.AuthorizationInfo;
050import org.apache.shiro.authz.SimpleAuthorizationInfo;
051import org.apache.shiro.realm.AuthorizingRealm;
052import org.apache.shiro.subject.PrincipalCollection;
053import org.slf4j.Logger;
054import org.springframework.beans.factory.annotation.Autowired;
055import org.springframework.beans.factory.annotation.Qualifier;
056/**
057 * Authorization-only realm that performs authorization checks using WebAC ACLs stored in a Fedora repository. It
058 * locates the ACL for the currently requested resource and parses the ACL RDF into a set of {@link WebACPermission}
059 * instances.
060 *
061 * @author peichman
062 */
063public class WebACAuthorizingRealm extends AuthorizingRealm {
064
065    private static final Logger log = getLogger(WebACAuthorizingRealm.class);
066
067    private static final ContainerRolesPrincipal adminPrincipal = new ContainerRolesPrincipal(FEDORA_ADMIN_ROLE);
068
069    private static final ContainerRolesPrincipal userPrincipal = new ContainerRolesPrincipal(FEDORA_USER_ROLE);
070
071    public static final String URIS_TO_AUTHORIZE = "URIS_TO_AUTHORIZE";
072
073    @Inject
074    private FedoraPropsConfig fedoraPropsConfig;
075
076    @Inject
077    private HttpServletRequest request;
078
079    @Inject
080    private WebACRolesProvider rolesProvider;
081
082    @Inject
083    private TransactionManager transactionManager;
084
085    @Inject
086    private ResourceFactory resourceFactory;
087
088    @Autowired
089    @Qualifier("containmentIndex")
090    private ContainmentIndex containmentIndex;
091
092    private Transaction transaction() {
093        final String txId = request.getHeader(ATOMIC_ID_HEADER);
094        if (txId == null) {
095            return ReadOnlyTransaction.INSTANCE;
096        }
097        final var txProvider = new TransactionProvider(transactionManager, request,
098                getBaseUri(request), fedoraPropsConfig.getJmsBaseUrl());
099        return txProvider.provide();
100    }
101
102    @Override
103    protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) {
104        final SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo();
105        boolean isAdmin = false;
106
107        final Collection<DelegatedHeaderPrincipal> delegatePrincipals =
108                principals.byType(DelegatedHeaderPrincipal.class);
109
110        // if the user was assigned the "fedoraAdmin" container role, they get the
111        // "fedoraAdmin" application role
112        if (principals.byType(ContainerRolesPrincipal.class).contains(adminPrincipal)) {
113            if (delegatePrincipals.size() > 1) {
114                throw new RepositoryConfigurationException("Too many delegates! " + delegatePrincipals);
115            } else if (delegatePrincipals.size() < 1) {
116                authzInfo.addRole(FEDORA_ADMIN_ROLE);
117                return authzInfo;
118            }
119            isAdmin = true;
120            // if Admin is delegating, they are a normal user
121            authzInfo.addRole(FEDORA_USER_ROLE);
122        } else if (principals.byType(ContainerRolesPrincipal.class).contains(userPrincipal)) {
123            authzInfo.addRole(FEDORA_USER_ROLE);
124        }
125
126        // for non-admins, we must check the ACL for the requested resource
127        @SuppressWarnings("unchecked")
128        Set<URI> targetURIs = (Set<URI>) request.getAttribute(URIS_TO_AUTHORIZE);
129        if (targetURIs == null) {
130            targetURIs = new HashSet<>();
131        }
132        final Map<URI, Map<String, Collection<String>>> rolesForURI = new HashMap<>();
133        final String contextPath = request.getContextPath() + request.getServletPath();
134        for (final URI uri : targetURIs) {
135            if (identifierConverter(request).inInternalDomain(uri.toString())) {
136                final FedoraId id = FedoraId.create(uri.toString());
137                log.debug("Getting roles for id {}", id.getFullId());
138                rolesForURI.put(uri, getRolesForId(id));
139            } else {
140                String path = uri.getPath();
141                if (path.startsWith(contextPath)) {
142                    path = path.replaceFirst(contextPath, "");
143                }
144                log.debug("Getting roles for path {}", path);
145                rolesForURI.put(uri, getRolesForPath(path));
146            }
147        }
148
149        for (final Object o : principals.asList()) {
150            log.debug("User has principal with name: {}", ((Principal) o).getName());
151        }
152        final Principal userPrincipal = principals.oneByType(BasicUserPrincipal.class);
153        final Collection<HttpHeaderPrincipal> headerPrincipals = principals.byType(HttpHeaderPrincipal.class);
154        // Add permissions for user or delegated user principal
155        if (isAdmin && delegatePrincipals.size() == 1) {
156            final DelegatedHeaderPrincipal delegatedPrincipal = delegatePrincipals.iterator().next();
157            log.debug("Admin user is delegating to {}", delegatedPrincipal);
158            addPermissions(authzInfo, rolesForURI, delegatedPrincipal.getName());
159            addPermissions(authzInfo, rolesForURI, WEBAC_AUTHENTICATED_AGENT_VALUE);
160        } else if (userPrincipal != null) {
161            log.debug("Basic user principal username: {}", userPrincipal.getName());
162            addPermissions(authzInfo, rolesForURI, userPrincipal.getName());
163            addPermissions(authzInfo, rolesForURI, WEBAC_AUTHENTICATED_AGENT_VALUE);
164        } else {
165            log.debug("No basic user principal found");
166        }
167        // Add permissions for header principals
168        if (headerPrincipals.isEmpty()) {
169            log.debug("No header principals found!");
170        }
171        headerPrincipals.forEach((headerPrincipal) -> {
172            addPermissions(authzInfo, rolesForURI, headerPrincipal.getName());
173        });
174
175        // Added FOAF_AGENT permissions for both authenticated and unauthenticated users
176        addPermissions(authzInfo, rolesForURI, FOAF_AGENT_VALUE);
177
178        return authzInfo;
179
180    }
181
182    private Map<String, Collection<String>> getRolesForPath(final String path) {
183        final FedoraId id = identifierConverter(request).pathToInternalId(path);
184        return getRolesForId(id);
185    }
186
187    private Map<String, Collection<String>> getRolesForId(final FedoraId id) {
188        Map<String, Collection<String>> roles = null;
189
190        final var txId = FEDORA_ID_PREFIX + "/" + FCR_TX;
191        final FedoraResource fedoraResource = getResourceOrParentFromPath(id);
192        if (id.getResourceId().startsWith(txId) && fedoraResource != null) {
193            roles = rolesProvider.getRoles(id, fedoraResource, transaction());
194        } else if (fedoraResource != null) {
195            // check ACL for the request URI and get a mapping of agent => modes
196            roles = rolesProvider.getRoles(fedoraResource, transaction());
197        }
198        return roles;
199    }
200
201    private void addPermissions(final SimpleAuthorizationInfo authzInfo,
202            final Map<URI, Map<String, Collection<String>>> rolesForURI, final String agentName) {
203        if (rolesForURI != null) {
204            for (final URI uri : rolesForURI.keySet()) {
205                log.debug("Adding permissions gathered for URI {}", uri);
206                final Map<String, Collection<String>> roles = rolesForURI.get(uri);
207                if (roles != null) {
208                    final Collection<String> modesForUser = roles.get(agentName);
209                    if (modesForUser != null) {
210                        // add WebACPermission instance for each mode in the Authorization
211                        for (final String mode : modesForUser) {
212                            final WebACPermission perm = new WebACPermission(URI.create(mode), uri);
213                            authzInfo.addObjectPermission(perm);
214                        }
215                    }
216                }
217            }
218        }
219    }
220
221    /**
222     * This realm is authorization-only.
223     */
224    @Override
225    protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken token)
226            throws AuthenticationException {
227        return null;
228    }
229
230    /**
231     * This realm is authorization-only.
232     */
233    @Override
234    public boolean supports(final AuthenticationToken token) {
235        return false;
236    }
237
238    private FedoraResource getResourceOrParentFromPath(final FedoraId fedoraId) {
239        try {
240            log.debug("Testing FedoraResource for {}", fedoraId.getFullIdPath());
241            return this.resourceFactory.getResource(transaction(), fedoraId);
242        } catch (final PathNotFoundException exc) {
243            log.debug("Resource {} not found getting container", fedoraId.getFullIdPath());
244            final FedoraId containerId =
245                    containmentIndex.getContainerIdByPath(transaction(), fedoraId, false);
246            log.debug("Attempting to get FedoraResource for {}", fedoraId.getFullIdPath());
247            try {
248                log.debug("Got FedoraResource for {}", containerId.getFullIdPath());
249                return this.resourceFactory.getResource(transaction(), containerId);
250            } catch (final PathNotFoundException exc2) {
251                log.debug("Path {} does not exist, but we should never end up here.", containerId.getFullIdPath());
252                return null;
253            }
254        }
255    }
256
257}