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}