package alien4cloud.security.users.rest;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.google.common.collect.Sets;
import alien4cloud.audit.annotation.Audit;
import alien4cloud.dao.model.FacetedSearchResult;
import alien4cloud.dao.model.GetMultipleDataResult;
import alien4cloud.rest.model.RestErrorBuilder;
import alien4cloud.rest.model.RestErrorCode;
import alien4cloud.rest.model.RestResponse;
import alien4cloud.rest.model.RestResponseBuilder;
import alien4cloud.security.ResourceRoleService;
import alien4cloud.security.groups.GroupService;
import alien4cloud.security.model.Role;
import alien4cloud.security.model.User;
import alien4cloud.security.users.IAlienUserDao;
import alien4cloud.security.users.UserService;
import io.swagger.annotations.ApiOperation;
/**
* UserController allows ALIEN administrators to create, delete, update, or search users.
*
* @author luc boutier
*/
@Component
@RestController
@RequestMapping({"/rest/users", "/rest/v1/users", "/rest/latest/users"})
public class UserController {
@Resource
private IAlienUserDao alienUserDao;
@Resource
private UserService userService;
@Resource
private GroupService groupService;
@Resource
private ResourceRoleService resourceRoleService;
/**
* Create a new user in the system.
*
* @param request The new user to create.
* @return an empty (void) rest {@link RestResponse}.
*/
@ApiOperation(value = "Create a new user in ALIEN.")
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('ADMIN')")
@Audit
public RestResponse<Void> create(@Valid @RequestBody CreateUserRequest request) {
userService.createUser(request.getUsername(), request.getEmail(), request.getFirstName(), request.getLastName(), request.getRoles(),
request.getPassword());
return RestResponseBuilder.<Void> builder().build();
}
@ApiOperation("Update an user by merging the userUpdateRequest into the existing user")
@RequestMapping(value = "/{username}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('ADMIN')")
@Audit
public RestResponse<Void> update(@PathVariable String username, @RequestBody UpdateUserRequest userUpdateRequest) {
userService.updateUser(username, userUpdateRequest);
return RestResponseBuilder.<Void> builder().build();
}
/**
* Get a user from it's username.
*
* @param username The unique username of the user to retrieve.
* @return The user matching the requested username.
*/
@ApiOperation(value = "Get a user based on it's username.", notes = "Returns a rest response that contains the user's details.")
@RequestMapping(value = "/{username}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("isAuthenticated()")
public RestResponse<User> getUser(@PathVariable String username) {
if (username == null || username.isEmpty()) {
return RestResponseBuilder.<User> builder()
.error(RestErrorBuilder.builder(RestErrorCode.ILLEGAL_PARAMETER).message("username cannot be null or empty").build()).build();
}
User user = alienUserDao.find(username);
if (user != null) {
user.setPassword(null);
}
return RestResponseBuilder.<User> builder().data(user).build();
}
/**
* Get multiple users from their usernames.
*
* @param usernames The list of unique usernames of the user to retrieve.
* @return The users matching the given usernames.
*/
@ApiOperation(value = "Get multiple users from their usernames.", notes = "Returns a rest response that contains the list of requested users.")
@RequestMapping(value = "/getUsers", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("isAuthenticated()")
public RestResponse<List<User>> getUsers(@RequestBody List<String> usernames) {
if (usernames == null || usernames.isEmpty()) {
return RestResponseBuilder.<List<User>> builder()
.error(RestErrorBuilder.builder(RestErrorCode.ILLEGAL_PARAMETER).message("usernames cannot be null or empty").build()).build();
}
List<User> users = alienUserDao.find(usernames.toArray(new String[0]));
return RestResponseBuilder.<List<User>> builder().data(users).build();
}
/**
* Search for users.
*
* @param searchRequest The request that contains parameters of the search request.
* @return A {@link RestResponse} that contains a {@link GetMultipleDataResult} of {@link User}.
*/
@ApiOperation(value = "Search for user's registered in alien.")
@RequestMapping(value = "/search", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("isAuthenticated()")
public RestResponse<FacetedSearchResult> searchUsers(@RequestBody UserSearchRequest searchRequest) {
FacetedSearchResult searchResult = alienUserDao.search(searchRequest.getQuery(), searchRequest.getGroup(), searchRequest.getFrom(),
searchRequest.getSize());
return RestResponseBuilder.<FacetedSearchResult> builder().data(searchResult).build();
}
/**
* Delete a user from the store based on it's username.
*
* @param username The unique username of the user to delete.
* @return an empty (void) rest {@link RestResponse}.
*/
@ApiOperation(value = "Delete an existing user from the internal user's repository.")
@RequestMapping(value = "/{username}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('ADMIN')")
@Audit
public RestResponse<Void> deleteUser(@PathVariable String username, HttpServletResponse servletResponse) throws IOException, ClassNotFoundException {
if (StringUtils.isBlank(username)) {
return RestResponseBuilder.<Void> builder()
.error(RestErrorBuilder.builder(RestErrorCode.ILLEGAL_PARAMETER).message("username cannot be null or empty").build()).build();
} else if (alienUserDao.find(username) == null) {
return RestResponseBuilder.<Void> builder().build();
} else if (userService.isAdmin(username) && userService.countAdminUser() == 1) {
servletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
return RestResponseBuilder.<Void> builder().error(
RestErrorBuilder.builder(RestErrorCode.DELETE_LAST_ADMIN_USER_ERROR).message("It's forbidden to remove the last admin user.").build())
.build();
}
userService.deleteUser(username);
return RestResponseBuilder.<Void> builder().build();
}
/**
* Add a role to a given user.
*
* @param username The unique username of the user for which to add a role.
* @param role The role to add to the user.
*/
@ApiOperation(value = "Add a role to a user.")
@RequestMapping(value = "/{username}/roles/{role}", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('ADMIN')")
@Audit
public RestResponse<Void> addRole(@PathVariable String username, @PathVariable String role) {
if (username == null || username.isEmpty()) {
return RestResponseBuilder.<Void> builder()
.error(RestErrorBuilder.builder(RestErrorCode.ILLEGAL_PARAMETER).message("username cannot be null or empty").build()).build();
}
String goodRoleToAdd = Role.getStringFormatedRole(role);
User user = userService.retrieveUser(username);
Set<String> roleSet = user.getRoles() == null ? new HashSet<String>() : Sets.newHashSet(user.getRoles());
roleSet.add(goodRoleToAdd);
user.setRoles(roleSet.toArray(new String[roleSet.size()]));
alienUserDao.save(user);
return RestResponseBuilder.<Void> builder().build();
}
/**
* Removes a role from a given user.
*
* @param username The unique username of the user from which to remove the role.
* @param role The role to remove to the user.
* @return an empty (void) rest {@link RestResponse}.
*/
@ApiOperation(value = "Remove a role from a user.")
@RequestMapping(value = "/{username}/roles/{role}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('ADMIN')")
@Audit
public RestResponse<Void> removeRole(@PathVariable String username, @PathVariable String role, HttpServletResponse servletResponse) {
if (username == null || username.isEmpty()) {
return RestResponseBuilder.<Void> builder()
.error(RestErrorBuilder.builder(RestErrorCode.ILLEGAL_PARAMETER).message("username cannot be null or empty").build()).build();
} else if (userService.isAdmin(username) && userService.countAdminUser() == 1) {
servletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
return RestResponseBuilder.<Void> builder().error(RestErrorBuilder.builder(RestErrorCode.DELETE_LAST_ADMIN_ROLE_ERROR)
.message("It's forbidden to remove the admin role of the last admin user.").build()).build();
}
String goodRoleToAdd = Role.getStringFormatedRole(role);
User user = userService.retrieveUser(username);
String[] roles = user.getRoles();
roles = ArrayUtils.removeElement(roles, goodRoleToAdd);
user.setRoles(roles);
alienUserDao.save(user);
return RestResponseBuilder.<Void> builder().build();
}
}