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.ServletContainerAuthFilter.FEDORA_ADMIN_ROLE; 021import static org.fcrepo.auth.common.ServletContainerAuthFilter.FEDORA_USER_ROLE; 022import static org.fcrepo.auth.webac.URIConstants.FOAF_AGENT_VALUE; 023import static org.fcrepo.auth.webac.URIConstants.WEBAC_AUTHENTICATED_AGENT_VALUE; 024import static org.fcrepo.auth.common.HttpHeaderPrincipalProvider.HttpHeaderPrincipal; 025import static org.fcrepo.auth.common.DelegateHeaderPrincipalProvider.DelegatedHeaderPrincipal; 026import static org.slf4j.LoggerFactory.getLogger; 027 028import java.net.URI; 029import java.security.Principal; 030import java.util.Collection; 031import java.util.Map; 032import javax.inject.Inject; 033import javax.jcr.Node; 034import javax.jcr.PathNotFoundException; 035import javax.servlet.http.HttpServletRequest; 036import javax.ws.rs.core.Context; 037import javax.ws.rs.core.UriBuilder; 038import javax.ws.rs.core.UriInfo; 039 040import org.apache.http.auth.BasicUserPrincipal; 041import org.apache.jena.rdf.model.Resource; 042import org.apache.shiro.authc.AuthenticationException; 043import org.apache.shiro.authc.AuthenticationInfo; 044import org.apache.shiro.authc.AuthenticationToken; 045import org.apache.shiro.authz.AuthorizationInfo; 046import org.apache.shiro.authz.SimpleAuthorizationInfo; 047import org.apache.shiro.realm.AuthorizingRealm; 048import org.apache.shiro.subject.PrincipalCollection; 049import org.fcrepo.auth.common.ContainerRolesPrincipalProvider.ContainerRolesPrincipal; 050import org.fcrepo.http.api.FedoraLdp; 051import org.fcrepo.http.commons.api.rdf.HttpResourceConverter; 052import org.fcrepo.http.commons.session.HttpSession; 053import org.fcrepo.http.commons.session.SessionFactory; 054import org.fcrepo.kernel.api.exception.RepositoryConfigurationException; 055import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 056import org.fcrepo.kernel.api.identifiers.IdentifierConverter; 057import org.fcrepo.kernel.api.models.FedoraResource; 058import org.fcrepo.kernel.modeshape.FedoraResourceImpl; 059import org.slf4j.Logger; 060/** 061 * Authorization-only realm that performs authorization checks using WebAC ACLs stored in a Fedora repository. It 062 * locates the ACL for the currently requested resource and parses the ACL RDF into a set of {@link WebACPermission} 063 * instances. 064 * 065 * @author peichman 066 */ 067public class WebACAuthorizingRealm extends AuthorizingRealm { 068 069 private static final Logger log = getLogger(WebACAuthorizingRealm.class); 070 071 private static final ContainerRolesPrincipal adminPrincipal = new ContainerRolesPrincipal(FEDORA_ADMIN_ROLE); 072 073 private static final ContainerRolesPrincipal userPrincipal = new ContainerRolesPrincipal(FEDORA_USER_ROLE); 074 075 @Inject 076 private SessionFactory sessionFactory; 077 078 @Inject 079 private HttpServletRequest request; 080 081 @Inject 082 private WebACRolesProvider rolesProvider; 083 084 private HttpSession session; 085 086 private HttpSession session() { 087 if (session == null) { 088 session = sessionFactory.getSession(request); 089 } 090 return session; 091 } 092 093 private IdentifierConverter<Resource, FedoraResource> idTranslator; 094 095 private IdentifierConverter<Resource, FedoraResource> translator() { 096 if (idTranslator == null) { 097 idTranslator = new HttpResourceConverter(session(), UriBuilder.fromResource(FedoraLdp.class)); 098 } 099 100 return idTranslator; 101 } 102 103 /** 104 * Useful for constructing URLs 105 */ 106 @Context 107 private UriInfo uriInfo; 108 109 110 @Override 111 protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) { 112 final SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo(); 113 final Map<String, Collection<String>> roles; 114 boolean isAdmin = false; 115 116 final Collection<DelegatedHeaderPrincipal> delegatePrincipals = 117 principals.byType(DelegatedHeaderPrincipal.class); 118 119 // if the user was assigned the "fedoraAdmin" container role, they get the 120 // "fedoraAdmin" application role 121 if (principals.byType(ContainerRolesPrincipal.class).contains(adminPrincipal)) { 122 if (delegatePrincipals.size() > 1) { 123 throw new RepositoryConfigurationException("Too many delegates! " + delegatePrincipals); 124 } else if (delegatePrincipals.size() < 1) { 125 authzInfo.addRole(FEDORA_ADMIN_ROLE); 126 return authzInfo; 127 } 128 isAdmin = true; 129 // if Admin is delegating, they are a normal user 130 authzInfo.addRole(FEDORA_USER_ROLE); 131 } else if (principals.byType(ContainerRolesPrincipal.class).contains(userPrincipal)) { 132 authzInfo.addRole(FEDORA_USER_ROLE); 133 } 134 135 // for non-admins, we must check the ACL for the requested resource 136 roles = getRolesForPath(); 137 138 for (Object o : principals.asList()) { 139 log.debug("User has principal with name: {}", ((Principal) o).getName()); 140 } 141 final Principal userPrincipal = principals.oneByType(BasicUserPrincipal.class); 142 final Collection<HttpHeaderPrincipal> headerPrincipals = principals.byType(HttpHeaderPrincipal.class); 143 // Add permissions for user or delegated user principal 144 if (isAdmin && delegatePrincipals.size() == 1) { 145 final DelegatedHeaderPrincipal delegatedPrincipal = delegatePrincipals.iterator().next(); 146 log.debug("Admin user is delegating to {}", delegatedPrincipal); 147 addPermissions(authzInfo, roles, delegatedPrincipal.getName()); 148 addPermissions(authzInfo, roles, WEBAC_AUTHENTICATED_AGENT_VALUE); 149 } else if (userPrincipal != null) { 150 log.debug("Basic user principal username: {}", userPrincipal.getName()); 151 addPermissions(authzInfo, roles, userPrincipal.getName()); 152 addPermissions(authzInfo, roles, WEBAC_AUTHENTICATED_AGENT_VALUE); 153 } else { 154 log.debug("No basic user principal found"); 155 } 156 // Add permissions for header principals 157 if (headerPrincipals.isEmpty()) { 158 log.debug("No header principals found!"); 159 } 160 headerPrincipals.forEach((headerPrincipal) -> { 161 addPermissions(authzInfo, roles, headerPrincipal.getName()); 162 }); 163 164 // Added FOAF_AGENT permissions for both authenticated and unauthenticated users 165 addPermissions(authzInfo, roles, FOAF_AGENT_VALUE); 166 167 return authzInfo; 168 169 } 170 171 private Map<String, Collection<String>> getRolesForPath() { 172 Map<String, Collection<String>> roles = null; 173 final FedoraResource fedoraResource = getResourceOrParentFromPath(request.getPathInfo()); 174 175 if (fedoraResource != null) { 176 final Node node = ((FedoraResourceImpl) fedoraResource).getNode(); 177 178 // check ACL for the request URI and get a mapping of agent => modes 179 roles = rolesProvider.getRoles(node); 180 } 181 return roles; 182 } 183 184 private void addPermissions(final SimpleAuthorizationInfo authzInfo, final Map<String, Collection<String>> roles, 185 final String agentName) { 186 if (roles != null) { 187 final Collection<String> modesForUser = roles.get(agentName); 188 if (modesForUser != null) { 189 // add WebACPermission instance for each mode in the Authorization 190 final URI fullRequestURI = URI.create(request.getRequestURL().toString()); 191 for (String mode : modesForUser) { 192 final WebACPermission perm = new WebACPermission(URI.create(mode), fullRequestURI); 193 authzInfo.addObjectPermission(perm); 194 log.debug("Added permission {}", perm); 195 } 196 } 197 } 198 } 199 200 /** 201 * This realm is authorization-only. 202 */ 203 @Override 204 protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken token) 205 throws AuthenticationException { 206 return null; 207 } 208 209 /** 210 * This realm is authorization-only. 211 */ 212 @Override 213 public boolean supports(final AuthenticationToken token) { 214 return false; 215 } 216 217 private FedoraResource getResourceOrParentFromPath(final String path) { 218 FedoraResource resource = null; 219 log.debug("Attempting to get FedoraResource for {}", path); 220 try { 221 resource = translator().convert(translator().toDomain(path)); 222 log.debug("Got FedoraResource for {}", path); 223 } catch (RepositoryRuntimeException e) { 224 if (e.getCause() instanceof PathNotFoundException) { 225 log.debug("Path {} does not exist", path); 226 // go up the path looking for a node that exists 227 if (path.length() > 1) { 228 final int lastSlash = path.lastIndexOf("/"); 229 final int end = lastSlash > 0 ? lastSlash : lastSlash + 1; 230 resource = getResourceOrParentFromPath(path.substring(0, end)); 231 } 232 } 233 } 234 return resource; 235 } 236 237}