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}