/******************************************************************************* * Copyright (c) 2012-2015 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.api.user.server; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.core.rest.Service; import org.eclipse.che.api.core.rest.annotations.GenerateLink; import org.eclipse.che.api.core.rest.annotations.Required; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.util.LinksHelper; import org.eclipse.che.api.user.server.dao.PreferenceDao; import org.eclipse.che.api.user.server.dao.Profile; import org.eclipse.che.api.user.server.dao.User; import org.eclipse.che.api.user.server.dao.UserDao; import org.eclipse.che.api.user.server.dao.UserProfileDao; import org.eclipse.che.api.user.shared.dto.UserDescriptor; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.dto.server.DtoFactory; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; import com.wordnik.swagger.annotations.ApiParam; import com.wordnik.swagger.annotations.ApiResponse; import com.wordnik.swagger.annotations.ApiResponses; import javax.annotation.security.RolesAllowed; import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; 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.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriBuilder; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import static org.eclipse.che.commons.lang.NameGenerator.generate; import static javax.ws.rs.core.Response.status; import static javax.ws.rs.core.Response.Status.CREATED; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static javax.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED; import static org.eclipse.che.api.user.server.Constants.LINK_REL_GET_USER_BY_ID; import static org.eclipse.che.api.user.server.Constants.LINK_REL_CREATE_USER; import static org.eclipse.che.api.user.server.Constants.LINK_REL_GET_CURRENT_USER; import static org.eclipse.che.api.user.server.Constants.LINK_REL_UPDATE_PASSWORD; import static org.eclipse.che.api.user.server.Constants.LINK_REL_GET_USER_BY_EMAIL; import static org.eclipse.che.api.user.server.Constants.LINK_REL_REMOVE_USER_BY_ID; import static org.eclipse.che.api.user.server.Constants.PASSWORD_LENGTH; import static org.eclipse.che.api.user.server.Constants.LINK_REL_GET_USER_PROFILE_BY_ID; import static org.eclipse.che.api.user.server.Constants.LINK_REL_GET_CURRENT_USER_PROFILE; /** * Provides REST API for user management * * @author Eugene Voevodin */ @Api(value = "/user", description = "User manager") @Path("/user") public class UserService extends Service { private final UserDao userDao; private final UserProfileDao profileDao; private final PreferenceDao preferenceDao; private final TokenValidator tokenValidator; @Inject public UserService(UserDao userDao, UserProfileDao profileDao, PreferenceDao preferenceDao, TokenValidator tokenValidator) { this.userDao = userDao; this.profileDao = profileDao; this.preferenceDao = preferenceDao; this.tokenValidator = tokenValidator; } /** * Creates new user and profile. * * @param token * authentication token * @param isTemporary * if it is {@code true} creates temporary user * @return entity of created user * @throws UnauthorizedException * when token is {@code null} * @throws ConflictException * when token is not valid * @throws ServerException * when some error occurred while persisting user or user profile * @see UserDescriptor * @see #getCurrent(SecurityContext) * @see #updatePassword(String) * @see #getById(String, SecurityContext) * @see #getByEmail(String, SecurityContext) * @see #remove(String) */ @ApiOperation(value = "Create a new user", notes = "Create a new user in the system", response = UserDescriptor.class, position = 1) @ApiResponses({@ApiResponse(code = 201, message = "Created"), @ApiResponse(code = 401, message = "Missed token parameter"), @ApiResponse(code = 409, message = "Invalid token"), @ApiResponse(code = 500, message = "Internal Server Error")}) @POST @Path("/create") @GenerateLink(rel = LINK_REL_CREATE_USER) @Produces(APPLICATION_JSON) public Response create(@ApiParam(value = "Authentication token", required = true) @QueryParam("token") @Required String token, @ApiParam(value = "User type") @QueryParam("temporary") boolean isTemporary, @Context SecurityContext context) throws UnauthorizedException, ConflictException, ServerException, NotFoundException { if (token == null) { throw new UnauthorizedException("Missed token parameter"); } final String email = tokenValidator.validateToken(token); final String id = generate("user", Constants.ID_LENGTH); //creating user final User user = new User().withId(id) .withEmail(email) .withPassword(generate("pass", PASSWORD_LENGTH)); userDao.create(user); //creating profile profileDao.create(new Profile().withId(id).withUserId(id)); //storing preferences final Map<String, String> preferences = new HashMap<>(4); preferences.put("temporary", String.valueOf(isTemporary)); preferences.put("codenvy:created", Long.toString(System.currentTimeMillis())); preferenceDao.setPreferences(id, preferences); return status(CREATED).entity(toDescriptor(user, context)).build(); } /** * Returns {@link UserDescriptor} of current user * * @return entity of current user. * @throws ServerException * when some error occurred while retrieving current user */ @ApiOperation(value = "Get current user", notes = "Get user currently logged in the system", response = UserDescriptor.class, position = 2) @ApiResponses({@ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 500, message = "Internal Server Error")}) @GET @GenerateLink(rel = LINK_REL_GET_CURRENT_USER) @RolesAllowed({"user", "temp_user"}) @Produces(APPLICATION_JSON) public UserDescriptor getCurrent(@Context SecurityContext context) throws NotFoundException, ServerException { final User user = userDao.getById(currentUser().getId()); return toDescriptor(user, context); } /** * Updates current user password. * * @param password * new user password * @throws ConflictException * when given password is {@code null} * @throws ServerException * when some error occurred while updating profile * @see UserDescriptor */ @ApiOperation(value = "Update password", notes = "Update current password", position = 3) @ApiResponses({@ApiResponse(code = 204, message = "OK"), @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 409, message = "Invalid password"), @ApiResponse(code = 500, message = "Internal Server Error")}) @POST @Path("/password") @GenerateLink(rel = LINK_REL_UPDATE_PASSWORD) @RolesAllowed("user") @Consumes(APPLICATION_FORM_URLENCODED) public void updatePassword(@ApiParam(value = "New password", required = true) @FormParam("password") String password) throws NotFoundException, ServerException, ConflictException { checkPassword(password); final User user = userDao.getById(currentUser().getId()); user.setPassword(password); userDao.update(user); } /** * Returns status <b>200</b> and {@link UserDescriptor} built from user with given {@code id} * or status <b>404</b> when user with given {@code id} was not found * * @param id * identifier to search user * @return entity of found user * @throws NotFoundException * when user with given identifier doesn't exist * @throws ServerException * when some error occurred while retrieving user * @see UserDescriptor * @see #getByEmail(String, SecurityContext) */ @ApiOperation(value = "Get user by ID", notes = "Get user by its ID in the system. Roles allowed: system/admin, system/manager.", response = UserDescriptor.class, position = 4) @ApiResponses({@ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 500, message = "Internal Server Error")}) @GET @Path("/{id}") @GenerateLink(rel = LINK_REL_GET_USER_BY_ID) @RolesAllowed({"user", "system/admin", "system/manager"}) @Produces(APPLICATION_JSON) public UserDescriptor getById(@ApiParam(value = "User ID") @PathParam("id") String id, @Context SecurityContext context) throws NotFoundException, ServerException { final User user = userDao.getById(id); return toDescriptor(user, context); } /** * Returns status <b>200</b> and {@link UserDescriptor} built from user with given {@code email} * or status <b>404</b> when user with given {@code email} was not found * * @param email * email to search user * @return entity of found user * @throws NotFoundException * when user with given email doesn't exist * @throws ServerException * when some error occurred while retrieving user * @see UserDescriptor * @see #getById(String, SecurityContext) * @see #remove(String) */ @ApiOperation(value = "Get user by email", notes = "Get user by registration email. Roles allowed: system/admin, system/manager.", response = UserDescriptor.class, position = 5) @ApiResponses({@ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 403, message = "Missed parameter email"), @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 500, message = "Internal Server Error")}) @GET @Path("/find") @GenerateLink(rel = LINK_REL_GET_USER_BY_EMAIL) @RolesAllowed({"user", "system/admin", "system/manager"}) @Produces(APPLICATION_JSON) public UserDescriptor getByEmail(@ApiParam(value = "User email", required = true) @QueryParam("email") @Required String email, @Context SecurityContext context) throws NotFoundException, ServerException, ConflictException { if (email == null) { throw new ConflictException("Missed parameter email"); } final User user = userDao.getByAlias(email); return toDescriptor(user, context); } /** * Removes user with given identifier. * * @param id * identifier to remove user * @throws NotFoundException * when user with given identifier doesn't exist * @throws ServerException * when some error occurred while removing user * @throws ConflictException * when some error occurred while removing user */ @ApiOperation(value = "Delete user", notes = "Delete a user from the system. Roles allowed: system/admin.", position = 6) @ApiResponses({@ApiResponse(code = 204, message = "Deleted"), @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 409, message = "Impossible to remove user"), @ApiResponse(code = 500, message = "Internal Server Error")}) @DELETE @Path("/{id}") @GenerateLink(rel = LINK_REL_REMOVE_USER_BY_ID) @RolesAllowed("system/admin") public void remove(@ApiParam(value = "User ID") @PathParam("id") String id) throws NotFoundException, ServerException, ConflictException { userDao.remove(id); } private void checkPassword(String password) throws ConflictException { if (password == null) { throw new ConflictException("Password required"); } if (password.length() < 8) { throw new ConflictException("Password should contain at least 8 characters"); } int numOfLetters = 0; int numOfDigits = 0; for (char passwordChar : password.toCharArray()) { if (Character.isDigit(passwordChar)) { numOfDigits++; } if (Character.isLetter(passwordChar)) { numOfLetters++; } } if (numOfDigits == 0 || numOfLetters == 0) { throw new ConflictException("Password should contain letters and digits"); } } private UserDescriptor toDescriptor(User user, SecurityContext context) { final List<Link> links = new LinkedList<>(); final UriBuilder uriBuilder = getServiceContext().getServiceUriBuilder(); if (context.isUserInRole("user")) { links.add(LinksHelper.createLink("GET", getServiceContext().getBaseUriBuilder().path(UserProfileService.class) .path(UserProfileService.class, "getCurrent") .build() .toString(), null, APPLICATION_JSON, LINK_REL_GET_CURRENT_USER_PROFILE)); links.add(LinksHelper.createLink("GET", uriBuilder.clone() .path(getClass(), "getCurrent") .build() .toString(), null, APPLICATION_JSON, LINK_REL_GET_CURRENT_USER)); links.add(LinksHelper.createLink("POST", uriBuilder.clone() .path(getClass(), "updatePassword") .build() .toString(), APPLICATION_FORM_URLENCODED, null, LINK_REL_UPDATE_PASSWORD)); } if (context.isUserInRole("system/admin") || context.isUserInRole("system/manager")) { links.add(LinksHelper.createLink("GET", uriBuilder.clone() .path(getClass(), "getById") .build(user.getId()) .toString(), null, APPLICATION_JSON, LINK_REL_GET_USER_BY_ID)); links.add(LinksHelper.createLink("GET", getServiceContext().getBaseUriBuilder() .path(UserProfileService.class).path(UserProfileService.class, "getById") .build(user.getId()) .toString(), null, APPLICATION_JSON, LINK_REL_GET_USER_PROFILE_BY_ID)); links.add(LinksHelper.createLink("GET", uriBuilder.clone() .path(getClass(), "getByEmail") .queryParam("email", user.getEmail()) .build() .toString(), null, APPLICATION_JSON, LINK_REL_GET_USER_BY_EMAIL)); } if (context.isUserInRole("system/admin")) { links.add(LinksHelper.createLink("DELETE", uriBuilder.clone() .path(getClass(), "remove") .build(user.getId()) .toString(), null, null, LINK_REL_REMOVE_USER_BY_ID)); } return DtoFactory.getInstance().createDto(UserDescriptor.class) .withId(user.getId()) .withEmail(user.getEmail()) .withAliases(user.getAliases()) .withPassword("<none>") .withLinks(links); } private org.eclipse.che.commons.user.User currentUser() { return EnvironmentContext.getCurrent().getUser(); } }