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