diff --git a/build.gradle b/build.gradle index 68f7d88..be79400 100644 --- a/build.gradle +++ b/build.gradle @@ -105,6 +105,9 @@ dependencies { compile group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity5' compile group: 'com.fasterxml.jackson.module', name: 'jackson-module-afterburner' compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-hibernate5' + compile group: 'org.springframework.security', name: 'spring-security-oauth2-client' + compile group: 'org.springframework.security', name: 'spring-security-oauth2-jose' + compile group: 'org.springframework.security.oauth.boot', name: 'spring-security-oauth2-autoconfigure' compile group: 'org.postgresql', name: 'postgresql', version: '42.2.5' diff --git a/src/main/java/ru/ulstu/configuration/MvcConfiguration.java b/src/main/java/ru/ulstu/configuration/MvcConfiguration.java index b1c5806..7bca53d 100644 --- a/src/main/java/ru/ulstu/configuration/MvcConfiguration.java +++ b/src/main/java/ru/ulstu/configuration/MvcConfiguration.java @@ -9,7 +9,5 @@ public class MvcConfiguration implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addRedirectViewController("/", "/index.xhtml"); - registry.addRedirectViewController("/default", "/index.xhtml"); - } } diff --git a/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java b/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java index 25d244f..61a0a8c 100644 --- a/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java +++ b/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java @@ -5,27 +5,48 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import ru.ulstu.core.model.AuthFailureHandler; -import ru.ulstu.user.controller.UserController; import ru.ulstu.user.model.UserRoleConstants; import ru.ulstu.user.service.UserService; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class); + private static List clients = Arrays.asList("google"); + private static String CLIENT_PROPERTY_KEY + = "spring.security.oauth2.client.registration."; + + @Autowired + private Environment env; @Value("${server.http.port}") private int httpPort; @@ -54,6 +75,124 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { } @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf() + .disable(); + if (applicationProperties.isDevMode()) { + log.debug("Security disabled"); + http.authorizeRequests() + .anyRequest() + .permitAll(); + http.anonymous() + .principal("admin") + .authorities(UserRoleConstants.ADMIN); + } else { + http.authorizeRequests() + .antMatchers("/login.xhtml", "/logout") + .permitAll() + .anyRequest() + .authenticated() + .and() + .formLogin() + .loginPage("/login.xhtml") + .successHandler(authenticationSuccessHandler) + .failureHandler(authenticationFailureHandler) + .permitAll() + .and() + .oauth2Login() + .loginPage("/login.xhtml") + .authorizationEndpoint() + .baseUri("/oauth2/authorize-client") + .authorizationRequestRepository(authorizationRequestRepository()) + .and() + .tokenEndpoint() + .accessTokenResponseClient(accessTokenResponseClient()) + .and() + .defaultSuccessUrl("/index.xhtml") + .failureUrl("/loginFailure") + .and() + .logout() + .logoutSuccessHandler(logoutSuccessHandler) + .logoutSuccessUrl(Constants.LOGOUT_URL) + .invalidateHttpSession(true) + .clearAuthentication(true) + .deleteCookies(Constants.COOKIES_NAME) + .permitAll(); + http.csrf().disable(); + } + if (applicationProperties.isUseHttps()) { + http.portMapper() + .http(httpPort) + .mapsTo(httpsPort) + .and() + .requiresChannel() + .anyRequest() + .requiresSecure(); + } + } + + @Bean + public AuthorizationRequestRepository authorizationRequestRepository() { + return new HttpSessionOAuth2AuthorizationRequestRepository(); + } + + @Bean + public OAuth2AccessTokenResponseClient accessTokenResponseClient() { + DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient(); + return accessTokenResponseClient; + } + + //@Bean + public ClientRegistrationRepository clientRegistrationRepository() { + List registrations = clients.stream() + .map(c -> getRegistration(c)) + .filter(registration -> registration != null) + .collect(Collectors.toList()); + + return new InMemoryClientRegistrationRepository(registrations); + } + + private ClientRegistration getRegistration(String client) { + String clientId = env.getProperty(CLIENT_PROPERTY_KEY + client + ".client-id"); + + if (clientId == null) { + return null; + } + + String clientSecret = env.getProperty(CLIENT_PROPERTY_KEY + client + ".client-secret"); + if (client.equals("google")) { + return CommonOAuth2Provider.GOOGLE.getBuilder(client) + .clientId(clientId) + .clientSecret(clientSecret) + .build(); + } + return null; + } + + @Override + public void configure(WebSecurity web) { + web.ignoring() + .antMatchers("/css/**") + .antMatchers("/javax.faces.resource/**") + .antMatchers("/js/**") + .antMatchers("/templates/**") + .antMatchers("/webjars/**") + .antMatchers("/img/**"); + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) { + if (applicationProperties.isDevMode()) { + return; + } + try { + auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder); + } catch (Exception e) { + throw new BeanInitializationException("Security configuration failed", e); + } + } + + /* @Override protected void configure(HttpSecurity http) throws Exception { http.csrf() .disable(); @@ -126,5 +265,5 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { } catch (Exception e) { throw new BeanInitializationException("Security configuration failed", e); } - } + }*/ } diff --git a/src/main/java/ru/ulstu/core/controller/AdviceController.java b/src/main/java/ru/ulstu/core/controller/AdviceController.java deleted file mode 100644 index b474c6c..0000000 --- a/src/main/java/ru/ulstu/core/controller/AdviceController.java +++ /dev/null @@ -1,120 +0,0 @@ -package ru.ulstu.core.controller; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.validation.FieldError; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ModelAttribute; -import ru.ulstu.core.error.EntityIdIsNullException; -import ru.ulstu.core.model.ErrorConstants; -import ru.ulstu.core.model.response.Response; -import ru.ulstu.core.model.response.ResponseExtended; -import ru.ulstu.user.error.UserActivationError; -import ru.ulstu.user.error.UserEmailExistsException; -import ru.ulstu.user.error.UserIdExistsException; -import ru.ulstu.user.error.UserIsUndeadException; -import ru.ulstu.user.error.UserLoginExistsException; -import ru.ulstu.user.error.UserNotActivatedException; -import ru.ulstu.user.error.UserPasswordsNotValidOrNotMatchException; -import ru.ulstu.user.error.UserResetKeyError; -import ru.ulstu.user.error.UserSendingMailException; -import ru.ulstu.user.service.UserService; - -import java.util.Set; -import java.util.stream.Collectors; - -@ControllerAdvice -public class AdviceController { - private final Logger log = LoggerFactory.getLogger(AdviceController.class); - private final UserService userService; - - public AdviceController(UserService userService) { - this.userService = userService; - } - - @ModelAttribute("currentUser") - public String getCurrentUser() { - return userService.getCurrentUser().getUserAbbreviate(); - } - - @ModelAttribute("flashMessage") - public String getFlashMessage() { - return null; - } - - private Response handleException(ErrorConstants error) { - log.warn(error.toString()); - return new Response<>(error); - } - - private ResponseExtended handleException(ErrorConstants error, E errorData) { - log.warn(error.toString()); - return new ResponseExtended<>(error, errorData); - } - - @ExceptionHandler(EntityIdIsNullException.class) - public Response handleEntityIdIsNullException(Throwable e) { - return handleException(ErrorConstants.ID_IS_NULL); - } - - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseExtended> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { - final Set errors = e.getBindingResult().getAllErrors().stream() - .filter(error -> error instanceof FieldError) - .map(error -> ((FieldError) error).getField()) - .collect(Collectors.toSet()); - return handleException(ErrorConstants.VALIDATION_ERROR, errors); - } - - @ExceptionHandler(UserIdExistsException.class) - public Response handleUserIdExistsException(Throwable e) { - return handleException(ErrorConstants.USER_ID_EXISTS); - } - - @ExceptionHandler(UserActivationError.class) - public ResponseExtended handleUserActivationError(Throwable e) { - return handleException(ErrorConstants.USER_ACTIVATION_ERROR, e.getMessage()); - } - - @ExceptionHandler(UserLoginExistsException.class) - public ResponseExtended handleUserLoginExistsException(Throwable e) { - return handleException(ErrorConstants.USER_LOGIN_EXISTS, e.getMessage()); - } - - @ExceptionHandler(UserEmailExistsException.class) - public ResponseExtended handleUserEmailExistsException(Throwable e) { - return handleException(ErrorConstants.USER_EMAIL_EXISTS, e.getMessage()); - } - - @ExceptionHandler(UserPasswordsNotValidOrNotMatchException.class) - public Response handleUserPasswordsNotValidOrNotMatchException(Throwable e) { - return handleException(ErrorConstants.USER_PASSWORDS_NOT_VALID_OR_NOT_MATCH); - } - -// @ExceptionHandler(UserNotFoundException.class) -// public ResponseExtended handleUserNotFoundException(Throwable e) { -// return handleException(ErrorConstants.USER_NOT_FOUND, e.getMessage()); -// } - - @ExceptionHandler(UserNotActivatedException.class) - public Response handleUserNotActivatedException(Throwable e) { - return handleException(ErrorConstants.USER_NOT_ACTIVATED); - } - - @ExceptionHandler(UserResetKeyError.class) - public ResponseExtended handleUserResetKeyError(Throwable e) { - return handleException(ErrorConstants.USER_RESET_ERROR, e.getMessage()); - } - - @ExceptionHandler(UserIsUndeadException.class) - public ResponseExtended handleUserIsUndeadException(Throwable e) { - return handleException(ErrorConstants.USER_UNDEAD_ERROR, e.getMessage()); - } - - @ExceptionHandler(UserSendingMailException.class) - public ResponseExtended handleUserSendingMailException(Throwable e) { - return handleException(ErrorConstants.USER_SENDING_MAIL_EXCEPTION, e.getMessage()); - } -} diff --git a/src/main/java/ru/ulstu/index/controller/IndexController.java b/src/main/java/ru/ulstu/index/controller/IndexController.java deleted file mode 100644 index f6ab100..0000000 --- a/src/main/java/ru/ulstu/index/controller/IndexController.java +++ /dev/null @@ -1,23 +0,0 @@ -package ru.ulstu.index.controller; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.ModelMap; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import ru.ulstu.core.controller.AdviceController; -import ru.ulstu.user.service.UserService; -import springfox.documentation.annotations.ApiIgnore; - -@Controller() -@RequestMapping(value = "/index") -@ApiIgnore -public class IndexController extends AdviceController { - public IndexController(UserService userService) { - super(userService); - } - - @GetMapping - public void currentUser(ModelMap modelMap) { - //нужен здесь для добавления параметров на стартовой странице - } -} diff --git a/src/main/java/ru/ulstu/user/service/UserService.java b/src/main/java/ru/ulstu/user/service/UserService.java index 0f69c8e..0285ca0 100644 --- a/src/main/java/ru/ulstu/user/service/UserService.java +++ b/src/main/java/ru/ulstu/user/service/UserService.java @@ -1,435 +1,438 @@ -package ru.ulstu.user.service; - -import com.google.common.collect.ImmutableMap; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Lazy; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Sort; -import org.springframework.mail.MailException; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; -import ru.ulstu.conference.service.ConferenceService; -import ru.ulstu.configuration.ApplicationProperties; -import ru.ulstu.core.error.EntityIdIsNullException; -import ru.ulstu.core.jpa.OffsetablePageRequest; -import ru.ulstu.core.model.BaseEntity; -import ru.ulstu.core.model.UserActivity; -import ru.ulstu.core.model.response.PageableItems; -import ru.ulstu.ping.model.Ping; -import ru.ulstu.ping.service.PingService; -import ru.ulstu.user.error.UserActivationError; -import ru.ulstu.user.error.UserBlockedException; -import ru.ulstu.user.error.UserEmailExistsException; -import ru.ulstu.user.error.UserIdExistsException; -import ru.ulstu.user.error.UserIsUndeadException; -import ru.ulstu.user.error.UserLoginExistsException; -import ru.ulstu.user.error.UserNotActivatedException; -import ru.ulstu.user.error.UserNotFoundException; -import ru.ulstu.user.error.UserPasswordsNotValidOrNotMatchException; -import ru.ulstu.user.error.UserResetKeyError; -import ru.ulstu.user.error.UserSendingMailException; -import ru.ulstu.user.model.User; -import ru.ulstu.user.model.UserDto; -import ru.ulstu.user.model.UserInfoNow; -import ru.ulstu.user.model.UserListDto; -import ru.ulstu.user.model.UserResetPasswordDto; -import ru.ulstu.user.model.UserRole; -import ru.ulstu.user.model.UserRoleConstants; -import ru.ulstu.user.model.UserRoleDto; -import ru.ulstu.user.repository.UserRepository; -import ru.ulstu.user.repository.UserRoleRepository; -import ru.ulstu.user.util.UserUtils; -import ru.ulstu.utils.timetable.TimetableService; -import ru.ulstu.utils.timetable.errors.TimetableClientException; -import ru.ulstu.utils.timetable.model.Lesson; - -import javax.mail.MessagingException; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -@Service -@Transactional -public class UserService implements UserDetailsService { - private static final String INVITE_USER_EXCEPTION = "Во время отправки приглашения произошла ошибка"; - - private final Logger log = LoggerFactory.getLogger(UserService.class); - private final UserRepository userRepository; - private final PasswordEncoder passwordEncoder; - private final UserRoleRepository userRoleRepository; - private final UserMapper userMapper; - private final MailService mailService; - private final ApplicationProperties applicationProperties; - private final TimetableService timetableService; - private final ConferenceService conferenceService; - private final UserSessionService userSessionService; - private final PingService pingService; - - public UserService(UserRepository userRepository, - PasswordEncoder passwordEncoder, - UserRoleRepository userRoleRepository, - UserMapper userMapper, - MailService mailService, - ApplicationProperties applicationProperties, - @Lazy PingService pingService, - @Lazy ConferenceService conferenceRepository, - @Lazy UserSessionService userSessionService) throws ParseException { - this.userRepository = userRepository; - this.passwordEncoder = passwordEncoder; - this.userRoleRepository = userRoleRepository; - this.userMapper = userMapper; - this.mailService = mailService; - this.applicationProperties = applicationProperties; - this.conferenceService = conferenceRepository; - this.timetableService = new TimetableService(); - this.userSessionService = userSessionService; - this.pingService = pingService; - } - - private User getUserByEmail(String email) { - return userRepository.findOneByEmailIgnoreCase(email); - } - - private User getUserByActivationKey(String activationKey) { - return userRepository.findOneByActivationKey(activationKey); - } - - public User getUserByLogin(String login) { - return userRepository.findOneByLoginIgnoreCase(login); - } - - @Transactional(readOnly = true) - public UserDto getUserWithRolesById(Integer userId) { - final User userEntity = userRepository.findOneWithRolesById(userId); - if (userEntity == null) { - throw new UserNotFoundException(userId.toString()); - } - return userMapper.userEntityToUserDto(userEntity); - } - - @Transactional(readOnly = true) - public PageableItems getAllUsers(int offset, int count) { - final Page page = userRepository.findAll(new OffsetablePageRequest(offset, count, Sort.by("id"))); - return new PageableItems<>(page.getTotalElements(), userMapper.userEntitiesToUserListDtos(page.getContent())); - } - - // TODO: read only active users - public List findAll() { - return userRepository.findAll(); - } - - @Transactional(readOnly = true) - public PageableItems getUserRoles() { - final List roles = userRoleRepository.findAll().stream() - .map(UserRoleDto::new) - .sorted(Comparator.comparing(UserRoleDto::getViewValue)) - .collect(Collectors.toList()); - return new PageableItems<>(roles.size(), roles); - } - - public UserDto createUser(UserDto userDto) { - if (userDto.getId() != null) { - throw new UserIdExistsException(); - } - if (getUserByLogin(userDto.getLogin()) != null) { - throw new UserLoginExistsException(userDto.getLogin()); - } - if (getUserByEmail(userDto.getEmail()) != null) { - throw new UserEmailExistsException(userDto.getEmail()); - } - if (userDto.isPasswordsValid()) { - throw new UserPasswordsNotValidOrNotMatchException(""); - } - User user = userMapper.userDtoToUserEntity(userDto); - user.setActivated(false); - user.setActivationKey(UserUtils.generateActivationKey()); - user.setRoles(Collections.singleton(new UserRole(UserRoleConstants.USER))); - user.setPassword(passwordEncoder.encode(userDto.getPassword())); - user = userRepository.save(user); - mailService.sendActivationEmail(user); - log.debug("Created Information for User: {}", user.getLogin()); - return userMapper.userEntityToUserDto(user); - } - - public UserDto activateUser(String activationKey) { - final User user = getUserByActivationKey(activationKey); - if (user == null) { - throw new UserActivationError(activationKey); - } - user.setActivated(true); - user.setActivationKey(null); - user.setActivationDate(null); - log.debug("Activated user: {}", user.getLogin()); - return userMapper.userEntityToUserDto(userRepository.save(user)); - } - - public UserDto updateUser(UserDto userDto) { - if (userDto.getId() == null) { - throw new EntityIdIsNullException(); - } - if (!Objects.equals( - Optional.ofNullable(getUserByEmail(userDto.getEmail())) - .map(BaseEntity::getId).orElse(userDto.getId()), - userDto.getId())) { - throw new UserEmailExistsException(userDto.getEmail()); - } - if (!Objects.equals( - Optional.ofNullable(getUserByLogin(userDto.getLogin())) - .map(BaseEntity::getId).orElse(userDto.getId()), - userDto.getId())) { - throw new UserLoginExistsException(userDto.getLogin()); - } - User user = userRepository.getOne(userDto.getId()); - if (user == null) { - throw new UserNotFoundException(userDto.getId().toString()); - } - if (applicationProperties.getUndeadUserLogin().equalsIgnoreCase(user.getLogin())) { - userDto.setLogin(applicationProperties.getUndeadUserLogin()); - userDto.setActivated(true); - userDto.setRoles(Collections.singletonList(new UserRoleDto(UserRoleConstants.ADMIN))); - } - user.setLogin(userDto.getLogin()); - user.setFirstName(userDto.getFirstName()); - user.setLastName(userDto.getLastName()); - user.setEmail(userDto.getEmail()); - if (userDto.isActivated() != user.getActivated()) { - if (userDto.isActivated()) { - user.setActivationKey(null); - user.setActivationDate(null); - } else { - user.setActivationKey(UserUtils.generateActivationKey()); - user.setActivationDate(new Date()); - } - } - user.setActivated(userDto.isActivated()); - final Set roles = userMapper.rolesFromDto(userDto.getRoles()); - user.setRoles(roles.isEmpty() - ? Collections.singleton(new UserRole(UserRoleConstants.USER)) - : roles); - if (!StringUtils.isEmpty(userDto.getOldPassword())) { - if (userDto.isPasswordsValid() || !userDto.isOldPasswordValid()) { - throw new UserPasswordsNotValidOrNotMatchException(""); - } - if (!passwordEncoder.matches(userDto.getOldPassword(), user.getPassword())) { - throw new UserPasswordsNotValidOrNotMatchException(""); - } - user.setPassword(passwordEncoder.encode(userDto.getPassword())); - log.debug("Changed password for User: {}", user.getLogin()); - } - user = userRepository.save(user); - log.debug("Changed Information for User: {}", user.getLogin()); - return userMapper.userEntityToUserDto(user); - } - - public UserDto updateUserInformation(User user, UserDto updateUser) { - user.setFirstName(updateUser.getFirstName()); - user.setLastName(updateUser.getLastName()); - user.setEmail(updateUser.getEmail()); - user.setLogin(updateUser.getLogin()); - user = userRepository.save(user); - log.debug("Updated Information for User: {}", user.getLogin()); - return userMapper.userEntityToUserDto(user); - } - - public void changeUserPassword(User user, Map payload) { - if (!payload.get("password").equals(payload.get("confirmPassword"))) { - throw new UserPasswordsNotValidOrNotMatchException(""); - } - if (!passwordEncoder.matches(payload.get("oldPassword"), user.getPassword())) { - throw new UserPasswordsNotValidOrNotMatchException("Старый пароль введен неправильно"); - } - user.setPassword(passwordEncoder.encode(payload.get("password"))); - log.debug("Changed password for User: {}", user.getLogin()); - userRepository.save(user); - - mailService.sendChangePasswordMail(user); - } - - public boolean requestUserPasswordReset(String email) { - User user = userRepository.findOneByEmailIgnoreCase(email); - if (user == null) { - throw new UserNotFoundException(email); - } - if (!user.getActivated()) { - throw new UserNotActivatedException(); - } - user.setResetKey(UserUtils.generateResetKey()); - user.setResetDate(new Date()); - user = userRepository.save(user); - try { - mailService.sendPasswordResetMail(user); - } catch (MessagingException | MailException e) { - throw new UserSendingMailException(email); - } - log.debug("Created Reset Password Request for User: {}", user.getLogin()); - return true; - } - - public boolean completeUserPasswordReset(UserResetPasswordDto userResetPasswordDto) { - if (!userResetPasswordDto.isPasswordsValid()) { - throw new UserPasswordsNotValidOrNotMatchException("Пароли не совпадают"); - } - User user = userRepository.findOneByResetKey(userResetPasswordDto.getResetKey()); - if (user == null) { - throw new UserResetKeyError(userResetPasswordDto.getResetKey()); - } - user.setPassword(passwordEncoder.encode(userResetPasswordDto.getPassword())); - user.setResetKey(null); - user.setResetDate(null); - user = userRepository.save(user); - - mailService.sendChangePasswordMail(user); - - log.debug("Reset Password for User: {}", user.getLogin()); - return true; - } - - public UserDto deleteUser(Integer userId) { - final User user = userRepository.getOne(userId); - if (user == null) { - throw new UserNotFoundException(userId.toString()); - } - if (applicationProperties.getUndeadUserLogin().equalsIgnoreCase(user.getLogin())) { - throw new UserIsUndeadException(user.getLogin()); - } - userRepository.delete(user); - log.debug("Deleted User: {}", user.getLogin()); - return userMapper.userEntityToUserDto(user); - } - - @Override - public UserDetails loadUserByUsername(String username) { - final User user = userRepository.findOneByLoginIgnoreCase(username); - if (user == null) { - throw new UserNotFoundException(username); - } - if (!user.getActivated()) { - throw new UserNotActivatedException(); - } - if (user.getBlocker() != null) { - throw new UserBlockedException(String.format("Вы заблокированы пользователем %s", user.getBlocker().getUserAbbreviate())); - } - return new org.springframework.security.core.userdetails.User(user.getLogin(), - user.getPassword(), - Optional.ofNullable(user.getRoles()).orElse(Collections.emptySet()).stream() - .map(role -> new SimpleGrantedAuthority(role.getName())) - .collect(Collectors.toList())); - } - - public List findByIds(List ids) { - return userRepository.findAllById(ids); - } - - public User findById(Integer id) { - return userRepository.getOne(id); - } - - public User getCurrentUser() { - String login = UserUtils.getCurrentUserLogin(SecurityContextHolder.getContext()); - User user = userRepository.findOneByLoginIgnoreCase(login); - if (user == null) { - throw new UserNotFoundException(login); - } - return user; - } - - public List filterByAgeAndDegree(boolean hasDegree, boolean hasAge) { - return userRepository.filterByAgeAndDegree(hasDegree, hasAge); - } - - public void inviteUser(String email) throws UserSendingMailException { - if (userRepository.findOneByEmailIgnoreCase(email) != null) { - throw new UserEmailExistsException(email); - } - - String password = UserUtils.generatePassword(); - - User user = new User(); - user.setPassword(passwordEncoder.encode(password)); - user.setLogin(email); - user.setEmail(email); - user.setFirstName("user"); - user.setLastName("user"); - user.setActivated(true); - userRepository.save(user); - - Map variables = ImmutableMap.of("password", password, "email", email); - try { - mailService.sendInviteMail(variables, email); - } catch (MessagingException | MailException e) { - throw new UserSendingMailException(email); - } - } - - public User findOneByLoginIgnoreCase(String login) { - return userRepository.findOneByLoginIgnoreCase(login); - } - - public Map getUsersInfo() { - List usersInfoNow = new ArrayList<>(); - String err = ""; - - for (User user : userRepository.findAll()) { - Lesson lesson = null; - try { - lesson = timetableService.getCurrentLesson(user.getUserAbbreviate()); - } catch (TimetableClientException e) { - err = "Не удалось загрузить расписание"; - } - usersInfoNow.add(new UserInfoNow( - lesson, - conferenceService.getActiveConferenceByUser(user), - user, - userSessionService.isOnline(user)) - ); - } - return ImmutableMap.of("users", usersInfoNow, "error", err); - } - - public Map getActivitiesPings(Integer userId, - String activityName) { - User user = null; - if (userId != null) { - user = findById(userId); - } - Map activitiesPings = new HashMap<>(); - - for (Ping ping : pingService.getPings(activityName)) { - UserActivity activity = ping.getActivity(); - - if (user != null && !activity.getActivityUsers().contains(user)) { - continue; - } - - if (activitiesPings.containsKey(activity.getTitle())) { - activitiesPings.put(activity.getTitle(), activitiesPings.get(activity.getTitle()) + 1); - } else { - activitiesPings.put(activity.getTitle(), 1); - } - - } - return activitiesPings; - } - - public void blockUser(int userId) { - User userToBlock = findById(userId); - userToBlock.setBlocker(getCurrentUser()); - userRepository.save(userToBlock); - } -} +package ru.ulstu.user.service; + +import com.google.common.collect.ImmutableMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Sort; +import org.springframework.mail.MailException; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; +import ru.ulstu.conference.service.ConferenceService; +import ru.ulstu.configuration.ApplicationProperties; +import ru.ulstu.core.error.EntityIdIsNullException; +import ru.ulstu.core.jpa.OffsetablePageRequest; +import ru.ulstu.core.model.BaseEntity; +import ru.ulstu.core.model.UserActivity; +import ru.ulstu.core.model.response.PageableItems; +import ru.ulstu.ping.model.Ping; +import ru.ulstu.ping.service.PingService; +import ru.ulstu.user.error.UserActivationError; +import ru.ulstu.user.error.UserBlockedException; +import ru.ulstu.user.error.UserEmailExistsException; +import ru.ulstu.user.error.UserIdExistsException; +import ru.ulstu.user.error.UserIsUndeadException; +import ru.ulstu.user.error.UserLoginExistsException; +import ru.ulstu.user.error.UserNotActivatedException; +import ru.ulstu.user.error.UserNotFoundException; +import ru.ulstu.user.error.UserPasswordsNotValidOrNotMatchException; +import ru.ulstu.user.error.UserResetKeyError; +import ru.ulstu.user.error.UserSendingMailException; +import ru.ulstu.user.model.User; +import ru.ulstu.user.model.UserDto; +import ru.ulstu.user.model.UserInfoNow; +import ru.ulstu.user.model.UserListDto; +import ru.ulstu.user.model.UserResetPasswordDto; +import ru.ulstu.user.model.UserRole; +import ru.ulstu.user.model.UserRoleConstants; +import ru.ulstu.user.model.UserRoleDto; +import ru.ulstu.user.repository.UserRepository; +import ru.ulstu.user.repository.UserRoleRepository; +import ru.ulstu.user.util.UserUtils; +import ru.ulstu.utils.timetable.TimetableService; +import ru.ulstu.utils.timetable.errors.TimetableClientException; +import ru.ulstu.utils.timetable.model.Lesson; + +import javax.mail.MessagingException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@Transactional +public class UserService implements UserDetailsService { + private static final String INVITE_USER_EXCEPTION = "Во время отправки приглашения произошла ошибка"; + + private final Logger log = LoggerFactory.getLogger(UserService.class); + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + private final UserRoleRepository userRoleRepository; + private final UserMapper userMapper; + private final MailService mailService; + private final ApplicationProperties applicationProperties; + private final TimetableService timetableService; + private final ConferenceService conferenceService; + private final UserSessionService userSessionService; + private final PingService pingService; + + public UserService(UserRepository userRepository, + PasswordEncoder passwordEncoder, + UserRoleRepository userRoleRepository, + UserMapper userMapper, + MailService mailService, + ApplicationProperties applicationProperties, + @Lazy PingService pingService, + @Lazy ConferenceService conferenceRepository, + @Lazy UserSessionService userSessionService) throws ParseException { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + this.userRoleRepository = userRoleRepository; + this.userMapper = userMapper; + this.mailService = mailService; + this.applicationProperties = applicationProperties; + this.conferenceService = conferenceRepository; + this.timetableService = new TimetableService(); + this.userSessionService = userSessionService; + this.pingService = pingService; + } + + private User getUserByEmail(String email) { + return userRepository.findOneByEmailIgnoreCase(email); + } + + private User getUserByActivationKey(String activationKey) { + return userRepository.findOneByActivationKey(activationKey); + } + + public User getUserByLogin(String login) { + return userRepository.findOneByLoginIgnoreCase(login); + } + + @Transactional(readOnly = true) + public UserDto getUserWithRolesById(Integer userId) { + final User userEntity = userRepository.findOneWithRolesById(userId); + if (userEntity == null) { + throw new UserNotFoundException(userId.toString()); + } + return userMapper.userEntityToUserDto(userEntity); + } + + @Transactional(readOnly = true) + public PageableItems getAllUsers(int offset, int count) { + final Page page = userRepository.findAll(new OffsetablePageRequest(offset, count, Sort.by("id"))); + return new PageableItems<>(page.getTotalElements(), userMapper.userEntitiesToUserListDtos(page.getContent())); + } + + // TODO: read only active users + public List findAll() { + return userRepository.findAll(); + } + + @Transactional(readOnly = true) + public PageableItems getUserRoles() { + final List roles = userRoleRepository.findAll().stream() + .map(UserRoleDto::new) + .sorted(Comparator.comparing(UserRoleDto::getViewValue)) + .collect(Collectors.toList()); + return new PageableItems<>(roles.size(), roles); + } + + public UserDto createUser(UserDto userDto) { + if (userDto.getId() != null) { + throw new UserIdExistsException(); + } + if (getUserByLogin(userDto.getLogin()) != null) { + throw new UserLoginExistsException(userDto.getLogin()); + } + if (getUserByEmail(userDto.getEmail()) != null) { + throw new UserEmailExistsException(userDto.getEmail()); + } + if (userDto.isPasswordsValid()) { + throw new UserPasswordsNotValidOrNotMatchException(""); + } + User user = userMapper.userDtoToUserEntity(userDto); + user.setActivated(false); + user.setActivationKey(UserUtils.generateActivationKey()); + user.setRoles(Collections.singleton(new UserRole(UserRoleConstants.USER))); + user.setPassword(passwordEncoder.encode(userDto.getPassword())); + user = userRepository.save(user); + mailService.sendActivationEmail(user); + log.debug("Created Information for User: {}", user.getLogin()); + return userMapper.userEntityToUserDto(user); + } + + public UserDto activateUser(String activationKey) { + final User user = getUserByActivationKey(activationKey); + if (user == null) { + throw new UserActivationError(activationKey); + } + user.setActivated(true); + user.setActivationKey(null); + user.setActivationDate(null); + log.debug("Activated user: {}", user.getLogin()); + return userMapper.userEntityToUserDto(userRepository.save(user)); + } + + public UserDto updateUser(UserDto userDto) { + if (userDto.getId() == null) { + throw new EntityIdIsNullException(); + } + if (!Objects.equals( + Optional.ofNullable(getUserByEmail(userDto.getEmail())) + .map(BaseEntity::getId).orElse(userDto.getId()), + userDto.getId())) { + throw new UserEmailExistsException(userDto.getEmail()); + } + if (!Objects.equals( + Optional.ofNullable(getUserByLogin(userDto.getLogin())) + .map(BaseEntity::getId).orElse(userDto.getId()), + userDto.getId())) { + throw new UserLoginExistsException(userDto.getLogin()); + } + User user = userRepository.getOne(userDto.getId()); + if (user == null) { + throw new UserNotFoundException(userDto.getId().toString()); + } + if (applicationProperties.getUndeadUserLogin().equalsIgnoreCase(user.getLogin())) { + userDto.setLogin(applicationProperties.getUndeadUserLogin()); + userDto.setActivated(true); + userDto.setRoles(Collections.singletonList(new UserRoleDto(UserRoleConstants.ADMIN))); + } + user.setLogin(userDto.getLogin()); + user.setFirstName(userDto.getFirstName()); + user.setLastName(userDto.getLastName()); + user.setEmail(userDto.getEmail()); + if (userDto.isActivated() != user.getActivated()) { + if (userDto.isActivated()) { + user.setActivationKey(null); + user.setActivationDate(null); + } else { + user.setActivationKey(UserUtils.generateActivationKey()); + user.setActivationDate(new Date()); + } + } + user.setActivated(userDto.isActivated()); + final Set roles = userMapper.rolesFromDto(userDto.getRoles()); + user.setRoles(roles.isEmpty() + ? Collections.singleton(new UserRole(UserRoleConstants.USER)) + : roles); + if (!StringUtils.isEmpty(userDto.getOldPassword())) { + if (userDto.isPasswordsValid() || !userDto.isOldPasswordValid()) { + throw new UserPasswordsNotValidOrNotMatchException(""); + } + if (!passwordEncoder.matches(userDto.getOldPassword(), user.getPassword())) { + throw new UserPasswordsNotValidOrNotMatchException(""); + } + user.setPassword(passwordEncoder.encode(userDto.getPassword())); + log.debug("Changed password for User: {}", user.getLogin()); + } + user = userRepository.save(user); + log.debug("Changed Information for User: {}", user.getLogin()); + return userMapper.userEntityToUserDto(user); + } + + public UserDto updateUserInformation(User user, UserDto updateUser) { + user.setFirstName(updateUser.getFirstName()); + user.setLastName(updateUser.getLastName()); + user.setEmail(updateUser.getEmail()); + user.setLogin(updateUser.getLogin()); + user = userRepository.save(user); + log.debug("Updated Information for User: {}", user.getLogin()); + return userMapper.userEntityToUserDto(user); + } + + public void changeUserPassword(User user, Map payload) { + if (!payload.get("password").equals(payload.get("confirmPassword"))) { + throw new UserPasswordsNotValidOrNotMatchException(""); + } + if (!passwordEncoder.matches(payload.get("oldPassword"), user.getPassword())) { + throw new UserPasswordsNotValidOrNotMatchException("Старый пароль введен неправильно"); + } + user.setPassword(passwordEncoder.encode(payload.get("password"))); + log.debug("Changed password for User: {}", user.getLogin()); + userRepository.save(user); + + mailService.sendChangePasswordMail(user); + } + + public boolean requestUserPasswordReset(String email) { + User user = userRepository.findOneByEmailIgnoreCase(email); + if (user == null) { + throw new UserNotFoundException(email); + } + if (!user.getActivated()) { + throw new UserNotActivatedException(); + } + user.setResetKey(UserUtils.generateResetKey()); + user.setResetDate(new Date()); + user = userRepository.save(user); + try { + mailService.sendPasswordResetMail(user); + } catch (MessagingException | MailException e) { + throw new UserSendingMailException(email); + } + log.debug("Created Reset Password Request for User: {}", user.getLogin()); + return true; + } + + public boolean completeUserPasswordReset(UserResetPasswordDto userResetPasswordDto) { + if (!userResetPasswordDto.isPasswordsValid()) { + throw new UserPasswordsNotValidOrNotMatchException("Пароли не совпадают"); + } + User user = userRepository.findOneByResetKey(userResetPasswordDto.getResetKey()); + if (user == null) { + throw new UserResetKeyError(userResetPasswordDto.getResetKey()); + } + user.setPassword(passwordEncoder.encode(userResetPasswordDto.getPassword())); + user.setResetKey(null); + user.setResetDate(null); + user = userRepository.save(user); + + mailService.sendChangePasswordMail(user); + + log.debug("Reset Password for User: {}", user.getLogin()); + return true; + } + + public UserDto deleteUser(Integer userId) { + final User user = userRepository.getOne(userId); + if (user == null) { + throw new UserNotFoundException(userId.toString()); + } + if (applicationProperties.getUndeadUserLogin().equalsIgnoreCase(user.getLogin())) { + throw new UserIsUndeadException(user.getLogin()); + } + userRepository.delete(user); + log.debug("Deleted User: {}", user.getLogin()); + return userMapper.userEntityToUserDto(user); + } + + @Override + public UserDetails loadUserByUsername(String username) { + final User user = userRepository.findOneByLoginIgnoreCase(username); + if (user == null) { + throw new UserNotFoundException(username); + } + if (!user.getActivated()) { + throw new UserNotActivatedException(); + } + if (user.getBlocker() != null) { + throw new UserBlockedException(String.format("Вы заблокированы пользователем %s", user.getBlocker().getUserAbbreviate())); + } + return new org.springframework.security.core.userdetails.User(user.getLogin(), + user.getPassword(), + Optional.ofNullable(user.getRoles()).orElse(Collections.emptySet()).stream() + .map(role -> new SimpleGrantedAuthority(role.getName())) + .collect(Collectors.toList())); + } + + public List findByIds(List ids) { + return userRepository.findAllById(ids); + } + + public User findById(Integer id) { + return userRepository.getOne(id); + } + + public User getCurrentUser() { + String loginOrEmail = UserUtils.getCurrentUserLoginOrEmail(SecurityContextHolder.getContext()); + User user = userRepository.findOneByLoginIgnoreCase(loginOrEmail); + if (user == null) { + user = userRepository.findOneByEmailIgnoreCase(loginOrEmail); + } + if (user == null) { + throw new UserNotFoundException(loginOrEmail); + } + return user; + } + + public List filterByAgeAndDegree(boolean hasDegree, boolean hasAge) { + return userRepository.filterByAgeAndDegree(hasDegree, hasAge); + } + + public void inviteUser(String email) throws UserSendingMailException { + if (userRepository.findOneByEmailIgnoreCase(email) != null) { + throw new UserEmailExistsException(email); + } + + String password = UserUtils.generatePassword(); + + User user = new User(); + user.setPassword(passwordEncoder.encode(password)); + user.setLogin(email); + user.setEmail(email); + user.setFirstName("user"); + user.setLastName("user"); + user.setActivated(true); + userRepository.save(user); + + Map variables = ImmutableMap.of("password", password, "email", email); + try { + mailService.sendInviteMail(variables, email); + } catch (MessagingException | MailException e) { + throw new UserSendingMailException(email); + } + } + + public User findOneByLoginIgnoreCase(String login) { + return userRepository.findOneByLoginIgnoreCase(login); + } + + public Map getUsersInfo() { + List usersInfoNow = new ArrayList<>(); + String err = ""; + + for (User user : userRepository.findAll()) { + Lesson lesson = null; + try { + lesson = timetableService.getCurrentLesson(user.getUserAbbreviate()); + } catch (TimetableClientException e) { + err = "Не удалось загрузить расписание"; + } + usersInfoNow.add(new UserInfoNow( + lesson, + conferenceService.getActiveConferenceByUser(user), + user, + userSessionService.isOnline(user)) + ); + } + return ImmutableMap.of("users", usersInfoNow, "error", err); + } + + public Map getActivitiesPings(Integer userId, + String activityName) { + User user = null; + if (userId != null) { + user = findById(userId); + } + Map activitiesPings = new HashMap<>(); + + for (Ping ping : pingService.getPings(activityName)) { + UserActivity activity = ping.getActivity(); + + if (user != null && !activity.getActivityUsers().contains(user)) { + continue; + } + + if (activitiesPings.containsKey(activity.getTitle())) { + activitiesPings.put(activity.getTitle(), activitiesPings.get(activity.getTitle()) + 1); + } else { + activitiesPings.put(activity.getTitle(), 1); + } + + } + return activitiesPings; + } + + public void blockUser(int userId) { + User userToBlock = findById(userId); + userToBlock.setBlocker(getCurrentUser()); + userRepository.save(userToBlock); + } +} diff --git a/src/main/java/ru/ulstu/user/util/UserUtils.java b/src/main/java/ru/ulstu/user/util/UserUtils.java index 090240b..6f51633 100644 --- a/src/main/java/ru/ulstu/user/util/UserUtils.java +++ b/src/main/java/ru/ulstu/user/util/UserUtils.java @@ -4,6 +4,7 @@ import org.apache.commons.lang3.RandomStringUtils; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; import ru.ulstu.configuration.Constants; public class UserUtils { @@ -17,7 +18,7 @@ public class UserUtils { return RandomStringUtils.randomNumeric(DEF_COUNT); } - public static String getCurrentUserLogin(SecurityContext securityContext) { + public static String getCurrentUserLoginOrEmail(SecurityContext securityContext) { if (securityContext == null) { return null; } @@ -27,6 +28,10 @@ public class UserUtils { final UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal(); return springSecurityUser.getUsername(); } + if (authentication.getPrincipal() instanceof DefaultOidcUser) { + final DefaultOidcUser oauth2User = (DefaultOidcUser) authentication.getPrincipal(); + return oauth2User.getEmail(); + } if (authentication.getPrincipal() instanceof String) { return (String) authentication.getPrincipal(); } diff --git a/src/main/resources/META-INF/resources/basicTemplate.xhtml b/src/main/resources/META-INF/resources/basicTemplate.xhtml index fb6778a..0e92956 100644 --- a/src/main/resources/META-INF/resources/basicTemplate.xhtml +++ b/src/main/resources/META-INF/resources/basicTemplate.xhtml @@ -38,7 +38,7 @@ - +
diff --git a/src/main/resources/META-INF/resources/login.xhtml b/src/main/resources/META-INF/resources/login.xhtml index 446f5c9..2cc141b 100644 --- a/src/main/resources/META-INF/resources/login.xhtml +++ b/src/main/resources/META-INF/resources/login.xhtml @@ -45,6 +45,8 @@ + Войти с учетной записью Google diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ce9b208..1e8d7a3 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -45,3 +45,5 @@ ng-tracker.debug_email=romanov73@gmail.com ng-tracker.use-https=false ng-tracker.check-run=false ng-tracker.driver-path= +spring.security.oauth2.client.registration.google.client-id=128435862215-4ltm7dr6enb6sfll0qhkt6a1op9juve6.apps.googleusercontent.com +spring.security.oauth2.client.registration.google.client-secret=W_B-KMpqsSdKahqFxpuK4OoT diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html index 48e325e..1478197 100644 --- a/src/main/resources/templates/login.html +++ b/src/main/resources/templates/login.html @@ -34,10 +34,14 @@
+

Login with:

+

+ Client +

- \ No newline at end of file + diff --git a/src/main/resources/templates/oauth_login.html b/src/main/resources/templates/oauth_login.html new file mode 100644 index 0000000..6e77ab3 --- /dev/null +++ b/src/main/resources/templates/oauth_login.html @@ -0,0 +1,22 @@ + + + + + Oauth2 Login + + + + +
+
+

Login with:

+
+

+ Client +

+
+
+
+ +