Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

495 lines
20 KiB
Java
Raw Normal View History

2023-08-27 11:59:08 +01:00
package stirling.software.SPDF.controller.web;
2023-12-30 19:11:27 +00:00
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
2023-08-27 11:59:08 +01:00
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
2023-08-27 11:59:08 +01:00
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import com.fasterxml.jackson.core.JsonProcessingException;
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.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.model.*;
import stirling.software.SPDF.model.ApplicationProperties.Security;
2024-05-25 18:19:03 +02:00
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
import stirling.software.SPDF.model.provider.GithubProvider;
import stirling.software.SPDF.model.provider.GoogleProvider;
import stirling.software.SPDF.model.provider.KeycloakProvider;
2023-08-27 11:59:08 +01:00
import stirling.software.SPDF.repository.UserRepository;
2023-12-30 19:11:27 +00:00
2023-08-27 11:59:08 +01:00
@Controller
@Slf4j
2023-08-27 11:59:08 +01:00
@Tag(name = "Account Security", description = "Account Security APIs")
public class AccountWebController {
@Autowired ApplicationProperties applicationProperties;
@Autowired SessionPersistentRegistry sessionPersistentRegistry;
@Autowired
private UserRepository userRepository; // Assuming you have a repository for user operations
2023-08-27 11:59:08 +01:00
@GetMapping("/login")
public String login(HttpServletRequest request, Model model, Authentication authentication) {
2024-07-04 23:04:21 +02:00
// If the user is already authenticated, redirect them to the home page.
2023-08-27 11:59:08 +01:00
if (authentication != null && authentication.isAuthenticated()) {
return "redirect:/";
2023-12-30 19:11:27 +00:00
}
2024-05-25 18:19:03 +02:00
Map<String, String> providerList = new HashMap<>();
Security securityProps = applicationProperties.getSecurity();
OAUTH2 oauth = securityProps.getOauth2();
2024-05-25 18:19:03 +02:00
if (oauth != null) {
if (oauth.getEnabled()) {
if (oauth.isSettingsValid()) {
providerList.put("/oauth2/authorization/oidc", oauth.getProvider());
2024-05-25 18:19:03 +02:00
}
Client client = oauth.getClient();
if (client != null) {
GoogleProvider google = client.getGoogle();
if (google.isSettingsValid()) {
providerList.put(
"/oauth2/authorization/" + google.getName(),
google.getClientName());
}
2024-05-25 18:19:03 +02:00
GithubProvider github = client.getGithub();
if (github.isSettingsValid()) {
providerList.put(
"/oauth2/authorization/" + github.getName(),
github.getClientName());
}
2024-05-25 18:19:03 +02:00
KeycloakProvider keycloak = client.getKeycloak();
if (keycloak.isSettingsValid()) {
providerList.put(
"/oauth2/authorization/" + keycloak.getName(),
keycloak.getClientName());
}
2024-05-25 18:19:03 +02:00
}
}
}
SAML2 saml2 = securityProps.getSaml2();
if (saml2 != null) {
if (saml2.getEnabled()) {
providerList.put("/saml2/authenticate/" + saml2.getRegistrationId(), "SAML 2");
}
}
2024-07-04 23:04:21 +02:00
// Remove any null keys/values from the providerList
providerList
.entrySet()
.removeIf(entry -> entry.getKey() == null || entry.getValue() == null);
2024-05-25 18:19:03 +02:00
model.addAttribute("providerlist", providerList);
model.addAttribute("loginMethod", securityProps.getLoginMethod());
model.addAttribute("altLogin", securityProps.isAltLogin());
model.addAttribute("currentPage", "login");
String error = request.getParameter("error");
if (error != null) {
2023-12-30 19:11:27 +00:00
switch (error) {
case "badcredentials":
error = "login.invalid";
break;
case "locked":
error = "login.locked";
break;
case "oauth2AuthenticationError":
error = "userAlreadyExistsOAuthMessage";
break;
default:
break;
}
model.addAttribute("error", error);
}
String erroroauth = request.getParameter("erroroauth");
if (erroroauth != null) {
switch (erroroauth) {
case "oauth2AutoCreateDisabled":
erroroauth = "login.oauth2AutoCreateDisabled";
break;
case "invalidUsername":
erroroauth = "login.invalid";
break;
case "userAlreadyExistsWeb":
erroroauth = "userAlreadyExistsWebMessage";
break;
case "oauth2AuthenticationErrorWeb":
erroroauth = "login.oauth2InvalidUserType";
break;
case "invalid_token_response":
erroroauth = "login.oauth2InvalidTokenResponse";
2024-05-25 18:19:03 +02:00
break;
case "authorization_request_not_found":
erroroauth = "login.oauth2RequestNotFound";
break;
case "access_denied":
erroroauth = "login.oauth2AccessDenied";
break;
case "invalid_user_info_response":
erroroauth = "login.oauth2InvalidUserInfoResponse";
break;
case "invalid_request":
erroroauth = "login.oauth2invalidRequest";
break;
2024-05-28 10:27:44 +02:00
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;
}
model.addAttribute("erroroauth", erroroauth);
}
if (request.getParameter("messageType") != null) {
model.addAttribute("messageType", "changedCredsMessage");
2023-12-30 19:11:27 +00:00
}
2023-08-27 11:59:08 +01:00
if (request.getParameter("logout") != null) {
2023-12-30 19:11:27 +00:00
2023-08-27 11:59:08 +01:00
model.addAttribute("logoutMessage", "You have been logged out.");
2023-12-30 19:11:27 +00:00
}
2023-08-27 11:59:08 +01:00
return "login";
2023-12-30 19:11:27 +00:00
}
2023-08-27 11:59:08 +01:00
@PreAuthorize("hasRole('ROLE_ADMIN')")
@GetMapping("/addUsers")
public String showAddUserForm(
HttpServletRequest request, Model model, Authentication authentication) {
2023-12-25 12:58:49 +00:00
List<User> allUsers = userRepository.findAll();
Iterator<User> iterator = allUsers.iterator();
Map<String, String> roleDetails = Role.getAllRoleDetails();
2023-12-30 19:11:27 +00:00
// 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;
2023-12-25 12:58:49 +00:00
while (iterator.hasNext()) {
User user = iterator.next();
if (user != null) {
for (Authority authority : user.getAuthorities()) {
if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {
iterator.remove();
roleDetails.remove(Role.INTERNAL_API_USER.getRoleId());
2023-12-25 12:58:49 +00:00
break; // Break out of the inner loop once the user is removed
2023-12-30 19:11:27 +00:00
}
}
// 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++;
}
2023-12-30 19:11:27 +00:00
}
}
// 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;
if (messageType != null) {
switch (messageType) {
case "deleteCurrentUser":
deleteMessage = "deleteCurrentUserMessage";
break;
case "deleteUsernameExists":
deleteMessage = "deleteUsernameExistsMessage";
break;
default:
break;
}
model.addAttribute("deleteMessage", deleteMessage);
String addMessage = null;
switch (messageType) {
case "usernameExists":
addMessage = "usernameExistsMessage";
break;
case "invalidUsername":
addMessage = "invalidUsernameMessage";
break;
case "invalidPassword":
addMessage = "invalidPasswordMessage";
break;
default:
break;
}
model.addAttribute("addMessage", addMessage);
}
String changeMessage = null;
if (messageType != null) {
switch (messageType) {
case "userNotFound":
changeMessage = "userNotFoundMessage";
break;
case "downgradeCurrentUser":
changeMessage = "downgradeCurrentUserMessage";
break;
case "disabledCurrentUser":
changeMessage = "disabledCurrentUserMessage";
break;
default:
changeMessage = messageType;
break;
}
model.addAttribute("changeMessage", changeMessage);
}
model.addAttribute("users", sortedUsers);
2023-08-30 22:52:38 +01:00
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);
2023-08-27 11:59:08 +01:00
return "addUsers";
2023-12-30 19:11:27 +00:00
}
2023-12-27 22:56:51 +00:00
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
2023-08-27 11:59:08 +01:00
@GetMapping("/account")
public String account(HttpServletRequest request, Model model, Authentication authentication) {
if (authentication == null || !authentication.isAuthenticated()) {
return "redirect:/";
2023-12-30 19:11:27 +00:00
}
2023-08-27 11:59:08 +01:00
if (authentication != null && authentication.isAuthenticated()) {
Object principal = authentication.getPrincipal();
String username = null;
2023-12-30 19:11:27 +00:00
2023-08-27 11:59:08 +01:00
if (principal instanceof UserDetails) {
// Cast the principal object to UserDetails
UserDetails userDetails = (UserDetails) principal;
2023-12-30 19:11:27 +00:00
2023-08-27 11:59:08 +01:00
// Retrieve username and other attributes
username = userDetails.getUsername();
// Add oAuth2 Login attributes to the model
model.addAttribute("oAuth2Login", false);
}
if (principal instanceof OAuth2User) {
// Cast the principal object to OAuth2User
OAuth2User userDetails = (OAuth2User) principal;
2023-12-30 19:11:27 +00:00
// Retrieve username and other attributes
username =
userDetails.getAttribute(
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
// Add oAuth2 Login attributes to the model
model.addAttribute("oAuth2Login", true);
}
if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
// Cast the principal object to OAuth2User
CustomSaml2AuthenticatedPrincipal userDetails =
(CustomSaml2AuthenticatedPrincipal) principal;
// Retrieve username and other attributes
username = userDetails.getName();
// Add oAuth2 Login attributes to the model
model.addAttribute("oAuth2Login", true);
}
if (username != null) {
2023-08-27 11:59:08 +01:00
// Fetch user details from the database
Optional<User> user =
userRepository.findByUsernameIgnoreCaseWithSettings(
2023-08-27 11:59:08 +01:00
username); // Assuming findByUsername method exists
if (!user.isPresent()) {
2024-06-02 11:59:43 +01:00
return "redirect:/error";
2023-12-30 19:11:27 +00:00
}
2023-08-27 11:59:08 +01:00
// Convert settings map to JSON string
ObjectMapper objectMapper = new ObjectMapper();
String settingsJson;
2023-12-30 19:11:27 +00:00
try {
2023-08-27 11:59:08 +01:00
settingsJson = objectMapper.writeValueAsString(user.get().getSettings());
} catch (JsonProcessingException e) {
// Handle JSON conversion error
log.error("exception", e);
2024-06-02 11:59:43 +01:00
return "redirect:/error";
2023-12-30 19:11:27 +00:00
}
String messageType = request.getParameter("messageType");
if (messageType != null) {
switch (messageType) {
case "notAuthenticated":
messageType = "notAuthenticatedMessage";
break;
case "userNotFound":
messageType = "userNotFoundMessage";
break;
case "incorrectPassword":
messageType = "incorrectPasswordMessage";
break;
case "usernameExists":
messageType = "usernameExistsMessage";
break;
case "invalidUsername":
messageType = "invalidUsernameMessage";
break;
default:
break;
}
model.addAttribute("messageType", messageType);
}
2023-08-27 11:59:08 +01:00
// Add attributes to the model
model.addAttribute("username", username);
model.addAttribute("role", user.get().getRolesAsString());
model.addAttribute("settings", settingsJson);
2023-09-03 16:40:40 +01:00
model.addAttribute("changeCredsFlag", user.get().isFirstLogin());
model.addAttribute("currentPage", "account");
2023-12-30 19:11:27 +00:00
}
} else {
2023-08-27 11:59:08 +01:00
return "redirect:/";
}
return "account";
}
2023-12-30 19:11:27 +00:00
2023-08-27 11:59:08 +01:00
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@GetMapping("/change-creds")
2023-12-25 12:58:49 +00:00
public String changeCreds(
2023-08-27 11:59:08 +01:00
HttpServletRequest request, Model model, Authentication authentication) {
if (authentication == null || !authentication.isAuthenticated()) {
return "redirect:/";
}
if (authentication != null && authentication.isAuthenticated()) {
Object principal = authentication.getPrincipal();
2023-12-30 19:11:27 +00:00
2023-08-27 11:59:08 +01:00
if (principal instanceof UserDetails) {
// Cast the principal object to UserDetails
UserDetails userDetails = (UserDetails) principal;
2023-12-30 19:11:27 +00:00
2023-08-27 11:59:08 +01:00
// Retrieve username and other attributes
String username = userDetails.getUsername();
2023-12-30 19:11:27 +00:00
2023-08-27 11:59:08 +01:00
// Fetch user details from the database
Optional<User> user =
userRepository.findByUsernameIgnoreCase(
2023-08-27 11:59:08 +01:00
username); // Assuming findByUsername method exists
if (!user.isPresent()) {
// Handle error appropriately
return "redirect:/error"; // Example redirection in case of error
}
String messageType = request.getParameter("messageType");
if (messageType != null) {
switch (messageType) {
case "notAuthenticated":
messageType = "notAuthenticatedMessage";
break;
case "userNotFound":
messageType = "userNotFoundMessage";
break;
case "incorrectPassword":
messageType = "incorrectPasswordMessage";
break;
case "usernameExists":
messageType = "usernameExistsMessage";
break;
default:
break;
}
model.addAttribute("messageType", messageType);
}
2023-08-27 11:59:08 +01:00
// Add attributes to the model
model.addAttribute("username", username);
}
} else {
2023-09-03 16:40:40 +01:00
return "redirect:/";
}
return "change-creds";
}
2023-08-27 11:59:08 +01:00
}