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.xacml; 019 020import java.io.ByteArrayOutputStream; 021import java.io.IOException; 022import java.security.Principal; 023import java.util.Set; 024 025import javax.annotation.PostConstruct; 026import javax.inject.Inject; 027import javax.jcr.Session; 028import javax.servlet.http.HttpServletRequest; 029 030import org.fcrepo.auth.roles.common.AbstractRolesAuthorizationDelegate; 031import org.jboss.security.xacml.sunxacml.EvaluationCtx; 032import org.jboss.security.xacml.sunxacml.PDP; 033import org.jboss.security.xacml.sunxacml.ctx.ResponseCtx; 034import org.jboss.security.xacml.sunxacml.ctx.Result; 035import org.jboss.security.xacml.sunxacml.finder.impl.CurrentEnvModule; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038import org.springframework.stereotype.Component; 039 040/** 041 * Responsible for resolving Fedora's permissions within ModeShape via a XACML 042 * Policy Decision Point (PDP). 043 * 044 * @author Gregory Jansen 045 */ 046@Component("fad") 047public class XACMLAuthorizationDelegate extends AbstractRolesAuthorizationDelegate { 048 049 public static final String EVERYONE_NAME = "EVERYONE"; 050 051 /** 052 * The security principal for every request, that represents the "EVERYONE" user. 053 */ 054 private static final Principal EVERYONE = new Principal() { 055 056 // Currently, this is identical to the EVERYONE principal defined in the RBACL module. This is done to 057 // preserve compatibility with earlier versions, where the definition of EVERYONE was part of the 058 // ServletContainerAuthenticationProvider and shared with all of the authorization modules. Currently, the 059 // XACML module does not appear to actually use this principal, and it may be worth reviewing in future for 060 // removal, or for changing it to a more XACML-specific concept of "everyone". 061 062 @Override 063 public String getName() { 064 return XACMLAuthorizationDelegate.EVERYONE_NAME; 065 } 066 067 @Override 068 public String toString() { 069 return getName(); 070 } 071 072 }; 073 074 /** 075 * Class-level logger. 076 */ 077 private static final Logger LOGGER = LoggerFactory.getLogger(XACMLAuthorizationDelegate.class); 078 079 @Inject 080 private PDPFactory pdpFactory; 081 082 /** 083 * The XACML PDP. 084 */ 085 private PDP pdp = null; 086 087 /** 088 * The standard environment attribute finder, supplies date/time. 089 */ 090 private final CurrentEnvModule currentEnvironmentAttributeModule = new CurrentEnvModule(); 091 092 /** 093 * The triple-based resource attribute finder module. 094 */ 095 @Inject 096 private TripleAttributeFinderModule tripleResourceAttributeFinderModule; 097 098 /** 099 * The SPARQL-based resource attribute finder module. 100 */ 101 @Inject 102 private SparqlResourceAttributeFinderModule sparqlResourceAttributeFinderModule; 103 104 /** 105 * Configures the delegate. 106 */ 107 @PostConstruct 108 public final void init() { 109 pdp = pdpFactory.makePDP(); 110 if (pdp == null) { 111 throw new Error("There is no PDP wired by the factory in the Spring context."); 112 } 113 } 114 115 /* 116 * (non-Javadoc) 117 * @see 118 * org.fcrepo.auth.common.FedoraAuthorizationDelegate#hasPermission(javax 119 * .jcr.Session, org.modeshape.jcr.value.Path, java.lang.String[]) 120 */ 121 @Override 122 public boolean rolesHavePermission(final Session session, 123 final String absPath, 124 final String[] actions, 125 final Set<String> roles) { 126 final EvaluationCtx evaluationCtx = buildEvaluationContext(session, absPath, actions, roles); 127 final ResponseCtx resp = pdp.evaluate(evaluationCtx); 128 129 boolean permit = true; 130 for (final Object o : resp.getResults()) { 131 final Result res = (Result) o; 132 if (LOGGER.isDebugEnabled()) { 133 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { 134 res.encode(baos); 135 LOGGER.debug("ResponseCtx dump:\n{}", baos.toString("utf-8")); 136 } catch (final IOException e) { 137 LOGGER.info("Cannot print response context", e); 138 } 139 } 140 if (Result.DECISION_PERMIT != res.getDecision()) { 141 permit = false; 142 break; 143 } 144 } 145 146 LOGGER.debug("Request for actions: {}, on path: {}, with roles: {}. Permission={}", 147 actions, 148 absPath, 149 roles, 150 permit); 151 return permit; 152 } 153 154 /** 155 * Builds a global attribute finder from injected modules that may use 156 * current session information. 157 * 158 * @param session the ModeShape session 159 * @param absPath the node or property path 160 * @param actions the actions requested 161 * @return an attribute finder 162 */ 163 private EvaluationCtx buildEvaluationContext(final Session session, 164 final String absPath, 165 final String[] actions, 166 final Set<String> roles) { 167 final FedoraEvaluationCtxBuilder builder = new FedoraEvaluationCtxBuilder(); 168 builder.addFinderModule(currentEnvironmentAttributeModule); 169 builder.addFinderModule(sparqlResourceAttributeFinderModule); 170 171 // A subject attribute finder prototype is injected with Session 172 // AttributeFinderModule subjectAttributeFinder = null; 173 // if (applicationContext 174 // .containsBeanDefinition(SUBJECT_ATTRIBUTE_FINDER_BEAN)) { 175 // subjectAttributeFinder = 176 // (AttributeFinderModule) applicationContext.getBean( 177 // SUBJECT_ATTRIBUTE_FINDER_BEAN, session); 178 // builder.addFinderModule(subjectAttributeFinder); 179 // } 180 181 // environment attribute finder is injected with Session 182 // AttributeFinderModule environmentAttributeFinder = null; 183 // if (applicationContext 184 // .containsBeanDefinition(ENVIRONMENT_ATTRIBUTE_FINDER_BEAN)) { 185 // environmentAttributeFinder = 186 // (AttributeFinderModule) applicationContext.getBean( 187 // ENVIRONMENT_ATTRIBUTE_FINDER_BEAN, session); 188 // builder.addFinderModule(environmentAttributeFinder); 189 // } 190 191 // Triple attribute finder will look in modeshape for any valid 192 // predicate URI, therefore it falls last in this list. 193 builder.addFinderModule(tripleResourceAttributeFinderModule); 194 LOGGER.debug("effective roles: {}", roles); 195 196 final Principal user = (Principal) session.getAttribute(FEDORA_USER_PRINCIPAL); 197 builder.addSubject(user.getName(), roles); 198 builder.addResourceID(absPath); 199 builder.addWorkspace(session.getWorkspace().getName()); 200 builder.addActions(actions); 201 202 // add the original IP address 203 final HttpServletRequest request = (HttpServletRequest) session.getAttribute(FEDORA_SERVLET_REQUEST); 204 builder.addOriginalRequestIP(request.getRemoteAddr()); 205 206 // add user's groups 207 @SuppressWarnings("unchecked") 208 final Set<Principal> allGroups = (Set<Principal>) session.getAttribute(FEDORA_ALL_PRINCIPALS); 209 LOGGER.debug("effective groups: {}", allGroups); 210 builder.addGroups(user, allGroups); 211 212 return builder.build(); 213 } 214 215 /** 216 * Get the principal that represents the "EVERYONE" user. 217 */ 218 @Override 219 public Principal getEveryonePrincipal() { 220 return EVERYONE; 221 } 222 223}