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 static java.util.Collections.singleton; 021import static java.util.Collections.singletonList; 022import static java.util.Collections.unmodifiableSet; 023import static org.fcrepo.kernel.api.RdfCollectors.toModel; 024import static org.fcrepo.kernel.api.RequiredRdfContext.PROPERTIES; 025import static org.fcrepo.kernel.modeshape.FedoraSessionImpl.getJcrSession; 026import static org.jboss.security.xacml.sunxacml.attr.AttributeDesignator.RESOURCE_TARGET; 027import static org.jboss.security.xacml.sunxacml.attr.BagAttribute.createEmptyBag; 028import static org.jboss.security.xacml.sunxacml.ctx.Status.STATUS_PROCESSING_ERROR; 029import static org.slf4j.LoggerFactory.getLogger; 030 031import java.net.URI; 032import java.util.HashSet; 033import java.util.Iterator; 034import java.util.Set; 035 036import javax.inject.Inject; 037 038import org.fcrepo.http.commons.session.SessionFactory; 039import org.fcrepo.kernel.api.FedoraSession; 040import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 041import org.fcrepo.kernel.api.identifiers.IdentifierConverter; 042import org.fcrepo.kernel.api.models.FedoraResource; 043import org.fcrepo.kernel.api.services.NodeService; 044import org.fcrepo.kernel.modeshape.rdf.impl.DefaultIdentifierTranslator; 045 046import org.jboss.security.xacml.sunxacml.EvaluationCtx; 047import org.jboss.security.xacml.sunxacml.attr.AnyURIAttribute; 048import org.jboss.security.xacml.sunxacml.attr.AttributeValue; 049import org.jboss.security.xacml.sunxacml.attr.BagAttribute; 050import org.jboss.security.xacml.sunxacml.cond.EvaluationResult; 051import org.jboss.security.xacml.sunxacml.ctx.Status; 052import org.jboss.security.xacml.sunxacml.finder.AttributeFinderModule; 053import org.slf4j.Logger; 054import org.springframework.stereotype.Component; 055 056import org.apache.jena.rdf.model.Model; 057import org.apache.jena.rdf.model.RDFNode; 058import org.apache.jena.rdf.model.Resource; 059 060/** 061 * Finds resource attributes based on triples in the Fedora graph. Retrieves values where the attribute URI matches the 062 * triple predicate and the triple object can be supplied as the requested data type. 063 * 064 * @author Gregory Jansen 065 * @author Andrew Woods 066 * @author Scott Prater 067 */ 068@Component("tripleAttributeFinderModule") 069public class TripleAttributeFinderModule extends AttributeFinderModule { 070 071 private static final Logger LOGGER = getLogger(TripleAttributeFinderModule.class); 072 073 private static BagAttribute empty_bag; 074 075 private static IdentifierConverter<Resource, FedoraResource> idTranslator; 076 077 /** 078 * Fedora's ModeShape session factory. 079 */ 080 @Inject 081 protected SessionFactory sessionFactory; 082 083 @Inject 084 protected NodeService nodeService; 085 086 /** 087 * Supported designator types. 088 */ 089 private static final Set<Integer> DESIGNATOR_TYPES = unmodifiableSet(singleton(RESOURCE_TARGET)); 090 091 /** 092 * Supports designators. 093 * 094 * @return if designator is supported. 095 * @see org.jboss.security.xacml.sunxacml.finder.AttributeFinderModule#isDesignatorSupported() 096 */ 097 @Override 098 public final boolean isDesignatorSupported() { 099 return true; 100 } 101 102 /** 103 * Supports resource attributes. 104 * 105 * @return the supported designator types. 106 * @see org.jboss.security.xacml.sunxacml.finder.AttributeFinderModule#getSupportedDesignatorTypes() 107 */ 108 @Override 109 public final Set<Integer> getSupportedDesignatorTypes() { 110 return DESIGNATOR_TYPES; 111 } 112 113 /** 114 * Finds the matching triples values. 115 * 116 * @param attributeId The URI of the attribute key (the predicate of the triple) 117 * @see org.jboss.security.xacml.sunxacml.finder.AttributeFinderModule#findAttribute (java.net.URI, java.net.URI, 118 * java.net.URI, java.net.URI, org.jboss.security.xacml.sunxacml.EvaluationCtx, int) 119 */ 120 @Override 121 public final EvaluationResult findAttribute(final URI attributeType, 122 final URI attributeId, 123 final URI issuer, 124 final URI subjectCategory, 125 final EvaluationCtx context, 126 final int designatorType) { 127 LOGGER.debug("findAttribute({}, {}, {}, {}, {}, {})", 128 attributeType, attributeId, issuer, subjectCategory, context, designatorType); 129 130 empty_bag = createEmptyBag(attributeType); 131 132 // Make sure this is a Resource attribute 133 if (designatorType != RESOURCE_TARGET) { 134 LOGGER.debug("Not looking for a resource attribute"); 135 return new EvaluationResult(empty_bag); 136 } 137 138 final FedoraSession session; 139 try { 140 session = sessionFactory.getInternalSession(); 141 } catch (final RepositoryRuntimeException e) { 142 LOGGER.debug("Error getting session!"); 143 final Status status = new Status(singletonList(STATUS_PROCESSING_ERROR), "Error getting session"); 144 return new EvaluationResult(status); 145 } 146 147 // The resourceId is the path of the object be acted on, retrieved from the PDP evaluation context 148 final EvaluationResult ridEvalRes = 149 context.getResourceAttribute(URI.create("http://www.w3.org/2001/XMLSchema#string"), 150 URIConstants.ATTRIBUTEID_RESOURCE_ID, null); 151 final AttributeValue resourceIdAttValue = ridEvalRes.getAttributeValue(); 152 if (resourceIdAttValue.getValue().toString().isEmpty()) { 153 LOGGER.debug("Context should have a resource-id attribute!"); 154 final Status status = new Status(singletonList(STATUS_PROCESSING_ERROR), "Resource Id not found!"); 155 return new EvaluationResult(status); 156 } 157 158 String resourceId = (String) resourceIdAttValue.getValue(); 159 160 // if dealing with set_property action, use parent node for triples 161 final Set<String> actions = PolicyUtil.getActions(context); 162 if (actions.contains("set_property") || actions.contains("add_node")) { 163 final int index = resourceId.lastIndexOf("/{"); 164 if (index > -1) { 165 resourceId = resourceId.substring(0, index); 166 } 167 168 if (resourceId.isEmpty()) { 169 resourceId = "/"; 170 } 171 } 172 173 // Get the resource to be acted on 174 final FedoraResource resource; 175 final String path; 176 try { 177 resource = nodeService.find(session, resourceId); 178 if (resource == null) { 179 LOGGER.debug("Cannot find a fedora resource for {}", resourceId); 180 return new EvaluationResult(empty_bag); 181 } 182 path = resource.getPath(); 183 idTranslator = new DefaultIdentifierTranslator(getJcrSession(session)); 184 185 } catch (final RepositoryRuntimeException e) { 186 // If the object does not exist, it may be due to the action being "create" 187 return new EvaluationResult(empty_bag); 188 } 189 190 LOGGER.debug("Looking for properties on modeshape path {} with repo path {}", resourceId, path); 191 192 // Get the properties of the resource 193 Model properties; 194 try { 195 properties = resource.getTriples(idTranslator, PROPERTIES).collect(toModel()); 196 197 } catch (final RepositoryRuntimeException e) { 198 LOGGER.debug("Cannot retrieve any properties for [{}]: {}", resourceId, e); 199 final Status status = 200 new Status(singletonList(STATUS_PROCESSING_ERROR), 201 "Error retrieving properties for [" + path + "]!"); 202 return new EvaluationResult(status); 203 } 204 205 final Resource graphNode = idTranslator.reverse().convert(resource); 206 if (null == graphNode) { 207 LOGGER.debug("Cannot get subject for[{}]", resource.getPath()); 208 final Status status = 209 new Status(singletonList(STATUS_PROCESSING_ERROR), 210 "Error retrieving properties for [" + path + "]!"); 211 return new EvaluationResult(status); 212 } 213 214 LOGGER.debug("Looking for properties on graph node: {}", graphNode.getURI()); 215 216 // Get the values of the properties matching the type 217 final Iterator<RDFNode> matches = 218 properties.listObjectsOfProperty(graphNode, properties.createProperty(attributeId.toString())); 219 220 final Set<AttributeValue> attr_bag = new HashSet<>(); 221 222 // Add the properties to the bag 223 while (matches.hasNext()) { 224 final RDFNode match = matches.next(); 225 final String uri = match.asResource().getURI(); 226 LOGGER.debug("Found property: {}", uri); 227 attr_bag.add(new AnyURIAttribute(URI.create(uri))); 228 } 229 230 // Return the results, or any empty bag 231 if (attr_bag.isEmpty()) { 232 LOGGER.debug("No matching properties found"); 233 return new EvaluationResult(empty_bag); 234 } 235 236 return new EvaluationResult(new BagAttribute(attributeType, attr_bag)); 237 } 238 239}