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