/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License
* at:
*
* http://opensource.org/licenses/ecl2.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package org.opencastproject.userdirectory.endpoint;
import static org.apache.http.HttpStatus.SC_BAD_REQUEST;
import static org.apache.http.HttpStatus.SC_CONFLICT;
import static org.apache.http.HttpStatus.SC_CREATED;
import static org.apache.http.HttpStatus.SC_FORBIDDEN;
import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
import static org.apache.http.HttpStatus.SC_NOT_FOUND;
import static org.apache.http.HttpStatus.SC_OK;
import static org.opencastproject.util.RestUtil.getEndpointUrl;
import static org.opencastproject.util.UrlSupport.uri;
import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING;
import org.opencastproject.security.api.JaxbUser;
import org.opencastproject.security.api.JaxbUserList;
import org.opencastproject.security.api.SecurityService;
import org.opencastproject.security.api.UnauthorizedException;
import org.opencastproject.security.api.User;
import org.opencastproject.security.impl.jpa.JpaOrganization;
import org.opencastproject.security.impl.jpa.JpaRole;
import org.opencastproject.security.impl.jpa.JpaUser;
import org.opencastproject.userdirectory.JpaUserAndRoleProvider;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.util.UrlSupport;
import org.opencastproject.util.data.Tuple;
import org.opencastproject.util.doc.rest.RestParameter;
import org.opencastproject.util.doc.rest.RestQuery;
import org.opencastproject.util.doc.rest.RestResponse;
import org.opencastproject.util.doc.rest.RestService;
import org.apache.commons.lang3.StringUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONValue;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
* Provides a sorted set of known users
*/
@Path("/")
@RestService(
name = "UsersUtils",
title = "User utils",
notes = "This service offers the default CRUD Operations for the internal Opencast users.",
abstractText = "Provides operations for internal Opencast users")
public class UserEndpoint {
/** The logger */
private static final Logger logger = LoggerFactory.getLogger(UserEndpoint.class);
private JpaUserAndRoleProvider jpaUserAndRoleProvider;
private SecurityService securityService;
private String endpointBaseUrl;
/** OSGi callback. */
public void activate(ComponentContext cc) {
logger.info("Start users endpoint");
final Tuple<String, String> endpointUrl = getEndpointUrl(cc);
endpointBaseUrl = UrlSupport.concat(endpointUrl.getA(), endpointUrl.getB());
}
/**
* @param securityService
* the securityService to set
*/
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
/**
* @param jpaUserAndRoleProvider
* the persistenceProperties to set
*/
public void setJpaUserAndRoleProvider(JpaUserAndRoleProvider jpaUserAndRoleProvider) {
this.jpaUserAndRoleProvider = jpaUserAndRoleProvider;
}
@GET
@Path("users.json")
@Produces(MediaType.APPLICATION_JSON)
@RestQuery(
name = "allusersasjson",
description = "Returns a list of users",
returnDescription = "Returns a JSON representation of the list of user accounts",
restParameters = {
@RestParameter(
name = "limit",
defaultValue = "100",
description = "The maximum number of items to return per page.",
isRequired = false,
type = RestParameter.Type.STRING),
@RestParameter(
name = "offset",
defaultValue = "0",
description = "The page number.",
isRequired = false,
type = RestParameter.Type.STRING)
}, reponses = {
@RestResponse(
responseCode = SC_OK,
description = "The user accounts.")
})
public JaxbUserList getUsersAsJson(@QueryParam("limit") int limit, @QueryParam("offset") int offset)
throws IOException {
// Set the maximum number of items to return to 100 if this limit parameter is not given
if (limit < 1) {
limit = 100;
}
JaxbUserList userList = new JaxbUserList();
for (Iterator<User> i = jpaUserAndRoleProvider.findUsers("%", offset, limit); i.hasNext();) {
userList.add(i.next());
}
return userList;
}
@GET
@Path("{username}.json")
@Produces(MediaType.APPLICATION_JSON)
@RestQuery(
name = "user",
description = "Returns a user",
returnDescription = "Returns a JSON representation of a user",
pathParameters = {
@RestParameter(
name = "username",
description = "The username.",
isRequired = true,
type = STRING)
}, reponses = {
@RestResponse(
responseCode = SC_OK,
description = "The user account."),
@RestResponse(
responseCode = SC_NOT_FOUND,
description = "User not found")
})
public Response getUserAsJson(@PathParam("username") String username) throws NotFoundException {
User user = jpaUserAndRoleProvider.loadUser(username);
if (user == null) {
logger.debug("Requested user not found: {}", username);
return Response.status(SC_NOT_FOUND).build();
}
return Response.ok(JaxbUser.fromUser(user)).build();
}
@POST
@Path("/")
@RestQuery(
name = "createUser",
description = "Create a new user",
returnDescription = "Location of the new ressource",
restParameters = {
@RestParameter(
name = "username",
description = "The username.",
isRequired = true,
type = STRING),
@RestParameter(
name = "password",
description = "The password.",
isRequired = true,
type = STRING),
@RestParameter(
name = "name",
description = "The name.",
isRequired = false,
type = STRING),
@RestParameter(
name = "email",
description = "The email.",
isRequired = false,
type = STRING),
@RestParameter(
name = "roles",
description = "The user roles as a json array, for example: [\"ROLE_USER\", \"ROLE_ADMIN\"]",
isRequired = false,
type = STRING)
}, reponses = {
@RestResponse(
responseCode = SC_BAD_REQUEST,
description = "Malformed request syntax."),
@RestResponse(
responseCode = SC_CREATED,
description = "User has been created."),
@RestResponse(
responseCode = SC_CONFLICT,
description = "An user with this username already exist."),
@RestResponse(
responseCode = SC_FORBIDDEN,
description = "Not enough permissions to create a user with the admin role.")
})
public Response createUser(@FormParam("username") String username, @FormParam("password") String password,
@FormParam("name") String name, @FormParam("email") String email, @FormParam("roles") String roles) {
if (jpaUserAndRoleProvider.loadUser(username) != null) {
return Response.status(SC_CONFLICT).build();
}
try {
Set<JpaRole> rolesSet = parseRoles(roles);
/* Add new user */
logger.debug("Updating user {}", username);
JpaOrganization organization = (JpaOrganization) securityService.getOrganization();
JpaUser user = new JpaUser(username, password, organization, name, email, jpaUserAndRoleProvider.getName(), true,
rolesSet);
try {
jpaUserAndRoleProvider.addUser(user);
return Response.created(uri(endpointBaseUrl, user.getUsername() + ".json")).build();
} catch (UnauthorizedException ex) {
logger.debug("Create user failed", ex);
return Response.status(Response.Status.FORBIDDEN).build();
}
} catch (IllegalArgumentException e) {
logger.debug("Request with malformed ROLE data: {}", roles);
return Response.status(SC_BAD_REQUEST).build();
}
}
@PUT
@Path("{username}.json")
@RestQuery(
name = "updateUser",
description = "Update an user",
returnDescription = "Status ok",
restParameters = {
@RestParameter(
name = "password",
description = "The password.",
isRequired = true,
type = STRING),
@RestParameter(
name = "name",
description = "The name.",
isRequired = false,
type = STRING),
@RestParameter(
name = "email",
description = "The email.",
isRequired = false,
type = STRING),
@RestParameter(
name = "roles",
description = "The user roles as a json array, for example: [\"ROLE_USER\", \"ROLE_ADMIN\"]",
isRequired = false,
type = STRING)
}, pathParameters = @RestParameter(
name = "username",
description = "The username",
isRequired = true,
type = STRING),
reponses = {
@RestResponse(
responseCode = SC_BAD_REQUEST,
description = "Malformed request syntax."),
@RestResponse(
responseCode = SC_FORBIDDEN,
description = "Not enough permissions to update a user with the admin role."),
@RestResponse(
responseCode = SC_OK,
description = "User has been updated.") })
public Response setUser(@PathParam("username") String username, @FormParam("password") String password,
@FormParam("name") String name, @FormParam("email") String email, @FormParam("roles") String roles) {
try {
User user = jpaUserAndRoleProvider.loadUser(username);
if (user == null) {
return createUser(username, password, name, email, roles);
}
Set<JpaRole> rolesSet = parseRoles(roles);
logger.debug("Updating user {}", username);
JpaOrganization organization = (JpaOrganization) securityService.getOrganization();
jpaUserAndRoleProvider.updateUser(new JpaUser(username, password, organization, name, email,
jpaUserAndRoleProvider.getName(), true, rolesSet));
return Response.status(SC_OK).build();
} catch (NotFoundException e) {
logger.debug("User {} not found.", username);
return Response.status(SC_NOT_FOUND).build();
} catch (UnauthorizedException e) {
logger.debug("Update user failed", e);
return Response.status(Response.Status.FORBIDDEN).build();
} catch (IllegalArgumentException e) {
logger.debug("Request with malformed ROLE data: {}", roles);
return Response.status(SC_BAD_REQUEST).build();
}
}
@DELETE
@Path("{username}.json")
@RestQuery(
name = "deleteUser",
description = "Delete a new user",
returnDescription = "Status ok",
pathParameters = @RestParameter(
name = "username",
type = STRING,
isRequired = true,
description = "The username"),
reponses = {
@RestResponse(
responseCode = SC_OK,
description = "User has been deleted."),
@RestResponse(
responseCode = SC_FORBIDDEN,
description = "Not enough permissions to delete a user with the admin role."),
@RestResponse(
responseCode = SC_NOT_FOUND,
description = "User not found.")
})
public Response deleteUser(@PathParam("username") String username) {
try {
jpaUserAndRoleProvider.deleteUser(username, securityService.getOrganization().getId());
} catch (NotFoundException e) {
logger.debug("User {} not found.", username);
return Response.status(SC_NOT_FOUND).build();
} catch (UnauthorizedException e) {
logger.debug("Error during deletion of user {}: {}", username, e);
return Response.status(SC_FORBIDDEN).build();
} catch (Exception e) {
logger.error("Error during deletion of user {}: {}", username, e);
return Response.status(SC_INTERNAL_SERVER_ERROR).build();
}
logger.debug("User {} removed.", username);
return Response.status(SC_OK).build();
}
/**
* Parse JSON roles array.
*
* @param roles
* String representation of JSON array containing roles
*/
private Set<JpaRole> parseRoles(String roles) throws IllegalArgumentException {
JSONArray rolesArray = null;
/* Try parsing JSON. Return Bad Request if malformed. */
try {
rolesArray = (JSONArray) JSONValue.parseWithException(StringUtils.isEmpty(roles) ? "[]" : roles);
} catch (Exception e) {
throw new IllegalArgumentException("Error parsing JSON array", e);
}
Set<JpaRole> rolesSet = new HashSet<JpaRole>();
/* Add given roles */
for (Object role : rolesArray) {
try {
rolesSet.add(new JpaRole((String) role, (JpaOrganization) securityService.getOrganization()));
} catch (ClassCastException e) {
throw new IllegalArgumentException("Error parsing array vales as String", e);
}
}
return rolesSet;
}
}