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.ATTRIBUTEID_ACTION_ID;
021import static org.fcrepo.auth.xacml.URIConstants.ATTRIBUTEID_ENVIRONMENT_ORIGINAL_IP_ADDRESS;
022import static org.fcrepo.auth.xacml.URIConstants.ATTRIBUTEID_RESOURCE_ID;
023import static org.fcrepo.auth.xacml.URIConstants.ATTRIBUTEID_RESOURCE_WORKSPACE;
024import static org.fcrepo.auth.xacml.URIConstants.ATTRIBUTEID_SUBJECT_ID;
025import static org.fcrepo.auth.xacml.URIConstants.FCREPO_SUBJECT_GROUP;
026import static org.fcrepo.auth.xacml.URIConstants.FCREPO_SUBJECT_ROLE;
027
028import java.io.ByteArrayOutputStream;
029import java.io.IOException;
030import java.security.Principal;
031import java.util.ArrayList;
032import java.util.List;
033import java.util.Set;
034
035import org.jboss.security.xacml.sunxacml.BasicEvaluationCtx;
036import org.jboss.security.xacml.sunxacml.EvaluationCtx;
037import org.jboss.security.xacml.sunxacml.ParsingException;
038import org.jboss.security.xacml.sunxacml.attr.StringAttribute;
039import org.jboss.security.xacml.sunxacml.ctx.Attribute;
040import org.jboss.security.xacml.sunxacml.ctx.RequestCtx;
041import org.jboss.security.xacml.sunxacml.ctx.Subject;
042import org.jboss.security.xacml.sunxacml.finder.AttributeFinder;
043import org.jboss.security.xacml.sunxacml.finder.AttributeFinderModule;
044import org.slf4j.Logger;
045import org.slf4j.LoggerFactory;
046
047
048/**
049 * @author Gregory Jansen
050 *
051 */
052public class FedoraEvaluationCtxBuilder {
053
054    /**
055     * Class-level logger.
056     */
057    private static final Logger LOGGER = LoggerFactory.getLogger(FedoraEvaluationCtxBuilder.class);
058
059    /**
060     * The list of other subjects.
061     */
062    private final List<Subject> subjectList = new ArrayList<>();
063
064    /**
065     * This list of resource attributes.
066     */
067    private final List<Attribute> resourceList = new ArrayList<>();
068
069    /**
070     * This list of action attributes.
071     */
072    private final List<Attribute> actionList = new ArrayList<>();
073
074    /**
075     * This list of environment attributes.
076     */
077    private final List<Attribute> environmentList = new ArrayList<>();
078
079    /**
080     * The list of attribute finder modules.
081     */
082    private final List<AttributeFinderModule> attributeFinderModules = new ArrayList<>();
083
084    /**
085     * Build the evaluation context.
086     *
087     * @return the evaluation context
088     */
089    public final EvaluationCtx build() {
090        final RequestCtx rc = new RequestCtx(subjectList, resourceList, actionList, environmentList);
091        if (LOGGER.isDebugEnabled()) {
092            try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
093                rc.encode(baos);
094                LOGGER.debug("RequestCtx dump:\n{}", baos.toString("utf-8"));
095            } catch (final IOException e) {
096                LOGGER.info("Cannot print request context", e);
097            }
098        }
099
100        final AttributeFinder af = new AttributeFinder();
101        af.setModules(attributeFinderModules);
102        try {
103            final BasicEvaluationCtx result = new BasicEvaluationCtx(rc, af);
104            // result.setResourceId(resourceId);
105            return result;
106        } catch (final ParsingException e) {
107            throw new Error(e);
108        }
109    }
110
111    /**
112     * Add a finder module to context.
113     *
114     * @param module module to add
115     * @return the builder
116     */
117    public final FedoraEvaluationCtxBuilder addFinderModule(final AttributeFinderModule module) {
118        this.attributeFinderModules.add(module);
119        return this;
120    }
121
122    /**
123     * Adds a basic Fedora subject to the context.
124     *
125     * @param username the user principal name, or null
126     * @param roles the effective roles for user, or null
127     * @return the builder
128     */
129    public final FedoraEvaluationCtxBuilder addSubject(final String username, final Set<String> roles) {
130        final List<Attribute> subjectAttrs = new ArrayList<>();
131        if (username != null) {
132            final StringAttribute v = new StringAttribute(username);
133            final Attribute sid = new Attribute(ATTRIBUTEID_SUBJECT_ID, null, null, v);
134            subjectAttrs.add(sid);
135        }
136
137        if (roles != null) {
138            for (final String role : roles) {
139                final StringAttribute roleAttr = new StringAttribute(role);
140                final Attribute roleId = new Attribute(FCREPO_SUBJECT_ROLE, null, null, roleAttr);
141                subjectAttrs.add(roleId);
142            }
143        }
144
145        this.subjectList.add(new Subject(subjectAttrs));
146        return this;
147    }
148
149    /**
150     * Add the node or property path as resource ID.
151     *
152     * @param rawModeShapePath the path to the node or property
153     * @return the builder
154     */
155    public final FedoraEvaluationCtxBuilder addResourceID(final String rawModeShapePath) {
156        final Attribute rid = new Attribute(ATTRIBUTEID_RESOURCE_ID, null, null, new StringAttribute(rawModeShapePath));
157        resourceList.add(rid);
158        return this;
159    }
160
161    /**
162     * Add the workspace name.
163     *
164     * @param name name of workspace
165     * @return the builder
166     */
167    public final FedoraEvaluationCtxBuilder addWorkspace(final String name) {
168        final Attribute wid = new Attribute(ATTRIBUTEID_RESOURCE_WORKSPACE, null, null, new StringAttribute(name));
169        resourceList.add(wid);
170        return this;
171    }
172
173    /**
174     * Adds actions as action ID and modify resource scope to handle remove.
175     *
176     * @param actions the requested actions
177     * @return the builder
178     */
179    public final FedoraEvaluationCtxBuilder addActions(final String[] actions) {
180        if (actions != null) {
181            for (final String action : actions) {
182                final Attribute a = new Attribute(ATTRIBUTEID_ACTION_ID, null, null, new StringAttribute(action));
183                actionList.add(a);
184                // if ("remove".equals(action)) {
185                // final Attribute scope =
186                // new Attribute(ATTRIBUTEID_RESOURCE_SCOPE, null, null,
187                // new StringAttribute("Descendants"));
188                // resourceList.add(scope);
189                // }
190            }
191        }
192        return this;
193    }
194
195    /**
196     * @param remoteAddr the remote address
197     */
198    public void addOriginalRequestIP(final String remoteAddr) {
199        final Attribute a = new Attribute(ATTRIBUTEID_ENVIRONMENT_ORIGINAL_IP_ADDRESS,
200                                          null,
201                                          null,
202                                          new StringAttribute(remoteAddr));
203        actionList.add(a);
204    }
205
206    /**
207     * This method adds group attributes to the subject-set.
208     *
209     * @param user      for arg groups
210     * @param allGroups to be added to the subject-set
211     * @return this object
212     */
213    public FedoraEvaluationCtxBuilder addGroups(final Principal user, final Set<Principal> allGroups) {
214        LOGGER.trace("For user, {}, adding groups {}", user.getName(), allGroups);
215
216        if (null == user || null == allGroups || allGroups.isEmpty()) {
217            LOGGER.trace("Not adding any groups!");
218            return this;
219        }
220
221        final List<Attribute> subjectAttrs = new ArrayList<>();
222        for (final Principal group : allGroups) {
223            // Do not include the user principal in the group attributes.
224            if (!group.equals(user)) {
225                final StringAttribute groupAttr = new StringAttribute(group.getName());
226                final Attribute groupId = new Attribute(FCREPO_SUBJECT_GROUP, null, null, groupAttr);
227                subjectAttrs.add(groupId);
228            }
229        }
230        this.subjectList.add(new Subject(subjectAttrs));
231        return this;
232    }
233
234}