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}