diff --git a/src/main/java/de/thm/arsnova/controller/AuthenticationController.java b/src/main/java/de/thm/arsnova/controller/AuthenticationController.java index 7c7df3663b54acfed83a8525134117b2375604e5..7dead0ff264aa83cea65e9f328e1cb3c89f9d57b 100644 --- a/src/main/java/de/thm/arsnova/controller/AuthenticationController.java +++ b/src/main/java/de/thm/arsnova/controller/AuthenticationController.java @@ -18,6 +18,7 @@ package de.thm.arsnova.controller; +import javax.servlet.http.HttpServletRequest; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -44,22 +45,23 @@ public class AuthenticationController { } @PostMapping("/login/registered") - public ClientAuthentication loginRegistered(@RequestBody final LoginCredentials loginCredentials) { + public ClientAuthentication loginRegistered(@RequestBody final LoginCredentials loginCredentials, + final HttpServletRequest request) { final String loginId = loginCredentials.getLoginId().toLowerCase(); userService.authenticate(new UsernamePasswordAuthenticationToken(loginId, loginCredentials.getPassword()), - UserProfile.AuthProvider.ARSNOVA); + UserProfile.AuthProvider.ARSNOVA, request.getRemoteAddr()); return userService.getCurrentClientAuthentication(); } @PostMapping("/login/guest") - public ClientAuthentication loginGuest() { + public ClientAuthentication loginGuest(final HttpServletRequest request) { final ClientAuthentication currentAuthentication = userService.getCurrentClientAuthentication(); if (currentAuthentication != null && currentAuthentication.getAuthProvider() == UserProfile.AuthProvider.ARSNOVA_GUEST) { return currentAuthentication; } userService.authenticate(new UsernamePasswordAuthenticationToken(null, null), - UserProfile.AuthProvider.ARSNOVA_GUEST); + UserProfile.AuthProvider.ARSNOVA_GUEST, request.getRemoteAddr()); return userService.getCurrentClientAuthentication(); } diff --git a/src/main/java/de/thm/arsnova/controller/UserController.java b/src/main/java/de/thm/arsnova/controller/UserController.java index abc74d0ce2ad0b03cdd998fb87d241559b1c79d6..f2e19777b1cfdd6bd4eb108b13a785b98d444b3d 100644 --- a/src/main/java/de/thm/arsnova/controller/UserController.java +++ b/src/main/java/de/thm/arsnova/controller/UserController.java @@ -19,6 +19,7 @@ package de.thm.arsnova.controller; import com.fasterxml.jackson.annotation.JsonView; +import javax.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -104,13 +105,11 @@ public class UserController extends AbstractEntityController<UserProfile> { @PostMapping(ACTIVATE_MAPPING) public void activate( @PathVariable final String id, - @RequestParam final String key) { - final UserProfile userProfile = userService.get(id, true); - if (userProfile == null || !key.equals(userProfile.getAccount().getActivationKey())) { + @RequestParam final String key, + final HttpServletRequest request) { + if (!userService.activateAccount(id, key, request.getRemoteAddr())) { throw new BadRequestException(); } - userProfile.getAccount().setActivationKey(null); - userService.update(userProfile); } @PostMapping(RESET_PASSWORD_MAPPING) diff --git a/src/main/java/de/thm/arsnova/controller/v2/AuthenticationController.java b/src/main/java/de/thm/arsnova/controller/v2/AuthenticationController.java index fd52193a804f77260d518bfec4cf578e7d5ca130..75e64453cbc64c3592186b08d30f7d0d471bd003 100644 --- a/src/main/java/de/thm/arsnova/controller/v2/AuthenticationController.java +++ b/src/main/java/de/thm/arsnova/controller/v2/AuthenticationController.java @@ -133,8 +133,8 @@ public class AuthenticationController extends AbstractController { final HttpServletRequest request, final HttpServletResponse response ) throws IOException { - final String addr = request.getRemoteAddr(); - if (userService.isBannedFromLogin(addr)) { + final String address = request.getRemoteAddr(); + if (userService.isBannedFromLogin(address)) { response.sendError(429, "Too Many Requests"); return; @@ -144,26 +144,23 @@ public class AuthenticationController extends AbstractController { if (registeredProperties.isEnabled() && "arsnova".equals(type)) { try { - userService.authenticate(authRequest, UserProfile.AuthProvider.ARSNOVA); + userService.authenticate(authRequest, UserProfile.AuthProvider.ARSNOVA, address); } catch (final AuthenticationException e) { logger.info("Database authentication failed.", e); - userService.increaseFailedLoginCount(addr); response.setStatus(HttpStatus.UNAUTHORIZED.value()); } } else if (ldapProperties.stream().anyMatch(p -> p.isEnabled()) && "ldap".equals(type)) { try { - userService.authenticate(authRequest, UserProfile.AuthProvider.LDAP); + userService.authenticate(authRequest, UserProfile.AuthProvider.LDAP, address); } catch (final AuthenticationException e) { logger.info("LDAP authentication failed.", e); - userService.increaseFailedLoginCount(addr); response.setStatus(HttpStatus.UNAUTHORIZED.value()); } } else if (guestProperties.isEnabled() && "guest".equals(type)) { try { - userService.authenticate(authRequest, UserProfile.AuthProvider.ARSNOVA_GUEST); + userService.authenticate(authRequest, UserProfile.AuthProvider.ARSNOVA_GUEST, address); } catch (final AuthenticationException e) { logger.debug("Guest authentication failed.", e); - userService.increaseFailedLoginCount(addr); response.setStatus(HttpStatus.UNAUTHORIZED.value()); } } else { diff --git a/src/main/java/de/thm/arsnova/controller/v2/UserController.java b/src/main/java/de/thm/arsnova/controller/v2/UserController.java index aec8a783b77155788c050abaf18b72c4194ba75a..b1836860dbdc79f649886a0872ed21fc9cf32eb5 100644 --- a/src/main/java/de/thm/arsnova/controller/v2/UserController.java +++ b/src/main/java/de/thm/arsnova/controller/v2/UserController.java @@ -61,17 +61,13 @@ public class UserController extends AbstractController { @PostMapping(value = "/{username}/activate") public void activate( @PathVariable final String username, - @RequestParam final String key, final HttpServletRequest request, + @RequestParam final String key, + final HttpServletRequest request, final HttpServletResponse response) { final UserProfile userProfile = userService.getByUsername(username); - if (null != userProfile && key.equals(userProfile.getAccount().getActivationKey())) { - userProfile.getAccount().setActivationKey(null); - userService.update(userProfile); - - return; + if (userProfile == null || !userService.activateAccount(userProfile.getId(), key, request.getRemoteAddr())) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); } - - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); } @DeleteMapping(value = "/{username}/") diff --git a/src/main/java/de/thm/arsnova/service/UserService.java b/src/main/java/de/thm/arsnova/service/UserService.java index fd09780c5488ca63640b22852e3ff3978e650860..bbfd163811c4f2dce21f2fe3f21cc035c0d97e58 100644 --- a/src/main/java/de/thm/arsnova/service/UserService.java +++ b/src/main/java/de/thm/arsnova/service/UserService.java @@ -69,7 +69,8 @@ public interface UserService extends EntityService<UserProfile> { int loggedInUsers(); - void authenticate(UsernamePasswordAuthenticationToken token, UserProfile.AuthProvider authProvider); + void authenticate(UsernamePasswordAuthenticationToken token, UserProfile.AuthProvider authProvider, + String clientAddress); User loadUser(UserProfile.AuthProvider authProvider, String loginId, Collection<GrantedAuthority> grantedAuthorities, boolean autoCreate) throws UsernameNotFoundException; @@ -90,6 +91,8 @@ public interface UserService extends EntityService<UserProfile> { void addRoomToHistory(UserProfile userProfile, Room room); + boolean activateAccount(String id, String key, String clientAddress); + void initiatePasswordReset(String username); boolean resetPassword(UserProfile userProfile, String key, String password); diff --git a/src/main/java/de/thm/arsnova/service/UserServiceImpl.java b/src/main/java/de/thm/arsnova/service/UserServiceImpl.java index 10802621170b17221dd445f9933941877c717dd9..2a93a39bf406966a400a7fd74656b2674f6d36fb 100644 --- a/src/main/java/de/thm/arsnova/service/UserServiceImpl.java +++ b/src/main/java/de/thm/arsnova/service/UserServiceImpl.java @@ -328,7 +328,11 @@ public class UserServiceImpl extends DefaultEntityServiceImpl<UserProfile> imple @Override public void authenticate(final UsernamePasswordAuthenticationToken token, - final UserProfile.AuthProvider authProvider) { + final UserProfile.AuthProvider authProvider, final String clientAddress) { + if (isBannedFromLogin(clientAddress)) { + throw new BadRequestException(); + } + final Authentication auth; switch (authProvider) { case LDAP: @@ -357,6 +361,7 @@ public class UserServiceImpl extends DefaultEntityServiceImpl<UserProfile> imple } if (!auth.isAuthenticated()) { + increaseFailedLoginCount(clientAddress); throw new BadRequestException(); } SecurityContextHolder.getContext().setAuthentication(auth); @@ -438,7 +443,7 @@ public class UserServiceImpl extends DefaultEntityServiceImpl<UserProfile> imple userProfile.setAuthProvider(UserProfile.AuthProvider.ARSNOVA); userProfile.setLoginId(lcUsername); account.setPassword(encodePassword(password)); - account.setActivationKey(RandomStringUtils.randomAlphanumeric(32)); + account.setActivationKey(RandomStringUtils.randomAlphanumeric(8)); userProfile.setCreationTimestamp(new Date()); /* Repository is accessed directly without EntityService to skip permission check */ @@ -462,15 +467,9 @@ public class UserServiceImpl extends DefaultEntityServiceImpl<UserProfile> imple private void sendActivationEmail(final UserProfile userProfile) { final String activationKey = userProfile.getAccount().getActivationKey(); - final String activationUrl = MessageFormat.format( - "{0}{1}/login?action=activate&username={3}&key={4}", - rootUrl, - customizationPath, - UriUtils.encodeQueryParam(userProfile.getLoginId(), "UTF-8"), - activationKey); sendEmail(userProfile, registeredProperties.getRegistrationMailSubject(), - MessageFormat.format(registeredProperties.getRegistrationMailBody(), activationUrl, activationKey)); + MessageFormat.format(registeredProperties.getRegistrationMailBody(), activationKey, rootUrl)); } private void parseMailAddressPattern() { @@ -544,6 +543,22 @@ public class UserServiceImpl extends DefaultEntityServiceImpl<UserProfile> imple } } + public boolean activateAccount(final String id, final String key, final String clientAddress) { + if (isBannedFromLogin(clientAddress)) { + return false; + } + final UserProfile userProfile = get(id, true); + if (userProfile == null || !key.equals(userProfile.getAccount().getActivationKey())) { + increaseFailedLoginCount(clientAddress); + return false; + } + + userProfile.getAccount().setActivationKey(null); + update(userProfile); + + return true; + } + @Override public void initiatePasswordReset(final String username) { final UserProfile userProfile = getByUsername(username); @@ -563,27 +578,15 @@ public class UserServiceImpl extends DefaultEntityServiceImpl<UserProfile> imple throw new BadRequestException(); } - account.setPasswordResetKey(RandomStringUtils.randomAlphanumeric(32)); + account.setPasswordResetKey(RandomStringUtils.randomAlphanumeric(8)); account.setPasswordResetTime(new Date()); if (null == userRepository.save(userProfile)) { logger.error("Password reset failed. {} could not be updated.", username); } - final String resetPasswordUrl = MessageFormat.format( - "{0}{1}/login?action=resetpassword&username={3}&key={4}", - rootUrl, - customizationPath, - UriUtils.encodeQueryParam(userProfile.getLoginId(), "UTF-8"), account.getPasswordResetKey()); - - final String mailBody = MessageFormat.format( - registeredProperties.getResetPasswordMailBody(), - resetPasswordUrl, - account.getPasswordResetKey() - ); - - sendEmail(userProfile, registeredProperties.getResetPasswordMailSubject(), - MessageFormat.format(mailBody, resetPasswordUrl)); + sendEmail(userProfile, registeredProperties.getResetPasswordMailSubject(), MessageFormat.format( + registeredProperties.getResetPasswordMailBody(), account.getPasswordResetKey(), rootUrl)); } @Override diff --git a/src/main/resources/config/defaults.yml b/src/main/resources/config/defaults.yml index 0209418cfac7056ea479ad07a9048c920f797eb4..80c46a9cf875d1152e861643150e2059f3387360 100644 --- a/src/main/resources/config/defaults.yml +++ b/src/main/resources/config/defaults.yml @@ -107,16 +107,20 @@ arsnova: registration-mail-body: |- Welcome to ARSnova! - Please confirm your registration by visiting the following web address: - {0} + Here is the activation code you need for your first login to ARSnova: - Afterwards, you can log into ARSnova with your e-mail address and password. + {0} + + {1} reset-password-mail-subject: ARSnova Password Reset reset-password-mail-body: |- You requested to reset your password. - Please follow the link below to set a new password: - {0} + Here is the confirmation code you need to set a new password: + + {0} + + {1} # LDAP authentication #