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 org.fcrepo.auth.xacml.URIConstants.POLICY_URI_PREFIX;
021import static org.fcrepo.auth.xacml.URIConstants.XACML_POLICY_PROPERTY;
022import static org.fcrepo.kernel.modeshape.FedoraSessionImpl.getJcrSession;
023import static org.slf4j.LoggerFactory.getLogger;
024
025import java.net.URI;
026
027import javax.inject.Inject;
028import javax.jcr.Node;
029import javax.jcr.Property;
030import javax.jcr.RepositoryException;
031import javax.xml.parsers.DocumentBuilder;
032import javax.xml.parsers.DocumentBuilderFactory;
033
034import org.fcrepo.http.commons.session.SessionFactory;
035import org.fcrepo.kernel.api.FedoraSession;
036import org.fcrepo.kernel.api.FedoraTypes;
037import org.fcrepo.kernel.api.models.FedoraBinary;
038import org.fcrepo.kernel.api.models.FedoraResource;
039import org.fcrepo.kernel.api.services.BinaryService;
040import org.fcrepo.kernel.api.services.NodeService;
041import org.jboss.security.xacml.sunxacml.AbstractPolicy;
042import org.jboss.security.xacml.sunxacml.EvaluationCtx;
043import org.jboss.security.xacml.sunxacml.MatchResult;
044import org.jboss.security.xacml.sunxacml.Policy;
045import org.jboss.security.xacml.sunxacml.PolicyMetaData;
046import org.jboss.security.xacml.sunxacml.PolicySet;
047import org.jboss.security.xacml.sunxacml.VersionConstraints;
048import org.jboss.security.xacml.sunxacml.attr.AttributeValue;
049import org.jboss.security.xacml.sunxacml.cond.EvaluationResult;
050import org.jboss.security.xacml.sunxacml.finder.PolicyFinder;
051import org.jboss.security.xacml.sunxacml.finder.PolicyFinderModule;
052import org.jboss.security.xacml.sunxacml.finder.PolicyFinderResult;
053import org.slf4j.Logger;
054import org.springframework.stereotype.Component;
055import org.w3c.dom.Document;
056import org.w3c.dom.Element;
057
058
059/**
060 * Locates a policy in ModeShape by evaluation context or by URI.
061 *
062 * @author Gregory Jansen
063 * @author bbpennel
064 */
065@Component("fedoraPolicyFinderModule")
066public class FedoraPolicyFinderModule extends PolicyFinderModule {
067
068    private static final Logger LOGGER = getLogger(FedoraPolicyFinderModule.class);
069
070    @Inject
071    private SessionFactory sessionFactory;
072
073    @Inject
074    private BinaryService binaryService;
075
076    @Inject
077    private NodeService nodeService;
078
079    private PolicyFinder finder;
080
081    /*
082     * This policy finder can find by request context.
083     * @see org.jboss.security.xacml.sunxacml.finder.PolicyFinderModule#
084     * isRequestSupported()
085     */
086    @Override
087    public final boolean isRequestSupported() {
088        return true;
089    }
090
091    /*
092     * This policy finder can find by reference (URI)
093     * @see org.jboss.security.xacml.sunxacml.finder.PolicyFinderModule#
094     * isIdReferenceSupported()
095     */
096    @Override
097    public final boolean isIdReferenceSupported() {
098        return true;
099    }
100
101    /**
102     * Retrieves the policy from the given policy node
103     *
104     * @param policyBinary
105     * @return
106     */
107    private AbstractPolicy getPolicy(final FedoraBinary policyBinary) {
108        return loadPolicy(policyBinary);
109    }
110
111    /**
112     * Creates a new policy or policy set object from the given policy node
113     *
114     * @param policyBinary
115     * @return
116     */
117    private AbstractPolicy loadPolicy(final FedoraBinary policyBinary) {
118        String policyName = "unparsed";
119        try {
120            // create the factory
121            final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
122            factory.setIgnoringComments(true);
123            factory.setNamespaceAware(true);
124            factory.setValidating(false);
125
126            final DocumentBuilder db = factory.newDocumentBuilder();
127
128            // Parse the policy content
129            final Document doc = db.parse(policyBinary.getContent());
130
131            // handle the policy, if it's a known type
132            final Element root = doc.getDocumentElement();
133            final String name = root.getTagName();
134
135            policyName = PolicyUtil.getID(doc);
136            if (name.equals("Policy")) {
137                return Policy.getInstance(root);
138            } else if (name.equals("PolicySet")) {
139                return PolicySet.getInstance(root, finder);
140            } else {
141                // this isn't a root type that we know how to handle
142                throw new Exception("Unknown root document type: " + name);
143            }
144        } catch (final Exception e) {
145            LOGGER.error("Unable to parse policy from {}", policyName, e);
146        }
147
148        // a default fall-through in the case of an error
149        return null;
150    }
151
152    /*
153     * Find a policy in ModeShape that is appropriate for the evaluation
154     * context.
155     * @see
156     * org.jboss.security.xacml.sunxacml.finder.PolicyFinderModule#findPolicy
157     * (org.jboss.security.xacml.sunxacml.EvaluationCtx)
158     */
159    @Override
160    public final PolicyFinderResult findPolicy(final EvaluationCtx context) {
161        final EvaluationResult ridEvalRes = context.getResourceAttribute(
162                URI.create("http://www.w3.org/2001/XMLSchema#string"), URIConstants.ATTRIBUTEID_RESOURCE_ID, null);
163        final AttributeValue resourceIdAttValue = ridEvalRes.getAttributeValue();
164        String path = resourceIdAttValue.getValue().toString();
165
166        LOGGER.debug("Finding policy for resource: {}", path);
167
168        if ("".equals(path.trim())) {
169            path = "/";
170        }
171
172        try {
173            final FedoraSession internalSession = sessionFactory.getInternalSession();
174
175            // Walk up the hierarchy to find the first node with a policy assigned
176            Node nodeWithPolicy = PolicyUtil.getFirstRealNode(path, getJcrSession(internalSession));
177            while (nodeWithPolicy != null && !nodeWithPolicy.hasProperty(XACML_POLICY_PROPERTY)) {
178                nodeWithPolicy = nodeWithPolicy.getParent();
179            }
180
181            // This should never happen, as PolicyUtil.getFirstRealNode() at least returns the root node.
182            if (null == nodeWithPolicy) {
183                LOGGER.warn("No policy found for: {}!", path);
184                return new PolicyFinderResult();
185            }
186
187            final Property prop = nodeWithPolicy.getProperty(XACML_POLICY_PROPERTY);
188
189            final FedoraBinary policyBinary;
190            final FedoraResource resource = nodeService.find(internalSession, prop.getNode().getPath());
191            if (resource.hasType(FedoraTypes.FEDORA_NON_RDF_SOURCE_DESCRIPTION)) {
192                policyBinary = binaryService.findOrCreate(internalSession, resource.getPath());
193
194            } else {
195                LOGGER.warn("Policy Binary not found for: {}", path);
196                return new PolicyFinderResult();
197            }
198
199            if (policyBinary == null) {
200                LOGGER.warn("Policy binary for path: {} was null!", nodeWithPolicy.getPath());
201                return new PolicyFinderResult();
202            }
203
204            final AbstractPolicy policy = getPolicy(policyBinary);
205
206            // Evaluate if the policy targets match the current context
207            final MatchResult match = policy.match(context);
208            final int result = match.getResult();
209
210            if (result == MatchResult.INDETERMINATE) {
211                return new PolicyFinderResult(match.getStatus());
212            }
213
214            // Found a good policy, return it
215            if (result == MatchResult.MATCH) {
216                return new PolicyFinderResult(policy);
217            }
218
219            return new PolicyFinderResult();
220        } catch (final RepositoryException e) {
221            LOGGER.warn("Failed to retrieve a policy for {}", e, path);
222            return new PolicyFinderResult();
223        }
224    }
225
226    /*
227     * Find a policy in ModeShape by reference URI.
228     * @see
229     * org.jboss.security.xacml.sunxacml.finder.PolicyFinderModule#findPolicy
230     * (java.net.URI, int, org.jboss.security.xacml.sunxacml.VersionConstraints,
231     * org.jboss.security.xacml.sunxacml.PolicyMetaData)
232     */
233    @Override
234    public final PolicyFinderResult findPolicy(final URI idReference,
235                                               final int type,
236                                               final VersionConstraints constraints,
237                                               final PolicyMetaData parentMetaData) {
238        final String id = idReference.toString();
239        if (!id.startsWith(POLICY_URI_PREFIX)) {
240            LOGGER.warn("Policy reference must begin with {}, but was {}", POLICY_URI_PREFIX, id);
241            return new PolicyFinderResult();
242        }
243
244        final String path = PolicyUtil.getPathForId(id);
245        final FedoraSession internalSession = sessionFactory.getInternalSession();
246
247        final FedoraBinary policyBinary;
248        final FedoraResource resource = nodeService.find(internalSession, path);
249        if (resource.hasType(FedoraTypes.FEDORA_NON_RDF_SOURCE_DESCRIPTION)) {
250            policyBinary = binaryService.findOrCreate(internalSession, resource.getPath());
251
252        } else {
253            LOGGER.warn("Policy Binary not found for: {}", path);
254            return new PolicyFinderResult();
255        }
256
257        final AbstractPolicy policy = getPolicy(policyBinary);
258
259        return new PolicyFinderResult(policy);
260    }
261
262    /*
263     * (non-Javadoc)
264     * @see
265     * org.jboss.security.xacml.sunxacml.finder.PolicyFinderModule#init(org.
266     * jboss.security.xacml.sunxacml.finder.PolicyFinder)
267     */
268    @Override
269    public void init(final PolicyFinder finder) {
270        this.finder = finder;
271    }
272
273}