001/**
002 * Copyright 2015 DuraSpace, Inc.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.fcrepo.auth.roles.common;
017
018import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
019
020import java.util.List;
021import java.util.Map;
022import java.util.Set;
023
024import javax.inject.Inject;
025import javax.jcr.Node;
026import javax.jcr.RepositoryException;
027import javax.jcr.Session;
028import javax.servlet.http.HttpServletResponse;
029import javax.ws.rs.Consumes;
030import javax.ws.rs.DELETE;
031import javax.ws.rs.GET;
032import javax.ws.rs.POST;
033import javax.ws.rs.Path;
034import javax.ws.rs.PathParam;
035import javax.ws.rs.Produces;
036import javax.ws.rs.QueryParam;
037import javax.ws.rs.WebApplicationException;
038import javax.ws.rs.core.Context;
039import javax.ws.rs.core.Request;
040import javax.ws.rs.core.Response;
041import javax.ws.rs.core.Response.Status;
042import javax.ws.rs.core.UriInfo;
043
044import org.fcrepo.http.commons.AbstractResource;
045import org.fcrepo.http.commons.api.rdf.HttpResourceConverter;
046import org.fcrepo.kernel.api.identifiers.IdentifierConverter;
047import org.fcrepo.kernel.api.models.FedoraBinary;
048import org.fcrepo.kernel.api.models.FedoraResource;
049
050import org.jvnet.hk2.annotations.Optional;
051import org.slf4j.Logger;
052import org.slf4j.LoggerFactory;
053import org.springframework.context.annotation.Scope;
054
055import com.codahale.metrics.annotation.Timed;
056import com.google.common.annotations.VisibleForTesting;
057import com.hp.hpl.jena.rdf.model.Resource;
058
059/**
060 * RESTful interface to create and manage access roles
061 *
062 * @author Gregory Jansen
063 * @since Sep 5, 2013
064 */
065@Scope("request")
066@Path("/{path: .*}/fcr:accessroles")
067public class AccessRoles extends AbstractResource {
068
069    private static final Logger LOGGER = LoggerFactory
070            .getLogger(AccessRoles.class);
071
072    protected IdentifierConverter<Resource,FedoraResource> identifierTranslator;
073
074
075    @Inject
076    protected Session session;
077
078    @Inject
079    @Optional
080    private AccessRolesProvider accessRolesProvider;
081
082    @Context protected Request request;
083    @Context protected HttpServletResponse servletResponse;
084    @Context protected UriInfo uriInfo;
085
086    protected FedoraResource resource;
087
088    @PathParam("path") protected String externalPath;
089
090
091    /**
092     * Default JAX-RS entry point
093     */
094    public AccessRoles() {
095        super();
096    }
097
098    /**
099     * Create a new FedoraNodes instance for a given path
100     * @param externalPath external path
101     */
102    @VisibleForTesting
103    public AccessRoles(final String externalPath) {
104        this.externalPath = externalPath;
105    }
106
107
108    /**
109     * @return the accessRolesProvider
110     */
111    private AccessRolesProvider getAccessRolesProvider() {
112        return accessRolesProvider;
113    }
114
115    /**
116     * Retrieve the roles assigned to each principal on this specific path.
117     *
118     * @param effective the effective roles
119     * @return JSON representation of assignment map
120     */
121    @GET
122    @Produces(APPLICATION_JSON)
123    @Timed
124    public Response get(@QueryParam("effective") final String effective) {
125        LOGGER.debug("Get access roles for: {}", externalPath);
126        LOGGER.debug("effective: {}", effective);
127        Response.ResponseBuilder response;
128        try {
129            final Node node;
130
131            if (resource instanceof FedoraBinary) {
132                node = ((FedoraBinary) resource()).getDescription().getNode();
133            } else {
134                node = resource().getNode();
135            }
136
137            final Map<String, List<String>> data =
138                    this.getAccessRolesProvider().getRoles(node,
139                            (effective != null));
140            if (data == null) {
141                LOGGER.debug("no content response");
142                response = Response.noContent();
143            } else {
144                response = Response.ok(data);
145            }
146        } finally {
147            session.logout();
148        }
149        return response.build();
150    }
151
152    /**
153     * Apply new role assignments at the specified node.
154     *
155     * @param data access roles
156     * @return response
157     * @throws RepositoryException if IllegalArgumentException can not handle
158     */
159    @POST
160    @Consumes(APPLICATION_JSON)
161    @Timed
162    public Response post(final Map<String, Set<String>> data)
163        throws RepositoryException {
164        LOGGER.debug("POST Received request param: {}", request);
165        Response.ResponseBuilder response;
166
167        try {
168            validatePOST(data);
169
170            final FedoraResource resource = resource();
171
172            if (resource instanceof FedoraBinary) {
173                this.getAccessRolesProvider().postRoles(((FedoraBinary) resource).getDescription().getNode(), data);
174            } else {
175                this.getAccessRolesProvider().postRoles(resource.getNode(), data);
176            }
177            session.save();
178            LOGGER.debug("Saved access roles {}", data);
179            response =
180                    Response.created(getUriInfo().getBaseUriBuilder()
181                            .path(externalPath).path("fcr:accessroles").build());
182
183        } catch (final IllegalArgumentException e) {
184            throw new WebApplicationException(e, Response.status(Status.BAD_REQUEST).build());
185        } finally {
186            session.logout();
187        }
188
189        return response.build();
190    }
191
192    /**
193     * @param data
194     */
195    private void validatePOST(final Map<String, Set<String>> data) {
196        if (data.isEmpty()) {
197            throw new IllegalArgumentException(
198                    "Posted access roles must include role assignments");
199        }
200        for (final Map.Entry<String, Set<String>> entry : data.entrySet()) {
201            if (entry.getKey() == null || entry.getValue() == null || entry.getValue().isEmpty()) {
202                throw new IllegalArgumentException(
203                        "Assignments must include principal name and one or more roles");
204            }
205            if (entry.getKey().trim().length() == 0) {
206                throw new IllegalArgumentException(
207                        "Principal names cannot be an empty strings or whitespace.");
208            }
209            for (final String r : entry.getValue()) {
210                if (r.trim().length() == 0) {
211                    throw new IllegalArgumentException(
212                            "Role names cannot be an empty strings or whitespace.");
213                }
214            }
215        }
216    }
217
218    /**
219     * Delete the access roles and node type.
220     * @return response
221     * @throws RepositoryException if delete failed
222     */
223    @DELETE
224    @Timed
225    public Response deleteNodeType() throws RepositoryException {
226        try {
227
228            final Node node;
229
230            if (resource instanceof FedoraBinary) {
231                node = ((FedoraBinary) resource()).getDescription().getNode();
232            } else {
233                node = resource().getNode();
234            }
235
236            this.getAccessRolesProvider().deleteRoles(node);
237            session.save();
238            return Response.noContent().build();
239        } finally {
240            session.logout();
241        }
242    }
243
244    private UriInfo getUriInfo() {
245        return this.uriInfo;
246    }
247
248    protected FedoraResource resource() {
249        if (resource == null) {
250            resource = getResourceFromPath(externalPath);
251        }
252
253        return resource;
254    }
255
256
257    protected IdentifierConverter<Resource,FedoraResource> translator() {
258        if (identifierTranslator == null) {
259            identifierTranslator = new HttpResourceConverter(session,
260                    uriInfo.getBaseUriBuilder().clone().path("{path: .*}"));
261        }
262
263        return identifierTranslator;
264    }
265
266    private FedoraResource getResourceFromPath(final String externalPath) {
267        return translator().convert(translator().toDomain(externalPath));
268    }
269
270}