* Prevents SSO login due to faulty verification * add translation & fix show error message * Update settings.yml.template --------- Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
This commit is contained in:
@@ -3,6 +3,7 @@ package stirling.software.SPDF.controller.api;
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -12,8 +13,8 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.session.SessionInformation;
|
||||
import org.springframework.security.core.session.SessionRegistry;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
@@ -30,6 +31,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import stirling.software.SPDF.config.security.UserService;
|
||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||
import stirling.software.SPDF.model.AuthenticationType;
|
||||
import stirling.software.SPDF.model.Role;
|
||||
import stirling.software.SPDF.model.User;
|
||||
import stirling.software.SPDF.model.api.user.UsernameAndPass;
|
||||
@@ -41,6 +44,8 @@ public class UserController {
|
||||
|
||||
@Autowired private UserService userService;
|
||||
|
||||
@Autowired SessionPersistentRegistry sessionRegistry;
|
||||
|
||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/register")
|
||||
public String register(@ModelAttribute UsernameAndPass requestModel, Model model)
|
||||
@@ -203,9 +208,10 @@ public class UserController {
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@PostMapping("/admin/saveUser")
|
||||
public RedirectView saveUser(
|
||||
@RequestParam(name = "username") String username,
|
||||
@RequestParam(name = "password") String password,
|
||||
@RequestParam String username,
|
||||
@RequestParam(name = "password", required = false) String password,
|
||||
@RequestParam(name = "role") String role,
|
||||
@RequestParam(name = "authType") String authType,
|
||||
@RequestParam(name = "forceChange", required = false, defaultValue = "false")
|
||||
boolean forceChange)
|
||||
throws IllegalArgumentException, IOException {
|
||||
@@ -237,7 +243,15 @@ public class UserController {
|
||||
return new RedirectView("/addUsers?messageType=invalidRole", true);
|
||||
}
|
||||
|
||||
userService.saveUser(username, password, role, forceChange);
|
||||
if (authType.equalsIgnoreCase(AuthenticationType.OAUTH2.toString())) {
|
||||
userService.saveUser(username, AuthenticationType.OAUTH2, role);
|
||||
} else {
|
||||
if (password.isBlank()) {
|
||||
return new RedirectView("/addUsers?messageType=invalidPassword", true);
|
||||
}
|
||||
userService.saveUser(username, password, role, forceChange);
|
||||
}
|
||||
|
||||
return new RedirectView(
|
||||
"/addUsers", true); // Redirect to account page after adding the user
|
||||
}
|
||||
@@ -247,7 +261,8 @@ public class UserController {
|
||||
public RedirectView changeRole(
|
||||
@RequestParam(name = "username") String username,
|
||||
@RequestParam(name = "role") String role,
|
||||
Authentication authentication) {
|
||||
Authentication authentication)
|
||||
throws IOException {
|
||||
|
||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
||||
|
||||
@@ -278,6 +293,60 @@ public class UserController {
|
||||
User user = userOpt.get();
|
||||
|
||||
userService.changeRole(user, role);
|
||||
|
||||
return new RedirectView(
|
||||
"/addUsers", true); // Redirect to account page after adding the user
|
||||
}
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@PostMapping("/admin/changeUserEnabled/{username}")
|
||||
public RedirectView changeUserEnabled(
|
||||
@PathVariable("username") String username,
|
||||
@RequestParam("enabled") boolean enabled,
|
||||
Authentication authentication)
|
||||
throws IOException {
|
||||
|
||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
||||
|
||||
if (!userOpt.isPresent()) {
|
||||
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
||||
}
|
||||
if (!userService.usernameExistsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
||||
}
|
||||
// Get the currently authenticated username
|
||||
String currentUsername = authentication.getName();
|
||||
|
||||
// Check if the provided username matches the current session's username
|
||||
if (currentUsername.equalsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=disabledCurrentUser", true);
|
||||
}
|
||||
User user = userOpt.get();
|
||||
|
||||
userService.changeUserEnabled(user, enabled);
|
||||
|
||||
if (!enabled) {
|
||||
// Invalidate all sessions if the user is being disabled
|
||||
List<Object> principals = sessionRegistry.getAllPrincipals();
|
||||
String userNameP = "";
|
||||
for (Object principal : principals) {
|
||||
List<SessionInformation> sessionsInformations =
|
||||
sessionRegistry.getAllSessions(principal, false);
|
||||
if (principal instanceof UserDetails) {
|
||||
userNameP = ((UserDetails) principal).getUsername();
|
||||
} else if (principal instanceof OAuth2User) {
|
||||
userNameP = ((OAuth2User) principal).getName();
|
||||
} else if (principal instanceof String) {
|
||||
userNameP = (String) principal;
|
||||
}
|
||||
if (userNameP.equalsIgnoreCase(username)) {
|
||||
for (SessionInformation sessionsInformation : sessionsInformations) {
|
||||
sessionRegistry.expireSession(sessionsInformation.getSessionId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new RedirectView(
|
||||
"/addUsers", true); // Redirect to account page after adding the user
|
||||
}
|
||||
@@ -285,7 +354,7 @@ public class UserController {
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@PostMapping("/admin/deleteUser/{username}")
|
||||
public RedirectView deleteUser(
|
||||
@PathVariable(name = "username") String username, Authentication authentication) {
|
||||
@PathVariable("username") String username, Authentication authentication) {
|
||||
|
||||
if (!userService.usernameExistsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=deleteUsernameExists", true);
|
||||
@@ -298,27 +367,18 @@ public class UserController {
|
||||
if (currentUsername.equalsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=deleteCurrentUser", true);
|
||||
}
|
||||
invalidateUserSessions(username);
|
||||
|
||||
// Invalidate all sessions before deleting the user
|
||||
List<SessionInformation> sessionsInformations =
|
||||
sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
|
||||
for (SessionInformation sessionsInformation : sessionsInformations) {
|
||||
sessionRegistry.expireSession(sessionsInformation.getSessionId());
|
||||
sessionRegistry.removeSessionInformation(sessionsInformation.getSessionId());
|
||||
}
|
||||
userService.deleteUser(username);
|
||||
return new RedirectView("/addUsers", true);
|
||||
}
|
||||
|
||||
@Autowired private SessionRegistry sessionRegistry;
|
||||
|
||||
private void invalidateUserSessions(String username) {
|
||||
for (Object principal : sessionRegistry.getAllPrincipals()) {
|
||||
if (principal instanceof UserDetails) {
|
||||
UserDetails userDetails = (UserDetails) principal;
|
||||
if (userDetails.getUsername().equals(username)) {
|
||||
for (SessionInformation session :
|
||||
sessionRegistry.getAllSessions(principal, false)) {
|
||||
session.expireNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/get-api-key")
|
||||
public ResponseEntity<String> getApiKey(Principal principal) {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package stirling.software.SPDF.controller.web;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -23,11 +25,14 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||
import stirling.software.SPDF.model.Authority;
|
||||
import stirling.software.SPDF.model.Role;
|
||||
import stirling.software.SPDF.model.SessionEntity;
|
||||
import stirling.software.SPDF.model.User;
|
||||
import stirling.software.SPDF.model.provider.GithubProvider;
|
||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||
@@ -35,15 +40,20 @@ import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||
import stirling.software.SPDF.repository.UserRepository;
|
||||
|
||||
@Controller
|
||||
@Slf4j
|
||||
@Tag(name = "Account Security", description = "Account Security APIs")
|
||||
public class AccountWebController {
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
private static final Logger logger = LoggerFactory.getLogger(AccountWebController.class);
|
||||
@Autowired SessionPersistentRegistry sessionPersistentRegistry;
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository; // Assuming you have a repository for user operations
|
||||
|
||||
@GetMapping("/login")
|
||||
public String login(HttpServletRequest request, Model model, Authentication authentication) {
|
||||
|
||||
// If the user is already authenticated, redirect them to the home page.
|
||||
if (authentication != null && authentication.isAuthenticated()) {
|
||||
return "redirect:/";
|
||||
}
|
||||
@@ -137,6 +147,13 @@ public class AccountWebController {
|
||||
break;
|
||||
case "invalid_id_token":
|
||||
erroroauth = "login.oauth2InvalidIdToken";
|
||||
break;
|
||||
case "oauth2_admin_blocked_user":
|
||||
erroroauth = "login.oauth2AdminBlockedUser";
|
||||
break;
|
||||
case "userIsDisabled":
|
||||
erroroauth = "login.userIsDisabled";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -155,9 +172,6 @@ public class AccountWebController {
|
||||
return "login";
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository; // Assuming you have a repository for user operations
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@GetMapping("/addUsers")
|
||||
public String showAddUserForm(
|
||||
@@ -166,6 +180,13 @@ public class AccountWebController {
|
||||
Iterator<User> iterator = allUsers.iterator();
|
||||
Map<String, String> roleDetails = Role.getAllRoleDetails();
|
||||
|
||||
// Map to store session information and user activity status
|
||||
Map<String, Boolean> userSessions = new HashMap<>();
|
||||
Map<String, Date> userLastRequest = new HashMap<>();
|
||||
|
||||
int activeUsers = 0;
|
||||
int disabledUsers = 0;
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
User user = iterator.next();
|
||||
if (user != null) {
|
||||
@@ -176,9 +197,73 @@ public class AccountWebController {
|
||||
break; // Break out of the inner loop once the user is removed
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the user's session status and last request time
|
||||
int maxInactiveInterval = sessionPersistentRegistry.getMaxInactiveInterval();
|
||||
boolean hasActiveSession = false;
|
||||
Date lastRequest = null;
|
||||
|
||||
Optional<SessionEntity> latestSession =
|
||||
sessionPersistentRegistry.findLatestSession(user.getUsername());
|
||||
if (latestSession.isPresent()) {
|
||||
SessionEntity sessionEntity = latestSession.get();
|
||||
Date lastAccessedTime = sessionEntity.getLastRequest();
|
||||
Instant now = Instant.now();
|
||||
|
||||
// Calculate session expiration and update session status accordingly
|
||||
Instant expirationTime =
|
||||
lastAccessedTime
|
||||
.toInstant()
|
||||
.plus(maxInactiveInterval, ChronoUnit.SECONDS);
|
||||
if (now.isAfter(expirationTime)) {
|
||||
sessionPersistentRegistry.expireSession(sessionEntity.getSessionId());
|
||||
hasActiveSession = false;
|
||||
} else {
|
||||
hasActiveSession = !sessionEntity.isExpired();
|
||||
}
|
||||
|
||||
lastRequest = sessionEntity.getLastRequest();
|
||||
} else {
|
||||
hasActiveSession = false;
|
||||
lastRequest = new Date(0); // No session, set default last request time
|
||||
}
|
||||
|
||||
userSessions.put(user.getUsername(), hasActiveSession);
|
||||
userLastRequest.put(user.getUsername(), lastRequest);
|
||||
|
||||
if (hasActiveSession) {
|
||||
activeUsers++;
|
||||
}
|
||||
if (!user.isEnabled()) {
|
||||
disabledUsers++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort users by active status and last request date
|
||||
List<User> sortedUsers =
|
||||
allUsers.stream()
|
||||
.sorted(
|
||||
(u1, u2) -> {
|
||||
boolean u1Active = userSessions.get(u1.getUsername());
|
||||
boolean u2Active = userSessions.get(u2.getUsername());
|
||||
|
||||
if (u1Active && !u2Active) {
|
||||
return -1;
|
||||
} else if (!u1Active && u2Active) {
|
||||
return 1;
|
||||
} else {
|
||||
Date u1LastRequest =
|
||||
userLastRequest.getOrDefault(
|
||||
u1.getUsername(), new Date(0));
|
||||
Date u2LastRequest =
|
||||
userLastRequest.getOrDefault(
|
||||
u2.getUsername(), new Date(0));
|
||||
return u2LastRequest.compareTo(u1LastRequest);
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
String messageType = request.getParameter("messageType");
|
||||
|
||||
String deleteMessage = null;
|
||||
@@ -203,6 +288,9 @@ public class AccountWebController {
|
||||
case "invalidUsername":
|
||||
addMessage = "invalidUsernameMessage";
|
||||
break;
|
||||
case "invalidPassword":
|
||||
addMessage = "invalidPasswordMessage";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -218,16 +306,24 @@ public class AccountWebController {
|
||||
case "downgradeCurrentUser":
|
||||
changeMessage = "downgradeCurrentUserMessage";
|
||||
break;
|
||||
|
||||
case "disabledCurrentUser":
|
||||
changeMessage = "disabledCurrentUserMessage";
|
||||
break;
|
||||
default:
|
||||
changeMessage = messageType;
|
||||
break;
|
||||
}
|
||||
model.addAttribute("changeMessage", changeMessage);
|
||||
}
|
||||
|
||||
model.addAttribute("users", allUsers);
|
||||
model.addAttribute("users", sortedUsers);
|
||||
model.addAttribute("currentUsername", authentication.getName());
|
||||
model.addAttribute("roleDetails", roleDetails);
|
||||
model.addAttribute("userSessions", userSessions);
|
||||
model.addAttribute("userLastRequest", userLastRequest);
|
||||
model.addAttribute("totalUsers", allUsers.size());
|
||||
model.addAttribute("activeUsers", activeUsers);
|
||||
model.addAttribute("disabledUsers", disabledUsers);
|
||||
return "addUsers";
|
||||
}
|
||||
|
||||
@@ -278,7 +374,7 @@ public class AccountWebController {
|
||||
settingsJson = objectMapper.writeValueAsString(user.get().getSettings());
|
||||
} catch (JsonProcessingException e) {
|
||||
// Handle JSON conversion error
|
||||
logger.error("exception", e);
|
||||
log.error("exception", e);
|
||||
return "redirect:/error";
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user