diff --git a/pom.xml b/pom.xml index 2e5afb68f..6b51459a6 100644 --- a/pom.xml +++ b/pom.xml @@ -98,6 +98,13 @@ ${spring-cloud-starter-security.version} + + + org.springframework.boot + spring-boot-starter-mail + + + com.h2database diff --git a/src/main/java/org/springframework/samples/petclinic/common/CommonAttribute.java b/src/main/java/org/springframework/samples/petclinic/common/CommonAttribute.java index 9c7beb87e..15890f0e0 100644 --- a/src/main/java/org/springframework/samples/petclinic/common/CommonAttribute.java +++ b/src/main/java/org/springframework/samples/petclinic/common/CommonAttribute.java @@ -8,6 +8,7 @@ package org.springframework.samples.petclinic.common; public final class CommonAttribute { public static final String DESCRIPTION = "description"; + public static final String ID = "id"; public static final String NAME = "name"; @@ -15,7 +16,7 @@ public final class CommonAttribute { public static final String OWNER = "owner"; - public static final String OWNER_ID = "id"; + public static final String OWNER_ID = "ownerId"; public static final String OWNER_LAST_NAME = "lastName"; @@ -39,9 +40,11 @@ public final class CommonAttribute { public static final String PET_TYPE = "type"; + public static final String TOKEN = "token"; + public static final String USER = "user"; - public static final String USER_ID = "id"; + public static final String USER_ID = "userId"; public static final String VETS = "vets"; diff --git a/src/main/java/org/springframework/samples/petclinic/common/CommonEndPoint.java b/src/main/java/org/springframework/samples/petclinic/common/CommonEndPoint.java index b292df05e..e148b08d5 100644 --- a/src/main/java/org/springframework/samples/petclinic/common/CommonEndPoint.java +++ b/src/main/java/org/springframework/samples/petclinic/common/CommonEndPoint.java @@ -25,7 +25,9 @@ public final class CommonEndPoint { public static final String USERS_EDIT = "/users/edit"; - public static final String USERS_ID_EDIT = "/users/{ownerId}/edit"; + public static final String USERS_ID_EDIT = "/users/{userId}/edit"; + + public static final String USERS_ID_EDIT_PASSWORD = "/users/{userId}/edit/password"; public static final String USERS_NEW = "/users/new"; @@ -35,6 +37,8 @@ public final class CommonEndPoint { public static final String OAUTH2_SUCCESS = "/oauth2/success"; + public static final String CONFIRM_ACCOUNT = "/confirm-account"; + public static final String LOGOUT = "/logout"; public static final String LOGOUT_SUCCESS = "/logout/success"; diff --git a/src/main/java/org/springframework/samples/petclinic/common/CommonParameter.java b/src/main/java/org/springframework/samples/petclinic/common/CommonParameter.java index 478e12567..33b6bf576 100644 --- a/src/main/java/org/springframework/samples/petclinic/common/CommonParameter.java +++ b/src/main/java/org/springframework/samples/petclinic/common/CommonParameter.java @@ -6,6 +6,8 @@ public class CommonParameter { public static final int COUNTRY_MAX = 50; + public static final String DEFAULT_PROVIDER = "local"; + public static final int EMAIL_MAX = 255; public static final int EMAIL_MIN = 4; @@ -32,6 +34,8 @@ public class CommonParameter { public static final int STREET_MAX = 50; + public static final int TOKEN_EXPIRATION = 60 * 24; + public static final int ROLE_MAX = 10; public static final int ZIP_MAX = 6; diff --git a/src/main/java/org/springframework/samples/petclinic/common/CommonView.java b/src/main/java/org/springframework/samples/petclinic/common/CommonView.java index 5059e39b9..f9869ea64 100644 --- a/src/main/java/org/springframework/samples/petclinic/common/CommonView.java +++ b/src/main/java/org/springframework/samples/petclinic/common/CommonView.java @@ -23,7 +23,7 @@ public final class CommonView { public static final String PET_CREATE_OR_UPDATE = "pets/createOrUpdatePetForm"; - public static final String USER_REGISTRATION = "users/registration"; + public static final String USER_REGISTRATION = "users/userRegistrationForm"; public static final String USER_LOGIN = "/login"; @@ -31,7 +31,9 @@ public final class CommonView { public static final String USER_USERS_ID_R = "redirect:/users/{userId}"; - public static final String USER_CREATE_OR_UPDATE = "users/createOrUpdateUserForm"; + public static final String USER_UPDATE = "users/userUpdateForm"; + + public static final String USER_CHANGE_PASSWORD = "users/userChangePasswordForm"; public static final String USER_DETAILS = "users/userDetails"; @@ -41,8 +43,6 @@ public final class CommonView { public static final String USER_READ_R = "redirect:/users/read/"; - public static final String USER_UPDATE = "users/user-update"; - public static final String USER_UPDATE_R = "redirect:/users/edit/"; public static final String USER_UPDATE_PASSWORD = "users/user-password"; diff --git a/src/main/java/org/springframework/samples/petclinic/configuration/MailConfig.java b/src/main/java/org/springframework/samples/petclinic/configuration/MailConfig.java new file mode 100644 index 000000000..2dfbb7c7b --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/configuration/MailConfig.java @@ -0,0 +1,110 @@ +package org.springframework.samples.petclinic.configuration; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ResourceBundleMessageSource; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.thymeleaf.ITemplateEngine; +import org.thymeleaf.spring5.SpringTemplateEngine; +import org.thymeleaf.templatemode.TemplateMode; +import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; +import org.thymeleaf.templateresolver.ITemplateResolver; +import org.thymeleaf.templateresolver.StringTemplateResolver; + +import java.util.Collections; + +@Configuration +public class MailConfig { + + public static final String EMAIL_TEMPLATE_ENCODING = "UTF-8"; + + @Value("${spring.mail.host}") + private String mailHost; + + @Value("${spring.mail.port}") + private String mailPort; + + @Value("${spring.mail.protocol}") + private String mailProtocol; + + @Value("${spring.mail.username}") + private String mailUsername; + + @Value("${spring.mail.password}") + private String mailPassword; + + @Bean + public JavaMailSender mailSender() { + + final JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); + + // Basic mail sender configuration, based on emailconfig.properties + mailSender.setHost(mailHost); + mailSender.setPort(Integer.parseInt(mailPort)); + mailSender.setProtocol(mailProtocol); + mailSender.setUsername(mailUsername); + mailSender.setPassword(mailPassword); + + return mailSender; + } + + @Bean + public ResourceBundleMessageSource emailMessageSource() { + final ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); + messageSource.setBasename("mail/MailMessages"); + return messageSource; + } + + @Bean + public ITemplateEngine emailTemplateEngine() { + final SpringTemplateEngine emailTemplateEngine = new SpringTemplateEngine(); + + // Resolver for TEXT emails + emailTemplateEngine.addTemplateResolver(textTemplateResolver()); + // Resolver for HTML emails (except the editable one) + emailTemplateEngine.addTemplateResolver(htmlTemplateResolver()); + // Resolver for HTML editable emails (which will be treated as a String) + emailTemplateEngine.addTemplateResolver(stringTemplateResolver()); + // Message source, internationalization specific to emails + emailTemplateEngine.setTemplateEngineMessageSource(emailMessageSource()); + + return emailTemplateEngine; + } + + private ITemplateResolver textTemplateResolver() { + final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); + templateResolver.setOrder(Integer.valueOf(1)); + templateResolver.setResolvablePatterns(Collections.singleton("text/*")); + templateResolver.setPrefix("/mail/"); + templateResolver.setSuffix(".txt"); + templateResolver.setTemplateMode(TemplateMode.TEXT); + templateResolver.setCharacterEncoding(EMAIL_TEMPLATE_ENCODING); + templateResolver.setCacheable(false); + return templateResolver; + } + + private ITemplateResolver htmlTemplateResolver() { + final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); + templateResolver.setOrder(Integer.valueOf(2)); + templateResolver.setResolvablePatterns(Collections.singleton("html/*")); + templateResolver.setPrefix("/mail/"); + templateResolver.setSuffix(".html"); + templateResolver.setTemplateMode(TemplateMode.HTML); + templateResolver.setCharacterEncoding(EMAIL_TEMPLATE_ENCODING); + templateResolver.setCacheable(false); + return templateResolver; + } + + private ITemplateResolver stringTemplateResolver() { + final StringTemplateResolver templateResolver = new StringTemplateResolver(); + templateResolver.setOrder(Integer.valueOf(3)); + // No resolvable pattern, will simply process as a String template everything not + // previously matched + templateResolver.setTemplateMode("HTML5"); + templateResolver.setCacheable(false); + return templateResolver; + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/configuration/WebSecurityConfig.java b/src/main/java/org/springframework/samples/petclinic/configuration/WebSecurityConfig.java index 3238935cd..9461ca86f 100644 --- a/src/main/java/org/springframework/samples/petclinic/configuration/WebSecurityConfig.java +++ b/src/main/java/org/springframework/samples/petclinic/configuration/WebSecurityConfig.java @@ -6,7 +6,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; -import org.springframework.samples.petclinic.model.common.AuthProvider; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -59,12 +58,13 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { // @formatter:off http.authorizeRequests() - .antMatchers("/").anonymous() - .antMatchers("/login", "/logout", "/register").permitAll() + .antMatchers("/").permitAll() + .antMatchers("/login", "/logout", "/register","/confirm-account").permitAll() .antMatchers("/websocket/**", "/topic/**", "/app/**").permitAll() .antMatchers("/resources/**").permitAll() - .antMatchers("/**").authenticated() .antMatchers("/h2-console/**").permitAll() + .antMatchers("/**").authenticated() + .antMatchers("/edit/**").authenticated() .anyRequest().authenticated() .and() .formLogin() @@ -94,10 +94,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { // @formatter:on } - private static final List clients = Arrays.asList("google", "facebook", "github"); + @Bean public ClientRegistrationRepository clientRegistrationRepository() { + List clients = Arrays.asList("google", "facebook", "github"); + List registrations = clients.stream().map(c -> getRegistration(c)) .filter(registration -> registration != null).collect(Collectors.toList()); @@ -113,14 +115,14 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { String clientSecret = env.getProperty(CLIENT_PROPERTY_KEY + client + ".client-secret"); - if (client.equals(AuthProvider.google.name())) { + if (client.equals("google")) { return CommonOAuth2Provider.GOOGLE.getBuilder(client).clientId(clientId).clientSecret(clientSecret).build(); } - if (client.equals(AuthProvider.facebook.name())) { + if (client.equals("facebook")) { return CommonOAuth2Provider.FACEBOOK.getBuilder(client).clientId(clientId).clientSecret(clientSecret) .build(); } - if (client.equals(AuthProvider.github.name())) { + if (client.equals("github")) { return CommonOAuth2Provider.GITHUB.getBuilder(client).clientId(clientId).clientSecret(clientSecret).build(); } diff --git a/src/main/java/org/springframework/samples/petclinic/controller/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/controller/OwnerController.java index a47289e54..cfdbd9a5b 100644 --- a/src/main/java/org/springframework/samples/petclinic/controller/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/controller/OwnerController.java @@ -18,8 +18,8 @@ package org.springframework.samples.petclinic.controller; import org.springframework.samples.petclinic.common.*; import org.springframework.samples.petclinic.controller.common.WebSocketSender; import org.springframework.samples.petclinic.dto.*; -import org.springframework.samples.petclinic.service.OwnerService; -import org.springframework.samples.petclinic.service.VisitService; +import org.springframework.samples.petclinic.service.business.OwnerService; +import org.springframework.samples.petclinic.service.business.VisitService; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; diff --git a/src/main/java/org/springframework/samples/petclinic/controller/PetController.java b/src/main/java/org/springframework/samples/petclinic/controller/PetController.java index 1cae0d2e3..b49786ccd 100644 --- a/src/main/java/org/springframework/samples/petclinic/controller/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/controller/PetController.java @@ -19,8 +19,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.samples.petclinic.common.*; import org.springframework.samples.petclinic.controller.common.WebSocketSender; import org.springframework.samples.petclinic.dto.*; +import org.springframework.samples.petclinic.service.business.OwnerService; +import org.springframework.samples.petclinic.service.business.PetService; +import org.springframework.samples.petclinic.service.business.PetTypeService; import org.springframework.samples.petclinic.validator.PetDTOValidator; -import org.springframework.samples.petclinic.service.*; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.util.StringUtils; diff --git a/src/main/java/org/springframework/samples/petclinic/controller/UserController.java b/src/main/java/org/springframework/samples/petclinic/controller/UserController.java index e0d5b93a4..3555e68a2 100644 --- a/src/main/java/org/springframework/samples/petclinic/controller/UserController.java +++ b/src/main/java/org/springframework/samples/petclinic/controller/UserController.java @@ -3,23 +3,15 @@ package org.springframework.samples.petclinic.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ResolvableType; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.samples.petclinic.common.CommonAttribute; -import org.springframework.samples.petclinic.common.CommonEndPoint; -import org.springframework.samples.petclinic.common.CommonView; -import org.springframework.samples.petclinic.common.CommonWebSocket; +import org.springframework.data.repository.query.Param; +import org.springframework.samples.petclinic.common.*; import org.springframework.samples.petclinic.controller.common.WebSocketSender; -import org.springframework.samples.petclinic.dto.UserDTO; -import org.springframework.samples.petclinic.service.RoleService; -import org.springframework.samples.petclinic.service.SecurityServiceImpl; -import org.springframework.samples.petclinic.service.UserService; +import org.springframework.samples.petclinic.dto.common.CredentialDTO; +import org.springframework.samples.petclinic.dto.common.MessageDTO; +import org.springframework.samples.petclinic.dto.common.UserDTO; +import org.springframework.samples.petclinic.service.common.*; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.registration.ClientRegistration; @@ -27,18 +19,16 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.*; -import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; -import java.security.Principal; import java.util.HashMap; +import java.util.Locale; import java.util.Map; /** @@ -48,30 +38,25 @@ import java.util.Map; @Controller public class UserController extends WebSocketSender { - private static final String ROLE_ADMIN = "ADMIN"; - - private static final String ROLE_STAFF = "STAFF"; - - private static final String ROLE_USER = "USER"; - - private static final String authorizationRequestBaseUri = "oauth2/authorization"; - private final UserService userService; + private final CredentialService credentialService; + private final RoleService roleService; private final SecurityServiceImpl securityService; - private final BCryptPasswordEncoder bCryptPasswordEncoder; + private final EmailService emailService; - public UserController(UserService userService, RoleService roleService, SecurityServiceImpl securityService, - BCryptPasswordEncoder bCryptPasswordEncoder) { + public UserController(UserService userService, CredentialService credentialService, RoleService roleService, SecurityServiceImpl securityService, EmailService emailService) { this.userService = userService; + this.credentialService = credentialService; this.roleService = roleService; this.securityService = securityService; - this.bCryptPasswordEncoder = bCryptPasswordEncoder; + this.emailService = emailService; } + @InitBinder("user") public void setAllowedFields(WebDataBinder dataBinder) { dataBinder.setDisallowedFields(CommonAttribute.USER_ID); @@ -89,36 +74,48 @@ public class UserController extends WebSocketSender { public String initCreationForm(Map model) { UserDTO user = new UserDTO(); model.put(CommonAttribute.USER, user); - return CommonView.USER_CREATE_OR_UPDATE; + return CommonView.USER_REGISTRATION; } @PostMapping(CommonEndPoint.REGISTER) public String processCreationForm(@ModelAttribute(CommonAttribute.USER) @Valid UserDTO user, BindingResult result) { if (result.hasErrors()) { sendErrorMessage(CommonWebSocket.USER_CREATION_ERROR); - return CommonView.USER_CREATE_OR_UPDATE; + return CommonView.USER_REGISTRATION; } - try { - userService.findByEmail(user.getEmail()); + if(userService.existByEmail(user.getEmail())) { result.rejectValue("email", "5", "Email already exist !"); sendErrorMessage(CommonWebSocket.USER_CREATION_ERROR); - return CommonView.USER_CREATE_OR_UPDATE; - } - catch (Exception ex) { + return CommonView.USER_REGISTRATION; } // set default role - user.addRole(roleService.findByName(ROLE_USER)); + user.addRole(roleService.findByName("ROLE_USER")); // encode password because we get clear password - user.setPassword(bCryptPasswordEncoder.encode(user.getPassword())); - user.setMatchingPassword(user.getPassword()); - + user.encode(user.getPassword()); user = this.userService.save(user); - sendSuccessMessage(CommonWebSocket.USER_CREATED); - return CommonView.HOME + user.getId(); + CredentialDTO credential = new CredentialDTO(user); + credential = credentialService.save(credential); + + sendSuccessMessage(CommonWebSocket.USER_CREATED); + + // send confirmation mail + MessageDTO message = new MessageDTO( + user.getFirstName(), user.getLastName(), + "admin@petclinic.com", + user.getEmail(), + "New connexion", + "Your attempt to create new account. To confirm your account, please click here : ", + "http://localhost:8080/confirm-account?token=" + credential.getToken()); + + // emailService.sendMailAsynch(message, Locale.getDefault()); + + log.info(message.toString()); + + return CommonView.HOME + user.getId(); } @GetMapping(CommonEndPoint.LOGIN) @@ -140,7 +137,7 @@ public class UserController extends WebSocketSender { } clientRegistrations.forEach(registration -> oauth2AuthenticationUrls.put(registration.getClientName(), - authorizationRequestBaseUri + "/" + registration.getRegistrationId())); + "oauth2/authorization/" + registration.getRegistrationId())); model.put("urls", oauth2AuthenticationUrls); return CommonView.USER_LOGIN; @@ -148,30 +145,85 @@ public class UserController extends WebSocketSender { @GetMapping(CommonEndPoint.LOGIN_SUCCESS) public String postLogin(Model model, Authentication authentication) { - UserDTO user = userService.findByEmail(authentication.getName()); + UserDTO user = (UserDTO) authentication.getPrincipal(); model.addAttribute(CommonAttribute.USER, user); String message = String.format(CommonWebSocket.USER_LOGGED_IN, user.getFirstName(), user.getLastName()); - sendSuccessMessage(message ); + sendSuccessMessage(message); return CommonView.HOME; } @GetMapping(CommonEndPoint.OAUTH2_SUCCESS) public String postLogin(Model model, OAuth2AuthenticationToken authentication) { + String firstName = authentication.getPrincipal().getAttribute("given_name"); + String lastName = authentication.getPrincipal().getAttribute("family_name"); - OAuth2AuthorizedClient client = authorizedClientService .loadAuthorizedClient(authentication.getAuthorizedClientRegistrationId(), authentication.getName()); + CredentialDTO credential = credentialService.findByAuthentication(authentication); - String userInfoEndpointUri = client.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri(); + if( credential.isNew()) { - UserDTO user = userService.findByEmail(authentication.getName()); + // first time authentification with this provider + credential = credentialService.saveNew(authentication); + String email = credential.getEmail(); - if( user!=null) { - model.addAttribute(CommonAttribute.USER, user); + UserDTO user = userService.findByEmail(email); - String message = String.format(CommonWebSocket.USER_LOGGED_IN, user.getFirstName(), user.getLastName()); + if(user == null) { + user = new UserDTO(); + user.setEmail(email); + user.encode(credential.getPassword()); + user.setFirstName(firstName); + user.setLastName(lastName); + user.setEnabled(true); + user.addRole(roleService.findByName("ROLE_USER")); + user = userService.save(user); + } + + // send confirmation mail + MessageDTO message = new MessageDTO( + firstName, lastName, + "admin@petclinic.com", + credential.getEmail(), + "New connexion from " + credential.getProvider(), + "Your attempt to connect from " + credential.getProvider() + " To confirm this connection, please click the link below : ", + "http://localhost:8080/confirm-account?token=" + credential.getToken()); + + log.info(message.toString()); + emailService.sendMailAsynch(message, Locale.getDefault()); + + // disconnect + authentication.eraseCredentials(); + SecurityContextHolder.clearContext(); + + } else if( credential.isVerified()) { + securityService.autoLogin(credential.getEmail(),credential.getPassword()); + String message = String.format(CommonWebSocket.USER_LOGGED_IN, firstName, lastName); sendSuccessMessage(message); } + + + return CommonView.HOME; + } + + @RequestMapping(value = CommonEndPoint.CONFIRM_ACCOUNT, method = { RequestMethod.GET, RequestMethod.POST }) + public String confirmUserAccount(@RequestParam(CommonAttribute.TOKEN) String token, Model model) { + CredentialDTO credential = credentialService.findByToken(token); + + if (!credential.isNew() && credential.isNotExpired()) { + credential.setVerified(true); + credential.setToken(""); + credential.setExpiration(null); + credential = credentialService.save(credential); + + // find corresponding user + UserDTO user = userService.findByEmail(credential.getEmail()); + + securityService.autoLogin(credential.getEmail(),credential.getPassword()); + model.addAttribute(CommonAttribute.USER, user); + return CommonView.USER_UPDATE; + } + return CommonView.HOME; } @@ -188,43 +240,48 @@ public class UserController extends WebSocketSender { @GetMapping(CommonEndPoint.LOGOUT_SUCCESS) public String postLogout(Model model) { - sendSuccessMessage(CommonWebSocket.USER_LOGGED_OUT); return CommonView.HOME; } @GetMapping(CommonEndPoint.USERS_EDIT) public String initUpdateOwnerForm(Model model) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - - if (!authentication.getName().equals("anonymousUser")) { - - UserDTO user = userService.findByEmail(authentication.getName()); + try { + UserDTO user = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); model.addAttribute(CommonAttribute.USER, user); - return CommonView.USER_CREATE_OR_UPDATE; + model.addAttribute(CommonAttribute.USER_ID, user.getId()); + return CommonView.USER_UPDATE; + } catch (Exception exception) { + // user don't have profile } return CommonView.HOME; } - @PostMapping(CommonEndPoint.USERS_ID_EDIT) + @PostMapping(CommonEndPoint.USERS_EDIT) public String processUpdateOwnerForm(@ModelAttribute(CommonAttribute.USER) @Valid UserDTO user, - BindingResult result, @PathVariable("userId") int userId) { + BindingResult result, Model model) { if (result.hasErrors()) { sendErrorMessage(CommonWebSocket.USER_UPDATED_ERROR); - return CommonView.USER_CREATE_OR_UPDATE; + return CommonView.USER_UPDATE; } - else { - user.setId(userId); - this.userService.save(user); + if(!user.getPassword().equals(user.getMatchingPassword())) { + sendErrorMessage(CommonWebSocket.USER_UPDATED_ERROR); + return CommonView.USER_UPDATE; + } + + else { + user = userService.save(user); + + model.addAttribute(CommonAttribute.USER, user); sendSuccessMessage(CommonWebSocket.USER_UPDATED); - return CommonView.USER_USERS_ID_R; + return CommonView.HOME; } } @GetMapping(CommonEndPoint.USERS_ID) - public ModelAndView showOwner(@PathVariable("userId") int userId) { + public ModelAndView showOwner(@PathVariable("userId") Integer userId) { ModelAndView modelAndView = new ModelAndView(CommonView.USER_DETAILS); UserDTO user = this.userService.findById(userId); @@ -232,4 +289,62 @@ public class UserController extends WebSocketSender { return modelAndView; } + @GetMapping("/user/{userId}/edit/password") + public String editPassword(@PathVariable("userId") Integer userId, Model model){ + try { + UserDTO operator = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + UserDTO user = userService.findById(userId); + + if (user.equals(operator) || operator.getRoles().contains(roleService.findByName("ROLE_ADMIN"))) { + model.addAttribute(CommonAttribute.USER, user); + model.addAttribute(CommonAttribute.USER_ID, user.getId()); + return CommonView.USER_CHANGE_PASSWORD; + } + } catch (Exception exception) { + // user don't have profile + } + + return CommonView.HOME; + } + + @PostMapping("/user/{userId}/edit/password") + public String updatePassword(@ModelAttribute(CommonAttribute.USER) @Valid UserDTO user, BindingResult bindingResult, + @PathVariable(CommonAttribute.USER_ID) Integer userId, + @Param("oldPassword") String oldPassword, + @Param("newPassword") String newPassword, + @Param("newMatchingPassword") String newMatchingPassword, Model model) { + + // verify the matching with old password + if(!user.matches(oldPassword)){ + bindingResult.rejectValue("password", "6", "Bad password !"); + model.addAttribute(CommonAttribute.USER, user); + return CommonView.USER_CHANGE_PASSWORD; + } + + // verify matching between two password + if(!newPassword.equals(newMatchingPassword)){ + bindingResult.rejectValue("password", "7", "Bad matching password !"); + model.addAttribute(CommonAttribute.USER, user); + return CommonView.USER_CHANGE_PASSWORD; + } + + try { + UserDTO operator = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + + if (user.equals(operator) || operator.getRoles().contains(roleService.findByName("ROLE_ADMIN"))) { + // encode password + user.encode(newPassword); + user = userService.save(user); + + model.addAttribute(CommonAttribute.USER, user); + return CommonView.USER_UPDATE_R; + } + } catch (NullPointerException exception) { + log.error(exception.getMessage()); + } + + return CommonView.HOME; + } + + } diff --git a/src/main/java/org/springframework/samples/petclinic/controller/VetController.java b/src/main/java/org/springframework/samples/petclinic/controller/VetController.java index b10b42caa..d4f44c895 100644 --- a/src/main/java/org/springframework/samples/petclinic/controller/VetController.java +++ b/src/main/java/org/springframework/samples/petclinic/controller/VetController.java @@ -21,7 +21,7 @@ import org.springframework.samples.petclinic.common.CommonView; import org.springframework.samples.petclinic.common.CommonWebSocket; import org.springframework.samples.petclinic.controller.common.WebSocketSender; import org.springframework.samples.petclinic.dto.VetsDTO; -import org.springframework.samples.petclinic.service.VetService; +import org.springframework.samples.petclinic.service.business.VetService; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; diff --git a/src/main/java/org/springframework/samples/petclinic/controller/VisitController.java b/src/main/java/org/springframework/samples/petclinic/controller/VisitController.java index 1709fd43d..a8d05e654 100644 --- a/src/main/java/org/springframework/samples/petclinic/controller/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/controller/VisitController.java @@ -26,8 +26,8 @@ import org.springframework.samples.petclinic.common.CommonWebSocket; import org.springframework.samples.petclinic.controller.common.WebSocketSender; import org.springframework.samples.petclinic.dto.PetDTO; import org.springframework.samples.petclinic.dto.VisitDTO; -import org.springframework.samples.petclinic.service.PetService; -import org.springframework.samples.petclinic.service.VisitService; +import org.springframework.samples.petclinic.service.business.PetService; +import org.springframework.samples.petclinic.service.business.VisitService; import org.springframework.samples.petclinic.validator.VisitDTOValidator; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; diff --git a/src/main/java/org/springframework/samples/petclinic/dto/common/AuthProviderDTO.java b/src/main/java/org/springframework/samples/petclinic/dto/common/AuthProviderDTO.java new file mode 100644 index 000000000..480a7714e --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/dto/common/AuthProviderDTO.java @@ -0,0 +1,12 @@ +package org.springframework.samples.petclinic.dto.common; + +import org.springframework.samples.petclinic.dto.NamedDTO; + +/** + * Simple Data Transfert Object representing a Authorization Provider. + * + * @author Paul-Emmanuel DOS SANTOS FACAO + */ +public class AuthProviderDTO extends NamedDTO { + +} diff --git a/src/main/java/org/springframework/samples/petclinic/dto/common/CredentialDTO.java b/src/main/java/org/springframework/samples/petclinic/dto/common/CredentialDTO.java new file mode 100644 index 000000000..d505778ab --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/dto/common/CredentialDTO.java @@ -0,0 +1,122 @@ +package org.springframework.samples.petclinic.dto.common; + +import org.springframework.samples.petclinic.common.CommonError; +import org.springframework.samples.petclinic.common.CommonParameter; +import org.springframework.samples.petclinic.dto.BaseDTO; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.Calendar; +import java.util.Date; +import java.util.UUID; + +/** + * Simple Data Transfert Object representing a Credential. + * + * @author Paul-Emmanuel DOS SANTOS FACAO + */ +public class CredentialDTO extends BaseDTO { + + @NotNull + private String provider; + + @NotNull + @Size(min = CommonParameter.EMAIL_MIN, max = CommonParameter.EMAIL_MAX, message = CommonError.FORMAT_BETWEEN + + CommonParameter.EMAIL_MIN + " AND " + CommonParameter.EMAIL_MAX + " !") + @Pattern(regexp = CommonParameter.EMAIL_REGEXP, message = CommonError.EMAIL_FORMAT) + private String email; + + @NotNull + private Boolean verified; + + private String token; + + private Date expiration; + + @NotNull + @Size(min = CommonParameter.PASSWORD_MIN, max = CommonParameter.PASSWORD_MAX, message = CommonError.FORMAT_BETWEEN + + CommonParameter.PASSWORD_MIN + " AND " + CommonParameter.PASSWORD_MAX + " !") + private String password; + + public CredentialDTO(UserDTO user) { + this.verified = false; + this.setToken(); + this.setExpiration(); + this.setProvider(CommonParameter.DEFAULT_PROVIDER); + this.email = user.getEmail(); + this.password = user.getId().toString(); + } + + public String getProvider() { + return provider; + } + + public void setProvider(String provider) { + this.provider = provider; + } + + public void setDefaultProvider() { + this.provider = CommonParameter.DEFAULT_PROVIDER; + } + + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public Boolean isVerified() { + return verified; + } + + public void setVerified(Boolean verified) { + this.verified = verified; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public Date getExpiration() { + return expiration; + } + + public void setExpiration(Date expiration) { + this.expiration = expiration; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setExpiration() { + Calendar cal = Calendar.getInstance(); + cal.setTime(new Timestamp(cal.getTime().getTime())); + cal.add(Calendar.MINUTE, CommonParameter.TOKEN_EXPIRATION); + this.expiration = cal.getTime(); + } + + public void setToken() { + this.token = UUID.randomUUID().toString(); + } + + public boolean isNotExpired() { + Calendar cal = Calendar.getInstance(); + cal.setTime(new Timestamp(cal.getTime().getTime())); + + return this.expiration.after(Date.from(Instant.now())); + } +} diff --git a/src/main/java/org/springframework/samples/petclinic/dto/common/MessageDTO.java b/src/main/java/org/springframework/samples/petclinic/dto/common/MessageDTO.java new file mode 100644 index 000000000..f9602a61e --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/dto/common/MessageDTO.java @@ -0,0 +1,126 @@ +package org.springframework.samples.petclinic.dto.common; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.samples.petclinic.common.CommonError; +import org.springframework.samples.petclinic.common.CommonParameter; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import java.io.Serializable; + +public class MessageDTO implements Serializable { + + @NotNull + @Size(min = CommonParameter.FIRSTNAME_MIN, max = CommonParameter.FIRSTNAME_MAX, message = CommonError.FORMAT_BETWEEN + + CommonParameter.FIRSTNAME_MIN + " AND " + CommonParameter.FIRSTNAME_MAX + " !") + private String firstName; + + @NotNull + @Size(min = CommonParameter.LASTNAME_MIN, max = CommonParameter.LASTNAME_MAX, message = CommonError.FORMAT_BETWEEN + + CommonParameter.LASTNAME_MIN + " AND " + CommonParameter.LASTNAME_MAX + " !") + private String lastName; + + @NotNull + @Size(min = CommonParameter.EMAIL_MIN, max = CommonParameter.EMAIL_MAX, message = CommonError.FORMAT_BETWEEN + + CommonParameter.EMAIL_MIN + " AND " + CommonParameter.EMAIL_MAX + " !") + @Pattern(regexp = CommonParameter.EMAIL_REGEXP, message = CommonError.EMAIL_FORMAT) + private String from; + + @NotNull + @Size(min = CommonParameter.EMAIL_MIN, max = CommonParameter.EMAIL_MAX, message = CommonError.FORMAT_BETWEEN + + CommonParameter.EMAIL_MIN + " AND " + CommonParameter.EMAIL_MAX + " !") + @Pattern(regexp = CommonParameter.EMAIL_REGEXP, message = CommonError.EMAIL_FORMAT) + private String to; + + @NotNull + private String subject; + + @NotNull + private String content; + + private String link; + + @JsonCreator + public MessageDTO(@JsonProperty("firstName") String firstName, @JsonProperty("lastName") String lastName, + @JsonProperty("from") String from, @JsonProperty("to") String to, @JsonProperty("subject") String subject, + @JsonProperty("content") String content, @JsonProperty("link") String link) { + this.firstName = firstName; + this.lastName = lastName; + this.from = from; + this.to = to; + this.subject = subject; + this.content = content; + this.link = link; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getFrom() { + return from; + } + + public void setFrom(String from) { + this.from = from; + } + + public String getTo() { + return to; + } + + public void setTo(String to) { + this.to = to; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getLink() { + return link; + } + + public void setLink(String link) { + this.link = link; + } + + @Override + public String toString() { + return "MessageDTO{" + + "first name='" + firstName + '\'' + + ", last name='" + lastName + '\'' + + ", from='" + from + '\'' + + ", to='" + to + '\'' + + ", subject='" + subject + '\'' + + ", content='" + content + '\'' + + ", link='" + link + '\'' + + '}'; + } +} diff --git a/src/main/java/org/springframework/samples/petclinic/dto/RoleDTO.java b/src/main/java/org/springframework/samples/petclinic/dto/common/RoleDTO.java similarity index 64% rename from src/main/java/org/springframework/samples/petclinic/dto/RoleDTO.java rename to src/main/java/org/springframework/samples/petclinic/dto/common/RoleDTO.java index ed12efc05..b578c8764 100644 --- a/src/main/java/org/springframework/samples/petclinic/dto/RoleDTO.java +++ b/src/main/java/org/springframework/samples/petclinic/dto/common/RoleDTO.java @@ -1,4 +1,6 @@ -package org.springframework.samples.petclinic.dto; +package org.springframework.samples.petclinic.dto.common; + +import org.springframework.samples.petclinic.dto.NamedDTO; import java.io.Serializable; @@ -8,5 +10,4 @@ import java.io.Serializable; * @author Paul-Emmanuel DOS SANTOS FACAO */ public class RoleDTO extends NamedDTO implements Serializable { - } diff --git a/src/main/java/org/springframework/samples/petclinic/dto/UserDTO.java b/src/main/java/org/springframework/samples/petclinic/dto/common/UserDTO.java similarity index 54% rename from src/main/java/org/springframework/samples/petclinic/dto/UserDTO.java rename to src/main/java/org/springframework/samples/petclinic/dto/common/UserDTO.java index da81f2e9c..b39570f18 100644 --- a/src/main/java/org/springframework/samples/petclinic/dto/UserDTO.java +++ b/src/main/java/org/springframework/samples/petclinic/dto/common/UserDTO.java @@ -1,37 +1,48 @@ -package org.springframework.samples.petclinic.dto; +package org.springframework.samples.petclinic.dto.common; import org.springframework.beans.support.MutableSortDefinition; import org.springframework.beans.support.PropertyComparator; import org.springframework.samples.petclinic.common.CommonError; import org.springframework.samples.petclinic.common.CommonParameter; +import org.springframework.samples.petclinic.dto.PersonDTO; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import javax.xml.bind.annotation.XmlElement; import java.io.Serializable; + import java.util.*; -public class UserDTO extends PersonDTO implements Serializable { - - @Size(min = CommonParameter.PASSWORD_MIN, max = CommonParameter.PASSWORD_MAX, message = CommonError.FORMAT_BETWEEN - + CommonParameter.PASSWORD_MIN + " AND " + CommonParameter.PASSWORD_MAX + " !") - private String password; - - @Size(min = CommonParameter.PASSWORD_MIN, max = CommonParameter.PASSWORD_MAX, message = CommonError.FORMAT_BETWEEN - + CommonParameter.PASSWORD_MIN + " AND " + CommonParameter.PASSWORD_MAX + " !") - private String matchingPassword; +public class UserDTO extends PersonDTO implements Serializable, UserDetails { @Size(min = CommonParameter.EMAIL_MIN, max = CommonParameter.EMAIL_MAX, message = CommonError.FORMAT_BETWEEN + CommonParameter.EMAIL_MIN + " AND " + CommonParameter.EMAIL_MAX + " !") @Pattern(regexp = CommonParameter.EMAIL_REGEXP, message = CommonError.EMAIL_FORMAT) private String email; - @Size(max = CommonParameter.PHONE_MAX, message = CommonError.FORMAT_LESS + CommonParameter.PHONE_MAX) - @Pattern(regexp = CommonParameter.PHONE_REGEXP, message = CommonError.PHONE_FORMAT) - private String telephone; + @Size(min = CommonParameter.PASSWORD_MIN, max = CommonParameter.PASSWORD_MAX, message = CommonError.FORMAT_BETWEEN + + CommonParameter.PASSWORD_MIN + " AND " + CommonParameter.PASSWORD_MAX + " !") + private String password; + @Size(min = CommonParameter.PASSWORD_MIN, max = CommonParameter.PASSWORD_MAX, message = CommonError.FORMAT_BETWEEN + + CommonParameter.PASSWORD_MIN + " AND " + CommonParameter.PASSWORD_MAX + " !") + private String matchingPassword; + + private boolean enabled; + private boolean accountNonExpired; + private boolean accountNonLocked; + private boolean credentialsNonExpired; private Set roles; + + @Size(max = CommonParameter.PHONE_MAX, message = CommonError.FORMAT_LESS + CommonParameter.PHONE_MAX) +// @Pattern(regexp = CommonParameter.PHONE_REGEXP, message = CommonError.PHONE_FORMAT) + private String telephone; + @Size(max = CommonParameter.STREET_MAX, message = CommonError.FORMAT_LESS + CommonParameter.STREET_MAX + " !") private String street1; @@ -51,6 +62,28 @@ public class UserDTO extends PersonDTO implements Serializable { @Size(max = CommonParameter.COUNTRY_MAX, message = CommonError.FORMAT_LESS + CommonParameter.COUNTRY_MAX + " !") private String country; + public UserDTO() { + super(); + this.enabled = false; + this.accountNonLocked = true; + this.accountNonExpired = true; + this.credentialsNonExpired = true; + } + + @Override + public String getUsername() { + return email; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + @Override public String getPassword() { return password; } @@ -67,20 +100,49 @@ public class UserDTO extends PersonDTO implements Serializable { this.matchingPassword = matchingPassword; } - public String getEmail() { - return email; + @Override + public boolean isEnabled() { + return enabled; } - public void setEmail(String email) { - this.email = email; + public void setEnabled(boolean enabled) { + this.enabled = enabled; } - public String getTelephone() { - return telephone; + @Override + public boolean isAccountNonExpired() { + return accountNonExpired; } - public void setTelephone(String telephone) { - this.telephone = telephone; + public void setAccountNonExpired(boolean accountNonExpired) { + this.accountNonExpired = accountNonExpired; + } + + @Override + public boolean isAccountNonLocked() { + return accountNonLocked; + } + + public void setAccountNonLocked(boolean accountNonLocked) { + this.accountNonLocked = accountNonLocked; + } + + @Override + public boolean isCredentialsNonExpired() { + return credentialsNonExpired; + } + + public void setCredentialsNonExpired(boolean credentialsNonExpired) { + this.credentialsNonExpired = credentialsNonExpired; + } + + @Override + public Collection getAuthorities() { + Set grantedAuthorities = new HashSet<>(); + + this.roles.forEach(role -> grantedAuthorities.add(new SimpleGrantedAuthority(role.getName()))); + + return grantedAuthorities; } protected Set getRolesInternal() { @@ -101,10 +163,27 @@ public class UserDTO extends PersonDTO implements Serializable { return Collections.unmodifiableList(sortedRoles); } + public int getNrOfRoles() { + return getRolesInternal().size(); + } + public void addRole(RoleDTO role) { getRolesInternal().add(role); } + public void setRoles(Set roles) { + this.roles = roles; + } + + public String getTelephone() { + return telephone; + } + + public void setTelephone(String telephone) { + this.telephone = telephone; + } + + public String getStreet1() { return street1; } @@ -153,4 +232,37 @@ public class UserDTO extends PersonDTO implements Serializable { this.country = country; } + @Override + public String toString() { + return "UserDTO{" + + "email='" + email + '\'' + + ", password='" + password + '\'' + + ", matchingPassword='" + matchingPassword + '\'' + + ", user enabled=" + enabled + + ", account not expired=" + accountNonExpired + + ", account not locked=" + accountNonLocked + + ", credentials not xxpired=" + credentialsNonExpired + + ", roles=" + roles + + ", telephone='" + telephone + '\'' + + ", street1='" + street1 + '\'' + + ", street2='" + street2 + '\'' + + ", street3='" + street3 + '\'' + + ", zipCode='" + zipCode + '\'' + + ", city='" + city + '\'' + + ", country='" + country + '\'' + + '}'; + } + + public void encode(String rawPassword) { + BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); + + this.password = bCryptPasswordEncoder.encode(rawPassword); + this.matchingPassword = this.password; + } + + public boolean matches(String rawPassword) { + BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); + + return bCryptPasswordEncoder.matches(rawPassword, this.password); + } } diff --git a/src/main/java/org/springframework/samples/petclinic/formatter/PetTypeFormatter.java b/src/main/java/org/springframework/samples/petclinic/formatter/PetTypeFormatter.java index a93537845..418980d03 100644 --- a/src/main/java/org/springframework/samples/petclinic/formatter/PetTypeFormatter.java +++ b/src/main/java/org/springframework/samples/petclinic/formatter/PetTypeFormatter.java @@ -21,8 +21,7 @@ import java.util.Locale; import org.springframework.format.Formatter; import org.springframework.samples.petclinic.dto.PetTypeDTO; -import org.springframework.samples.petclinic.service.PetService; -import org.springframework.samples.petclinic.service.PetTypeService; +import org.springframework.samples.petclinic.service.business.PetService; import org.springframework.stereotype.Component; /** diff --git a/src/main/java/org/springframework/samples/petclinic/model/common/AuthProvider.java b/src/main/java/org/springframework/samples/petclinic/model/common/AuthProvider.java index d6fe764f0..946c79f01 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/common/AuthProvider.java +++ b/src/main/java/org/springframework/samples/petclinic/model/common/AuthProvider.java @@ -1,7 +1,15 @@ package org.springframework.samples.petclinic.model.common; -public enum AuthProvider { +import javax.persistence.Entity; +import javax.persistence.Table; - local, facebook, google, github +/** + * Class used to manage Authorization providers + * + * @author Paul-Emmanuel DOS SANTOS FACAO + */ +@Entity(name = "AuthProvider") +@Table(name = "auth_providers") +public class AuthProvider extends NamedEntity { } diff --git a/src/main/java/org/springframework/samples/petclinic/model/common/Credential.java b/src/main/java/org/springframework/samples/petclinic/model/common/Credential.java new file mode 100644 index 000000000..aa396018b --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/model/common/Credential.java @@ -0,0 +1,112 @@ +package org.springframework.samples.petclinic.model.common; + +import org.springframework.samples.petclinic.common.CommonError; +import org.springframework.samples.petclinic.common.CommonParameter; + +import javax.persistence.*; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.Date; +import java.util.UUID; + +/** + * Class used to manage Credentials for users + * + * @author Paul-Emmanuel DOS SANTOS FACAO + */ +@Entity(name = "Credential") +@Table(name = "credentials") +public class Credential extends BaseEntity { + private static final int TOKEN_EXPIRATION = 60 * 24; + + @NotNull + @Column(name = "provider_id") + private Integer providerId; + + @NotNull + @Size(min = CommonParameter.EMAIL_MIN, max = CommonParameter.EMAIL_MAX, message = CommonError.FORMAT_BETWEEN + + CommonParameter.EMAIL_MIN + " AND " + CommonParameter.EMAIL_MAX + " !") + @Pattern(regexp = CommonParameter.EMAIL_REGEXP, message = CommonError.EMAIL_FORMAT) + @Column(name = "email", length = CommonParameter.EMAIL_MAX) + private String email; + + @NotNull + @Size(min = CommonParameter.PASSWORD_MIN, max = CommonParameter.PASSWORD_MAX, message = CommonError.FORMAT_BETWEEN + + CommonParameter.PASSWORD_MIN + " AND " + CommonParameter.PASSWORD_MAX + " !") + @Column(name = "password", length = CommonParameter.PASSWORD_MAX) + private String password; + + @NotNull + @Column(name = "verified") + private Boolean verified; + + @Column(name = "token") + private String token; + + @Column(name = "expiration") + private Date expiration; + + + public Integer getProviderId() { + return providerId; + } + + public void setProviderId(Integer providerId) { + this.providerId = providerId; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public Boolean isVerified() { + return verified; + } + + public void setVerified(Boolean verified) { + this.verified = verified; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public Date getExpiration() { + return expiration; + } + + public void setExpiration(Date expirationDate) { + this.expiration = expirationDate; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setExpiration() { + Calendar cal = Calendar.getInstance(); + cal.setTime(new Timestamp(cal.getTime().getTime())); + cal.add(Calendar.MINUTE, TOKEN_EXPIRATION); + this.expiration = cal.getTime(); + } + + public void setToken() { + this.token = UUID.randomUUID().toString(); + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/model/common/Role.java b/src/main/java/org/springframework/samples/petclinic/model/common/Role.java index 4aefcda52..353a14955 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/common/Role.java +++ b/src/main/java/org/springframework/samples/petclinic/model/common/Role.java @@ -1,7 +1,5 @@ package org.springframework.samples.petclinic.model.common; -import org.springframework.samples.petclinic.model.common.NamedEntity; - import javax.persistence.*; import java.io.Serializable; diff --git a/src/main/java/org/springframework/samples/petclinic/model/common/User.java b/src/main/java/org/springframework/samples/petclinic/model/common/User.java index 61cae26bc..16bce7241 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/common/User.java +++ b/src/main/java/org/springframework/samples/petclinic/model/common/User.java @@ -4,6 +4,9 @@ import org.springframework.beans.support.MutableSortDefinition; import org.springframework.beans.support.PropertyComparator; import org.springframework.samples.petclinic.common.CommonError; import org.springframework.samples.petclinic.common.CommonParameter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; import javax.persistence.*; import javax.validation.constraints.NotNull; @@ -20,7 +23,7 @@ import java.util.*; */ @Entity(name = "User") @Table(name = "users") -public class User extends Person implements Serializable { +public class User extends Person implements Serializable, UserDetails { @NotNull @Size(min = CommonParameter.EMAIL_MIN, max = CommonParameter.EMAIL_MAX, message = CommonError.FORMAT_BETWEEN @@ -29,34 +32,38 @@ public class User extends Person implements Serializable { @Column(name = "email", unique = true, length = CommonParameter.EMAIL_MAX) private String email; - @NotNull - @Column(name = "email_verified") - private Boolean emailVerified = false; - - @NotNull @Size(min = CommonParameter.PASSWORD_MIN, max = CommonParameter.PASSWORD_MAX, message = CommonError.FORMAT_BETWEEN - + CommonParameter.PASSWORD_MIN + " AND " + CommonParameter.PASSWORD_MAX + " !") + + CommonParameter.PASSWORD_MIN + " AND " + CommonParameter.PASSWORD_MAX + " !") @Column(name = "password", length = CommonParameter.PASSWORD_MAX) private String password; @NotNull - @Enumerated(EnumType.STRING) - private AuthProvider provider; + @Column(name = "enabled") + private boolean enabled; - @Column(name = "provider_id") - private String providerId; + @NotNull + @Column(name = "account_unexpired") + private boolean accountNonExpired; - @Size(max = CommonParameter.PHONE_MAX, message = CommonError.FORMAT_LESS + CommonParameter.PHONE_MAX) - @Pattern(regexp = CommonParameter.PHONE_REGEXP, message = CommonError.PHONE_FORMAT) - @Column(name = "telephone", length = CommonParameter.EMAIL_MAX) - private String telephone; + @NotNull + @Column(name = "account_unlocked") + private boolean accountNonLocked; + + @NotNull + @Column(name = "credential_unexpired") + private boolean credentialsNonExpired; @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "users_roles", joinColumns = @JoinColumn(name = "user_id"), - inverseJoinColumns = @JoinColumn(name = "role_id")) + inverseJoinColumns = @JoinColumn(name = "role_id")) private Set roles; - @NotNull + @Size(max = CommonParameter.PHONE_MAX, message = CommonError.FORMAT_LESS + CommonParameter.PHONE_MAX) +// @Pattern(regexp = CommonParameter.PHONE_REGEXP, message = CommonError.PHONE_FORMAT) + @Column(name = "telephone", length = CommonParameter.EMAIL_MAX) + private String telephone; + + @Size(max = CommonParameter.STREET_MAX, message = CommonError.FORMAT_LESS + CommonParameter.STREET_MAX + " !") @Column(name = "street1", length = CommonParameter.STREET_MAX) private String street1; @@ -69,12 +76,10 @@ public class User extends Person implements Serializable { @Column(name = "street3", length = CommonParameter.STREET_MAX) private String street3; - @NotNull @Size(max = CommonParameter.ZIP_MAX, message = CommonError.FORMAT_LESS + CommonParameter.ZIP_MAX + " !") @Column(name = "zip_code", length = CommonParameter.ZIP_MAX) private String zipCode; - @NotNull @Size(max = CommonParameter.CITY_MAX, message = CommonError.FORMAT_LESS + CommonParameter.CITY_MAX + " !") @Column(name = "city", length = CommonParameter.CITY_MAX) private String city; @@ -83,6 +88,11 @@ public class User extends Person implements Serializable { @Column(name = "country", length = CommonParameter.COUNTRY_MAX) private String country; + @Override + public String getUsername() { + return email; + } + public String getEmail() { return email; } @@ -91,14 +101,7 @@ public class User extends Person implements Serializable { this.email = email; } - public Boolean getEmailVerified() { - return emailVerified; - } - - public void setEmailVerified(Boolean emailVerified) { - this.emailVerified = emailVerified; - } - + @Override public String getPassword() { return password; } @@ -107,28 +110,50 @@ public class User extends Person implements Serializable { this.password = password; } - public AuthProvider getProvider() { - return provider; + @Override + public boolean isEnabled() { + return enabled; } - public void setProvider(AuthProvider provider) { - this.provider = provider; + public void setEnabled(boolean enabled) { + this.enabled = enabled; } - public String getProviderId() { - return providerId; + @Override + public boolean isAccountNonExpired() { + return accountNonExpired; } - public void setProviderId(String providerId) { - this.providerId = providerId; + public void setAccountNonExpired(boolean accountNonExpired) { + this.accountNonExpired = accountNonExpired; } - public String getTelephone() { - return telephone; + @Override + public boolean isAccountNonLocked() { + return accountNonLocked; } - public void setTelephone(String telephone) { - this.telephone = telephone; + public void setAccountNonLocked(boolean accountNonLocked) { + this.accountNonLocked = accountNonLocked; + } + + @Override + public boolean isCredentialsNonExpired() { + return credentialsNonExpired; + } + + public void setCredentialsNonExpired(boolean credentialsNonExpired) { + this.credentialsNonExpired = credentialsNonExpired; + } + + + @Override + public Collection getAuthorities() { + Set grantedAuthorities = new HashSet<>(); + + this.roles.forEach(role -> grantedAuthorities.add(new SimpleGrantedAuthority(role.getName()))); + + return grantedAuthorities; } protected Set getRolesInternal() { @@ -157,6 +182,20 @@ public class User extends Person implements Serializable { getRolesInternal().add(role); } + + public void setRoles(Set roles) { + this.roles = roles; + } + + public String getTelephone() { + return telephone; + } + + public void setTelephone(String telephone) { + this.telephone = telephone; + } + + public String getStreet1() { return street1; } @@ -205,4 +244,5 @@ public class User extends Person implements Serializable { this.country = country; } + } diff --git a/src/main/java/org/springframework/samples/petclinic/repository/AuthProviderRepository.java b/src/main/java/org/springframework/samples/petclinic/repository/AuthProviderRepository.java new file mode 100644 index 000000000..97d35b93c --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/repository/AuthProviderRepository.java @@ -0,0 +1,43 @@ +package org.springframework.samples.petclinic.repository; + +import org.springframework.data.repository.Repository; +import org.springframework.samples.petclinic.model.common.AuthProvider; + +import java.util.List; + +/** + * Repository class for AuthProvider domain objects All method names are + * compliant with Spring Data naming conventions so this interface can easily be extended + * for Spring + * + * @author Paul-Emmanuel DOS SANTOS FACAO + */ +public interface AuthProviderRepository extends Repository { + + /** + * Retrieve a {@link AuthProvider} from the data store by id. + * @param providerId the id to search for + * @return the {@link AuthProvider} if found + */ + AuthProvider findById(Integer providerId); + + /** + * Retrieve a {@link AuthProvider} from the data store by id. + * @param providerName the name to search for + * @return the {@link AuthProvider} if found + */ + AuthProvider findByName(String providerName); + + /** + * Retrieve all {@link AuthProvider}s from the data store + * @return a Collection of {@link AuthProvider}s (or an empty Collection if none + */ + List findAll(); + + /** + * Save a {@link AuthProvider} to the data store, either inserting or updating it. + * @param authProvider the {@link AuthProvider} to save + */ + AuthProvider save(AuthProvider authProvider); + +} diff --git a/src/main/java/org/springframework/samples/petclinic/repository/CredentialRepository.java b/src/main/java/org/springframework/samples/petclinic/repository/CredentialRepository.java new file mode 100644 index 000000000..18ca70364 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/repository/CredentialRepository.java @@ -0,0 +1,51 @@ +package org.springframework.samples.petclinic.repository; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.query.Param; +import org.springframework.samples.petclinic.model.common.Credential; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * Repository class for Credential domain objects All method names are + * compliant with Spring Data naming conventions so this interface can easily be extended + * for Spring + * + * @author Paul-Emmanuel DOS SANTOS FACAO + */ +public interface CredentialRepository extends Repository { + + /** + * Retrieve a {@link Credential} from the data store by email. + * @param email the email to search for + * @param providerId the provider to search for authorization + * @return the {@link Credential} if found + */ + @Query("SELECT c FROM Credential c WHERE c.email = :email AND c.providerId = :providerId") + @Transactional(readOnly = true) + Credential findByEmailAndProvider(@Param("email") String email, @Param("providerId") Integer providerId); + + /** + * * Retrieve a {@link Credential} from the data store by token. + * @param token the token to search for + * @return the {@link Credential} if found + */ + @Query("SELECT DISTINCT c FROM Credential c WHERE c.token = :token") + @Transactional(readOnly = true) + Credential findByToken(@Param("token") String token); + + /** + * Retrieve all {@link Credential}s from the data store + * @return a Collection of {@link Credential}s (or an empty Collection if none + */ + List findAll(); + + /** + * Save a {@link Credential} to the data store, either inserting or updating it. + * @param credential the {@link Credential} to save + */ + Credential save(Credential credential); + +} diff --git a/src/main/java/org/springframework/samples/petclinic/service/BaseService.java b/src/main/java/org/springframework/samples/petclinic/service/business/BaseService.java similarity index 94% rename from src/main/java/org/springframework/samples/petclinic/service/BaseService.java rename to src/main/java/org/springframework/samples/petclinic/service/business/BaseService.java index 3578d4ab2..d32206054 100644 --- a/src/main/java/org/springframework/samples/petclinic/service/BaseService.java +++ b/src/main/java/org/springframework/samples/petclinic/service/business/BaseService.java @@ -1,4 +1,4 @@ -package org.springframework.samples.petclinic.service; +package org.springframework.samples.petclinic.service.business; import java.util.List; diff --git a/src/main/java/org/springframework/samples/petclinic/service/OwnerService.java b/src/main/java/org/springframework/samples/petclinic/service/business/OwnerService.java similarity index 98% rename from src/main/java/org/springframework/samples/petclinic/service/OwnerService.java rename to src/main/java/org/springframework/samples/petclinic/service/business/OwnerService.java index b79cff504..05dfa5858 100644 --- a/src/main/java/org/springframework/samples/petclinic/service/OwnerService.java +++ b/src/main/java/org/springframework/samples/petclinic/service/business/OwnerService.java @@ -1,4 +1,4 @@ -package org.springframework.samples.petclinic.service; +package org.springframework.samples.petclinic.service.business; import org.modelmapper.ModelMapper; import org.modelmapper.internal.util.Lists; diff --git a/src/main/java/org/springframework/samples/petclinic/service/PetService.java b/src/main/java/org/springframework/samples/petclinic/service/business/PetService.java similarity index 98% rename from src/main/java/org/springframework/samples/petclinic/service/PetService.java rename to src/main/java/org/springframework/samples/petclinic/service/business/PetService.java index e331284a9..82c43182b 100644 --- a/src/main/java/org/springframework/samples/petclinic/service/PetService.java +++ b/src/main/java/org/springframework/samples/petclinic/service/business/PetService.java @@ -1,4 +1,4 @@ -package org.springframework.samples.petclinic.service; +package org.springframework.samples.petclinic.service.business; import org.modelmapper.ModelMapper; import org.springframework.samples.petclinic.dto.OwnerDTO; diff --git a/src/main/java/org/springframework/samples/petclinic/service/PetTypeService.java b/src/main/java/org/springframework/samples/petclinic/service/business/PetTypeService.java similarity index 96% rename from src/main/java/org/springframework/samples/petclinic/service/PetTypeService.java rename to src/main/java/org/springframework/samples/petclinic/service/business/PetTypeService.java index 498e61d6b..8afbb41db 100644 --- a/src/main/java/org/springframework/samples/petclinic/service/PetTypeService.java +++ b/src/main/java/org/springframework/samples/petclinic/service/business/PetTypeService.java @@ -1,4 +1,4 @@ -package org.springframework.samples.petclinic.service; +package org.springframework.samples.petclinic.service.business; import org.modelmapper.ModelMapper; import org.springframework.samples.petclinic.dto.PetTypeDTO; diff --git a/src/main/java/org/springframework/samples/petclinic/service/SpecialtyService.java b/src/main/java/org/springframework/samples/petclinic/service/business/SpecialtyService.java similarity index 96% rename from src/main/java/org/springframework/samples/petclinic/service/SpecialtyService.java rename to src/main/java/org/springframework/samples/petclinic/service/business/SpecialtyService.java index 98234a027..79f9de533 100644 --- a/src/main/java/org/springframework/samples/petclinic/service/SpecialtyService.java +++ b/src/main/java/org/springframework/samples/petclinic/service/business/SpecialtyService.java @@ -1,4 +1,4 @@ -package org.springframework.samples.petclinic.service; +package org.springframework.samples.petclinic.service.business; import org.modelmapper.ModelMapper; import org.springframework.samples.petclinic.dto.SpecialtyDTO; diff --git a/src/main/java/org/springframework/samples/petclinic/service/VetService.java b/src/main/java/org/springframework/samples/petclinic/service/business/VetService.java similarity index 97% rename from src/main/java/org/springframework/samples/petclinic/service/VetService.java rename to src/main/java/org/springframework/samples/petclinic/service/business/VetService.java index 11a7967fe..601969e5e 100644 --- a/src/main/java/org/springframework/samples/petclinic/service/VetService.java +++ b/src/main/java/org/springframework/samples/petclinic/service/business/VetService.java @@ -1,4 +1,4 @@ -package org.springframework.samples.petclinic.service; +package org.springframework.samples.petclinic.service.business; import org.modelmapper.ModelMapper; import org.modelmapper.internal.util.Lists; diff --git a/src/main/java/org/springframework/samples/petclinic/service/VisitService.java b/src/main/java/org/springframework/samples/petclinic/service/business/VisitService.java similarity index 97% rename from src/main/java/org/springframework/samples/petclinic/service/VisitService.java rename to src/main/java/org/springframework/samples/petclinic/service/business/VisitService.java index 76be1578c..5612565d7 100644 --- a/src/main/java/org/springframework/samples/petclinic/service/VisitService.java +++ b/src/main/java/org/springframework/samples/petclinic/service/business/VisitService.java @@ -1,4 +1,4 @@ -package org.springframework.samples.petclinic.service; +package org.springframework.samples.petclinic.service.business; import org.modelmapper.ModelMapper; import org.springframework.samples.petclinic.dto.VisitDTO; diff --git a/src/main/java/org/springframework/samples/petclinic/service/common/AuthProviderService.java b/src/main/java/org/springframework/samples/petclinic/service/common/AuthProviderService.java new file mode 100644 index 000000000..953b9bcc7 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/service/common/AuthProviderService.java @@ -0,0 +1,87 @@ +package org.springframework.samples.petclinic.service.common; + +import org.modelmapper.ModelMapper; +import org.springframework.samples.petclinic.dto.common.AuthProviderDTO; +import org.springframework.samples.petclinic.model.common.AuthProvider; +import org.springframework.samples.petclinic.repository.AuthProviderRepository; +import org.springframework.samples.petclinic.service.business.BaseService; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +/** + * Simple Service between AuthProvider entity and AuthProviderDTO Data Transfert Object. + * + * @author Paul-Emmanuel DOS SANTOS FACAO + */ +@Service("AuthProviderService") +public class AuthProviderService implements BaseService { + + private final AuthProviderRepository authProviderRepository; + + private final ModelMapper modelMapper = new ModelMapper(); + + public AuthProviderService(AuthProviderRepository authProviderRepository) { + this.authProviderRepository = authProviderRepository; + } + + @Override + public AuthProvider dtoToEntity(AuthProviderDTO dto) { + if (dto != null) { + return modelMapper.map(dto, AuthProvider.class); + } + + return new AuthProvider(); + } + + @Override + public AuthProviderDTO entityToDTO(AuthProvider entity) { + if (entity != null) { + return modelMapper.map(entity, AuthProviderDTO.class); + } + + return new AuthProviderDTO(); + } + + @Override + public List entitiesToDTOS(List entities) { + List dtos = new ArrayList<>(); + + entities.forEach(entity -> dtos.add(entityToDTO(entity))); + + return dtos; + } + + @Override + public List dtosToEntities(List dtos) { + List entities = new ArrayList<>(); + + dtos.forEach(dto -> entities.add(dtoToEntity(dto))); + + return entities; + } + + @Override + public AuthProviderDTO findById(int providerId) { + return entityToDTO(authProviderRepository.findById(providerId)); + } + + @Override + public List findAll() { + return entitiesToDTOS(authProviderRepository.findAll()); + } + + @Override + public AuthProviderDTO save(AuthProviderDTO dto) { + AuthProvider authProvider = dtoToEntity(dto); + authProvider = authProviderRepository.save(authProvider); + + return entityToDTO(authProvider); + } + + public AuthProviderDTO findByName(String providerName) { + return entityToDTO(authProviderRepository.findByName(providerName)); + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/service/common/CredentialService.java b/src/main/java/org/springframework/samples/petclinic/service/common/CredentialService.java new file mode 100644 index 000000000..e9f5d2f15 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/service/common/CredentialService.java @@ -0,0 +1,129 @@ +package org.springframework.samples.petclinic.service.common; + +import org.modelmapper.ModelMapper; +import org.springframework.samples.petclinic.common.CommonParameter; +import org.springframework.samples.petclinic.dto.common.CredentialDTO; +import org.springframework.samples.petclinic.dto.common.UserDTO; +import org.springframework.samples.petclinic.model.common.AuthProvider; +import org.springframework.samples.petclinic.model.common.Credential; +import org.springframework.samples.petclinic.repository.AuthProviderRepository; +import org.springframework.samples.petclinic.repository.CredentialRepository; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.stereotype.Service; + +/** + * Simple Service between User entity and UserDTO Data Transfert Object. + * + * @author Paul-Emmanuel DOS SANTOS FACAO + */ +@Service("CredentialService") +public class CredentialService { + + private final CredentialRepository credentialRepository; + + private final BCryptPasswordEncoder bCryptPasswordEncoder; + + private final AuthProviderRepository authProviderRepository; + + private final ModelMapper modelMapper = new ModelMapper(); + + public CredentialService(CredentialRepository credentialRepository, BCryptPasswordEncoder bCryptPasswordEncoder, AuthProviderRepository authProviderRepository) { + this.credentialRepository = credentialRepository; + this.bCryptPasswordEncoder = bCryptPasswordEncoder; + this.authProviderRepository = authProviderRepository; + } + + public Credential dtoToEntity(CredentialDTO dto) { + if (dto != null) { + Credential entity = modelMapper.map(dto, Credential.class); + AuthProvider authProvider = authProviderRepository.findByName(dto.getProvider()); + if (authProvider == null) { + authProvider = authProviderRepository.findByName(CommonParameter.DEFAULT_PROVIDER); + } + entity.setProviderId(authProvider.getId()); + return entity; + } + + return new Credential(); + } + + public CredentialDTO entityToDTO(Credential entity) { + if (entity != null) { + CredentialDTO dto = modelMapper.map(entity, CredentialDTO.class); + AuthProvider authProvider = authProviderRepository.findById(entity.getProviderId()); + + if (authProvider == null) { + dto.setProvider(CommonParameter.DEFAULT_PROVIDER); + } + else { + dto.setProvider(authProvider.getName()); + } + + return dto; + } + + return new CredentialDTO(); + } + + public CredentialDTO findByEmailAndProvider(String email, String provider) { + AuthProvider authProvider = authProviderRepository.findByName(provider); + Credential credential = credentialRepository.findByEmailAndProvider(email, authProvider.getId()); + return entityToDTO(credential); + } + + public CredentialDTO findByToken(String token) { + Credential credential = credentialRepository.findByToken(token); + return entityToDTO(credential); + } + + public CredentialDTO findByAuthentication(OAuth2AuthenticationToken authentication) { + String email = authentication.getPrincipal().getAttribute("email"); + String provider = authentication.getAuthorizedClientRegistrationId(); + + AuthProvider authProvider = authProviderRepository.findByName(provider); + Credential credential = credentialRepository.findByEmailAndProvider(email, authProvider.getId()); + return entityToDTO(credential); + } + + + public CredentialDTO save(CredentialDTO dto) { + Credential credential = dtoToEntity(dto); + credential = credentialRepository.save(credential); + return entityToDTO(credential); + } + + public CredentialDTO saveNew(UserDTO user) { + Credential credential = new Credential(); + + AuthProvider authProvider = authProviderRepository.findByName(CommonParameter.DEFAULT_PROVIDER); + + credential.setEmail(user.getEmail()); + credential.setProviderId(authProvider.getId()); + credential.setPassword(user.getPassword()); + credential.setVerified(false); + credential.setToken(); + credential.setExpiration(); + + credential = credentialRepository.save(credential); + return entityToDTO(credential); + } + + public CredentialDTO saveNew(OAuth2AuthenticationToken authentication) { + Credential credential = new Credential(); + + AuthProvider authProvider = authProviderRepository.findByName(authentication.getAuthorizedClientRegistrationId()); + + credential.setEmail(authentication.getPrincipal().getAttribute("email")); + credential.setProviderId(authProvider.getId()); + credential.setPassword(authentication.getPrincipal().getAttribute("sub")); + credential.setVerified(false); + credential.setToken(); + credential.setExpiration(); + + credential = credentialRepository.save(credential); + return entityToDTO(credential); + } + + +} diff --git a/src/main/java/org/springframework/samples/petclinic/service/common/EmailService.java b/src/main/java/org/springframework/samples/petclinic/service/common/EmailService.java new file mode 100644 index 000000000..c9730478a --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/service/common/EmailService.java @@ -0,0 +1,97 @@ +package org.springframework.samples.petclinic.service.common; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ClassPathResource; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.samples.petclinic.dto.common.MessageDTO; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.thymeleaf.ITemplateEngine; +import org.thymeleaf.context.Context; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import java.util.Date; +import java.util.Locale; + +@Slf4j +@Service("EmailService") +public class EmailService { + + private static final String EMAIL_TEMPLATE = "email.html"; + + private static final String PNG_MIME = "image/png"; + + @Value("${mail.background}") + private String mailBackground; + + @Value("${mail.banner}") + private String mailBanner; + + @Value("${mail.logo}") + private String mailLogo; + + @Autowired + protected JavaMailSender mailSender; + + @Autowired + protected ITemplateEngine templateEngine; + + + /** + * sendMailAsynch : for the controller MailController + * send mail asynchronously + * + * @param messageDTO : message to be send by mail + * @param locale : not used now + */ + @Async + public void sendMailAsynch(MessageDTO messageDTO, Locale locale){ + sendMail(messageDTO, locale); + } + + /** + * sendMail : build mail according to a Thymeleaf template and a MessageDTO object + * @param messageDTO : message to be send by mail + * @param locale : not used now + */ + public void sendMail(MessageDTO messageDTO, Locale locale) { + MimeMessage mimeMessage = mailSender.createMimeMessage(); + MimeMessageHelper message; + + // Prepare the evaluation context + Context ctx = new Context(locale); + ctx.setVariable("toFirstName", messageDTO.getFirstName()); + ctx.setVariable("toLastName", messageDTO.getLastName()); + ctx.setVariable("mailSubject", messageDTO.getSubject()); + ctx.setVariable("mailContent", messageDTO.getContent()); + ctx.setVariable("mailLink", messageDTO.getLink()); + ctx.setVariable("mailDate", new Date()); + + // Create the HTML body using Thymeleaf + String output = templateEngine.process(EMAIL_TEMPLATE, ctx); + + try { + message = new MimeMessageHelper(mimeMessage, true /* multipart */, "UTF-8"); + + message.setFrom(messageDTO.getFrom()); + message.setTo(messageDTO.getTo()); + message.setSubject(messageDTO.getSubject()); + message.setText(output, true /* isHtml */); + + message.addInline("mailBackground", new ClassPathResource(mailBackground), PNG_MIME); + message.addInline("mailBanner", new ClassPathResource(mailBanner), PNG_MIME); + message.addInline("mailLogo", new ClassPathResource(mailLogo), PNG_MIME); + } + catch (MessagingException ex) { + log.error("Error sending mail !", ex); + } + + // Send mail + this.mailSender.send(mimeMessage); + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/service/RoleService.java b/src/main/java/org/springframework/samples/petclinic/service/common/RoleService.java similarity index 90% rename from src/main/java/org/springframework/samples/petclinic/service/RoleService.java rename to src/main/java/org/springframework/samples/petclinic/service/common/RoleService.java index 2194bc21b..d0f2e43ce 100644 --- a/src/main/java/org/springframework/samples/petclinic/service/RoleService.java +++ b/src/main/java/org/springframework/samples/petclinic/service/common/RoleService.java @@ -1,9 +1,10 @@ -package org.springframework.samples.petclinic.service; +package org.springframework.samples.petclinic.service.common; import org.modelmapper.ModelMapper; -import org.springframework.samples.petclinic.dto.RoleDTO; +import org.springframework.samples.petclinic.dto.common.RoleDTO; import org.springframework.samples.petclinic.model.common.Role; import org.springframework.samples.petclinic.repository.RoleRepository; +import org.springframework.samples.petclinic.service.business.BaseService; import org.springframework.stereotype.Service; import java.util.ArrayList; diff --git a/src/main/java/org/springframework/samples/petclinic/service/SecurityService.java b/src/main/java/org/springframework/samples/petclinic/service/common/SecurityService.java similarity index 66% rename from src/main/java/org/springframework/samples/petclinic/service/SecurityService.java rename to src/main/java/org/springframework/samples/petclinic/service/common/SecurityService.java index 1e173e61c..60818f5fd 100644 --- a/src/main/java/org/springframework/samples/petclinic/service/SecurityService.java +++ b/src/main/java/org/springframework/samples/petclinic/service/common/SecurityService.java @@ -1,4 +1,4 @@ -package org.springframework.samples.petclinic.service; +package org.springframework.samples.petclinic.service.common; public interface SecurityService { diff --git a/src/main/java/org/springframework/samples/petclinic/service/SecurityServiceImpl.java b/src/main/java/org/springframework/samples/petclinic/service/common/SecurityServiceImpl.java similarity index 96% rename from src/main/java/org/springframework/samples/petclinic/service/SecurityServiceImpl.java rename to src/main/java/org/springframework/samples/petclinic/service/common/SecurityServiceImpl.java index a5f5936eb..639e9675d 100644 --- a/src/main/java/org/springframework/samples/petclinic/service/SecurityServiceImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/service/common/SecurityServiceImpl.java @@ -1,4 +1,4 @@ -package org.springframework.samples.petclinic.service; +package org.springframework.samples.petclinic.service.common; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/main/java/org/springframework/samples/petclinic/service/UserDetailsServiceImpl.java b/src/main/java/org/springframework/samples/petclinic/service/common/UserDetailsServiceImpl.java similarity index 59% rename from src/main/java/org/springframework/samples/petclinic/service/UserDetailsServiceImpl.java rename to src/main/java/org/springframework/samples/petclinic/service/common/UserDetailsServiceImpl.java index 86a249205..1858299f8 100644 --- a/src/main/java/org/springframework/samples/petclinic/service/UserDetailsServiceImpl.java +++ b/src/main/java/org/springframework/samples/petclinic/service/common/UserDetailsServiceImpl.java @@ -1,20 +1,14 @@ -package org.springframework.samples.petclinic.service; +package org.springframework.samples.petclinic.service.common; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.samples.petclinic.dto.RoleDTO; -import org.springframework.samples.petclinic.dto.UserDTO; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.samples.petclinic.dto.common.UserDTO; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; -import java.util.HashSet; -import java.util.Set; - @Slf4j @Service("UserDetailsService") public class UserDetailsServiceImpl implements UserDetailsService { @@ -36,15 +30,7 @@ public class UserDetailsServiceImpl implements UserDetailsService { if (userDTO == null) throw new UsernameNotFoundException("User not found with email :" + email); - Set grantedAuthorities = new HashSet<>(); - - for (RoleDTO role : userDTO.getRoles()) { - grantedAuthorities.add(new SimpleGrantedAuthority(role.getName())); - } - - return new org.springframework.security.core.userdetails.User(userDTO.getEmail(), userDTO.getMatchingPassword(), - grantedAuthorities); - + return userDTO; } } diff --git a/src/main/java/org/springframework/samples/petclinic/service/UserService.java b/src/main/java/org/springframework/samples/petclinic/service/common/UserService.java similarity index 86% rename from src/main/java/org/springframework/samples/petclinic/service/UserService.java rename to src/main/java/org/springframework/samples/petclinic/service/common/UserService.java index b15ff430f..7b1243b77 100644 --- a/src/main/java/org/springframework/samples/petclinic/service/UserService.java +++ b/src/main/java/org/springframework/samples/petclinic/service/common/UserService.java @@ -1,11 +1,12 @@ -package org.springframework.samples.petclinic.service; +package org.springframework.samples.petclinic.service.common; import org.modelmapper.ModelMapper; -import org.springframework.samples.petclinic.dto.RoleDTO; -import org.springframework.samples.petclinic.dto.UserDTO; +import org.springframework.samples.petclinic.dto.common.RoleDTO; +import org.springframework.samples.petclinic.dto.common.UserDTO; import org.springframework.samples.petclinic.model.common.Role; import org.springframework.samples.petclinic.model.common.User; import org.springframework.samples.petclinic.repository.UserRepository; +import org.springframework.samples.petclinic.service.business.BaseService; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -37,13 +38,14 @@ public class UserService implements BaseService { User user = modelMapper.map(dto, User.class); user.setPassword(dto.getPassword()); + /* if (dto.getRoles() != null) { for (RoleDTO roleDTO : dto.getRoles()) { Role role = modelMapper.map(roleDTO, Role.class); user.addRole(role); } } - +*/ return user; } @@ -56,14 +58,14 @@ public class UserService implements BaseService { UserDTO userDto = modelMapper.map(entity, UserDTO.class); userDto.setPassword(entity.getPassword()); userDto.setMatchingPassword(entity.getPassword()); - +/* if (entity.getRoles() != null) { for (Role role : entity.getRoles()) { RoleDTO roleDTO = modelMapper.map(role, RoleDTO.class); userDto.addRole(roleDTO); } } - +*/ return userDto; } @@ -114,4 +116,8 @@ public class UserService implements BaseService { return entityToDTO(user); } + public boolean existByEmail(String email) { + return userRepository.existsByEmail(email); + } + } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 11809f262..2d00e9be3 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -30,8 +30,6 @@ spring.resources.cache.cachecontrol.max-age=12h #logging.level.org.hibernate: DEBUG #logging.level.org.springframework.context.annotation=TRACE - - spring.datasource.hikari.connectionTimeout=20000 spring.datasource.hikari.maximumPoolSize=5 spring.datasource.initialize=true @@ -42,6 +40,7 @@ spring.datasource.password= spring.h2.console.enabled=true spring.h2.console.path=/h2-console +######################################################################### OAUTH2 spring.security.oauth2.client.registration.google.client-id=${OAUTH2_GOOGLE_CLIENT_ID} spring.security.oauth2.client.registration.google.client-secret=${OAUTH2_GOOGLE_CLIENT_SECRET} @@ -52,6 +51,17 @@ spring.security.oauth2.client.registration.github.client-secret=${OAUTH2_GITHUB_ #spring.security.oauth2.client.registration.facebook.client-id= #spring.security.oauth2.client.registration.facebook.client-secret= - #spring.security.oauth2.client.registration.twitter.client-id= #spring.security.oauth2.client.registration.twitter.client-secret= + +#################################################################### SPRING MAIL +spring.mail.host=smtp.mailtrap.io +spring.mail.port=2525 +spring.mail.protocol=smtp +spring.mail.username=${SPRING_MAIL_USERNAME} +spring.mail.password=${SPRING_MAIL_PASSWORD} +mail.background=static/resources/images/mail-background.png +mail.banner=static/resources/images/mail-banner.png +mail.logo=static/resources/images/mail-logo.png + + diff --git a/src/main/resources/db/h2/data.sql b/src/main/resources/db/h2/data.sql index a5aa7dd2d..56734e0f6 100644 --- a/src/main/resources/db/h2/data.sql +++ b/src/main/resources/db/h2/data.sql @@ -52,16 +52,30 @@ INSERT INTO visits VALUES (2, 8, '2013-01-02', 'rabies shot'); INSERT INTO visits VALUES (3, 8, '2013-01-03', 'neutered'); INSERT INTO visits VALUES (4, 7, '2013-01-04', 'spayed'); -INSERT INTO roles VALUES (1,'ADMIN'); -INSERT INTO roles VALUES (2,'STAFF'); -INSERT INTO roles VALUES (3,'USER'); +INSERT INTO roles (id, name) VALUES + (1,'ROLE_ADMIN'), + (2,'ROLE_STAFF'), + (3,'ROLE_USER'); -INSERT INTO users VALUES (1, 'George', 'Franklin', 'georges.franklin@petclinic.com', true,'$2a$10$8KypNYtPopFo8Sk5jbKJ4.lCKeBhdApsrkmFfhwjB8nCls8qpzjZG', 'local', null, '6085551023', '110 W. Liberty St.','','',12354,'Madison','USA'); -INSERT INTO users VALUES (2, 'Betty', 'Davis', 'betty.davis@petclinic.com', true, '$2a$10$InKx/fhX3CmLi8zKpHYx/.ETHUlZwvT1xn.Za/pp2JR0iEtYV9a9O', 'local', null, '6085551749','638 Cardinal Ave.', '', '', 6546, 'Sun Prairie', 'USA'); -INSERT INTO users VALUES (3, 'Eduardo', 'Rodriquez', 'eduardo.rodriguez@petclinic.com', true, '$2a$10$P55nbvVibHpoyWzenHngjOf.oEmcj74mI/VJaUZwGX9v8klctzsNW', 'local', null, '6085558763','2693 Commerce St.', '', '', 65454, 'McFarland', 'USA'); +INSERT INTO users (id, first_name, last_name, email, password, enabled, telephone, street1, zip_code, city, country) VALUES + (1, 'George', 'Franklin', 'georges.franklin@petclinic.com', '$2a$10$8KypNYtPopFo8Sk5jbKJ4.lCKeBhdApsrkmFfhwjB8nCls8qpzjZG', true, '6085551023', '110 W. Liberty St.',12354,'Madison','USA'), + (2, 'Betty', 'Davis', 'betty.davis@petclinic.com', '$2a$10$InKx/fhX3CmLi8zKpHYx/.ETHUlZwvT1xn.Za/pp2JR0iEtYV9a9O', true, '6085551749','638 Cardinal Ave.', 6546, 'Sun Prairie', 'USA'), + (3, 'Eduardo', 'Rodriquez', 'eduardo.rodriguez@petclinic.com', '$2a$10$P55nbvVibHpoyWzenHngjOf.oEmcj74mI/VJaUZwGX9v8klctzsNW', true, '6085558763','2693 Commerce St.', 65454, 'McFarland', 'USA'), + (4, 'Paul-Emmanuel','DOS SANTOS FACAO','pedsf.fullstack@gmail.com','$2a$10$AzoUxi1IQFJMzLHcCGmDjuDHAQqAcAiRLz6UMeItdTL3mMWxMZEPC', true, '6085558763','2693 Commerce St.', 65454, 'McFarland', 'USA'); + +INSERT INTO users_roles (user_id, role_id) VALUES + (1,1),(1,2),(1,3), + (2,3),(3,3); + +INSERT INTO auth_providers (id, name) VALUES + (1,'local'), + (2,'google'), + (3,'github'), + (4,'twitter'); + +INSERT INTO credentials (provider_id, email, password, verified) VALUES + (1, 'georges.franklin@petclinic.com', '$2a$10$8KypNYtPopFo8Sk5jbKJ4.lCKeBhdApsrkmFfhwjB8nCls8qpzjZG', true), + (1, 'betty.davis@petclinic.com', '$2a$10$InKx/fhX3CmLi8zKpHYx/.ETHUlZwvT1xn.Za/pp2JR0iEtYV9a9O', true), + (1, 'eduardo.rodriguez@petclinic.com', '$2a$10$P55nbvVibHpoyWzenHngjOf.oEmcj74mI/VJaUZwGX9v8klctzsNW', true), + (2, 'pedsf.fullstack@gmail.com','117496521794255275093', true); -INSERT INTO users_roles VALUES (1,1); -INSERT INTO users_roles VALUES (1,2); -INSERT INTO users_roles VALUES (1,3); -INSERT INTO users_roles VALUES (2,3); -INSERT INTO users_roles VALUES (3,3); diff --git a/src/main/resources/db/h2/schema.sql b/src/main/resources/db/h2/schema.sql index 3c80a0076..8aa68cc4f 100644 --- a/src/main/resources/db/h2/schema.sql +++ b/src/main/resources/db/h2/schema.sql @@ -7,7 +7,10 @@ DROP TABLE types IF EXISTS; DROP TABLE owners IF EXISTS; DROP TABLE roles IF EXISTS; DROP TABLE users IF EXISTS; - +DROP TABLE users_email IF EXISTS; +DROP TABLE users_roles IF EXISTS; +DROP TABLE auth_providers IF EXISTS; +DROP TABLE credentials IF EXISTS; CREATE TABLE vets ( id INTEGER IDENTITY PRIMARY KEY, @@ -72,21 +75,22 @@ CREATE TABLE roles ( CREATE INDEX roles_name ON roles (name); CREATE TABLE users ( - id INTEGER IDENTITY PRIMARY KEY, - first_name VARCHAR(30) NOT NULL, - last_name VARCHAR_IGNORECASE(30) NOT NULL, - email VARCHAR(50) NOT NULL, - email_verified BOOLEAN NOT NULL, - password VARCHAR(255) NOT NULL, - provider VARCHAR(20), - provider_id VARCHAR(20), - telephone VARCHAR(20), - street1 VARCHAR(50), - street2 VARCHAR(50), - street3 VARCHAR(50), - zip_code VARCHAR(6), - city VARCHAR(80), - country VARCHAR(50) + id INTEGER IDENTITY PRIMARY KEY, + first_name VARCHAR(30) NOT NULL, + last_name VARCHAR_IGNORECASE(30) NOT NULL, + email VARCHAR(50) NOT NULL, + password VARCHAR(255) NOT NULL, + enabled BOOLEAN NOT NULL, + account_unexpired BOOLEAN NOT NULL DEFAULT true, + account_unlocked BOOLEAN NOT NULL DEFAULT true, + credential_unexpired BOOLEAN NOT NULL DEFAULT true, + telephone VARCHAR(20), + street1 VARCHAR(50), + street2 VARCHAR(50), + street3 VARCHAR(50), + zip_code VARCHAR(6), + city VARCHAR(80), + country VARCHAR(50) ); CREATE INDEX users_email ON users (email); @@ -97,3 +101,21 @@ CREATE TABLE public.users_roles ( ALTER TABLE users_roles ADD CONSTRAINT fk_users_roles_user_id FOREIGN KEY (user_id) REFERENCES users (id); ALTER TABLE users_roles ADD CONSTRAINT fk_users_roles_role_id FOREIGN KEY (role_id) REFERENCES roles (id); CREATE INDEX users_roles_user_id ON users_roles (user_id); + +CREATE TABLE auth_providers ( + id INTEGER IDENTITY PRIMARY KEY, + name VARCHAR(20) NOT NULL +); +CREATE INDEX auth_providers_name ON auth_providers (name); + +CREATE TABLE credentials ( + id INTEGER IDENTITY PRIMARY KEY, + provider_id INTEGER NOT NULL, + email VARCHAR(50) NOT NULL, + password VARCHAR(255) NOT NULL, + verified BOOLEAN NOT NULL, + token VARCHAR(255) DEFAULT NULL, + expiration DATE DEFAULT NULL +); +ALTER TABLE credentials ADD CONSTRAINT fk_credentials_provider_id FOREIGN KEY (provider_id) REFERENCES auth_providers (id); +CREATE INDEX credentials_email ON credentials (email); diff --git a/src/main/resources/static/resources/images/mail-background.png b/src/main/resources/static/resources/images/mail-background.png new file mode 100644 index 000000000..512185839 Binary files /dev/null and b/src/main/resources/static/resources/images/mail-background.png differ diff --git a/src/main/resources/static/resources/images/mail-banner.png b/src/main/resources/static/resources/images/mail-banner.png new file mode 100644 index 000000000..1840af274 Binary files /dev/null and b/src/main/resources/static/resources/images/mail-banner.png differ diff --git a/src/main/resources/static/resources/images/mail-logo.png b/src/main/resources/static/resources/images/mail-logo.png new file mode 100644 index 000000000..bb5cf3a34 Binary files /dev/null and b/src/main/resources/static/resources/images/mail-logo.png differ diff --git a/src/main/resources/templates/email.html b/src/main/resources/templates/email.html new file mode 100644 index 000000000..da26f3aa3 --- /dev/null +++ b/src/main/resources/templates/email.html @@ -0,0 +1,49 @@ + + + + Template for HTML email (editable) + + + +
+
+
+
+
+ +
+
+ +
+

+ Subject

+

+

+ Hello + FirstName + toLastName! +

+ +

mail content

+ Approve the connection + +

+ date +

+

Regards

+
+
+
+ +
+
+
+
+
+ + diff --git a/src/main/resources/templates/fragments/layout.html b/src/main/resources/templates/fragments/layout.html index 663ab0d8c..72de108c6 100755 --- a/src/main/resources/templates/fragments/layout.html +++ b/src/main/resources/templates/fragments/layout.html @@ -19,7 +19,7 @@ - + diff --git a/src/main/resources/templates/owners/createOrUpdateOwnerForm.html b/src/main/resources/templates/owners/createOrUpdateOwnerForm.html index c835f8ccb..d290c7d36 100644 --- a/src/main/resources/templates/owners/createOrUpdateOwnerForm.html +++ b/src/main/resources/templates/owners/createOrUpdateOwnerForm.html @@ -21,8 +21,9 @@
+ class="btn btn-default" type="submit" th:text="${text}">Add Owner + + Cancel
diff --git a/src/main/resources/templates/users/userChangePasswordForm.html b/src/main/resources/templates/users/userChangePasswordForm.html new file mode 100644 index 000000000..36c70d389 --- /dev/null +++ b/src/main/resources/templates/users/userChangePasswordForm.html @@ -0,0 +1,53 @@ + + + + +

Change password

+
+ + + + + + + + + ; + + + + + + + + + +
+
+ + + We'll never share your emailConfiguration with anyone else. +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + Cancel +
+
+
+
+ + diff --git a/src/main/resources/templates/users/createOrUpdateUserForm.html b/src/main/resources/templates/users/userRegistrationForm.html similarity index 79% rename from src/main/resources/templates/users/createOrUpdateUserForm.html rename to src/main/resources/templates/users/userRegistrationForm.html index 9fba098f1..b0d8c008c 100644 --- a/src/main/resources/templates/users/createOrUpdateUserForm.html +++ b/src/main/resources/templates/users/userRegistrationForm.html @@ -3,9 +3,16 @@ -

User

+

User registration

+ + + + +
+ -
- - Cancel + + Cancel
diff --git a/src/main/resources/templates/users/userUpdateForm.html b/src/main/resources/templates/users/userUpdateForm.html new file mode 100644 index 000000000..5fef33bb4 --- /dev/null +++ b/src/main/resources/templates/users/userUpdateForm.html @@ -0,0 +1,49 @@ + + + + +

User update

+
+ + + + + + + + + + +
+ + + + + + + + + + +
+
+
+ + Change password + Cancel +
+
+
+ + diff --git a/src/main/resources/templates/welcome.html b/src/main/resources/templates/welcome.html index 4fa1cd328..6e6206792 100644 --- a/src/main/resources/templates/welcome.html +++ b/src/main/resources/templates/welcome.html @@ -1,6 +1,5 @@ - - - + @@ -13,4 +12,4 @@ - \ No newline at end of file + diff --git a/src/test/java/org/springframework/samples/petclinic/controller/OwnerControllerIntegrationTest.java b/src/test/java/org/springframework/samples/petclinic/controller/OwnerControllerIntegrationTest.java index 55ff114ca..cdaef709b 100644 --- a/src/test/java/org/springframework/samples/petclinic/controller/OwnerControllerIntegrationTest.java +++ b/src/test/java/org/springframework/samples/petclinic/controller/OwnerControllerIntegrationTest.java @@ -12,7 +12,7 @@ import org.springframework.samples.petclinic.dto.OwnerDTO; import org.springframework.samples.petclinic.dto.PetDTO; import org.springframework.samples.petclinic.dto.PetTypeDTO; import org.springframework.samples.petclinic.dto.VisitDTO; -import org.springframework.samples.petclinic.service.OwnerService; +import org.springframework.samples.petclinic.service.business.OwnerService; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; diff --git a/src/test/java/org/springframework/samples/petclinic/controller/OwnerControllerTest.java b/src/test/java/org/springframework/samples/petclinic/controller/OwnerControllerTest.java index 62d7c9540..25b1d60d5 100644 --- a/src/test/java/org/springframework/samples/petclinic/controller/OwnerControllerTest.java +++ b/src/test/java/org/springframework/samples/petclinic/controller/OwnerControllerTest.java @@ -40,8 +40,8 @@ import org.springframework.samples.petclinic.dto.OwnerDTO; import org.springframework.samples.petclinic.dto.PetDTO; import org.springframework.samples.petclinic.dto.PetTypeDTO; import org.springframework.samples.petclinic.dto.VisitDTO; -import org.springframework.samples.petclinic.service.OwnerService; -import org.springframework.samples.petclinic.service.VisitService; +import org.springframework.samples.petclinic.service.business.OwnerService; +import org.springframework.samples.petclinic.service.business.VisitService; import org.springframework.test.web.servlet.MockMvc; import static org.hamcrest.Matchers.empty; diff --git a/src/test/java/org/springframework/samples/petclinic/controller/PetControllerIntegrationTest.java b/src/test/java/org/springframework/samples/petclinic/controller/PetControllerIntegrationTest.java index 113c7f16b..db30e66d0 100644 --- a/src/test/java/org/springframework/samples/petclinic/controller/PetControllerIntegrationTest.java +++ b/src/test/java/org/springframework/samples/petclinic/controller/PetControllerIntegrationTest.java @@ -11,8 +11,8 @@ import org.springframework.samples.petclinic.common.CommonView; import org.springframework.samples.petclinic.dto.OwnerDTO; import org.springframework.samples.petclinic.dto.PetDTO; import org.springframework.samples.petclinic.repository.PetRepository; -import org.springframework.samples.petclinic.service.OwnerService; -import org.springframework.samples.petclinic.service.PetService; +import org.springframework.samples.petclinic.service.business.OwnerService; +import org.springframework.samples.petclinic.service.business.PetService; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; diff --git a/src/test/java/org/springframework/samples/petclinic/controller/PetControllerTest.java b/src/test/java/org/springframework/samples/petclinic/controller/PetControllerTest.java index af989653b..1e9583481 100644 --- a/src/test/java/org/springframework/samples/petclinic/controller/PetControllerTest.java +++ b/src/test/java/org/springframework/samples/petclinic/controller/PetControllerTest.java @@ -42,9 +42,9 @@ import org.springframework.samples.petclinic.dto.OwnerDTO; import org.springframework.samples.petclinic.dto.PetDTO; import org.springframework.samples.petclinic.dto.PetTypeDTO; import org.springframework.samples.petclinic.formatter.PetTypeFormatter; -import org.springframework.samples.petclinic.service.OwnerService; -import org.springframework.samples.petclinic.service.PetService; -import org.springframework.samples.petclinic.service.PetTypeService; +import org.springframework.samples.petclinic.service.business.OwnerService; +import org.springframework.samples.petclinic.service.business.PetService; +import org.springframework.samples.petclinic.service.business.PetTypeService; import org.springframework.test.web.servlet.MockMvc; /** diff --git a/src/test/java/org/springframework/samples/petclinic/controller/VetControllerIntegrationTest.java b/src/test/java/org/springframework/samples/petclinic/controller/VetControllerIntegrationTest.java index 8e630a475..c4ce76a16 100644 --- a/src/test/java/org/springframework/samples/petclinic/controller/VetControllerIntegrationTest.java +++ b/src/test/java/org/springframework/samples/petclinic/controller/VetControllerIntegrationTest.java @@ -15,7 +15,7 @@ import org.springframework.samples.petclinic.common.CommonEndPoint; import org.springframework.samples.petclinic.common.CommonView; import org.springframework.samples.petclinic.dto.VetsDTO; import org.springframework.samples.petclinic.repository.VetRepository; -import org.springframework.samples.petclinic.service.VetService; +import org.springframework.samples.petclinic.service.business.VetService; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; diff --git a/src/test/java/org/springframework/samples/petclinic/controller/VetControllerTest.java b/src/test/java/org/springframework/samples/petclinic/controller/VetControllerTest.java index 79c1d6548..d24a7caa9 100644 --- a/src/test/java/org/springframework/samples/petclinic/controller/VetControllerTest.java +++ b/src/test/java/org/springframework/samples/petclinic/controller/VetControllerTest.java @@ -19,7 +19,6 @@ package org.springframework.samples.petclinic.controller; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -40,10 +39,9 @@ import org.springframework.samples.petclinic.common.CommonView; import org.springframework.samples.petclinic.dto.SpecialtyDTO; import org.springframework.samples.petclinic.dto.VetDTO; import org.springframework.samples.petclinic.dto.VetsDTO; -import org.springframework.samples.petclinic.service.VetService; +import org.springframework.samples.petclinic.service.business.VetService; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.ResultActions; import java.nio.charset.StandardCharsets; import java.util.List; diff --git a/src/test/java/org/springframework/samples/petclinic/controller/VisitControllerIntegrationTest.java b/src/test/java/org/springframework/samples/petclinic/controller/VisitControllerIntegrationTest.java index 0ed46b527..b5ad7fcaf 100644 --- a/src/test/java/org/springframework/samples/petclinic/controller/VisitControllerIntegrationTest.java +++ b/src/test/java/org/springframework/samples/petclinic/controller/VisitControllerIntegrationTest.java @@ -12,7 +12,7 @@ import org.springframework.samples.petclinic.dto.PetDTO; import org.springframework.samples.petclinic.dto.VisitDTO; import org.springframework.samples.petclinic.model.business.Visit; import org.springframework.samples.petclinic.repository.VisitRepository; -import org.springframework.samples.petclinic.service.PetService; +import org.springframework.samples.petclinic.service.business.PetService; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; diff --git a/src/test/java/org/springframework/samples/petclinic/controller/VisitControllerTest.java b/src/test/java/org/springframework/samples/petclinic/controller/VisitControllerTest.java index ca39cb720..e6d3dcab9 100644 --- a/src/test/java/org/springframework/samples/petclinic/controller/VisitControllerTest.java +++ b/src/test/java/org/springframework/samples/petclinic/controller/VisitControllerTest.java @@ -40,8 +40,8 @@ import org.springframework.samples.petclinic.dto.PetDTO; import org.springframework.samples.petclinic.dto.PetTypeDTO; import org.springframework.samples.petclinic.dto.VisitDTO; import org.springframework.samples.petclinic.model.business.Visit; -import org.springframework.samples.petclinic.service.PetService; -import org.springframework.samples.petclinic.service.VisitService; +import org.springframework.samples.petclinic.service.business.PetService; +import org.springframework.samples.petclinic.service.business.VisitService; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; diff --git a/src/test/java/org/springframework/samples/petclinic/formater/PetTypeDTOFormatterTest.java b/src/test/java/org/springframework/samples/petclinic/formater/PetTypeDTOFormatterTest.java index 7aa7f2d3c..80b06a4bb 100644 --- a/src/test/java/org/springframework/samples/petclinic/formater/PetTypeDTOFormatterTest.java +++ b/src/test/java/org/springframework/samples/petclinic/formater/PetTypeDTOFormatterTest.java @@ -25,7 +25,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.samples.petclinic.dto.PetTypeDTO; import org.springframework.samples.petclinic.formatter.PetTypeFormatter; import org.springframework.samples.petclinic.model.business.PetType; -import org.springframework.samples.petclinic.service.PetService; +import org.springframework.samples.petclinic.service.business.PetService; import java.text.ParseException; import java.util.ArrayList; diff --git a/src/test/java/org/springframework/samples/petclinic/formater/PetTypeFormatterTest.java b/src/test/java/org/springframework/samples/petclinic/formater/PetTypeFormatterTest.java index 7aecc89d8..f52be257a 100644 --- a/src/test/java/org/springframework/samples/petclinic/formater/PetTypeFormatterTest.java +++ b/src/test/java/org/springframework/samples/petclinic/formater/PetTypeFormatterTest.java @@ -31,7 +31,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.samples.petclinic.dto.PetTypeDTO; import org.springframework.samples.petclinic.formatter.PetTypeFormatter; import org.springframework.samples.petclinic.model.business.PetType; -import org.springframework.samples.petclinic.service.PetService; +import org.springframework.samples.petclinic.service.business.PetService; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; diff --git a/src/test/java/org/springframework/samples/petclinic/service/OwnerServiceTest.java b/src/test/java/org/springframework/samples/petclinic/service/OwnerServiceTest.java index f3c1dfbe1..a12b7a600 100644 --- a/src/test/java/org/springframework/samples/petclinic/service/OwnerServiceTest.java +++ b/src/test/java/org/springframework/samples/petclinic/service/OwnerServiceTest.java @@ -19,6 +19,9 @@ import org.springframework.samples.petclinic.repository.OwnerRepository; import org.springframework.samples.petclinic.repository.PetRepository; import org.springframework.samples.petclinic.repository.PetTypeRepository; import org.springframework.samples.petclinic.repository.VisitRepository; +import org.springframework.samples.petclinic.service.business.OwnerService; +import org.springframework.samples.petclinic.service.business.PetService; +import org.springframework.samples.petclinic.service.business.PetTypeService; import org.springframework.stereotype.Service; import java.time.LocalDate; diff --git a/src/test/java/org/springframework/samples/petclinic/service/PetServiceTest.java b/src/test/java/org/springframework/samples/petclinic/service/PetServiceTest.java index e599eca39..dfa9d981c 100644 --- a/src/test/java/org/springframework/samples/petclinic/service/PetServiceTest.java +++ b/src/test/java/org/springframework/samples/petclinic/service/PetServiceTest.java @@ -17,6 +17,9 @@ import org.springframework.samples.petclinic.model.business.PetType; import org.springframework.samples.petclinic.repository.PetRepository; import org.springframework.samples.petclinic.repository.PetTypeRepository; import org.springframework.samples.petclinic.repository.VisitRepository; +import org.springframework.samples.petclinic.service.business.OwnerService; +import org.springframework.samples.petclinic.service.business.PetService; +import org.springframework.samples.petclinic.service.business.PetTypeService; import org.springframework.stereotype.Service; import java.time.LocalDate; diff --git a/src/test/java/org/springframework/samples/petclinic/service/VetServiceTest.java b/src/test/java/org/springframework/samples/petclinic/service/VetServiceTest.java index cbe2c985f..135448577 100644 --- a/src/test/java/org/springframework/samples/petclinic/service/VetServiceTest.java +++ b/src/test/java/org/springframework/samples/petclinic/service/VetServiceTest.java @@ -12,6 +12,7 @@ import org.springframework.samples.petclinic.dto.VetDTO; import org.springframework.samples.petclinic.model.business.Vet; import org.springframework.samples.petclinic.repository.SpecialtyRepository; import org.springframework.samples.petclinic.repository.VetRepository; +import org.springframework.samples.petclinic.service.business.VetService; import org.springframework.stereotype.Service; import java.util.ArrayList;