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