/******************************************************************************* * 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.rest.Service; import org.eclipse.che.api.core.rest.annotations.Description; 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.ProfileDescriptor; 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.security.RolesAllowed; import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; 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.SecurityContext; import javax.ws.rs.core.UriBuilder; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import static org.eclipse.che.api.user.server.Constants.LINK_REL_UPDATE_CURRENT_USER_PROFILE; import static org.eclipse.che.api.user.server.Constants.LINK_REL_GET_CURRENT_USER_PROFILE; 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_REMOVE_ATTRIBUTES; import static org.eclipse.che.api.user.server.Constants.LINK_REL_REMOVE_PREFERENCES; import static org.eclipse.che.api.user.server.Constants.LINK_REL_UPDATE_PREFERENCES; import static org.eclipse.che.api.user.server.Constants.LINK_REL_UPDATE_USER_PROFILE_BY_ID; import static com.google.common.base.Strings.nullToEmpty; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; /** * User Profile API * * @author Eugene Voevodin * @author Max Shaposhnik */ @Api(value = "/profile", description = "User profile manager") @Path("/profile") public class UserProfileService extends Service { private static final Logger LOG = LoggerFactory.getLogger(UserProfileService.class); private final UserProfileDao profileDao; private final UserDao userDao; private final PreferenceDao preferenceDao; @Inject public UserProfileService(UserProfileDao profileDao, PreferenceDao preferenceDao, UserDao userDao) { this.profileDao = profileDao; this.userDao = userDao; this.preferenceDao = preferenceDao; } /** * <p>Returns {@link ProfileDescriptor} for current user profile.</p> * <p>By default user email will be added to attributes with key <i>'email'</i>.</p> * * @return descriptor of profile * @throws ServerException * when some error occurred while retrieving/updating profile * @see ProfileDescriptor * @see #updateCurrent(Map, SecurityContext) */ @ApiOperation(value = "Get user profile", notes = "Get user profile details", response = ProfileDescriptor.class, position = 1) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 500, message = "Internal Server Error")}) @GET @RolesAllowed({"user", "temp_user"}) @GenerateLink(rel = LINK_REL_GET_CURRENT_USER_PROFILE) @Produces(APPLICATION_JSON) public ProfileDescriptor getCurrent(@Context SecurityContext context) throws NotFoundException, ServerException { final User user = userDao.getById(currentUser().getId()); final Profile profile = profileDao.getById(user.getId()); profile.getAttributes().put("email", user.getEmail()); return toDescriptor(profile, context); } /** * Returns preferences for current user */ @GET @Path("/prefs") @Produces(APPLICATION_JSON) @RolesAllowed({"user", "temp_user"}) public Map<String, String> getPreferences(@QueryParam("filter") String filter) throws ServerException { if (filter != null) { return preferenceDao.getPreferences(currentUser().getId(), filter); } return preferenceDao.getPreferences(currentUser().getId()); } /** * Updates attributes of current user profile. * * @param updates * attributes to update * @return descriptor of updated profile * @throws ServerException * when some error occurred while retrieving/persisting profile * @see ProfileDescriptor */ @POST @RolesAllowed("user") @GenerateLink(rel = LINK_REL_UPDATE_CURRENT_USER_PROFILE) @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) public ProfileDescriptor updateCurrent(@Description("attributes to update") Map<String, String> updates, @Context SecurityContext context) throws NotFoundException, ServerException, ConflictException { if (updates == null || updates.isEmpty()) { throw new ConflictException("Attributes to update required"); } final User user = userDao.getById(currentUser().getId()); final Profile profile = profileDao.getById(user.getId()); profile.getAttributes().putAll(updates); profileDao.update(profile); logEventUserUpdateProfile(user, profile.getAttributes()); return toDescriptor(profile, context); } /** * Updates attributes of certain profile. * * @param profileId * profile identifier * @param updates * attributes to update * @return descriptor of updated profile * @throws NotFoundException * when profile with given identifier doesn't exist * @throws ServerException * when some error occurred while retrieving/updating profile * @see ProfileDescriptor * @see #getById(String, SecurityContext) */ @POST @Path("/{id}") @RolesAllowed({"system/admin"}) @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) public ProfileDescriptor update(@PathParam("id") String profileId, Map<String, String> updates, @Context SecurityContext context) throws NotFoundException, ServerException, ConflictException { if (updates == null || updates.isEmpty()) { throw new ConflictException("Attributes to update required"); } final Profile profile = profileDao.getById(profileId); profile.getAttributes().putAll(updates); profileDao.update(profile); final User user = userDao.getById(profile.getUserId()); logEventUserUpdateProfile(user, profile.getAttributes()); return toDescriptor(profile, context); } /** * Searches for profile with given identifier and {@link ProfileDescriptor} if found. * * @param profileId * profile identifier * @return descriptor of found profile * @throws NotFoundException * when profile with given identifier doesn't exist * @throws ServerException * when some error occurred while retrieving user or profile * @see ProfileDescriptor * @see #getById(String, SecurityContext) */ @ApiOperation(value = "Get profile of a specific user", notes = "Get profile of a specific user. Roles allowed: system/admin, system/manager", response = ProfileDescriptor.class, position = 4) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 500, message = "Internal Server Error")}) @GET @Path("/{id}") @RolesAllowed({"user", "system/admin", "system/manager"}) @Produces(APPLICATION_JSON) public ProfileDescriptor getById(@ApiParam(value = " ID") @PathParam("id") String profileId, @Context SecurityContext context) throws NotFoundException, ServerException { final Profile profile = profileDao.getById(profileId); final User user = userDao.getById(profile.getUserId()); profile.getAttributes().put("email", user.getEmail()); return toDescriptor(profile, context); } /** * <p>Updates preferences of current user profile.</p> * * @param update * update preferences * @return descriptor of updated profile * @throws ServerException * when some error occurred while retrieving/updating profile * @throws ConflictException * when update is {@code null} or <i>empty</i> * @see ProfileDescriptor * @see #updateCurrent(Map, SecurityContext) */ @POST @Path("/prefs") @RolesAllowed({"user", "temp_user"}) @GenerateLink(rel = LINK_REL_UPDATE_PREFERENCES) @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) public Map<String, String> updatePreferences(@Required Map<String, String> update) throws NotFoundException, ServerException, ConflictException { if (update == null || update.isEmpty()) { throw new ConflictException("Preferences to update required"); } final Map<String, String> preferences = preferenceDao.getPreferences(currentUser().getId()); preferences.putAll(update); preferenceDao.setPreferences(currentUser().getId(), preferences); return preferences; } /** * Removes attributes with given names from current user profile. * If names are {@code null} - all attributes will be removed * * @param attrNames * attributes names to remove * @throws ConflictException * when given list of attributes names is {@code null} * @throws ServerException * when some error occurred while retrieving/updating profile */ @ApiOperation(value = "Remove attributes of a current user", notes = "Remove attributes of a current user", position = 6) @ApiResponses(value = { @ApiResponse(code = 204, message = "OK"), @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 409, message = "Attributes names required"), @ApiResponse(code = 500, message = "Internal Server Error")}) @DELETE @Path("/attributes") @GenerateLink(rel = LINK_REL_REMOVE_ATTRIBUTES) @RolesAllowed({"user", "temp_user"}) @Consumes(APPLICATION_JSON) public void removeAttributes(@ApiParam(value = "Attributes", required = true) @Required @Description("Attributes names to remove") List<String> attrNames, @Context SecurityContext context) throws NotFoundException, ServerException, ConflictException { final Profile currentProfile = profileDao.getById(currentUser().getId()); if (attrNames == null) { currentProfile.getAttributes().clear(); } else { for (String attributeName : attrNames) { currentProfile.getAttributes().remove(attributeName); } } profileDao.update(currentProfile); } /** * Removes preferences with given name from current user profile. * If names are {@code null} - all preferences will be removed * * @param names * preferences names to remove * @throws ServerException * when some error occurred while retrieving/updating profile * @see #removeAttributes(List, SecurityContext) */ @ApiOperation(value = "Remove profile references of a current user", notes = "Remove profile references of a current user", position = 7) @ApiResponses(value = { @ApiResponse(code = 204, message = "OK"), @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 409, message = "Preferences names required"), @ApiResponse(code = 500, message = "Internal Server Error")}) @DELETE @Path("/prefs") @GenerateLink(rel = LINK_REL_REMOVE_PREFERENCES) @RolesAllowed({"user", "temp_user"}) @Consumes(APPLICATION_JSON) public void removePreferences(@ApiParam(value = "Preferences to remove", required = true) @Required List<String> names) throws ServerException, NotFoundException { if (names == null) { preferenceDao.remove(currentUser().getId()); } else { final Map<String, String> preferences = preferenceDao.getPreferences(currentUser().getId()); for (String name : names) { preferences.remove(name); } preferenceDao.setPreferences(currentUser().getId(), preferences); } } /** * Converts {@link Profile} to {@link ProfileDescriptor} */ /* package-private used in tests*/ProfileDescriptor toDescriptor(Profile profile, SecurityContext context) { final UriBuilder uriBuilder = getServiceContext().getServiceUriBuilder(); final List<Link> links = new LinkedList<>(); if (context.isUserInRole("user")) { links.add(LinksHelper.createLink("GET", uriBuilder.clone() .path(getClass(), "getCurrent") .build() .toString(), null, APPLICATION_JSON, LINK_REL_GET_CURRENT_USER_PROFILE)); links.add(LinksHelper.createLink("GET", uriBuilder.clone() .path(getClass(), "getById") .build(profile.getId()) .toString(), null, APPLICATION_JSON, LINK_REL_GET_USER_PROFILE_BY_ID)); links.add(LinksHelper.createLink("POST", uriBuilder.clone() .path(getClass(), "updateCurrent") .build() .toString(), APPLICATION_JSON, APPLICATION_JSON, LINK_REL_UPDATE_CURRENT_USER_PROFILE)); links.add(LinksHelper.createLink("POST", uriBuilder.clone() .path(getClass(), "updatePreferences") .build() .toString(), APPLICATION_JSON, APPLICATION_JSON, LINK_REL_UPDATE_PREFERENCES)); } if (context.isUserInRole("system/admin") || context.isUserInRole("system/manager")) { links.add(LinksHelper.createLink("GET", uriBuilder.clone() .path(getClass(), "getById") .build(profile.getId()) .toString(), null, APPLICATION_JSON, LINK_REL_GET_USER_PROFILE_BY_ID)); } if (context.isUserInRole("system/admin")) { links.add(LinksHelper.createLink("POST", uriBuilder.clone() .path(getClass(), "update") .build(profile.getId()) .toString(), APPLICATION_JSON, APPLICATION_JSON, LINK_REL_UPDATE_USER_PROFILE_BY_ID)); } return DtoFactory.getInstance().createDto(ProfileDescriptor.class) .withId(profile.getId()) .withUserId(profile.getUserId()) .withAttributes(profile.getAttributes()) .withLinks(links); } private org.eclipse.che.commons.user.User currentUser() { return EnvironmentContext.getCurrent().getUser(); } private void logEventUserUpdateProfile(User user, Map<String, String> attributes) { final Set<String> emails = new HashSet<>(user.getAliases()); emails.add(user.getEmail()); LOG.info("EVENT#user-update-profile# USER#{}# FIRSTNAME#{}# LASTNAME#{}# COMPANY#{}# PHONE#{}# JOBTITLE#{}# EMAILS#{}# USER-ID#{}#", user.getEmail(), nullToEmpty(attributes.get("firstName")), nullToEmpty(attributes.get("lastName")), nullToEmpty(attributes.get("employer")), nullToEmpty(attributes.get("phone")), nullToEmpty(attributes.get("jobtitle")), user.getAliases(), user.getId()); } }