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