Java tutorial
/*- * #%L * Eureka! Clinical User Services * %% * Copyright (C) 2016 Emory University * %% * 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. * #L% */ package org.eurekaclinical.user.service.resource; import org.eurekaclinical.user.service.dao.AuthenticationMethodDao; import org.eurekaclinical.user.service.dao.LocalUserDao; import org.eurekaclinical.user.service.dao.LoginTypeDao; import org.eurekaclinical.user.service.dao.RoleDao; import org.eurekaclinical.user.service.dao.OAuthProviderDao; import org.eurekaclinical.user.service.dao.UserDao; import java.net.URI; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import javax.annotation.security.RolesAllowed; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; import org.apache.commons.lang3.StringUtils; import org.jasig.cas.client.authentication.AttributePrincipal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.inject.Inject; import com.google.inject.persist.Transactional; import org.eurekaclinical.user.client.comm.PasswordChangeRequest; import org.eurekaclinical.user.client.comm.User; import org.eurekaclinical.user.service.entity.LocalUserEntity; import org.eurekaclinical.user.service.entity.RoleEntity; import org.eurekaclinical.user.service.entity.UserEntity; import org.eurekaclinical.user.service.entity.UserEntityToUserVisitor; import org.eurekaclinical.user.service.email.EmailException; import org.eurekaclinical.user.service.email.EmailSender; import org.eurekaclinical.user.service.util.UserToUserEntityVisitor; import org.eurekaclinical.standardapis.exception.HttpStatusException; import org.eurekaclinical.user.service.util.StringUtil; /** * RESTful end-point for {@link UserEntity} related methods. * * @author miaoai */ @Transactional @Path("/protected/users") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class UserResource { /** * The class logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(UserResource.class); /** * Data access object to work with User objects. */ private final UserDao userDao; /** * Data access object to work with LocalUser objects. */ private final LocalUserDao localUserDao; /** * Data access object to work with Role objects. */ private final RoleDao roleDao; /** * Used to send emails to the user when needed. */ private final EmailSender emailSender; /** * And validation errors that we may have encountered while validating a new * user request, or a user update. */ private String validationError; private UserToUserEntityVisitor visitor; /** * Create a UserResource object with a User DAO and a Role DAO. * * @param inUserDao DAO used to access {@link UserEntity} related * functionality. * @param inLocalUserDao Local user dao * @param inRoleDao DAO used to access {@link RoleEntity} related * functionality. * @param inEmailSender Used to send emails to the user when necessary. * @param inOAuthProviderDao OAuth provider dao * @param inLoginTypeDao Login type dao * @param inAuthenticationMethodDao Authentication method dao */ @Inject public UserResource(UserDao inUserDao, LocalUserDao inLocalUserDao, RoleDao inRoleDao, EmailSender inEmailSender, OAuthProviderDao inOAuthProviderDao, LoginTypeDao inLoginTypeDao, AuthenticationMethodDao inAuthenticationMethodDao) { this.userDao = inUserDao; this.localUserDao = inLocalUserDao; this.roleDao = inRoleDao; this.emailSender = inEmailSender; this.visitor = new UserToUserEntityVisitor(inOAuthProviderDao, inRoleDao, inLoginTypeDao, inAuthenticationMethodDao); } /** * Get a list of all users in the system. * * @return A list of {@link UserEntity} objects. */ @RolesAllowed({ "admin" }) @GET public List<User> getUsers() { List<UserEntity> users = this.userDao.getAll(); LOGGER.debug("Returning list of users"); UserEntityToUserVisitor visitor = new UserEntityToUserVisitor(); visitor.visit(users); return visitor.getUsers(); } /** * Get a user by the user's identification number. * * @param req in request * @param inId The identification number for the user to fetch. * @return The user referenced by the identification number. */ @RolesAllowed({ "researcher", "admin" }) @Path("/{id}") @GET public User getUserById(@Context HttpServletRequest req, @PathParam("id") Long inId) { UserEntity userEntity = this.userDao.retrieve(inId); if (userEntity == null) { throw new HttpStatusException(Response.Status.NOT_FOUND); } if (!req.isUserInRole("admin") && !req.getRemoteUser().equals(userEntity.getUsername())) { throw new HttpStatusException(Response.Status.FORBIDDEN); } this.userDao.refresh(userEntity); LOGGER.debug("Returning user for ID {}", inId); UserEntityToUserVisitor visitor = new UserEntityToUserVisitor(); userEntity.accept(visitor); return visitor.getUser(); } /** * Get a user using the username. * * @param req The HTTP request containing the user name. * * @return The user corresponding to the given name. */ @RolesAllowed({ "researcher", "admin" }) @Path("/me") @GET public User getMe(@Context HttpServletRequest req) { AttributePrincipal principal = (AttributePrincipal) req.getUserPrincipal(); String username = principal.getName(); UserEntity userEntity = this.userDao.getByName(username); if (userEntity != null) { this.userDao.refresh(userEntity); } else { throw new HttpStatusException(Response.Status.NOT_FOUND); } LOGGER.debug("Returning user for name {}", username); UserEntityToUserVisitor visitor = new UserEntityToUserVisitor(); userEntity.accept(visitor); return visitor.getUser(); } /** * Add a new user to the system. The user is activated immediately. * * @param user Object containing all the information about the user to add. * @param uriInfo URI * @return Response */ @RolesAllowed({ "admin" }) @POST public Response addUser(final User user, @Context UriInfo uriInfo) { if (this.userDao.getByName(user.getUsername()) != null) { throw new HttpStatusException(Response.Status.CONFLICT); } String[] errors = user.validate(); if (errors.length == 0) { user.accept(visitor); UserEntity userEntity = visitor.getUserEntity(); LOGGER.debug("Saving new user {}", userEntity.getEmail()); this.userDao.create(userEntity); try { LOGGER.debug("Sending email to {}", userEntity.getEmail()); this.emailSender.sendActivationMessage(userEntity); } catch (EmailException e) { LOGGER.error("Error sending email to {}", userEntity.getEmail(), e); } } else { LOGGER.info("Invalid new user request: {}, reason {}", user, this.validationError); throw new HttpStatusException(Response.Status.BAD_REQUEST, StringUtils.join(errors, ", ")); } UserEntity addedUser = this.userDao.getByName(user.getUsername()); URI uri = uriInfo.getAbsolutePathBuilder().path(addedUser.getId().toString()).build(); return Response.created(uri).entity(user).build(); } /** * Changes a user's password. * * @param request the incoming servlet request * @param passwordChangeRequest the request to use to make the password * change * @return the response. * * @throws HttpStatusException Thrown when a password cannot be properly * hashed, or the passwords are mismatched. */ @RolesAllowed({ "researcher", "admin" }) @Path("/passwordchange") @POST public Response changePassword(@Context HttpServletRequest request, PasswordChangeRequest passwordChangeRequest) { String username = request.getUserPrincipal().getName(); LocalUserEntity user = this.localUserDao.getByName(username); Response response = null; if (user == null) { throw new HttpStatusException(Response.Status.NOT_FOUND); } String newPassword = passwordChangeRequest.getNewPassword(); String oldPasswordHash; String newPasswordHash; try { oldPasswordHash = StringUtil.md5(passwordChangeRequest.getOldPassword()); newPasswordHash = StringUtil.md5(newPassword); } catch (NoSuchAlgorithmException e) { LOGGER.error(e.getMessage(), e); throw new HttpStatusException(Response.Status.INTERNAL_SERVER_ERROR, e); } if (user.getPassword().equals(oldPasswordHash)) { user.setPassword(newPasswordHash); user.setPasswordExpiration(this.getExpirationDate()); this.localUserDao.update(user); try { this.emailSender.sendPasswordChangeMessage(user); response = Response.status(Status.NO_CONTENT).build(); } catch (EmailException ee) { LOGGER.error(ee.getMessage(), ee); } } else { throw new HttpStatusException(Response.Status.BAD_REQUEST, "Error while changing password. Old password is incorrect."); } return response; } /** * Put an updated user to the system. Unless the user has the admin role, * s/he may only update their own user info. * * @param req in request * @param inUser Object containing all the information about the user to * add. * @param inId in Id * @return A "Created" response with a link to the user page if successful. */ @RolesAllowed({ "researcher", "admin" }) @Path("/{id}") @PUT public Response putUser(@Context HttpServletRequest req, User inUser, @PathParam("id") Long inId) { String username = req.getUserPrincipal().getName(); if (!req.isUserInRole("admin") && !username.equals(inUser.getUsername())) { throw new HttpStatusException(Response.Status.FORBIDDEN); } LOGGER.debug("Received updated user: {}", inUser); Response response; UserEntity currentUser = this.userDao.retrieve(inId); User me = getMe(req); boolean activation = (!currentUser.isActive()) && (inUser.isActive()); if (this.validateUpdatedUser(currentUser, inUser, me)) { currentUser.setFirstName(inUser.getFirstName()); currentUser.setLastName(inUser.getLastName()); currentUser.setEmail(inUser.getEmail()); currentUser.setOrganization(inUser.getOrganization()); currentUser.setTitle(inUser.getTitle()); currentUser.setDepartment(inUser.getDepartment()); currentUser.setFullName(inUser.getFullName()); List<RoleEntity> updatedRoles = this.roleIdsToRoles(inUser.getRoles()); currentUser.setRoles(updatedRoles); currentUser.setActive(inUser.isActive()); currentUser.setLastLogin(inUser.getLastLogin()); LOGGER.debug("Saving updated user: {}", currentUser.getEmail()); this.userDao.update(currentUser); if (activation) { try { this.emailSender.sendActivationMessage(currentUser); } catch (EmailException ee) { LOGGER.error(ee.getMessage(), ee); } } response = Response.ok().entity(currentUser).build(); } else { response = Response.notModified(this.validationError).build(); } return response; } /** * Validate that a user being updated does not violate any rules. * * @param currentUser The user before update. * @param inUser The updated user that to be validate. * @param me The current login user. * @return True if the user is valid, false otherwise. */ private boolean validateUpdatedUser(UserEntity currentUser, User inUser, User me) { boolean result = true; boolean updateByMe = true; // the roles to check RoleEntity adminUserRole = this.roleDao.getByName("admin"); updateByMe = me.getUsername().equals(currentUser.getUsername()); // a admin user can not be stripped of admin rights, // or be de-activated by him/herself. if (currentUser.getRoles().contains(adminUserRole) && updateByMe) { if (!inUser.getRoles().contains(Long.valueOf(2))) { this.validationError = "admin user can not be stripped of admin rights by him/herself "; result = false; } else { if (currentUser.isActive() && !inUser.isActive()) { this.validationError = "admin user can not be de-activated by him/herself"; result = false; } } } return result; } private Date getExpirationDate() { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DATE, 90); return calendar.getTime(); } private List<RoleEntity> roleIdsToRoles(List<Long> inRoleIds) { List<RoleEntity> roles = new ArrayList<>(inRoleIds.size()); for (Long roleId : inRoleIds) { roles.add(this.roleDao.retrieve(roleId)); } return roles; } }