/* * This file is subject to the terms and conditions defined in file 'LICENSE.txt', which is part of this source code package. */ package won.owner.web.rest; import com.fasterxml.jackson.databind.JsonNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.WebAuthenticationDetails; import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices; import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.Errors; import org.springframework.web.bind.annotation.*; import won.owner.model.User; import won.owner.model.UserNeed; import won.owner.pojo.UserPojo; import won.owner.pojo.UserSettingsPojo; import won.owner.repository.UserNeedRepository; import won.owner.repository.UserRepository; import won.owner.service.impl.WONUserDetailService; import won.owner.web.WonOwnerMailSender; import won.owner.web.validator.UserRegisterValidator; import won.protocol.util.CheapInsecureRandomString; import javax.print.DocFlavor; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; /** * User: t.kozel * Date: 11/12/13 */ @Controller @RequestMapping("/rest/users") public class RestUserController { private final Logger logger = LoggerFactory.getLogger(getClass()); private WONUserDetailService wonUserDetailService; private AuthenticationManager authenticationManager; private SecurityContextRepository securityContextRepository; private UserRegisterValidator userRegisterValidator; private WonOwnerMailSender emailSender; private UserNeedRepository userNeedRepository; private UserRepository userRepository; @Autowired ServletContext context; @Autowired public RestUserController(final WONUserDetailService wonUserDetailService, final AuthenticationManager authenticationManager, final SecurityContextRepository securityContextRepository, final UserRegisterValidator userRegisterValidator, final WonOwnerMailSender emailSender, final UserRepository userRepository, final UserNeedRepository userNeedRepository) { this.wonUserDetailService = wonUserDetailService; this.authenticationManager = authenticationManager; this.securityContextRepository = securityContextRepository; this.userRegisterValidator = userRegisterValidator; this.emailSender = emailSender; this.userRepository = userRepository; this.userNeedRepository = userNeedRepository; } /** * registers user * * @param user registration data of a user * @param errors * @return ResponseEntity with Http Status Code */ @ResponseBody @RequestMapping( value = "/", method = RequestMethod.POST ) //TODO: move transactionality annotation into the service layer @Transactional(propagation = Propagation.SUPPORTS) public ResponseEntity registerUser(@RequestBody UserPojo user, Errors errors) { try { userRegisterValidator.validate(user, errors); if (errors.hasErrors()) { if (errors.getFieldErrorCount() > 0) { // someone trying to go around js validation return new ResponseEntity(errors.getAllErrors().get(0).getDefaultMessage(), HttpStatus.BAD_REQUEST); } else { // username is already in database return new ResponseEntity("\"Cannot create user: name is already in use.\"", HttpStatus.CONFLICT); } } else { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); wonUserDetailService.save(new User(user.getUsername(), passwordEncoder.encode(user.getPassword()))); } } catch (DataIntegrityViolationException e) { // username is already in database return new ResponseEntity("\"Cannot create user: name is already in use.\"", HttpStatus.CONFLICT); } return new ResponseEntity("\"New user was created\"", HttpStatus.CREATED); } @ResponseBody @RequestMapping( value = "/settings", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET ) //TODO: move transactionality annotation into the service layer @Transactional(propagation = Propagation.SUPPORTS) public UserSettingsPojo getUserSettings(@RequestParam("uri") String uri) { String username = SecurityContextHolder.getContext().getAuthentication().getName(); // cannot use user object from context since hw doesn't know about created in this session need, // therefore, we have to retrieve the user object from the user repository User user = userRepository.findByUsername(username); UserSettingsPojo userSettingsPojo = new UserSettingsPojo(user.getUsername(), user.getEmail()); URI needUri = null; try { needUri = new URI(uri); userSettingsPojo.setNeedUri(uri); for (UserNeed userNeed : user.getUserNeeds()) { if (userNeed.getUri().equals(needUri)) { userSettingsPojo.setNotify(userNeed.isMatches(), userNeed.isRequests(), userNeed.isConversations()); //userSettingsPojo.setEmail(user.getEmail()); break; } } } catch (URISyntaxException e) { // TODO error response logger.warn(uri + " need uri problem", e); } return userSettingsPojo; } @ResponseBody @RequestMapping( value = "/settings", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.POST ) //TODO: move transactionality annotation into the service layer @Transactional(propagation = Propagation.SUPPORTS) public ResponseEntity setUserSettings(@RequestBody UserSettingsPojo userSettingsPojo) { String username = SecurityContextHolder.getContext().getAuthentication().getName(); // cannot use user object from context since hw doesn't know about created in this session need, // therefore, we have to retrieve the user object from the user repository User user = userRepository.findByUsername(username); if (!user.getUsername().equals(userSettingsPojo.getUsername())) { logger.warn("user name wrong"); return new ResponseEntity("\"user name problem\"", HttpStatus.BAD_REQUEST); } if (user.getEmail() == null) { //TODO validate email server-side? // set email: user.setEmail(userSettingsPojo.getEmail()); userRepository.save(user); } else if (!user.getEmail().equals(userSettingsPojo.getEmail())) { //TODO validate email server-side? // change email: user.setEmail(userSettingsPojo.getEmail()); userRepository.save(user); logger.info("change email requested - email changed"); } // retrieve UserNeed URI needUri = null; try { needUri = new URI(userSettingsPojo.getNeedUri()); for (UserNeed userNeed : user.getUserNeeds()) { if (userNeed.getUri().equals(needUri)) { userNeed.setMatches(userSettingsPojo.isNotifyMatches()); userNeed.setRequests(userSettingsPojo.isNotifyRequests()); userNeed.setConversations(userSettingsPojo.isNotifyConversations()); userNeedRepository.save(userNeed); break; } } } catch (URISyntaxException e) { logger.warn(userSettingsPojo.getNeedUri() + " need uri problem.", e); return new ResponseEntity("\"" + userSettingsPojo.getNeedUri() + " need uri problem.\"", HttpStatus.BAD_REQUEST); } return new ResponseEntity("\"Settings created\"", HttpStatus.CREATED); } /** * registers user * * @param user registration data of a user * @param errors * @return ResponseEntity with Http Status Code */ @ResponseBody @RequestMapping( value = "/private", method = RequestMethod.POST ) //TODO: move transactionality annotation into the service layer @Transactional(propagation = Propagation.SUPPORTS) public ResponseEntity registerPrivateLinkAsUser(@RequestBody UserPojo user, Errors errors) { String privateLink = null; try { privateLink = (new CheapInsecureRandomString()).nextString(32); // TODO more secure random alphanum string user.setUsername(privateLink); userRegisterValidator.validate(user, errors); if (errors.hasErrors()) { if (errors.getFieldErrorCount() > 0) { // someone trying to go around js validation return new ResponseEntity("\"" + errors.getAllErrors().get(0).getDefaultMessage() + "\"", HttpStatus.BAD_REQUEST); } else { // username is already in database return new ResponseEntity("\"Cannot create user: name is already in use.\"", HttpStatus.CONFLICT); } } else { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); User userDetails = new User(user.getUsername(), passwordEncoder.encode(user.getPassword()), "ROLE_PRIVATE"); wonUserDetailService.save(userDetails); } } catch (DataIntegrityViolationException e) { // username is already in database return new ResponseEntity("\"Cannot create user: name is already in use.\"", HttpStatus.CONFLICT); } return new ResponseEntity("\"" + privateLink + "\"", HttpStatus.CREATED); } /** * check authentication and returrn ResponseEntity with HTTP status code * * @param user user object * @param request * @param response * @return */ @RequestMapping( value = "/signin", method = RequestMethod.POST ) //TODO: move transactionality annotation into the service layer @Transactional(propagation = Propagation.SUPPORTS) public ResponseEntity logIn(@RequestBody User user, HttpServletRequest request, HttpServletResponse response) { SecurityContext context = SecurityContextHolder.getContext(); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()); try { Authentication auth = authenticationManager.authenticate(token); SecurityContextHolder.getContext().setAuthentication(auth); securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response); return new ResponseEntity("\"Signed in.\"", HttpStatus.OK); } catch (BadCredentialsException ex) { return new ResponseEntity("\"No such username/password combination registered.\"", HttpStatus.FORBIDDEN); } } /** * Method only accessible if the user's still signed in / the session's still valid -> Use it to check the session cookie. */ //* @param user user object //* @param request //* @param response //* @return // @ResponseBody @RequestMapping( value = "/isSignedIn", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET ) //TODO: move transactionality annotation into the service layer @Transactional(propagation = Propagation.SUPPORTS) //public ResponseEntity isSignedIn(@RequestBody User user, HttpServletRequest request, HttpServletResponse response) { public ResponseEntity isSignedIn() { // Execution will only get here, if the session is still valid, so sending OK here is enough. Spring sends an error // code by itself if the session isn't valid any more SecurityContext context = SecurityContextHolder.getContext(); //if(context.getAuthentication() ) if (context == null || context.getAuthentication() == null) { return new ResponseEntity("\"User not signed in.\"", HttpStatus.UNAUTHORIZED); } else if ("anonymousUser".equals(context.getAuthentication().getPrincipal())) { return new ResponseEntity("\"User not signed in.\"", HttpStatus.UNAUTHORIZED); } else { User user = (User) context.getAuthentication().getPrincipal(); Map values = new HashMap<String, String>(); values.put("username", user.getUsername()); values.put("authorities", user.getAuthorities()); values.put("role", user.getRole()); return new ResponseEntity<Map>(values, HttpStatus.OK); } } @RequestMapping( value = "/isSignedInRole", method = RequestMethod.GET ) //TODO: move transactionality annotation into the service layer @Transactional(propagation = Propagation.SUPPORTS) //public ResponseEntity isSignedIn(@RequestBody User user, HttpServletRequest request, HttpServletResponse response) { public ResponseEntity isSignedInRole() { // Execution will only get here, if the session is still valid, so sending OK here is enough. Spring sends an error // code by itself if the session isn't valid any more SecurityContext context = SecurityContextHolder.getContext(); //if(context.getAuthentication() ) if (context == null || context.getAuthentication() == null) { return new ResponseEntity("\"User not signed in.\"", HttpStatus.UNAUTHORIZED); } else if ("anonymousUser".equals(context.getAuthentication().getPrincipal())) { return new ResponseEntity("\"User not signed in.\"", HttpStatus.UNAUTHORIZED); } else { return new ResponseEntity("\"" + SecurityContextHolder.getContext().getAuthentication().getAuthorities() + "\"", HttpStatus.OK); } } /** * @return */ @RequestMapping( value = "/signout", method = RequestMethod.POST ) //TODO: move transactionality annotation into the service layer @Transactional(propagation = Propagation.SUPPORTS) public ResponseEntity logOut(HttpServletRequest request, HttpServletResponse response) { SecurityContext context = SecurityContextHolder.getContext(); if (context.getAuthentication() == null) { return new ResponseEntity("\"No user is signed in, ignoring this request.\"", HttpStatus.NOT_MODIFIED); } myLogoff(request, response); return new ResponseEntity("\"Signed out\"", HttpStatus.OK); } @RequestMapping( value = "/{userId}/favourites", method = RequestMethod.POST ) @Transactional(propagation = Propagation.SUPPORTS) public ResponseEntity saveAsFavourite() { return null; } @RequestMapping( value = "/{userId}/resetPassword", method = RequestMethod.POST ) @Transactional(propagation = Propagation.SUPPORTS) public ResponseEntity resetPassword(@RequestBody String password) { return null; } private static void myLogoff(HttpServletRequest request, HttpServletResponse response) { CookieClearingLogoutHandler cookieClearingLogoutHandler = new CookieClearingLogoutHandler( AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); SecurityContextLogoutHandler securityContextLogoutHandler = new SecurityContextLogoutHandler(); cookieClearingLogoutHandler.logout(request, response, null); securityContextLogoutHandler.logout(request, response, null); } }