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}