/*
* Copyright 2012 Nodeable Inc
*
* Licensed under the Apache 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://www.apache.org/licenses/LICENSE-2.0
*
* 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 com.streamreduce.rest.resource.api;
import com.google.common.collect.ImmutableMap;
import com.streamreduce.Constants;
import com.streamreduce.InvalidUserAliasException;
import com.streamreduce.core.model.Account;
import com.streamreduce.core.model.Role;
import com.streamreduce.core.model.User;
import com.streamreduce.core.service.UserService;
import com.streamreduce.core.service.exception.UserNotFoundException;
import com.streamreduce.rest.dto.response.ConstraintViolationExceptionResponseDTO;
import com.streamreduce.rest.dto.response.UserResponseDTO;
import com.streamreduce.rest.resource.ErrorMessages;
import com.streamreduce.security.Roles;
import com.streamreduce.util.JSONObjectBuilder;
import com.streamreduce.util.SecurityUtil;
import net.sf.json.JSONObject;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
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.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Map;
import java.util.Set;
@Component
@Path("api/user")
public class UserResource extends AbstractTagableSobaResource {
@Autowired
UserService userService;
/**
* Returns the currently logged in user.
*
* @response.representation.200.doc The currently logged in user is returned.
* @response.representation.200.mediaType application/json
*
* @return The requested item.
*/
@GET
public Response getCurrentUser() {
User currentUser = securityService.getCurrentUser();
UserResponseDTO userResponseDTO = toFullDTO(currentUser);
return Response.ok(userResponseDTO).build();
}
/**
* Returns a user looked up by ID or username.
*
* @response.representation.200.doc The requested user is returned
* @response.representation.200.mediaType application/json
* @response.representation.404.doc Returned if no such user is found
* @response.representation.404.mediaType text/plain
*
* @param idOrUsername the requested ID or username
* @return The requested user or null
*/
@GET
@Path("{idOrUsername}")
public Response getUser(@PathParam("idOrUsername") String idOrUsername) {
User currentUser = securityService.getCurrentUser();
User requestedUser;
try {
if (ObjectId.isValid(idOrUsername)) {
requestedUser = userService.getUserById(new ObjectId(idOrUsername), currentUser.getAccount());
} else {
requestedUser = userService.getUser(idOrUsername, currentUser.getAccount());
}
} catch (UserNotFoundException unfe) {
return error("No user found with the following id: " + idOrUsername, Response.status(Response.Status.NOT_FOUND));
}
UserResponseDTO userResponseDTO = toFullDTO(requestedUser);
return Response.ok(userResponseDTO).build();
}
/**
* The user associated to the auth token is logged out of the application and the token is invalidated.
*
* @response.representation.200.doc Success status is always returned.
* @response.representation.200.mediaType text/plain
*
* @return response
*/
@GET
@Path("logout")
public Response logout(@HeaderParam(Constants.NODEABLE_AUTH_TOKEN) String authToken) {
securityService.logoutCurrentUser(authToken);
return Response
.ok()
.build();
}
/**
* Updates password for the currently logged in user.
*
* @response.representation.204.doc Returned if the password was updated successfully.
* @response.representation.400.doc Returned if the password is invalid.
* @response.representation.400.mediaType text/plain
*
* @return the response object
*
*/
@PUT
@Path("password")
@Consumes(MediaType.APPLICATION_JSON)
public Response changePassword(JSONObject json) {
// update just the password
User u = securityService.getCurrentUser();
String password = getJSON(json, "password");
// validate password
if (!SecurityUtil.isValidPassword(password)) {
return error(ErrorMessages.INVALID_PASSWORD_ERROR, Response.status(Response.Status.BAD_REQUEST));
}
u.setPassword(password);
userService.updateUser(u);
return Response
.status(Response.Status.NO_CONTENT)
.build();
}
/**
* Updates the currently logged in user profile.
*
* @response.representation.204.doc Returned if the password was updated successfully.
* @response.representation.400.doc If the request is empty, or if the user alias contains invalid characters.
* @response.representation.400.mediaType text/plain
* @response.representation.409.doc If the request is empty, or if the user alias is already in use.
* @response.representation.409.mediaType text/plain
* @response.representation.500.doc Returned if a general error occurs while updating the user profile.
* @response.representation.500.mediaType text/plain
*
* @return the response object
*/
@PUT
@Path("profile")
@Consumes(MediaType.APPLICATION_JSON)
public Response changeUserProfile(JSONObject json) {
User user = securityService.getCurrentUser();
if (isNullOrEmpty(json)) {
return error("JSON Payload Missing.", Response.status(Response.Status.BAD_REQUEST));
}
// Handle alias changes
if (json.containsKey("alias")) {
String alias = json.getString("alias");
if (!alias.equalsIgnoreCase(user.getAlias())) {
if (!(userService.isAliasAvailable(user.getAccount(), alias))) {
ConstraintViolationExceptionResponseDTO dto = new ConstraintViolationExceptionResponseDTO();
dto.setViolations(ImmutableMap.of("alias", json.getString("alias") + " is already in use"));
return Response.status(Response.Status.CONFLICT).entity(dto).build();
}
}
}
user.mergeWithJSON(json);
try {
userService.updateUser(user);
} catch (InvalidUserAliasException e) {
ConstraintViolationExceptionResponseDTO dto = new ConstraintViolationExceptionResponseDTO();
dto.setViolations(ImmutableMap.of("alias", e.getMessage()));
return Response.status(Response.Status.BAD_REQUEST).entity(dto).build();
} catch (Exception e) {
//Something unexpected, so return a 500.
return error(e.getMessage(), Response.status(Response.Status.INTERNAL_SERVER_ERROR));
}
return Response
.status(Response.Status.NO_CONTENT)
.build();
}
/**
* Updates the account profile associated to the currently logged in user.
*
* @response.representation.204.doc Returned if the password was updated successfully.
* @response.representation.401.doc Returned if the user is not an account administrator.
* @response.representation.401.mediaType text/plain
*
* @param json
* @return the response object
*/
@PUT
@Path("account/profile")
@Consumes(MediaType.APPLICATION_JSON)
public Response changeAccountProfile(JSONObject json) {
// require admin role
if (!securityService.hasRole(Roles.ADMIN_ROLE)) {
return error(ErrorMessages.APPLICATION_ACCESS_DENIED, Response.status(Response.Status.UNAUTHORIZED));
}
User user = securityService.getCurrentUser();
Account account = user.getAccount();
account.mergeWithJSON(json);
userService.updateAccount(account);
return Response
.status(Response.Status.NO_CONTENT)
.build();
}
/**
* Deletes a specific user.
*
* @response.representation.400.doc Returned if the user tries to delete his/her own account.
* @response.representation.400.mediaType text/plain
* @response.representation.401.doc Returned if the user is not an account administrator.
* @response.representation.401.mediaType text/plain
* @response.representation.404.doc Returned if the user is not found.
* @response.representation.404.mediaType text/plain
*
* @param userId
* @return the response object
*/
@DELETE
@Path("{userId}")
@Consumes(MediaType.APPLICATION_JSON)
public Response deleteUser(@PathParam("userId") ObjectId userId) {
User currentUser = securityService.getCurrentUser();
// require admin role
if (!securityService.hasRole(Roles.ADMIN_ROLE)) {
return error(ErrorMessages.APPLICATION_ACCESS_DENIED, Response.status(Response.Status.UNAUTHORIZED));
}
try {
User user = userService.getUserById(userId, currentUser.getAccount());
if (securityService.getCurrentUser().getId().equals(userId)) {
return error("You can not delete yourself. Things will get better, hang in there.", Response.status(Response.Status.BAD_REQUEST));
}
userService.deleteUser(user);
} catch (UserNotFoundException e) {
return error("User not found.", Response.status(Response.Status.NOT_FOUND));
}
return Response.status(Response.Status.OK).build();
}
/**
* Disables a user account.
*
* @response.representation.204.doc If the user was successfully disabled.
* @response.representation.400.doc Returned if the user tries to disable himself.
* @response.representation.400.mediaType text/plain
* @response.representation.401.doc Returned if the user is not an account administrator.
* @response.representation.401.mediaType text/plain
* @response.representation.404.doc Returned if the user is not found.
* @response.representation.404.mediaType text/plain
*
* @param userId
* @return the response object
*/
@PUT
@Path("{userId}/disable")
@Consumes(MediaType.APPLICATION_JSON)
public Response disableUser(@PathParam("userId") ObjectId userId) {
User currentUser = securityService.getCurrentUser();
// require admin role
if (!securityService.hasRole(Roles.ADMIN_ROLE)) {
return error(ErrorMessages.APPLICATION_ACCESS_DENIED, Response.status(Response.Status.UNAUTHORIZED));
}
try {
User user = userService.getUserById(userId, currentUser.getAccount());
user.setUserStatus(User.UserStatus.DISABLED);
user.setUserLocked(true);
// can't disable yourself
if (user.getId().equals(currentUser.getId())) {
return error("You can't disable yourself", Response.status(Response.Status.BAD_REQUEST));
}
// TODO: need to lock this user somehow... this will do for now
if (user.getUsername().equals(Constants.NODEABLE_SUPER_USERNAME)) {
return error("Can't delete super user", Response.status(Response.Status.BAD_REQUEST));
}
userService.updateUser(user);
} catch (UserNotFoundException e) {
return error("User not found.", Response.status(Response.Status.NOT_FOUND));
}
return Response.status(Response.Status.NO_CONTENT).build();
}
/**
* Enables a previously disabled user account
*
* @response.representation.204.doc If the user was successfully enabled.
* @response.representation.401.doc Returned if the user is not an account administrator.
* @response.representation.401.mediaType text/plain
* @response.representation.404.doc Returned if the user is not found.
* @response.representation.404.mediaType text/plain
*
* @param userId
* @return
*/
@PUT
@Path("{userId}/enable")
@Consumes(MediaType.APPLICATION_JSON)
public Response enableUser(@PathParam("userId") ObjectId userId) {
User currentUser = securityService.getCurrentUser();
// require admin role
if (!securityService.hasRole(Roles.ADMIN_ROLE)) {
return error(ErrorMessages.APPLICATION_ACCESS_DENIED, Response.status(Response.Status.UNAUTHORIZED));
}
try {
User user = userService.getUserById(userId, currentUser.getAccount());
user.setUserStatus(User.UserStatus.ACTIVATED);
user.setUserLocked(false);
userService.updateUser(user);
} catch (UserNotFoundException e) {
return error("User not found.", Response.status(Response.Status.NOT_FOUND));
}
return Response.status(Response.Status.NO_CONTENT).build();
}
/**
* Returns the currently logged in user's configuration.
*
* @response.representation.200.doc Returns the user's configuration preferences.
* @response.representation.200.mediaType application/json
*
* @return
*/
@GET
@Path("config")
@Produces(MediaType.APPLICATION_JSON)
public Response getConfig() {
User currentUser = securityService.getCurrentUser();
return Response.ok(currentUser.getConfig()).build();
}
/**
* Sets the currently logged in user's configuration preferences.
*
* @response.representation.200.doc Returns the user's updated configuration preferences.
* @response.representation.200.mediaType application/json
* @response.representation.400.doc Returned if a general exception occurs.
* @response.representation.400.mediaType text/plain
*
* @param jsonObject
* @return
*/
@POST
@Path("config")
@Produces(MediaType.APPLICATION_JSON)
public Response setConfigValues(JSONObject jsonObject) {
User currentUser = securityService.getCurrentUser();
try {
currentUser.appendToConfig(jsonObject);
userService.updateUser(currentUser);
return Response.ok(currentUser.getConfig()).build();
} catch (Exception e) {
return error(e.getMessage(), Response.status(Response.Status.BAD_REQUEST));
}
}
/**
* Removes a configuration preference for the currently logged in user.
*
* @response.representation.201.doc Returns the user's updated configuration preferences.
* @response.representation.400.doc Returned if a general exception occurs.
* @response.representation.400.mediaType text/plain
* @response.representation.404.doc Returned if the configuration preference does not exist.
* @response.representation.404.mediaType text/plain
*
* @param key
* @return the response object
*/
@DELETE
@Path("config/{key}")
@Produces(MediaType.APPLICATION_JSON)
public Response removeConfigKey(@PathParam("key") String key) {
User currentUser = securityService.getCurrentUser();
Map<String,Object> userConfig = currentUser.getConfig();
if (!userConfig.containsKey(key)) {
return error(key + " does not exist", Response.status(Response.Status.NOT_FOUND));
}
try {
currentUser.removeConfigValue(key);
userService.updateUser(currentUser);
return Response.noContent().build();
} catch (Exception e) {
return error(e.getMessage(), Response.status(Response.Status.BAD_REQUEST));
}
}
/**
* Returns configuration preference values for a specific key.
*
* @response.representation.200.doc The configuration preference value.
* @response.representation.200.mediaType application/json
* @response.representation.404.doc Returned if the configuration preference does not exist.
* @response.representation.404.mediaType text/plain
*
* @param key
* @return
*/
@GET
@Path("config/{key}")
@Produces(MediaType.APPLICATION_JSON)
public Response getConfigValueByKey(@PathParam("key") String key) {
User currentUser = securityService.getCurrentUser();
Map<String,Object> userConfig = currentUser.getConfig();
if (userConfig.containsKey(key)) {
return Response.ok(
new JSONObjectBuilder().add(key, userConfig.get(key)).build()
).build();
} else {
return error(key + " does not exist", Response.status(Response.Status.NOT_FOUND));
}
}
/**
* Adds hashtags to a given user account.
*
* @response.representation.204.doc Successful update.
* @response.representation.400.doc Returned if the hashtag payload is empty
* @response.representation.400.mediaType text/plain
* @response.representation.404.doc Returned if the user is not found.
* @response.representation.404.mediaType text/plain
*
* @param userId
* @param json
* @return the response object
*/
@POST
@Path("{userId}/hashtag")
@Consumes(MediaType.APPLICATION_JSON)
@Override
public Response addTag(@PathParam("userId") String userId, JSONObject json) {
String hashtag = getJSON(json, HASHTAG);
User currentUser = securityService.getCurrentUser();
if (isEmpty(hashtag)) {
return error("Hashtag payload is empty", Response.status(Response.Status.BAD_REQUEST));
}
try {
User user = userService.getUserById(new ObjectId(userId), currentUser.getAccount());
user.addHashtag(hashtag);
userService.updateUser(user);
} catch (UserNotFoundException e) {
return error(e.getMessage(), Response.status(Response.Status.NOT_FOUND));
}
return Response.noContent().build();
}
/**
* Returns the hashtags associated with a given account.
*
* @response.representation.200.doc Hashtags associated with the user.
* @response.representation.200.mediaType text/plain
* @response.representation.404.doc Returned if the user is not found.
* @response.representation.404.mediaType text/plain
*
* @param userId
* @return
*/
@GET
@Path("{userId}/hashtag")
@Override
public Response getTags(@PathParam("userId") String userId) {
User currentUser = securityService.getCurrentUser();
Set<String> tags;
try {
User user = userService.getUserById(new ObjectId(userId), currentUser.getAccount());
tags = user.getHashtags();
} catch (UserNotFoundException e) {
return error(e.getMessage(), Response.status(Response.Status.NOT_FOUND));
}
return Response
.ok(tags)
.build();
}
/**
* Removes a hashtag from a user account.
*
* @response.representation.200.doc Hashtags associated with the user.
* @response.representation.200.mediaType text/plain
* @response.representation.400.doc Returned if the user is not found.
* @response.representation.400.mediaType text/plain
* @response.representation.404.doc Returned if the user is not found.
* @response.representation.404.mediaType text/plain
*
* @param userId
* @param hashtag
* @return
*/
@DELETE
@Path("{userId}/hashtag/{tagname}")
@Override
public Response removeTag(@PathParam("userId") String userId, @PathParam("tagname") String hashtag) {
if (isEmpty(hashtag)) {
return error("Hashtag payload is empty", Response.status(Response.Status.BAD_REQUEST));
}
User currentUser = securityService.getCurrentUser();
try {
User user = userService.getUserById(new ObjectId(userId), currentUser.getAccount());
user.removeHashtag(hashtag);
userService.updateUser(user);
} catch (UserNotFoundException e) {
return error(e.getMessage(), Response.status(Response.Status.NOT_FOUND));
}
return Response.ok().build();
}
/**
* Adds a role to a given user.
*
* @response.representation.201.doc Role added.
* @response.representation.201.mediaType text/plain
* @response.representation.400.doc If the role identifier is empty.
* @response.representation.400.mediaType text/plain
* @response.representation.401.doc If the requesting user does not have the admin role.
* @response.representation.401.mediaType text/plain
* @response.representation.404.doc If the target user is not found.
* @response.representation.404.mediaType text/plain
*
* @param userId
* @param roleId
* @return the response object
*/
@POST
@Path("{userId}/roles/{roleId}")
@Consumes(MediaType.APPLICATION_JSON)
public Response addRole(@PathParam("userId") ObjectId userId, @PathParam("roleId") ObjectId roleId) {
if (isNullOrEmpty(roleId)) {
return error(ErrorMessages.MISSING_REQUIRED_FIELD, Response.status(Response.Status.BAD_REQUEST));
}
// require admin role
if (!securityService.hasRole(Roles.ADMIN_ROLE)) {
return error(ErrorMessages.APPLICATION_ACCESS_DENIED, Response.status(Response.Status.UNAUTHORIZED));
}
User currentUser = securityService.getCurrentUser();
try {
User user = userService.getUserById(userId, currentUser.getAccount());
userService.addRole(user, roleId);
} catch (UserNotFoundException e) {
return error("User not found.", Response.status(Response.Status.NOT_FOUND));
}
return Response
.status(Response.Status.CREATED)
.build();
}
/**
* Removes a role from a target user.
*
* @response.representation.201.doc Role added.
* @response.representation.201.mediaType text/plain
* @response.representation.400.doc If the role identifier is empty, or if the user tries to remove the admin role from himself/herself.
* @response.representation.400.mediaType text/plain
* @response.representation.401.doc If the requesting user does not have the admin role.
* @response.representation.401.mediaType text/plain
* @response.representation.404.doc If the target user is not found.
* @response.representation.404.mediaType text/plain
*
* @param userId
* @param roleId
* @return the response object
*/
@DELETE
@Path("{userId}/roles/{roleId}")
@Consumes(MediaType.APPLICATION_JSON)
public Response removeRole(@PathParam("userId") ObjectId userId, @PathParam("roleId") ObjectId roleId) {
if (isNullOrEmpty(roleId)) {
return error(ErrorMessages.MISSING_REQUIRED_FIELD, Response.status(Response.Status.BAD_REQUEST));
}
// require admin role
if (!securityService.hasRole(Roles.ADMIN_ROLE)) {
return error(ErrorMessages.APPLICATION_ACCESS_DENIED, Response.status(Response.Status.UNAUTHORIZED));
}
User currentUser = securityService.getCurrentUser();
try {
Role adminRole = securityService.findRole(Roles.ADMIN_ROLE);
if (currentUser.getId().equals(userId) && adminRole.getId().equals(roleId)) {
// you can't remove the admin role from yourself!
return error("You can not remove the administrator role from your own user.", Response.status(Response.Status.BAD_REQUEST));
}
User user = userService.getUserById(userId, currentUser.getAccount());
userService.removeRole(user, roleId);
} catch (UserNotFoundException e) {
return error("User not found.", Response.status(Response.Status.NOT_FOUND));
}
return Response
.ok()
.build();
}
}