diff --git a/src/main/java/stirling/software/SPDF/EE/KeygenLicenseVerifier.java b/src/main/java/stirling/software/SPDF/EE/KeygenLicenseVerifier.java index a562cbcba..ca481532c 100644 --- a/src/main/java/stirling/software/SPDF/EE/KeygenLicenseVerifier.java +++ b/src/main/java/stirling/software/SPDF/EE/KeygenLicenseVerifier.java @@ -20,6 +20,7 @@ import stirling.software.SPDF.utils.GeneralUtils; @Service @Slf4j public class KeygenLicenseVerifier { + // todo: place in config files? private static final String ACCOUNT_ID = "e5430f69-e834-4ae4-befd-b602aae5f372"; private static final String BASE_URL = "https://api.keygen.sh/v1/accounts"; private static final ObjectMapper objectMapper = new ObjectMapper(); @@ -68,7 +69,7 @@ public class KeygenLicenseVerifier { return false; } catch (Exception e) { - log.error("Error verifying license: " + e.getMessage()); + log.error("Error verifying license: {}", e.getMessage()); return false; } } @@ -95,10 +96,9 @@ public class KeygenLicenseVerifier { .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - log.debug(" validateLicenseResponse body: " + response.body()); + log.debug("ValidateLicenseResponse body: {}", response.body()); JsonNode jsonResponse = objectMapper.readTree(response.body()); if (response.statusCode() == 200) { - JsonNode metaNode = jsonResponse.path("meta"); boolean isValid = metaNode.path("valid").asBoolean(); @@ -120,7 +120,7 @@ public class KeygenLicenseVerifier { log.info(applicationProperties.toString()); } else { - log.error("Error validating license. Status code: " + response.statusCode()); + log.error("Error validating license. Status code: {}", response.statusCode()); } return jsonResponse; } diff --git a/src/main/java/stirling/software/SPDF/config/AppConfig.java b/src/main/java/stirling/software/SPDF/config/AppConfig.java index cb9dbcd80..62f82a278 100644 --- a/src/main/java/stirling/software/SPDF/config/AppConfig.java +++ b/src/main/java/stirling/software/SPDF/config/AppConfig.java @@ -35,10 +35,7 @@ public class AppConfig { } @Bean - @ConditionalOnProperty( - name = "system.customHTMLFiles", - havingValue = "true", - matchIfMissing = false) + @ConditionalOnProperty(name = "system.customHTMLFiles", havingValue = "true") public SpringTemplateEngine templateEngine(ResourceLoader resourceLoader) { SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.addTemplateResolver(new FileFallbackTemplateResolver(resourceLoader)); @@ -129,8 +126,8 @@ public class AppConfig { } @ConditionalOnMissingClass("stirling.software.SPDF.config.security.SecurityConfiguration") - @Bean(name = "activSecurity") - public boolean missingActivSecurity() { + @Bean(name = "activeSecurity") + public boolean missingActiveSecurity() { return false; } diff --git a/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java b/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java index 0fb1e26fc..cc9daff83 100644 --- a/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java +++ b/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java @@ -20,7 +20,7 @@ public class CleanUrlInterceptor implements HandlerInterceptor { "endpoints", "logout", "error", - "erroroauth", + "errorOAuth", "file", "messageType", "infoMessage"); diff --git a/src/main/java/stirling/software/SPDF/config/interfaces/DatabaseInterface.java b/src/main/java/stirling/software/SPDF/config/interfaces/DatabaseInterface.java index 18fbf7883..34b457e89 100644 --- a/src/main/java/stirling/software/SPDF/config/interfaces/DatabaseInterface.java +++ b/src/main/java/stirling/software/SPDF/config/interfaces/DatabaseInterface.java @@ -3,7 +3,7 @@ package stirling.software.SPDF.config.interfaces; import java.sql.SQLException; import java.util.List; -import stirling.software.SPDF.model.provider.UnsupportedProviderException; +import stirling.software.SPDF.model.exception.UnsupportedProviderException; import stirling.software.SPDF.utils.FileInfo; public interface DatabaseInterface { diff --git a/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java b/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java index 1a1e2bc31..f4f103190 100644 --- a/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java @@ -69,7 +69,7 @@ public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationF } if (exception instanceof BadCredentialsException || exception instanceof UsernameNotFoundException) { - getRedirectStrategy().sendRedirect(request, response, "/login?error=badcredentials"); + getRedirectStrategy().sendRedirect(request, response, "/login?error=badCredentials"); return; } if (exception instanceof InternalAuthenticationServiceException diff --git a/src/main/java/stirling/software/SPDF/config/security/CustomLogoutSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/CustomLogoutSuccessHandler.java index 88260ea5b..c455f0ebd 100644 --- a/src/main/java/stirling/software/SPDF/config/security/CustomLogoutSuccessHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/CustomLogoutSuccessHandler.java @@ -14,8 +14,8 @@ import org.springframework.security.saml2.provider.service.authentication.Saml2A import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import com.coveo.saml.SamlClient; +import com.coveo.saml.SamlException; -import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -28,62 +28,43 @@ import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrin import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2; -import stirling.software.SPDF.model.Provider; -import stirling.software.SPDF.model.provider.UnsupportedProviderException; +import stirling.software.SPDF.model.provider.KeycloakProvider; import stirling.software.SPDF.utils.UrlUtils; @Slf4j @AllArgsConstructor public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { + public static final String LOGOUT_PATH = "/login?logout=true"; + private final ApplicationProperties applicationProperties; @Override public void onLogoutSuccess( HttpServletRequest request, HttpServletResponse response, Authentication authentication) - throws IOException, ServletException { - + throws IOException { if (!response.isCommitted()) { - // Handle user logout due to disabled account - if (request.getParameter("userIsDisabled") != null) { - response.sendRedirect( - request.getContextPath() + "/login?erroroauth=userIsDisabled"); - return; - } - // Handle OAuth2 authentication error - if (request.getParameter("oauth2AuthenticationErrorWeb") != null) { - response.sendRedirect( - request.getContextPath() + "/login?erroroauth=userAlreadyExistsWeb"); - return; - } if (authentication != null) { - // Handle SAML2 logout redirection if (authentication instanceof Saml2Authentication) { + // Handle SAML2 logout redirection getRedirect_saml2(request, response, authentication); - return; - } - // Handle OAuth2 logout redirection - else if (authentication instanceof OAuth2AuthenticationToken) { + } else if (authentication instanceof OAuth2AuthenticationToken) { + // Handle OAuth2 logout redirection getRedirect_oauth2(request, response, authentication); - return; - } - // Handle Username/Password logout - else if (authentication instanceof UsernamePasswordAuthenticationToken) { - getRedirectStrategy().sendRedirect(request, response, "/login?logout=true"); - return; - } - // Handle unknown authentication types - else { + } else if (authentication instanceof UsernamePasswordAuthenticationToken) { + // Handle Username/Password logout + getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH); + } else { + // Handle unknown authentication types log.error( - "authentication class unknown: " - + authentication.getClass().getSimpleName()); - getRedirectStrategy().sendRedirect(request, response, "/login?logout=true"); - return; + "Authentication class unknown: {}", + authentication.getClass().getSimpleName()); + getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH); } } else { // Redirect to login page after logout - getRedirectStrategy().sendRedirect(request, response, "/login?logout=true"); - return; + String path = checkForErrors(request); + getRedirectStrategy().sendRedirect(request, response, path); } } } @@ -100,7 +81,7 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { CustomSaml2AuthenticatedPrincipal principal = (CustomSaml2AuthenticatedPrincipal) samlAuthentication.getPrincipal(); - String nameIdValue = principal.getName(); + String nameIdValue = principal.name(); try { // Read certificate from the resource @@ -111,27 +92,7 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { certificates.add(certificate); // Construct URLs required for SAML configuration - String serverUrl = - SPDFApplication.getStaticBaseUrl() + ":" + SPDFApplication.getStaticPort(); - - String relyingPartyIdentifier = - serverUrl + "/saml2/service-provider-metadata/" + registrationId; - - String assertionConsumerServiceUrl = serverUrl + "/login/saml2/sso/" + registrationId; - - String idpUrl = samlConf.getIdpSingleLogoutUrl(); - - String idpIssuer = samlConf.getIdpIssuer(); - - // Create SamlClient instance for SAML logout - SamlClient samlClient = - new SamlClient( - relyingPartyIdentifier, - assertionConsumerServiceUrl, - idpUrl, - idpIssuer, - certificates, - SamlClient.SamlIdpBinding.POST); + SamlClient samlClient = getSamlClient(registrationId, samlConf, certificates); // Read private key for service provider Resource privateKeyResource = samlConf.getPrivateKey(); @@ -143,8 +104,12 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { // Redirect to identity provider for logout samlClient.redirectToIdentityProvider(response, null, nameIdValue); } catch (Exception e) { - log.error(nameIdValue, e); - getRedirectStrategy().sendRedirect(request, response, "/login?logout=true"); + log.error( + "Error retrieving logout URL from Provider {} for user {}", + samlConf.getProvider(), + nameIdValue, + e); + getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH); } } @@ -152,87 +117,107 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { private void getRedirect_oauth2( HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { - String param = "logout=true"; - String registrationId = null; - String issuer = null; - String clientId = null; + String registrationId; OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); + String path = checkForErrors(request); - if (authentication instanceof OAuth2AuthenticationToken) { - OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; + if (authentication instanceof OAuth2AuthenticationToken oauthToken) { registrationId = oauthToken.getAuthorizedClientRegistrationId(); - - try { - // Get OAuth2 provider details from configuration - Provider provider = oauth.getClient().get(registrationId); - issuer = provider.getIssuer(); - clientId = provider.getClientId(); - } catch (UnsupportedProviderException e) { - log.error(e.getMessage()); - } } else { registrationId = oauth.getProvider() != null ? oauth.getProvider() : ""; - issuer = oauth.getIssuer(); - clientId = oauth.getClientId(); - } - String errorMessage = ""; - // Handle different error scenarios during logout - if (request.getParameter("oauth2AuthenticationErrorWeb") != null) { - param = "erroroauth=oauth2AuthenticationErrorWeb"; - } else if ((errorMessage = request.getParameter("error")) != null) { - param = "error=" + sanitizeInput(errorMessage); - } else if ((errorMessage = request.getParameter("erroroauth")) != null) { - param = "erroroauth=" + sanitizeInput(errorMessage); - } else if (request.getParameter("oauth2AutoCreateDisabled") != null) { - param = "error=oauth2AutoCreateDisabled"; - } else if (request.getParameter("oauth2_admin_blocked_user") != null) { - param = "erroroauth=oauth2_admin_blocked_user"; - } else if (request.getParameter("userIsDisabled") != null) { - param = "erroroauth=userIsDisabled"; - } else if (request.getParameter("badcredentials") != null) { - param = "error=badcredentials"; } - String redirect_url = UrlUtils.getOrigin(request) + "/login?" + param; + String redirectUrl = UrlUtils.getOrigin(request) + "/login?" + path; // Redirect based on OAuth2 provider switch (registrationId.toLowerCase()) { - case "keycloak": - // Add Keycloak specific logout URL if needed + case "keycloak" -> { + KeycloakProvider keycloak = oauth.getClient().getKeycloak(); String logoutUrl = - issuer + keycloak.getIssuer() + "/protocol/openid-connect/logout" + "?client_id=" - + clientId + + keycloak.getClientId() + "&post_logout_redirect_uri=" - + response.encodeRedirectURL(redirect_url); - log.info("Redirecting to Keycloak logout URL: " + logoutUrl); + + response.encodeRedirectURL(redirectUrl); + log.info("Redirecting to Keycloak logout URL: {}", logoutUrl); response.sendRedirect(logoutUrl); - break; - case "github": - // Add GitHub specific logout URL if needed - String githubLogoutUrl = "https://github.com/logout"; - log.info("Redirecting to GitHub logout URL: " + githubLogoutUrl); - response.sendRedirect(githubLogoutUrl); - break; - case "google": - // Add Google specific logout URL if needed - // String googleLogoutUrl = - // "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue=" - // + response.encodeRedirectURL(redirect_url); - log.info("Google does not have a specific logout URL"); - // log.info("Redirecting to Google logout URL: " + googleLogoutUrl); - // response.sendRedirect(googleLogoutUrl); - // break; - default: - String defaultRedirectUrl = request.getContextPath() + "/login?" + param; - log.info("Redirecting to default logout URL: " + defaultRedirectUrl); - response.sendRedirect(defaultRedirectUrl); - break; + } + case "github", "google" -> { + log.info( + "No redirect URL for {} available. Redirecting to default logout URL: {}", + registrationId, + redirectUrl); + response.sendRedirect(redirectUrl); + } + default -> { + log.info("Redirecting to default logout URL: {}", redirectUrl); + response.sendRedirect(redirectUrl); + } } } - // Sanitize input to avoid potential security vulnerabilities + private static SamlClient getSamlClient( + String registrationId, SAML2 samlConf, List certificates) + throws SamlException { + String serverUrl = + SPDFApplication.getStaticBaseUrl() + ":" + SPDFApplication.getStaticPort(); + + String relyingPartyIdentifier = + serverUrl + "/saml2/service-provider-metadata/" + registrationId; + + String assertionConsumerServiceUrl = serverUrl + "/login/saml2/sso/" + registrationId; + + String idpSLOUrl = samlConf.getIdpSingleLogoutUrl(); + + String idpIssuer = samlConf.getIdpIssuer(); + + // Create SamlClient instance for SAML logout + return new SamlClient( + relyingPartyIdentifier, + assertionConsumerServiceUrl, + idpSLOUrl, + idpIssuer, + certificates, + SamlClient.SamlIdpBinding.POST); + } + + /** + * Handles different error scenarios during logout. Will return a String containing + * the error request parameter. + * + * @param request the user's HttpServletRequest request. + * @return a String containing the error request parameter. + */ + private String checkForErrors(HttpServletRequest request) { + String errorMessage; + String path = "logout=true"; + + if (request.getParameter("oAuth2AuthenticationErrorWeb") != null) { + path = "errorOAuth=userAlreadyExistsWeb"; + } else if ((errorMessage = request.getParameter("errorOAuth")) != null) { + path = "errorOAuth=" + sanitizeInput(errorMessage); + } else if (request.getParameter("oAuth2AutoCreateDisabled") != null) { + path = "errorOAuth=oAuth2AutoCreateDisabled"; + } else if (request.getParameter("oAuth2AdminBlockedUser") != null) { + path = "errorOAuth=oAuth2AdminBlockedUser"; + } else if (request.getParameter("userIsDisabled") != null) { + path = "errorOAuth=userIsDisabled"; + } else if ((errorMessage = request.getParameter("error")) != null) { + path = "errorOAuth=" + sanitizeInput(errorMessage); + } else if (request.getParameter("badCredentials") != null) { + path = "errorOAuth=badCredentials"; + } + + return path; + } + + /** + * Sanitize input to avoid potential security vulnerabilities. Will return a sanitised + * String. + * + * @return a sanitised String + */ private String sanitizeInput(String input) { return input.replaceAll("[^a-zA-Z0-9 ]", ""); } diff --git a/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java b/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java index 432e2d2fc..261fc307e 100644 --- a/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java +++ b/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java @@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.config.interfaces.DatabaseInterface; import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.Role; -import stirling.software.SPDF.model.provider.UnsupportedProviderException; +import stirling.software.SPDF.model.exception.UnsupportedProviderException; @Slf4j @Component diff --git a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java index d140f7492..318ca1909 100644 --- a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java @@ -1,6 +1,6 @@ package stirling.software.SPDF.config.security; -import java.util.*; +import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -51,11 +51,7 @@ public class SecurityConfiguration { private final CustomUserDetailsService userDetailsService; private final UserService userService; - - @Qualifier("loginEnabled") private final boolean loginEnabledValue; - - @Qualifier("runningEE") private final boolean runningEE; private final ApplicationProperties applicationProperties; @@ -109,6 +105,7 @@ public class SecurityConfiguration { if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) { http.csrf(csrf -> csrf.disable()); } + if (loginEnabledValue) { http.addFilterBefore( userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); @@ -164,8 +161,7 @@ public class SecurityConfiguration { .logoutSuccessHandler( new CustomLogoutSuccessHandler(applicationProperties)) .clearAuthentication(true) - .invalidateHttpSession( // Invalidate session - true) + .invalidateHttpSession(true) .deleteCookies("JSESSIONID", "remember-me")); http.rememberMe( rememberMeConfigurer -> // Use the configurator directly @@ -227,14 +223,14 @@ public class SecurityConfiguration { .permitAll()); } // Handle OAUTH2 Logins - if (applicationProperties.getSecurity().isOauth2Activ()) { + if (applicationProperties.getSecurity().isOauth2Active()) { http.oauth2Login( oauth2 -> oauth2.loginPage("/oauth2") . /* This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database. - If user exists, login proceeds as usual. If user does not exist, then it is autocreated but only if 'OAUTH2AutoCreateUser' + If user exists, login proceeds as usual. If user does not exist, then it is auto-created but only if 'OAUTH2AutoCreateUser' is set as true, else login fails with an error message advising the same. */ successHandler( @@ -258,8 +254,7 @@ public class SecurityConfiguration { .permitAll()); } // Handle SAML - if (applicationProperties.getSecurity().isSaml2Activ()) { - // && runningEE + if (applicationProperties.getSecurity().isSaml2Active() && runningEE) { // Configure the authentication provider OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider(); @@ -284,12 +279,13 @@ public class SecurityConfiguration { .authenticationRequestResolver( saml2AuthenticationRequestResolver); } catch (Exception e) { - log.error("Error configuring SAML2 login", e); + log.error("Error configuring SAML 2 login", e); throw new RuntimeException(e); } }); } } else { + log.info("SAML 2 login is not enabled. Using default."); http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); } return http.build(); @@ -315,7 +311,7 @@ public class SecurityConfiguration { } @Bean - public boolean activSecurity() { + public boolean activeSecurity() { return true; } } diff --git a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java index 6f51d3d32..e8fafda74 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java @@ -88,7 +88,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { // Use API key to authenticate. This requires you to have an authentication // provider for API keys. Optional user = userService.getUserByApiKey(apiKey); - if (!user.isPresent()) { + if (user.isEmpty()) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("Invalid API Key."); return; @@ -150,7 +150,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { OAUTH2 oAuth = securityProp.getOauth2(); blockRegistration = oAuth != null && oAuth.getBlockRegistration(); } else if (principal instanceof CustomSaml2AuthenticatedPrincipal) { - username = ((CustomSaml2AuthenticatedPrincipal) principal).getName(); + username = ((CustomSaml2AuthenticatedPrincipal) principal).name(); loginMethod = LoginMethod.SAML2USER; SAML2 saml2 = securityProp.getSaml2(); blockRegistration = saml2 != null && saml2.getBlockRegistration(); @@ -177,7 +177,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { if (blockRegistration && !isUserExists) { log.warn("Blocked registration for OAuth2/SAML user: {}", username); response.sendRedirect( - request.getContextPath() + "/logout?oauth2_admin_blocked_user=true"); + request.getContextPath() + "/logout?oAuth2AdminBlockedUser=true"); return; } @@ -193,7 +193,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { // Redirect to logout if credentials are invalid if (!isUserExists && notSsoLogin) { - response.sendRedirect(request.getContextPath() + "/logout?badcredentials=true"); + response.sendRedirect(request.getContextPath() + "/logout?badCredentials=true"); return; } if (isUserDisabled) { diff --git a/src/main/java/stirling/software/SPDF/config/security/UserService.java b/src/main/java/stirling/software/SPDF/config/security/UserService.java index a45019bca..71c9f7799 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserService.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserService.java @@ -27,7 +27,7 @@ import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrin import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface; import stirling.software.SPDF.model.*; -import stirling.software.SPDF.model.provider.UnsupportedProviderException; +import stirling.software.SPDF.model.exception.UnsupportedProviderException; import stirling.software.SPDF.repository.AuthorityRepository; import stirling.software.SPDF.repository.UserRepository; @@ -78,20 +78,18 @@ public class UserService implements UserServiceInterface { } // Handle OAUTH2 login and user auto creation. - public boolean processSSOPostLogin(String username, boolean autoCreateUser) + public void processSSOPostLogin(String username, boolean autoCreateUser) throws IllegalArgumentException, SQLException, UnsupportedProviderException { if (!isUsernameValid(username)) { - return false; + return; } Optional existingUser = findByUsernameIgnoreCase(username); if (existingUser.isPresent()) { - return true; + return; } if (autoCreateUser) { saveUser(username, AuthenticationType.SSO); - return true; } - return false; } public Authentication getAuthentication(String apiKey) { @@ -373,19 +371,16 @@ public class UserService implements UserServiceInterface { public void invalidateUserSessions(String username) { String usernameP = ""; + for (Object principal : sessionRegistry.getAllPrincipals()) { for (SessionInformation sessionsInformation : sessionRegistry.getAllSessions(principal, false)) { - if (principal instanceof UserDetails) { - UserDetails userDetails = (UserDetails) principal; + if (principal instanceof UserDetails userDetails) { usernameP = userDetails.getUsername(); - } else if (principal instanceof OAuth2User) { - OAuth2User oAuth2User = (OAuth2User) principal; + } else if (principal instanceof OAuth2User oAuth2User) { usernameP = oAuth2User.getName(); - } else if (principal instanceof CustomSaml2AuthenticatedPrincipal) { - CustomSaml2AuthenticatedPrincipal saml2User = - (CustomSaml2AuthenticatedPrincipal) principal; - usernameP = saml2User.getName(); + } else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) { + usernameP = saml2User.name(); } else if (principal instanceof String) { usernameP = (String) principal; } @@ -398,6 +393,7 @@ public class UserService implements UserServiceInterface { public String getCurrentUsername() { Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + if (principal instanceof UserDetails) { return ((UserDetails) principal).getUsername(); } else if (principal instanceof OAuth2User) { @@ -405,42 +401,48 @@ public class UserService implements UserServiceInterface { .getAttribute( applicationProperties.getSecurity().getOauth2().getUseAsUsername()); } else if (principal instanceof CustomSaml2AuthenticatedPrincipal) { - return ((CustomSaml2AuthenticatedPrincipal) principal).getName(); - } else if (principal instanceof String) { - return (String) principal; + return ((CustomSaml2AuthenticatedPrincipal) principal).name(); } else { return principal.toString(); } } @Transactional - public void syncCustomApiUser(String customApiKey) - throws SQLException, UnsupportedProviderException { - if (customApiKey == null || customApiKey.trim().length() == 0) { + public void syncCustomApiUser(String customApiKey) { + if (customApiKey == null || customApiKey.trim().isBlank()) { return; } + String username = "CUSTOM_API_USER"; Optional existingUser = findByUsernameIgnoreCase(username); - if (!existingUser.isPresent()) { - // Create new user with API role - User user = new User(); - user.setUsername(username); - user.setPassword(UUID.randomUUID().toString()); - user.setEnabled(true); - user.setFirstLogin(false); - user.setAuthenticationType(AuthenticationType.WEB); - user.setApiKey(customApiKey); - user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user)); - userRepository.save(user); + + existingUser.ifPresentOrElse( + user -> { + // Update API key if it has changed + User updatedUser = existingUser.get(); + + if (!customApiKey.equals(updatedUser.getApiKey())) { + updatedUser.setApiKey(customApiKey); + userRepository.save(updatedUser); + } + }, + () -> { + // Create new user with API role + User user = new User(); + user.setUsername(username); + user.setPassword(UUID.randomUUID().toString()); + user.setEnabled(true); + user.setFirstLogin(false); + user.setAuthenticationType(AuthenticationType.WEB); + user.setApiKey(customApiKey); + user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user)); + userRepository.save(user); + }); + + try { databaseService.exportDatabase(); - } else { - // Update API key if it has changed - User user = existingUser.get(); - if (!customApiKey.equals(user.getApiKey())) { - user.setApiKey(customApiKey); - userRepository.save(user); - databaseService.exportDatabase(); - } + } catch (SQLException | UnsupportedProviderException e) { + log.error("Error exporting database after synchronising custom API user", e); } } diff --git a/src/main/java/stirling/software/SPDF/config/security/database/DatabaseConfig.java b/src/main/java/stirling/software/SPDF/config/security/database/DatabaseConfig.java index d2b301c01..704fd013d 100644 --- a/src/main/java/stirling/software/SPDF/config/security/database/DatabaseConfig.java +++ b/src/main/java/stirling/software/SPDF/config/security/database/DatabaseConfig.java @@ -14,7 +14,7 @@ import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.config.InstallationPathConfig; import stirling.software.SPDF.model.ApplicationProperties; -import stirling.software.SPDF.model.provider.UnsupportedProviderException; +import stirling.software.SPDF.model.exception.UnsupportedProviderException; @Slf4j @Getter diff --git a/src/main/java/stirling/software/SPDF/config/security/database/ScheduledTasks.java b/src/main/java/stirling/software/SPDF/config/security/database/ScheduledTasks.java index 8597935f5..c318f1d97 100644 --- a/src/main/java/stirling/software/SPDF/config/security/database/ScheduledTasks.java +++ b/src/main/java/stirling/software/SPDF/config/security/database/ScheduledTasks.java @@ -8,7 +8,7 @@ import org.springframework.stereotype.Component; import stirling.software.SPDF.config.interfaces.DatabaseInterface; import stirling.software.SPDF.controller.api.H2SQLCondition; -import stirling.software.SPDF.model.provider.UnsupportedProviderException; +import stirling.software.SPDF.model.exception.UnsupportedProviderException; @Component @Conditional(H2SQLCondition.class) diff --git a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationFailureHandler.java b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationFailureHandler.java index 79ea7bfc6..be58ac776 100644 --- a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationFailureHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationFailureHandler.java @@ -29,7 +29,7 @@ public class CustomOAuth2AuthenticationFailureHandler if (exception instanceof BadCredentialsException) { log.error("BadCredentialsException", exception); - getRedirectStrategy().sendRedirect(request, response, "/login?error=badcredentials"); + getRedirectStrategy().sendRedirect(request, response, "/login?error=badCredentials"); return; } if (exception instanceof DisabledException) { @@ -50,10 +50,12 @@ public class CustomOAuth2AuthenticationFailureHandler if (error.getErrorCode().equals("Password must not be null")) { errorCode = "userAlreadyExistsWeb"; } - log.error("OAuth2 Authentication error: " + errorCode); - log.error("OAuth2AuthenticationException", exception); - getRedirectStrategy().sendRedirect(request, response, "/login?erroroauth=" + errorCode); - return; + + log.error( + "OAuth2 Authentication error: {}", + errorCode != null ? errorCode : exception.getMessage(), + exception); + getRedirectStrategy().sendRedirect(request, response, "/login?errorOAuth=" + errorCode); } log.error("Unhandled authentication exception", exception); super.onAuthenticationFailure(request, response, exception); diff --git a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java index 8c40137ce..b33efd9ca 100644 --- a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java @@ -20,19 +20,18 @@ import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; import stirling.software.SPDF.model.AuthenticationType; -import stirling.software.SPDF.model.provider.UnsupportedProviderException; +import stirling.software.SPDF.model.exception.UnsupportedProviderException; import stirling.software.SPDF.utils.RequestUriUtils; public class CustomOAuth2AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { - private LoginAttemptService loginAttemptService; - - private ApplicationProperties applicationProperties; - private UserService userService; + private final LoginAttemptService loginAttemptService; + private final ApplicationProperties applicationProperties; + private final UserService userService; public CustomOAuth2AuthenticationSuccessHandler( - final LoginAttemptService loginAttemptService, + LoginAttemptService loginAttemptService, ApplicationProperties applicationProperties, UserService userService) { this.applicationProperties = applicationProperties; @@ -48,11 +47,9 @@ public class CustomOAuth2AuthenticationSuccessHandler Object principal = authentication.getPrincipal(); String username = ""; - if (principal instanceof OAuth2User) { - OAuth2User oauthUser = (OAuth2User) principal; + if (principal instanceof OAuth2User oauthUser) { username = oauthUser.getName(); - } else if (principal instanceof UserDetails) { - UserDetails oauthUser = (UserDetails) principal; + } else if (principal instanceof UserDetails oauthUser) { username = oauthUser.getUsername(); } @@ -78,6 +75,7 @@ public class CustomOAuth2AuthenticationSuccessHandler throw new LockedException( "Your account has been locked due to too many failed login attempts."); } + if (userService.isUserDisabled(username)) { getRedirectStrategy() .sendRedirect(request, response, "/logout?userIsDisabled=true"); @@ -87,13 +85,14 @@ public class CustomOAuth2AuthenticationSuccessHandler && userService.hasPassword(username) && !userService.isAuthenticationTypeByUsername(username, AuthenticationType.SSO) && oAuth.getAutoCreateUser()) { - response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true"); + response.sendRedirect(contextPath + "/logout?oAuth2AuthenticationErrorWeb=true"); return; } + try { if (oAuth.getBlockRegistration() && !userService.usernameExistsIgnoreCase(username)) { - response.sendRedirect(contextPath + "/logout?oauth2_admin_blocked_user=true"); + response.sendRedirect(contextPath + "/logout?oAuth2AdminBlockedUser=true"); return; } if (principal instanceof OAuth2User) { diff --git a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2UserService.java b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2UserService.java index 2c683b387..117c9de8f 100644 --- a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2UserService.java +++ b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2UserService.java @@ -17,19 +17,19 @@ import stirling.software.SPDF.config.security.LoginAttemptService; import stirling.software.SPDF.config.security.UserService; 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.User; +import stirling.software.SPDF.model.UsernameAttribute; @Slf4j public class CustomOAuth2UserService implements OAuth2UserService { private final OidcUserService delegate = new OidcUserService(); - private UserService userService; + private final UserService userService; - private LoginAttemptService loginAttemptService; + private final LoginAttemptService loginAttemptService; - private ApplicationProperties applicationProperties; + private final ApplicationProperties applicationProperties; public CustomOAuth2UserService( ApplicationProperties applicationProperties, @@ -42,34 +42,26 @@ public class CustomOAuth2UserService implements OAuth2UserService internalUser = + userService.findByUsernameIgnoreCase(user.getAttribute(usernameAttributeKey)); - Optional duser = userService.findByUsernameIgnoreCase(username); - if (duser.isPresent()) { - if (loginAttemptService.isBlocked(username)) { + if (internalUser.isPresent()) { + String internalUsername = internalUser.get().getUsername(); + if (loginAttemptService.isBlocked(internalUsername)) { throw new LockedException( - "Your account has been locked due to too many failed login attempts."); + "The account " + + internalUsername + + " has been locked due to too many failed login attempts."); } - if (userService.hasPassword(username)) { + if (userService.hasPassword(usernameAttributeKey)) { throw new IllegalArgumentException("Password must not be null"); } } @@ -79,7 +71,7 @@ public class CustomOAuth2UserService implements OAuth2UserService registrations = new ArrayList<>(); githubClientRegistration().ifPresent(registrations::add); oidcClientRegistration().ifPresent(registrations::add); googleClientRegistration().ifPresent(registrations::add); keycloakClientRegistration().ifPresent(registrations::add); + if (registrations.isEmpty()) { - log.error("At least one OAuth2 provider must be configured"); - System.exit(1); + log.error("No OAuth2 provider registered"); + throw new NoProviderFoundException("At least one OAuth2 provider must be configured."); } + return new InMemoryClientRegistrationRepository(registrations); } - private Optional googleClientRegistration() { - OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); - if (oauth == null || !oauth.getEnabled()) { - return Optional.empty(); - } - Client client = oauth.getClient(); - if (client == null) { - return Optional.empty(); - } - GoogleProvider google = client.getGoogle(); - return google != null && google.isSettingsValid() - ? Optional.of( - ClientRegistration.withRegistrationId(google.getName()) - .clientId(google.getClientId()) - .clientSecret(google.getClientSecret()) - .scope(google.getScopes()) - .authorizationUri(google.getAuthorizationuri()) - .tokenUri(google.getTokenuri()) - .userInfoUri(google.getUserinfouri()) - .userNameAttributeName(google.getUseAsUsername()) - .clientName(google.getClientName()) - .redirectUri("{baseUrl}/login/oauth2/code/" + google.getName()) - .authorizationGrantType( - org.springframework.security.oauth2.core - .AuthorizationGrantType.AUTHORIZATION_CODE) - .build()) - : Optional.empty(); - } - private Optional keycloakClientRegistration() { - OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); - if (oauth == null || !oauth.getEnabled()) { + OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2(); + + if (isOAuth2Enabled(oauth2) || isClientInitialised(oauth2)) { return Optional.empty(); } - Client client = oauth.getClient(); - if (client == null) { - return Optional.empty(); - } - KeycloakProvider keycloak = client.getKeycloak(); - return keycloak != null && keycloak.isSettingsValid() + + Client client = oauth2.getClient(); + KeycloakProvider keycloakClient = client.getKeycloak(); + Provider keycloak = + new KeycloakProvider( + keycloakClient.getIssuer(), + keycloakClient.getClientId(), + keycloakClient.getClientSecret(), + keycloakClient.getScopes(), + keycloakClient.getUseAsUsername()); + + return validateProvider(keycloak) ? Optional.of( ClientRegistrations.fromIssuerLocation(keycloak.getIssuer()) .registrationId(keycloak.getName()) .clientId(keycloak.getClientId()) .clientSecret(keycloak.getClientSecret()) .scope(keycloak.getScopes()) - .userNameAttributeName(keycloak.getUseAsUsername()) + .userNameAttributeName(keycloak.getUseAsUsername().getName()) .clientName(keycloak.getClientName()) .build()) : Optional.empty(); } + private Optional googleClientRegistration() { + OAUTH2 oAuth2 = applicationProperties.getSecurity().getOauth2(); + + if (isOAuth2Enabled(oAuth2) || isClientInitialised(oAuth2)) { + return Optional.empty(); + } + + Client client = oAuth2.getClient(); + GoogleProvider googleClient = client.getGoogle(); + Provider google = + new GoogleProvider( + googleClient.getClientId(), + googleClient.getClientSecret(), + googleClient.getScopes(), + googleClient.getUseAsUsername()); + + return validateProvider(google) + ? Optional.of( + ClientRegistration.withRegistrationId(google.getName()) + .clientId(google.getClientId()) + .clientSecret(google.getClientSecret()) + .scope(google.getScopes()) + .authorizationUri(google.getAuthorizationUri()) + .tokenUri(google.getTokenUri()) + .userInfoUri(google.getUserInfoUri()) + .userNameAttributeName(google.getUseAsUsername().getName()) + .clientName(google.getClientName()) + .redirectUri(REDIRECT_URI_PATH + google.getName()) + .authorizationGrantType(AUTHORIZATION_CODE) + .build()) + : Optional.empty(); + } + private Optional githubClientRegistration() { - OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); - if (oauth == null || !oauth.getEnabled()) { + OAUTH2 oAuth2 = applicationProperties.getSecurity().getOauth2(); + + if (isOAuth2Enabled(oAuth2)) { return Optional.empty(); } - Client client = oauth.getClient(); - if (client == null) { - return Optional.empty(); - } - GithubProvider github = client.getGithub(); - return github != null && github.isSettingsValid() + + Client client = oAuth2.getClient(); + GitHubProvider githubClient = client.getGithub(); + Provider github = + new GitHubProvider( + githubClient.getClientId(), + githubClient.getClientSecret(), + githubClient.getScopes(), + githubClient.getUseAsUsername()); + + return validateProvider(github) ? Optional.of( ClientRegistration.withRegistrationId(github.getName()) .clientId(github.getClientId()) .clientSecret(github.getClientSecret()) .scope(github.getScopes()) - .authorizationUri(github.getAuthorizationuri()) - .tokenUri(github.getTokenuri()) - .userInfoUri(github.getUserinfouri()) - .userNameAttributeName(github.getUseAsUsername()) + .authorizationUri(github.getAuthorizationUri()) + .tokenUri(github.getTokenUri()) + .userInfoUri(github.getUserInfoUri()) + .userNameAttributeName(github.getUseAsUsername().getName()) .clientName(github.getClientName()) - .redirectUri("{baseUrl}/login/oauth2/code/" + github.getName()) - .authorizationGrantType( - org.springframework.security.oauth2.core - .AuthorizationGrantType.AUTHORIZATION_CODE) + .redirectUri(REDIRECT_URI_PATH + github.getName()) + .authorizationGrantType(AUTHORIZATION_CODE) .build()) : Optional.empty(); } private Optional oidcClientRegistration() { OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); - if (oauth == null - || oauth.getIssuer() == null - || oauth.getIssuer().isEmpty() - || oauth.getClientId() == null - || oauth.getClientId().isEmpty() - || oauth.getClientSecret() == null - || oauth.getClientSecret().isEmpty() - || oauth.getScopes() == null - || oauth.getScopes().isEmpty() - || oauth.getUseAsUsername() == null - || oauth.getUseAsUsername().isEmpty()) { + + if (isOAuth2Enabled(oauth) || isClientInitialised(oauth)) { return Optional.empty(); } - return Optional.of( - ClientRegistrations.fromIssuerLocation(oauth.getIssuer()) - .registrationId("oidc") - .clientId(oauth.getClientId()) - .clientSecret(oauth.getClientSecret()) - .scope(oauth.getScopes()) - .userNameAttributeName(oauth.getUseAsUsername()) - .clientName("OIDC") - .build()); + + String name = oauth.getProvider(); + String firstChar = String.valueOf(name.charAt(0)); + String clientName = name.replaceFirst(firstChar, firstChar.toUpperCase()); + + Provider oidcProvider = + new Provider( + oauth.getIssuer(), + name, + clientName, + oauth.getClientId(), + oauth.getClientSecret(), + oauth.getScopes(), + UsernameAttribute.valueOf(oauth.getUseAsUsername().toUpperCase()), + oauth.getLogoutUrl(), + null, + null, + null); + + return !isStringEmpty(oidcProvider.getIssuer()) || validateProvider(oidcProvider) + ? Optional.of( + ClientRegistrations.fromIssuerLocation(oauth.getIssuer()) + .registrationId(name) + .clientId(oidcProvider.getClientId()) + .clientSecret(oidcProvider.getClientSecret()) + .scope(oidcProvider.getScopes()) + .userNameAttributeName(oidcProvider.getUseAsUsername().getName()) + .clientName(clientName) + .redirectUri(REDIRECT_URI_PATH + name) + .authorizationGrantType(AUTHORIZATION_CODE) + .build()) + : Optional.empty(); + } + + private boolean isOAuth2Enabled(OAUTH2 oAuth2) { + return oAuth2 == null || !oAuth2.getEnabled(); + } + + private boolean isClientInitialised(OAUTH2 oauth2) { + Client client = oauth2.getClient(); + return client == null; } /* This following function is to grant Authorities to the OAUTH2 user from the values stored in the database. This is required for the internal; 'hasRole()' function to give out the correct role. */ + @Bean - @ConditionalOnProperty( - value = "security.oauth2.enabled", - havingValue = "true", - matchIfMissing = false) + @ConditionalOnProperty(value = "security.oauth2.enabled", havingValue = "true") GrantedAuthoritiesMapper userAuthoritiesMapper() { return (authorities) -> { Set mappedAuthorities = new HashSet<>(); @@ -200,11 +241,9 @@ public class OAuth2Configuration { (String) oauth2Auth.getAttributes().get(useAsUsername)); if (userOpt.isPresent()) { User user = userOpt.get(); - if (user != null) { - mappedAuthorities.add( - new SimpleGrantedAuthority( - userService.findRole(user).getAuthority())); - } + mappedAuthorities.add( + new SimpleGrantedAuthority( + userService.findRole(user).getAuthority())); } } }); diff --git a/src/main/java/stirling/software/SPDF/config/security/saml2/CertificateUtils.java b/src/main/java/stirling/software/SPDF/config/security/saml2/CertificateUtils.java index 8ec512265..6788d6716 100644 --- a/src/main/java/stirling/software/SPDF/config/security/saml2/CertificateUtils.java +++ b/src/main/java/stirling/software/SPDF/config/security/saml2/CertificateUtils.java @@ -13,8 +13,10 @@ import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemReader; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.core.io.Resource; +@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true") public class CertificateUtils { public static X509Certificate readCertificate(Resource certificateResource) throws Exception { diff --git a/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticatedPrincipal.java b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticatedPrincipal.java index efd0dc199..04a83f4d8 100644 --- a/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticatedPrincipal.java +++ b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticatedPrincipal.java @@ -4,27 +4,13 @@ import java.io.Serializable; import java.util.List; import java.util.Map; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; -public class CustomSaml2AuthenticatedPrincipal +@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true") +public record CustomSaml2AuthenticatedPrincipal(String name, Map> attributes, String nameId, List sessionIndexes) implements Saml2AuthenticatedPrincipal, Serializable { - private final String name; - private final Map> attributes; - private final String nameId; - private final List sessionIndexes; - - public CustomSaml2AuthenticatedPrincipal( - String name, - Map> attributes, - String nameId, - List sessionIndexes) { - this.name = name; - this.attributes = attributes; - this.nameId = nameId; - this.sessionIndexes = sessionIndexes; - } - @Override public String getName() { return this.name; @@ -35,11 +21,4 @@ public class CustomSaml2AuthenticatedPrincipal return this.attributes; } - public String getNameId() { - return this.nameId; - } - - public List getSessionIndexes() { - return this.sessionIndexes; - } } diff --git a/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationFailureHandler.java b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationFailureHandler.java index 32bc25db6..884c3bd2e 100644 --- a/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationFailureHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationFailureHandler.java @@ -2,19 +2,20 @@ package stirling.software.SPDF.config.security.saml2; import java.io.IOException; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.authentication.ProviderNotFoundException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; -import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @Slf4j +@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true") public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Override @@ -22,18 +23,19 @@ public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthentica HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) - throws IOException, ServletException { + throws IOException { + log.error("Authentication error", exception); + if (exception instanceof Saml2AuthenticationException) { Saml2Error error = ((Saml2AuthenticationException) exception).getSaml2Error(); getRedirectStrategy() - .sendRedirect(request, response, "/login?erroroauth=" + error.getErrorCode()); + .sendRedirect(request, response, "/login?errorOAuth=" + error.getErrorCode()); } else if (exception instanceof ProviderNotFoundException) { getRedirectStrategy() .sendRedirect( request, response, - "/login?erroroauth=not_authentication_provider_found"); + "/login?errorOAuth=not_authentication_provider_found"); } - log.error("AuthenticationException: " + exception); } } diff --git a/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationSuccessHandler.java index 0f8893239..b7c2256c4 100644 --- a/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationSuccessHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationSuccessHandler.java @@ -21,7 +21,7 @@ import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2; import stirling.software.SPDF.model.AuthenticationType; -import stirling.software.SPDF.model.provider.UnsupportedProviderException; +import stirling.software.SPDF.model.exception.UnsupportedProviderException; import stirling.software.SPDF.utils.RequestUriUtils; @AllArgsConstructor @@ -42,7 +42,7 @@ public class CustomSaml2AuthenticationSuccessHandler log.debug("Starting SAML2 authentication success handling"); if (principal instanceof CustomSaml2AuthenticatedPrincipal) { - String username = ((CustomSaml2AuthenticatedPrincipal) principal).getName(); + String username = ((CustomSaml2AuthenticatedPrincipal) principal).name(); log.debug("Authenticated principal found for user: {}", username); HttpSession session = request.getSession(false); @@ -97,7 +97,7 @@ public class CustomSaml2AuthenticationSuccessHandler "User {} exists with password but is not SSO user, redirecting to logout", username); response.sendRedirect( - contextPath + "/logout?oauth2AuthenticationErrorWeb=true"); + contextPath + "/logout?oAuth2AuthenticationErrorWeb=true"); return; } @@ -105,20 +105,18 @@ public class CustomSaml2AuthenticationSuccessHandler if (saml2.getBlockRegistration() && !userExists) { log.debug("Registration blocked for new user: {}", username); response.sendRedirect( - contextPath + "/login?erroroauth=oauth2_admin_blocked_user"); + contextPath + "/login?errorOAuth=oAuth2AdminBlockedUser"); return; } log.debug("Processing SSO post-login for user: {}", username); userService.processSSOPostLogin(username, saml2.getAutoCreateUser()); log.debug("Successfully processed authentication for user: {}", username); response.sendRedirect(contextPath + "/"); - return; } catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) { log.debug( "Invalid username detected for user: {}, redirecting to logout", username); response.sendRedirect(contextPath + "/logout?invalidUsername=true"); - return; } } } else { diff --git a/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2ResponseAuthenticationConverter.java b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2ResponseAuthenticationConverter.java index 47a89414d..3a576aea5 100644 --- a/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2ResponseAuthenticationConverter.java +++ b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2ResponseAuthenticationConverter.java @@ -7,6 +7,7 @@ import org.opensaml.saml.saml2.core.Assertion; import org.opensaml.saml.saml2.core.Attribute; import org.opensaml.saml.saml2.core.AttributeStatement; import org.opensaml.saml.saml2.core.AuthnStatement; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.core.convert.converter.Converter; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken; @@ -18,10 +19,11 @@ import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.model.User; @Slf4j +@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true") public class CustomSaml2ResponseAuthenticationConverter implements Converter { - private UserService userService; + private final UserService userService; public CustomSaml2ResponseAuthenticationConverter(UserService userService) { this.userService = userService; @@ -61,10 +63,10 @@ public class CustomSaml2ResponseAuthenticationConverter Map> attributes = extractAttributes(assertion); // Debug log with actual values - log.debug("Extracted SAML Attributes: " + attributes); + log.debug("Extracted SAML Attributes: {}", attributes); // Try to get username/identifier in order of preference - String userIdentifier = null; + String userIdentifier; if (hasAttribute(attributes, "username")) { userIdentifier = getFirstAttributeValue(attributes, "username"); } else if (hasAttribute(attributes, "emailaddress")) { @@ -84,10 +86,8 @@ public class CustomSaml2ResponseAuthenticationConverter SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_USER"); if (userOpt.isPresent()) { User user = userOpt.get(); - if (user != null) { - simpleGrantedAuthority = - new SimpleGrantedAuthority(userService.findRole(user).getAuthority()); - } + simpleGrantedAuthority = + new SimpleGrantedAuthority(userService.findRole(user).getAuthority()); } List sessionIndexes = new ArrayList<>(); @@ -102,7 +102,7 @@ public class CustomSaml2ResponseAuthenticationConverter return new Saml2Authentication( principal, responseToken.getToken().getSaml2Response(), - Collections.singletonList(simpleGrantedAuthority)); + List.of(simpleGrantedAuthority)); } private boolean hasAttribute(Map> attributes, String name) { diff --git a/src/main/java/stirling/software/SPDF/config/security/saml2/SAML2Configuration.java b/src/main/java/stirling/software/SPDF/config/security/saml2/SAML2Configuration.java index bc72df7ad..a6b487ec6 100644 --- a/src/main/java/stirling/software/SPDF/config/security/saml2/SAML2Configuration.java +++ b/src/main/java/stirling/software/SPDF/config/security/saml2/SAML2Configuration.java @@ -11,10 +11,12 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType; +import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; +import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository; import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver; import jakarta.servlet.http.HttpServletRequest; @@ -26,27 +28,20 @@ import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2; @Configuration @Slf4j -@ConditionalOnProperty( - value = "security.saml2.enabled", - havingValue = "true", - matchIfMissing = false) +@ConditionalOnProperty(value = "security.saml2.enabled", havingValue = "true") public class SAML2Configuration { private final ApplicationProperties applicationProperties; public SAML2Configuration(ApplicationProperties applicationProperties) { - this.applicationProperties = applicationProperties; } @Bean - @ConditionalOnProperty( - name = "security.saml2.enabled", - havingValue = "true", - matchIfMissing = false) + @ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true") public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception { SAML2 samlConf = applicationProperties.getSecurity().getSaml2(); - X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getidpCert()); + X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getIdpCert()); Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert); Resource privateKeyResource = samlConf.getPrivateKey(); Resource certificateResource = samlConf.getSpCert(); @@ -58,81 +53,124 @@ public class SAML2Configuration { RelyingPartyRegistration rp = RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId()) .signingX509Credentials(c -> c.add(signingCredential)) + .entityId(samlConf.getIdpIssuer()) + .singleLogoutServiceBinding(Saml2MessageBinding.POST) + .singleLogoutServiceLocation(samlConf.getIdpSingleLogoutUrl()) + .singleLogoutServiceResponseLocation("http://localhost:8080/login") + .assertionConsumerServiceBinding(Saml2MessageBinding.POST) + .assertionConsumerServiceLocation( + "{baseUrl}/login/saml2/sso/{registrationId}") .assertingPartyMetadata( metadata -> metadata.entityId(samlConf.getIdpIssuer()) - .singleSignOnServiceLocation( - samlConf.getIdpSingleLoginUrl()) .verificationX509Credentials( c -> c.add(verificationCredential)) .singleSignOnServiceBinding( Saml2MessageBinding.POST) + .singleSignOnServiceLocation( + samlConf.getIdpSingleLoginUrl()) + .singleLogoutServiceBinding( + Saml2MessageBinding.POST) + .singleLogoutServiceLocation( + samlConf.getIdpSingleLogoutUrl()) .wantAuthnRequestsSigned(true)) .build(); return new InMemoryRelyingPartyRegistrationRepository(rp); } @Bean - @ConditionalOnProperty( - name = "security.saml2.enabled", - havingValue = "true", - matchIfMissing = false) + @ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true") public OpenSaml4AuthenticationRequestResolver authenticationRequestResolver( RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) { OpenSaml4AuthenticationRequestResolver resolver = new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository); + resolver.setAuthnRequestCustomizer( customizer -> { - log.debug("Customizing SAML Authentication request"); - AuthnRequest authnRequest = customizer.getAuthnRequest(); - log.debug("AuthnRequest ID: {}", authnRequest.getID()); - if (authnRequest.getID() == null) { - authnRequest.setID("ARQ" + UUID.randomUUID().toString()); - } - log.debug("AuthnRequest new ID after set: {}", authnRequest.getID()); - log.debug("AuthnRequest IssueInstant: {}", authnRequest.getIssueInstant()); - log.debug( - "AuthnRequest Issuer: {}", - authnRequest.getIssuer() != null - ? authnRequest.getIssuer().getValue() - : "null"); HttpServletRequest request = customizer.getRequest(); - // Log HTTP request details - log.debug("HTTP Request Method: {}", request.getMethod()); - log.debug("Request URI: {}", request.getRequestURI()); - log.debug("Request URL: {}", request.getRequestURL().toString()); - log.debug("Query String: {}", request.getQueryString()); - log.debug("Remote Address: {}", request.getRemoteAddr()); - // Log headers - Collections.list(request.getHeaderNames()) - .forEach( - headerName -> { - log.debug( - "Header - {}: {}", - headerName, - request.getHeader(headerName)); - }); - // Log SAML specific parameters - log.debug("SAML Request Parameters:"); - log.debug("SAMLRequest: {}", request.getParameter("SAMLRequest")); - log.debug("RelayState: {}", request.getParameter("RelayState")); - // Log session debugrmation if exists - if (request.getSession(false) != null) { - log.debug("Session ID: {}", request.getSession().getId()); - } - // Log any assertions consumer service details if present - if (authnRequest.getAssertionConsumerServiceURL() != null) { + AuthnRequest authnRequest = customizer.getAuthnRequest(); + HttpSessionSaml2AuthenticationRequestRepository requestRepository = + new HttpSessionSaml2AuthenticationRequestRepository(); + AbstractSaml2AuthenticationRequest saml2AuthenticationRequest = + requestRepository.loadAuthenticationRequest(request); + + if (saml2AuthenticationRequest != null) { + String sessionId = request.getSession(false).getId(); + log.debug( - "AssertionConsumerServiceURL: {}", - authnRequest.getAssertionConsumerServiceURL()); - } - // Log NameID policy if present - if (authnRequest.getNameIDPolicy() != null) { - log.debug( - "NameIDPolicy Format: {}", - authnRequest.getNameIDPolicy().getFormat()); + "Retrieving SAML 2 authentication request ID from the current HTTP session {}", + sessionId); + + String authenticationRequestId = saml2AuthenticationRequest.getId(); + + if (!authenticationRequestId.isBlank()) { + authnRequest.setID(authenticationRequestId); + } else { + log.warn( + "No authentication request found for HTTP session {}. Generating new ID", + sessionId); + authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1)); + } + } else { + log.debug("Generating new authentication request ID"); + authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1)); } + + logAuthnRequestDetails(authnRequest); + logHttpRequestDetails(request); }); return resolver; } + + private static void logAuthnRequestDetails(AuthnRequest authnRequest) { + String message = + """ + AuthnRequest: + + ID: {} + Issuer: {} + IssueInstant: {} + AssertionConsumerService (ACS) URL: {} + """; + log.debug( + message, + authnRequest.getID(), + authnRequest.getIssuer() != null ? authnRequest.getIssuer().getValue() : null, + authnRequest.getIssueInstant(), + authnRequest.getAssertionConsumerServiceURL()); + + if (authnRequest.getNameIDPolicy() != null) { + log.debug("NameIDPolicy Format: {}", authnRequest.getNameIDPolicy().getFormat()); + } + } + + private static void logHttpRequestDetails(HttpServletRequest request) { + log.debug("HTTP Headers: "); + Collections.list(request.getHeaderNames()) + .forEach( + headerName -> + log.debug("{}: {}", headerName, request.getHeader(headerName))); + String message = + """ + HTTP Request Method: {} + Session ID: {} + Request Path: {} + Query String: {} + Remote Address: {} + + SAML Request Parameters: + + SAMLRequest: {} + RelayState: {} + """; + log.debug( + message, + request.getMethod(), + request.getSession().getId(), + request.getRequestURI(), + request.getQueryString(), + request.getRemoteAddr(), + request.getParameter("SAMLRequest"), + request.getParameter("RelayState")); + } } diff --git a/src/main/java/stirling/software/SPDF/config/security/session/SessionPersistentRegistry.java b/src/main/java/stirling/software/SPDF/config/security/session/SessionPersistentRegistry.java index 3c9ea74dc..be3de6f90 100644 --- a/src/main/java/stirling/software/SPDF/config/security/session/SessionPersistentRegistry.java +++ b/src/main/java/stirling/software/SPDF/config/security/session/SessionPersistentRegistry.java @@ -48,7 +48,7 @@ public class SessionPersistentRegistry implements SessionRegistry { } else if (principal instanceof OAuth2User) { principalName = ((OAuth2User) principal).getName(); } else if (principal instanceof CustomSaml2AuthenticatedPrincipal) { - principalName = ((CustomSaml2AuthenticatedPrincipal) principal).getName(); + principalName = ((CustomSaml2AuthenticatedPrincipal) principal).name(); } else if (principal instanceof String) { principalName = (String) principal; } @@ -79,7 +79,7 @@ public class SessionPersistentRegistry implements SessionRegistry { } else if (principal instanceof OAuth2User) { principalName = ((OAuth2User) principal).getName(); } else if (principal instanceof CustomSaml2AuthenticatedPrincipal) { - principalName = ((CustomSaml2AuthenticatedPrincipal) principal).getName(); + principalName = ((CustomSaml2AuthenticatedPrincipal) principal).name(); } else if (principal instanceof String) { principalName = (String) principal; } diff --git a/src/main/java/stirling/software/SPDF/controller/api/UserController.java b/src/main/java/stirling/software/SPDF/controller/api/UserController.java index 3e37a8ed8..9af61553c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/UserController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/UserController.java @@ -36,7 +36,7 @@ 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; -import stirling.software.SPDF.model.provider.UnsupportedProviderException; +import stirling.software.SPDF.model.exception.UnsupportedProviderException; @Controller @Tag(name = "User", description = "User APIs") @@ -126,7 +126,7 @@ public class UserController { return new RedirectView("/change-creds?messageType=notAuthenticated", true); } Optional userOpt = userService.findByUsernameIgnoreCase(principal.getName()); - if (userOpt == null || userOpt.isEmpty()) { + if (userOpt.isEmpty()) { return new RedirectView("/change-creds?messageType=userNotFound", true); } User user = userOpt.get(); @@ -154,7 +154,7 @@ public class UserController { return new RedirectView("/account?messageType=notAuthenticated", true); } Optional userOpt = userService.findByUsernameIgnoreCase(principal.getName()); - if (userOpt == null || userOpt.isEmpty()) { + if (userOpt.isEmpty()) { return new RedirectView("/account?messageType=userNotFound", true); } User user = userOpt.get(); @@ -176,7 +176,7 @@ public class UserController { for (Map.Entry entry : paramMap.entrySet()) { updates.put(entry.getKey(), entry.getValue()[0]); } - log.debug("Processed updates: " + updates); + log.debug("Processed updates: {}", updates); // Assuming you have a method in userService to update the settings for a user userService.updateUserSettings(principal.getName(), updates); // Redirect to a page of your choice after updating @@ -199,7 +199,7 @@ public class UserController { Optional userOpt = userService.findByUsernameIgnoreCase(username); if (userOpt.isPresent()) { User user = userOpt.get(); - if (user != null && user.getUsername().equalsIgnoreCase(username)) { + if (user.getUsername().equalsIgnoreCase(username)) { return new RedirectView("/addUsers?messageType=usernameExists", true); } } @@ -276,7 +276,7 @@ public class UserController { Authentication authentication) throws SQLException, UnsupportedProviderException { Optional userOpt = userService.findByUsernameIgnoreCase(username); - if (!userOpt.isPresent()) { + if (userOpt.isEmpty()) { return new RedirectView("/addUsers?messageType=userNotFound", true); } if (!userService.usernameExistsIgnoreCase(username)) { @@ -295,20 +295,20 @@ public class UserController { List principals = sessionRegistry.getAllPrincipals(); String userNameP = ""; for (Object principal : principals) { - List sessionsInformations = + List sessionsInformation = 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 CustomSaml2AuthenticatedPrincipal) { - userNameP = ((CustomSaml2AuthenticatedPrincipal) principal).getName(); + userNameP = ((CustomSaml2AuthenticatedPrincipal) principal).name(); } else if (principal instanceof String) { userNameP = (String) principal; } if (userNameP.equalsIgnoreCase(username)) { - for (SessionInformation sessionsInformation : sessionsInformations) { - sessionRegistry.expireSession(sessionsInformation.getSessionId()); + for (SessionInformation sessionInfo : sessionsInformation) { + sessionRegistry.expireSession(sessionInfo.getSessionId()); } } } diff --git a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java index 845ad675e..b979e5184 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java @@ -1,8 +1,15 @@ package stirling.software.SPDF.controller.web; +import static stirling.software.SPDF.utils.validation.Validator.validateProvider; + import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.util.*; +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.springframework.security.access.prepost.PreAuthorize; @@ -24,12 +31,16 @@ 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; import stirling.software.SPDF.model.ApplicationProperties.Security; 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.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; import stirling.software.SPDF.model.provider.KeycloakProvider; import stirling.software.SPDF.repository.UserRepository; @@ -39,12 +50,12 @@ import stirling.software.SPDF.repository.UserRepository; @Tag(name = "Account Security", description = "Account Security APIs") public class AccountWebController { + public static final String OAUTH_2_AUTHORIZATION = "/oauth2/authorization/"; + private final ApplicationProperties applicationProperties; - private final SessionPersistentRegistry sessionPersistentRegistry; - - private final UserRepository // Assuming you have a repository for user operations - userRepository; + // Assuming you have a repository for user operations + private final UserRepository userRepository; public AccountWebController( ApplicationProperties applicationProperties, @@ -61,132 +72,127 @@ public class AccountWebController { if (authentication != null && authentication.isAuthenticated()) { return "redirect:/"; } + Map providerList = new HashMap<>(); Security securityProps = applicationProperties.getSecurity(); OAUTH2 oauth = securityProps.getOauth2(); + if (oauth != null) { if (oauth.getEnabled()) { if (oauth.isSettingsValid()) { - providerList.put("/oauth2/authorization/oidc", oauth.getProvider()); + String firstChar = String.valueOf(oauth.getProvider().charAt(0)); + String clientName = + oauth.getProvider().replaceFirst(firstChar, firstChar.toUpperCase()); + providerList.put(OAUTH_2_AUTHORIZATION + oauth.getProvider(), clientName); } + Client client = oauth.getClient(); + if (client != null) { GoogleProvider google = client.getGoogle(); - if (google.isSettingsValid()) { + + if (validateProvider(google)) { providerList.put( - "/oauth2/authorization/" + google.getName(), - google.getClientName()); + OAUTH_2_AUTHORIZATION + google.getName(), google.getClientName()); } - GithubProvider github = client.getGithub(); - if (github.isSettingsValid()) { + + GitHubProvider github = client.getGithub(); + + if (validateProvider(github)) { providerList.put( - "/oauth2/authorization/" + github.getName(), - github.getClientName()); + OAUTH_2_AUTHORIZATION + github.getName(), github.getClientName()); } + KeycloakProvider keycloak = client.getKeycloak(); - if (keycloak.isSettingsValid()) { + + if (validateProvider(keycloak)) { providerList.put( - "/oauth2/authorization/" + keycloak.getName(), + OAUTH_2_AUTHORIZATION + keycloak.getName(), keycloak.getClientName()); } } } } + SAML2 saml2 = securityProps.getSaml2(); - if (securityProps.isSaml2Activ() - && applicationProperties.getSystem().getEnableAlphaFunctionality()) { - providerList.put("/saml2/authenticate/" + saml2.getRegistrationId(), "SAML 2"); + + if (securityProps.isSaml2Active() + && applicationProperties.getSystem().getEnableAlphaFunctionality() + && applicationProperties.getEnterpriseEdition().isEnabled()) { + String samlIdp = saml2.getProvider(); + String saml2AuthenticationPath = "/saml2/authenticate/" + saml2.getRegistrationId(); + + if (applicationProperties.getEnterpriseEdition().isSsoAutoLogin()) { + return "redirect:" + + request.getRequestURL() + + saml2AuthenticationPath; + } else { + providerList.put(saml2AuthenticationPath, samlIdp + " (SAML 2)"); + } } + // Remove any null keys/values from the providerList providerList .entrySet() .removeIf(entry -> entry.getKey() == null || entry.getValue() == null); - model.addAttribute("providerlist", providerList); + model.addAttribute("providerList", providerList); model.addAttribute("loginMethod", securityProps.getLoginMethod()); - boolean altLogin = providerList.size() > 0 ? securityProps.isAltLogin() : false; + + boolean altLogin = !providerList.isEmpty() ? securityProps.isAltLogin() : false; + model.addAttribute("altLogin", altLogin); model.addAttribute("currentPage", "login"); String error = request.getParameter("error"); + if (error != null) { switch (error) { - case "badcredentials": - error = "login.invalid"; - break; - case "locked": - error = "login.locked"; - break; - case "oauth2AuthenticationError": - error = "userAlreadyExistsOAuthMessage"; - break; - default: - break; + case "badCredentials" -> error = "login.invalid"; + case "locked" -> error = "login.locked"; + case "oauth2AuthenticationError" -> error = "userAlreadyExistsOAuthMessage"; } + 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"; - 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; - case "invalid_id_token": - erroroauth = "login.oauth2InvalidIdToken"; - break; - case "oauth2_admin_blocked_user": - erroroauth = "login.oauth2AdminBlockedUser"; - break; - case "userIsDisabled": - erroroauth = "login.userIsDisabled"; - break; - case "invalid_destination": - erroroauth = "login.invalid_destination"; - break; - case "relying_party_registration_not_found": - erroroauth = "login.relyingPartyRegistrationNotFound"; - break; + + String errorOAuth = request.getParameter("errorOAuth"); + + if (errorOAuth != null) { + switch (errorOAuth) { + case "oAuth2AutoCreateDisabled" -> errorOAuth = "login.oAuth2AutoCreateDisabled"; + case "invalidUsername" -> errorOAuth = "login.invalid"; + case "userAlreadyExistsWeb" -> errorOAuth = "userAlreadyExistsWebMessage"; + case "oAuth2AuthenticationErrorWeb" -> errorOAuth = "login.oauth2InvalidUserType"; + case "invalid_token_response" -> errorOAuth = "login.oauth2InvalidTokenResponse"; + case "authorization_request_not_found" -> + errorOAuth = "login.oauth2RequestNotFound"; + case "access_denied" -> errorOAuth = "login.oauth2AccessDenied"; + case "invalid_user_info_response" -> + errorOAuth = "login.oauth2InvalidUserInfoResponse"; + case "invalid_request" -> errorOAuth = "login.oauth2invalidRequest"; + case "invalid_id_token" -> errorOAuth = "login.oauth2InvalidIdToken"; + case "oAuth2AdminBlockedUser" -> errorOAuth = "login.oAuth2AdminBlockedUser"; + case "userIsDisabled" -> errorOAuth = "login.userIsDisabled"; + case "invalid_destination" -> errorOAuth = "login.invalid_destination"; + case "relying_party_registration_not_found" -> + errorOAuth = "login.relyingPartyRegistrationNotFound"; // Valid InResponseTo was not available from the validation context, unable to // evaluate - case "invalid_in_response_to": - erroroauth = "login.invalid_in_response_to"; - break; - case "not_authentication_provider_found": - erroroauth = "login.not_authentication_provider_found"; - break; - default: - break; + case "invalid_in_response_to" -> errorOAuth = "login.invalid_in_response_to"; + case "not_authentication_provider_found" -> + errorOAuth = "login.not_authentication_provider_found"; } - model.addAttribute("erroroauth", erroroauth); + + model.addAttribute("errorOAuth", errorOAuth); } + if (request.getParameter("messageType") != null) { model.addAttribute("messageType", "changedCredsMessage"); } + if (request.getParameter("logout") != null) { model.addAttribute("logoutMessage", "You have been logged out."); } + return "login"; } @@ -230,13 +236,11 @@ public class AccountWebController { .plus(maxInactiveInterval, ChronoUnit.SECONDS); if (now.isAfter(expirationTime)) { sessionPersistentRegistry.expireSession(sessionEntity.getSessionId()); - hasActiveSession = false; } else { hasActiveSession = !sessionEntity.isExpired(); } lastRequest = sessionEntity.getLastRequest(); } else { - hasActiveSession = false; // No session, set default last request time lastRequest = new Date(0); } @@ -273,53 +277,41 @@ public class AccountWebController { }) .collect(Collectors.toList()); String messageType = request.getParameter("messageType"); - String deleteMessage = null; + + String deleteMessage; if (messageType != null) { - switch (messageType) { - case "deleteCurrentUser": - deleteMessage = "deleteCurrentUserMessage"; - break; - case "deleteUsernameExists": - deleteMessage = "deleteUsernameExistsMessage"; - break; - default: - break; - } + deleteMessage = + switch (messageType) { + case "deleteCurrentUser" -> "deleteCurrentUserMessage"; + case "deleteUsernameExists" -> "deleteUsernameExistsMessage"; + default -> null; + }; + 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; - } + + String addMessage; + addMessage = + switch (messageType) { + case "usernameExists" -> "usernameExistsMessage"; + case "invalidUsername" -> "invalidUsernameMessage"; + case "invalidPassword" -> "invalidPasswordMessage"; + default -> null; + }; model.addAttribute("addMessage", addMessage); } - String changeMessage = null; + + String changeMessage; if (messageType != null) { - switch (messageType) { - case "userNotFound": - changeMessage = "userNotFoundMessage"; - break; - case "downgradeCurrentUser": - changeMessage = "downgradeCurrentUserMessage"; - break; - case "disabledCurrentUser": - changeMessage = "disabledCurrentUserMessage"; - break; - default: - changeMessage = messageType; - break; - } + changeMessage = + switch (messageType) { + case "userNotFound" -> "userNotFoundMessage"; + case "downgradeCurrentUser" -> "downgradeCurrentUserMessage"; + case "disabledCurrentUser" -> "disabledCurrentUserMessage"; + default -> messageType; + }; model.addAttribute("changeMessage", changeMessage); } + model.addAttribute("users", sortedUsers); model.addAttribute("currentUsername", authentication.getName()); model.addAttribute("roleDetails", roleDetails); @@ -340,78 +332,51 @@ public class AccountWebController { if (authentication != null && authentication.isAuthenticated()) { Object principal = authentication.getPrincipal(); String username = null; - if (principal instanceof UserDetails) { - // Cast the principal object to UserDetails - UserDetails userDetails = (UserDetails) principal; - // Retrieve username and other attributes + + // Retrieve username and other attributes and add login attributes to the model + if (principal instanceof UserDetails userDetails) { 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; - // Retrieve username and other attributes - username = - userDetails.getAttribute( - applicationProperties.getSecurity().getOauth2().getUseAsUsername()); - // Add oAuth2 Login attributes to the model + if (principal instanceof OAuth2User userDetails) { + username = userDetails.getName(); 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 (principal instanceof CustomSaml2AuthenticatedPrincipal userDetails) { + username = userDetails.name(); + model.addAttribute("saml2Login", true); } if (username != null) { // Fetch user details from the database - Optional user = - userRepository - .findByUsernameIgnoreCaseWithSettings( // Assuming findByUsername - // method exists - username); - if (!user.isPresent()) { + Optional user = userRepository.findByUsernameIgnoreCaseWithSettings(username); + + if (user.isEmpty()) { return "redirect:/error"; } + // Convert settings map to JSON string ObjectMapper objectMapper = new ObjectMapper(); String settingsJson; try { settingsJson = objectMapper.writeValueAsString(user.get().getSettings()); } catch (JsonProcessingException e) { - // Handle JSON conversion error - log.error("exception", e); + log.error("Error converting settings map", e); return "redirect:/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; - case "invalidUsername": - messageType = "invalidUsernameMessage"; - break; - default: - break; + case "notAuthenticated" -> messageType = "notAuthenticatedMessage"; + case "userNotFound" -> messageType = "userNotFoundMessage"; + case "incorrectPassword" -> messageType = "incorrectPasswordMessage"; + case "usernameExists" -> messageType = "usernameExistsMessage"; + case "invalidUsername" -> messageType = "invalidUsernameMessage"; } - model.addAttribute("messageType", messageType); } - // Add attributes to the model + model.addAttribute("username", username); + model.addAttribute("messageType", messageType); model.addAttribute("role", user.get().getRolesAsString()); model.addAttribute("settings", settingsJson); model.addAttribute("changeCredsFlag", user.get().isFirstLogin()); @@ -432,19 +397,12 @@ public class AccountWebController { } if (authentication != null && authentication.isAuthenticated()) { Object principal = authentication.getPrincipal(); - if (principal instanceof UserDetails) { - // Cast the principal object to UserDetails - UserDetails userDetails = (UserDetails) principal; - // Retrieve username and other attributes + if (principal instanceof UserDetails userDetails) { String username = userDetails.getUsername(); // Fetch user details from the database - Optional user = - userRepository - .findByUsernameIgnoreCase( // Assuming findByUsername method exists - username); - if (!user.isPresent()) { - // Handle error appropriately - // Example redirection in case of error + Optional user = userRepository.findByUsernameIgnoreCase(username); + if (user.isEmpty()) { + // Handle error appropriately, example redirection in case of error return "redirect:/error"; } String messageType = request.getParameter("messageType"); @@ -467,7 +425,7 @@ public class AccountWebController { } model.addAttribute("messageType", messageType); } - // Add attributes to the model + model.addAttribute("username", username); } } else { diff --git a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java index 0400b3d8b..0b4783783 100644 --- a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java +++ b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java @@ -1,5 +1,7 @@ package stirling.software.SPDF.model; +import static stirling.software.SPDF.utils.validation.Validator.*; + import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -12,7 +14,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.stream.Collectors; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -34,10 +35,11 @@ import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.config.InstallationPathConfig; import stirling.software.SPDF.config.YamlPropertySourceFactory; -import stirling.software.SPDF.model.provider.GithubProvider; +import stirling.software.SPDF.model.exception.UnsupportedProviderException; +import stirling.software.SPDF.model.provider.GitHubProvider; import stirling.software.SPDF.model.provider.GoogleProvider; import stirling.software.SPDF.model.provider.KeycloakProvider; -import stirling.software.SPDF.model.provider.UnsupportedProviderException; +import stirling.software.SPDF.model.provider.Provider; @Configuration @ConfigurationProperties(prefix = "") @@ -136,13 +138,13 @@ public class ApplicationProperties { || loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString())); } - public boolean isOauth2Activ() { + public boolean isOauth2Active() { return (oauth2 != null && oauth2.getEnabled() && !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString())); } - public boolean isSaml2Activ() { + public boolean isSaml2Active() { return (saml2 != null && saml2.getEnabled() && !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString())); @@ -158,6 +160,7 @@ public class ApplicationProperties { @Setter @ToString public static class SAML2 { + private String provider; private Boolean enabled = false; private Boolean autoCreateUser = false; private Boolean blockRegistration = false; @@ -195,7 +198,7 @@ public class ApplicationProperties { } } - public Resource getidpCert() { + public Resource getIdpCert() { if (idpCert == null) return null; if (idpCert.startsWith("classpath:")) { return new ClassPathResource(idpCert.substring("classpath:".length())); @@ -225,12 +228,11 @@ public class ApplicationProperties { private Collection scopes = new ArrayList<>(); private String provider; private Client client = new Client(); + private String logoutUrl; public void setScopes(String scopes) { List scopesList = - Arrays.stream(scopes.split(",")) - .map(String::trim) - .collect(Collectors.toList()); + Arrays.stream(scopes.split(",")).map(String::trim).toList(); this.scopes.addAll(scopesList); } @@ -243,32 +245,31 @@ public class ApplicationProperties { } public boolean isSettingsValid() { - return isValid(this.getIssuer(), "issuer") - && isValid(this.getClientId(), "clientId") - && isValid(this.getClientSecret(), "clientSecret") - && isValid(this.getScopes(), "scopes") - && isValid(this.getUseAsUsername(), "useAsUsername"); + return !isStringEmpty(this.getIssuer()) + && !isStringEmpty(this.getClientId()) + && !isStringEmpty(this.getClientSecret()) + && !isCollectionEmpty(this.getScopes()) + && !isStringEmpty(this.getUseAsUsername()); } @Data public static class Client { private GoogleProvider google = new GoogleProvider(); - private GithubProvider github = new GithubProvider(); + private GitHubProvider github = new GitHubProvider(); private KeycloakProvider keycloak = new KeycloakProvider(); public Provider get(String registrationId) throws UnsupportedProviderException { - switch (registrationId.toLowerCase()) { - case "google": - return getGoogle(); - case "github": - return getGithub(); - case "keycloak": - return getKeycloak(); - default: - throw new UnsupportedProviderException( - "Logout from the provider is not supported? Report it at" - + " https://github.com/Stirling-Tools/Stirling-PDF/issues"); - } + return switch (registrationId.toLowerCase()) { + case "google" -> getGoogle(); + case "github" -> getGithub(); + case "keycloak" -> getKeycloak(); + default -> + throw new UnsupportedProviderException( + "Logout from the provider " + + registrationId + + " is not supported. " + + "Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues"); + }; } } } @@ -335,10 +336,10 @@ public class ApplicationProperties { @Override public String toString() { return """ - Driver { - driverName='%s' - } - """ + Driver { + driverName='%s' + } + """ .formatted(driverName); } } diff --git a/src/main/java/stirling/software/SPDF/model/Provider.java b/src/main/java/stirling/software/SPDF/model/Provider.java deleted file mode 100644 index 87f5fa298..000000000 --- a/src/main/java/stirling/software/SPDF/model/Provider.java +++ /dev/null @@ -1,80 +0,0 @@ -package stirling.software.SPDF.model; - -import java.util.Collection; - -public class Provider implements ProviderInterface { - private String name; - private String clientName; - - public String getName() { - return name; - } - - public String getClientName() { - return clientName; - } - - protected boolean isValid(String value, String name) { - if (value != null && !value.trim().isEmpty()) { - return true; - } - return false; - } - - protected boolean isValid(Collection value, String name) { - if (value != null && !value.isEmpty()) { - return true; - } - return false; - } - - @Override - public Collection getScopes() { - throw new UnsupportedOperationException("Unimplemented method 'getScope'"); - } - - @Override - public void setScopes(String scopes) { - throw new UnsupportedOperationException("Unimplemented method 'setScope'"); - } - - @Override - public String getUseAsUsername() { - throw new UnsupportedOperationException("Unimplemented method 'getUseAsUsername'"); - } - - @Override - public void setUseAsUsername(String useAsUsername) { - throw new UnsupportedOperationException("Unimplemented method 'setUseAsUsername'"); - } - - @Override - public String getIssuer() { - throw new UnsupportedOperationException("Unimplemented method 'getIssuer'"); - } - - @Override - public void setIssuer(String issuer) { - throw new UnsupportedOperationException("Unimplemented method 'setIssuer'"); - } - - @Override - public String getClientSecret() { - throw new UnsupportedOperationException("Unimplemented method 'getClientSecret'"); - } - - @Override - public void setClientSecret(String clientSecret) { - throw new UnsupportedOperationException("Unimplemented method 'setClientSecret'"); - } - - @Override - public String getClientId() { - throw new UnsupportedOperationException("Unimplemented method 'getClientId'"); - } - - @Override - public void setClientId(String clientId) { - throw new UnsupportedOperationException("Unimplemented method 'setClientId'"); - } -} diff --git a/src/main/java/stirling/software/SPDF/model/ProviderInterface.java b/src/main/java/stirling/software/SPDF/model/ProviderInterface.java deleted file mode 100644 index d0d54827a..000000000 --- a/src/main/java/stirling/software/SPDF/model/ProviderInterface.java +++ /dev/null @@ -1,26 +0,0 @@ -package stirling.software.SPDF.model; - -import java.util.Collection; - -public interface ProviderInterface { - - public Collection getScopes(); - - public void setScopes(String scopes); - - public String getUseAsUsername(); - - public void setUseAsUsername(String useAsUsername); - - public String getIssuer(); - - public void setIssuer(String issuer); - - public String getClientSecret(); - - public void setClientSecret(String clientSecret); - - public String getClientId(); - - public void setClientId(String clientId); -} diff --git a/src/main/java/stirling/software/SPDF/model/UsernameAttribute.java b/src/main/java/stirling/software/SPDF/model/UsernameAttribute.java new file mode 100644 index 000000000..23e098a49 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/UsernameAttribute.java @@ -0,0 +1,24 @@ +package stirling.software.SPDF.model; + +import lombok.Getter; + +@Getter +public enum UsernameAttribute { + EMAIL("email"), + LOGIN("login"), + PROFILE("profile"), + NAME("name"), + USERNAME("username"), + NICKNAME("nickname"), + GIVEN_NAME("given_name"), + MIDDLE_NAME("middle_name"), + FAMILY_NAME("family_name"), + PREFERRED_NAME("preferred_name"), + PREFERRED_USERNAME("preferred_username"); + + private final String name; + + UsernameAttribute(final String name) { + this.name = name; + } +} diff --git a/src/main/java/stirling/software/SPDF/model/exception/NoProviderFoundException.java b/src/main/java/stirling/software/SPDF/model/exception/NoProviderFoundException.java new file mode 100644 index 000000000..162070f38 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/exception/NoProviderFoundException.java @@ -0,0 +1,11 @@ +package stirling.software.SPDF.model.exception; + +public class NoProviderFoundException extends Exception { + public NoProviderFoundException(String message) { + super(message); + } + + public NoProviderFoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/stirling/software/SPDF/model/provider/UnsupportedProviderException.java b/src/main/java/stirling/software/SPDF/model/exception/UnsupportedProviderException.java similarity index 76% rename from src/main/java/stirling/software/SPDF/model/provider/UnsupportedProviderException.java rename to src/main/java/stirling/software/SPDF/model/exception/UnsupportedProviderException.java index f90106015..d0bd8330c 100644 --- a/src/main/java/stirling/software/SPDF/model/provider/UnsupportedProviderException.java +++ b/src/main/java/stirling/software/SPDF/model/exception/UnsupportedProviderException.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.model.provider; +package stirling.software.SPDF.model.exception; public class UnsupportedProviderException extends Exception { public UnsupportedProviderException(String message) { diff --git a/src/main/java/stirling/software/SPDF/model/exception/UnsupportedUsernameAttribute.java b/src/main/java/stirling/software/SPDF/model/exception/UnsupportedUsernameAttribute.java new file mode 100644 index 000000000..0bf06ee20 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/exception/UnsupportedUsernameAttribute.java @@ -0,0 +1,7 @@ +package stirling.software.SPDF.model.exception; + +public class UnsupportedUsernameAttribute extends RuntimeException { + public UnsupportedUsernameAttribute(String message) { + super(message); + } +} diff --git a/src/main/java/stirling/software/SPDF/model/provider/GitHubProvider.java b/src/main/java/stirling/software/SPDF/model/provider/GitHubProvider.java new file mode 100644 index 000000000..8ca61094f --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/provider/GitHubProvider.java @@ -0,0 +1,86 @@ +package stirling.software.SPDF.model.provider; + +import java.util.ArrayList; +import java.util.Collection; + +import lombok.NoArgsConstructor; +import stirling.software.SPDF.model.UsernameAttribute; + +@NoArgsConstructor +public class GitHubProvider extends Provider { + + private static final String NAME = "github"; + private static final String CLIENT_NAME = "GitHub"; + private static final String AUTHORIZATION_URI = "https://github.com/login/oauth/authorize"; + private static final String TOKEN_URI = "https://github.com/login/oauth/access_token"; + private static final String USER_INFO_URI = "https://api.github.com/user"; + + public GitHubProvider( + String clientId, + String clientSecret, + Collection scopes, + UsernameAttribute useAsUsername) { + super( + null, + NAME, + CLIENT_NAME, + clientId, + clientSecret, + scopes, + useAsUsername != null ? useAsUsername : UsernameAttribute.LOGIN, + null, + AUTHORIZATION_URI, + TOKEN_URI, + USER_INFO_URI); + } + + @Override + public String getAuthorizationUri() { + return AUTHORIZATION_URI; + } + + @Override + public String getTokenUri() { + return TOKEN_URI; + } + + @Override + public String getUserInfoUri() { + return USER_INFO_URI; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getClientName() { + return CLIENT_NAME; + } + + @Override + public Collection getScopes() { + Collection scopes = super.getScopes(); + + if (scopes == null || scopes.isEmpty()) { + scopes = new ArrayList<>(); + scopes.add("read:user"); + } + + return scopes; + } + + @Override + public String toString() { + return "GitHub [clientId=" + + getClientId() + + ", clientSecret=" + + (getClientSecret() != null && !getClientSecret().isEmpty() ? "*****" : "NULL") + + ", scopes=" + + getScopes() + + ", useAsUsername=" + + getUseAsUsername() + + "]"; + } +} diff --git a/src/main/java/stirling/software/SPDF/model/provider/GithubProvider.java b/src/main/java/stirling/software/SPDF/model/provider/GithubProvider.java deleted file mode 100644 index afe7fcb77..000000000 --- a/src/main/java/stirling/software/SPDF/model/provider/GithubProvider.java +++ /dev/null @@ -1,114 +0,0 @@ -package stirling.software.SPDF.model.provider; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.stream.Collectors; - -import stirling.software.SPDF.model.Provider; - -public class GithubProvider extends Provider { - - private static final String authorizationUri = "https://github.com/login/oauth/authorize"; - private static final String tokenUri = "https://github.com/login/oauth/access_token"; - private static final String userInfoUri = "https://api.github.com/user"; - private String clientId; - private String clientSecret; - private Collection scopes = new ArrayList<>(); - private String useAsUsername = "login"; - - public String getAuthorizationuri() { - return authorizationUri; - } - - public String getTokenuri() { - return tokenUri; - } - - public String getUserinfouri() { - return userInfoUri; - } - - @Override - public String getIssuer() { - return new String(); - } - - @Override - public void setIssuer(String issuer) {} - - @Override - public String getClientId() { - return this.clientId; - } - - @Override - public void setClientId(String clientId) { - this.clientId = clientId; - } - - @Override - public String getClientSecret() { - return this.clientSecret; - } - - @Override - public void setClientSecret(String clientSecret) { - this.clientSecret = clientSecret; - } - - @Override - public Collection getScopes() { - if (scopes == null || scopes.isEmpty()) { - scopes = new ArrayList<>(); - scopes.add("read:user"); - } - return scopes; - } - - @Override - public void setScopes(String scopes) { - this.scopes = - Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList()); - } - - @Override - public String getUseAsUsername() { - return this.useAsUsername; - } - - @Override - public void setUseAsUsername(String useAsUsername) { - this.useAsUsername = useAsUsername; - } - - @Override - public String toString() { - return "GitHub [clientId=" - + clientId - + ", clientSecret=" - + (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL") - + ", scopes=" - + scopes - + ", useAsUsername=" - + useAsUsername - + "]"; - } - - @Override - public String getName() { - return "github"; - } - - @Override - public String getClientName() { - return "GitHub"; - } - - public boolean isSettingsValid() { - return super.isValid(this.getClientId(), "clientId") - && super.isValid(this.getClientSecret(), "clientSecret") - && super.isValid(this.getScopes(), "scopes") - && isValid(this.getUseAsUsername(), "useAsUsername"); - } -} diff --git a/src/main/java/stirling/software/SPDF/model/provider/GoogleProvider.java b/src/main/java/stirling/software/SPDF/model/provider/GoogleProvider.java index e43e1327b..4cf29c402 100644 --- a/src/main/java/stirling/software/SPDF/model/provider/GoogleProvider.java +++ b/src/main/java/stirling/software/SPDF/model/provider/GoogleProvider.java @@ -1,116 +1,85 @@ package stirling.software.SPDF.model.provider; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.stream.Collectors; -import stirling.software.SPDF.model.Provider; +import lombok.NoArgsConstructor; +import stirling.software.SPDF.model.UsernameAttribute; +@NoArgsConstructor public class GoogleProvider extends Provider { - private static final String authorizationUri = "https://accounts.google.com/o/oauth2/v2/auth"; - private static final String tokenUri = "https://www.googleapis.com/oauth2/v4/token"; - private static final String userInfoUri = + private static final String NAME = "google"; + private static final String CLIENT_NAME = "Google"; + private static final String AUTHORIZATION_URI = "https://accounts.google.com/o/oauth2/v2/auth"; + private static final String TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token"; + private static final String USER_INFO_URI = "https://www.googleapis.com/oauth2/v3/userinfo?alt=json"; - private String clientId; - private String clientSecret; - private Collection scopes = new ArrayList<>(); - private String useAsUsername = "email"; - public String getAuthorizationuri() { - return authorizationUri; + public GoogleProvider( + String clientId, + String clientSecret, + Collection scopes, + UsernameAttribute useAsUsername) { + super( + null, + NAME, + CLIENT_NAME, + clientId, + clientSecret, + scopes, + useAsUsername, + null, + AUTHORIZATION_URI, + TOKEN_URI, + USER_INFO_URI); } - public String getTokenuri() { - return tokenUri; + public String getAuthorizationUri() { + return AUTHORIZATION_URI; } - public String getUserinfouri() { - return userInfoUri; + public String getTokenUri() { + return TOKEN_URI; + } + + public String getUserinfoUri() { + return USER_INFO_URI; } @Override - public String getIssuer() { - return new String(); + public String getName() { + return NAME; } @Override - public void setIssuer(String issuer) {} - - @Override - public String getClientId() { - return this.clientId; - } - - @Override - public void setClientId(String clientId) { - this.clientId = clientId; - } - - @Override - public String getClientSecret() { - return this.clientSecret; - } - - @Override - public void setClientSecret(String clientSecret) { - this.clientSecret = clientSecret; + public String getClientName() { + return CLIENT_NAME; } @Override public Collection getScopes() { + Collection scopes = super.getScopes(); + if (scopes == null || scopes.isEmpty()) { scopes = new ArrayList<>(); scopes.add("https://www.googleapis.com/auth/userinfo.email"); scopes.add("https://www.googleapis.com/auth/userinfo.profile"); } + return scopes; } - @Override - public void setScopes(String scopes) { - this.scopes = - Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList()); - } - - @Override - public String getUseAsUsername() { - return this.useAsUsername; - } - - @Override - public void setUseAsUsername(String useAsUsername) { - this.useAsUsername = useAsUsername; - } - @Override public String toString() { return "Google [clientId=" - + clientId + + getClientId() + ", clientSecret=" - + (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL") + + (getClientSecret() != null && !getClientSecret().isEmpty() ? "*****" : "NULL") + ", scopes=" - + scopes + + getScopes() + ", useAsUsername=" - + useAsUsername + + getUseAsUsername() + "]"; } - - @Override - public String getName() { - return "google"; - } - - @Override - public String getClientName() { - return "Google"; - } - - public boolean isSettingsValid() { - return super.isValid(this.getClientId(), "clientId") - && super.isValid(this.getClientSecret(), "clientSecret") - && super.isValid(this.getScopes(), "scopes") - && isValid(this.getUseAsUsername(), "useAsUsername"); - } } diff --git a/src/main/java/stirling/software/SPDF/model/provider/KeycloakProvider.java b/src/main/java/stirling/software/SPDF/model/provider/KeycloakProvider.java index d715b1038..6b89e5b1e 100644 --- a/src/main/java/stirling/software/SPDF/model/provider/KeycloakProvider.java +++ b/src/main/java/stirling/software/SPDF/model/provider/KeycloakProvider.java @@ -1,106 +1,72 @@ package stirling.software.SPDF.model.provider; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.stream.Collectors; -import stirling.software.SPDF.model.Provider; +import lombok.NoArgsConstructor; +import stirling.software.SPDF.model.UsernameAttribute; +@NoArgsConstructor public class KeycloakProvider extends Provider { - private String issuer; - private String clientId; - private String clientSecret; - private Collection scopes = new ArrayList<>(); - private String useAsUsername = "email"; + private static final String NAME = "keycloak"; + private static final String CLIENT_NAME = "Keycloak"; - @Override - public String getIssuer() { - return this.issuer; + public KeycloakProvider( + String issuer, + String clientId, + String clientSecret, + Collection scopes, + UsernameAttribute useAsUsername) { + super( + issuer, + NAME, + CLIENT_NAME, + clientId, + clientSecret, + scopes, + useAsUsername, + null, + null, + null, + null); } @Override - public void setIssuer(String issuer) { - this.issuer = issuer; + public String getName() { + return NAME; } @Override - public String getClientId() { - return this.clientId; - } - - @Override - public void setClientId(String clientId) { - this.clientId = clientId; - } - - @Override - public String getClientSecret() { - return this.clientSecret; - } - - @Override - public void setClientSecret(String clientSecret) { - this.clientSecret = clientSecret; + public String getClientName() { + return CLIENT_NAME; } @Override public Collection getScopes() { + Collection scopes = super.getScopes(); + if (scopes == null || scopes.isEmpty()) { scopes = new ArrayList<>(); scopes.add("profile"); scopes.add("email"); } + return scopes; } - @Override - public void setScopes(String scopes) { - this.scopes = - Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList()); - } - - @Override - public String getUseAsUsername() { - return this.useAsUsername; - } - - @Override - public void setUseAsUsername(String useAsUsername) { - this.useAsUsername = useAsUsername; - } - @Override public String toString() { return "Keycloak [issuer=" - + issuer + + getIssuer() + ", clientId=" - + clientId + + getClientId() + ", clientSecret=" - + (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL") + + (getClientSecret() != null && !getClientSecret().isBlank() ? "*****" : "NULL") + ", scopes=" - + scopes + + getScopes() + ", useAsUsername=" - + useAsUsername + + getUseAsUsername() + "]"; } - - @Override - public String getName() { - return "keycloak"; - } - - @Override - public String getClientName() { - return "Keycloak"; - } - - public boolean isSettingsValid() { - return isValid(this.getIssuer(), "issuer") - && isValid(this.getClientId(), "clientId") - && isValid(this.getClientSecret(), "clientSecret") - && isValid(this.getScopes(), "scopes") - && isValid(this.getUseAsUsername(), "useAsUsername"); - } } diff --git a/src/main/java/stirling/software/SPDF/model/provider/Provider.java b/src/main/java/stirling/software/SPDF/model/provider/Provider.java new file mode 100644 index 000000000..4e20c15f4 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/provider/Provider.java @@ -0,0 +1,134 @@ +package stirling.software.SPDF.model.provider; + +import static stirling.software.SPDF.model.UsernameAttribute.EMAIL; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import stirling.software.SPDF.model.UsernameAttribute; +import stirling.software.SPDF.model.exception.UnsupportedUsernameAttribute; + +@Data +@NoArgsConstructor +public class Provider { + + public static final String EXCEPTION_MESSAGE = "The attribute %s is not supported for %s."; + + private String issuer; + private String name; + private String clientName; + private String clientId; + private String clientSecret; + private Collection scopes; + private UsernameAttribute useAsUsername; + private String logoutUrl; + private String authorizationUri; + private String tokenUri; + private String userInfoUri; + + public Provider( + String issuer, + String name, + String clientName, + String clientId, + String clientSecret, + Collection scopes, + UsernameAttribute useAsUsername, + String logoutUrl, + String authorizationUri, + String tokenUri, + String userInfoUri) { + this.issuer = issuer; + this.name = name; + this.clientName = clientName; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.scopes = scopes == null ? new ArrayList<>() : scopes; + this.useAsUsername = + useAsUsername != null ? validateUsernameAttribute(useAsUsername) : EMAIL; + this.logoutUrl = logoutUrl; + this.authorizationUri = authorizationUri; + this.tokenUri = tokenUri; + this.userInfoUri = userInfoUri; + } + + public void setScopes(String scopes) { + if (scopes != null && !scopes.isBlank()) { + this.scopes = + Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList()); + } + } + + private UsernameAttribute validateUsernameAttribute(UsernameAttribute usernameAttribute) { + switch (name) { + case "google" -> { + return validateGoogleUsernameAttribute(usernameAttribute); + } + case "github" -> { + return validateGitHubUsernameAttribute(usernameAttribute); + } + case "keycloak" -> { + return validateKeycloakUsernameAttribute(usernameAttribute); + } + default -> { + return usernameAttribute; + } + } + } + + private UsernameAttribute validateKeycloakUsernameAttribute( + UsernameAttribute usernameAttribute) { + switch (usernameAttribute) { + case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME, PREFERRED_USERNAME -> { + return usernameAttribute; + } + default -> + throw new UnsupportedUsernameAttribute( + String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName)); + } + } + + private UsernameAttribute validateGoogleUsernameAttribute(UsernameAttribute usernameAttribute) { + switch (usernameAttribute) { + case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME -> { + return usernameAttribute; + } + default -> + throw new UnsupportedUsernameAttribute( + String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName)); + } + } + + private UsernameAttribute validateGitHubUsernameAttribute(UsernameAttribute usernameAttribute) { + switch (usernameAttribute) { + case LOGIN, EMAIL, NAME -> { + return usernameAttribute; + } + default -> + throw new UnsupportedUsernameAttribute( + String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName)); + } + } + + @Override + public String toString() { + return "Provider [name=" + + getName() + + ", clientName=" + + getClientName() + + ", clientId=" + + getClientId() + + ", clientSecret=" + + (getClientSecret() != null && !getClientSecret().isEmpty() ? "*****" : "NULL") + + ", scopes=" + + getScopes() + + ", useAsUsername=" + + getUseAsUsername() + + "]"; + } +} diff --git a/src/main/java/stirling/software/SPDF/utils/UrlUtils.java b/src/main/java/stirling/software/SPDF/utils/UrlUtils.java index d0de88af0..d4d0d6619 100644 --- a/src/main/java/stirling/software/SPDF/utils/UrlUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/UrlUtils.java @@ -7,8 +7,6 @@ import jakarta.servlet.http.HttpServletRequest; public class UrlUtils { - private UrlUtils() {} - public static String getOrigin(HttpServletRequest request) { String scheme = request.getScheme(); // http or https String serverName = request.getServerName(); // localhost diff --git a/src/main/java/stirling/software/SPDF/utils/validation/Validator.java b/src/main/java/stirling/software/SPDF/utils/validation/Validator.java new file mode 100644 index 000000000..83b906857 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/utils/validation/Validator.java @@ -0,0 +1,36 @@ +package stirling.software.SPDF.utils.validation; + +import java.util.Collection; + +import stirling.software.SPDF.model.provider.Provider; + +public class Validator { + + public static boolean validateProvider(Provider provider) { + if (provider == null) { + return false; + } + + if (isStringEmpty(provider.getClientId())) { + return false; + } + + if (isStringEmpty(provider.getClientSecret())) { + return false; + } + + if (isCollectionEmpty(provider.getScopes())) { + return false; + } + + return true; + } + + public static boolean isStringEmpty(String input) { + return input == null || input.isBlank(); + } + + public static boolean isCollectionEmpty(Collection input) { + return input == null || input.isEmpty(); + } +} diff --git a/src/main/resources/messages_ar_AR.properties b/src/main/resources/messages_ar_AR.properties index c9efd61cb..a95d56920 100644 --- a/src/main/resources/messages_ar_AR.properties +++ b/src/main/resources/messages_ar_AR.properties @@ -572,8 +572,8 @@ login.invalid=اسم المستخدم أو كلمة المرور غير صالح login.locked=تم قفل حسابك. login.signinTitle=الرجاء تسجيل الدخول login.ssoSignIn=تسجيل الدخول عبر تسجيل الدخول الأحادي -login.oauth2AutoCreateDisabled=تم تعطيل الإنشاء التلقائي لمستخدم OAuth2 -login.oauth2AdminBlockedUser=تم حظر تسجيل أو تسجيل دخول المستخدمين غير المسجلين حاليًا. يرجى الاتصال بالمسؤول. +login.oAuth2AutoCreateDisabled=تم تعطيل الإنشاء التلقائي لمستخدم OAuth2 +login.oAuth2AdminBlockedUser=تم حظر تسجيل أو تسجيل دخول المستخدمين غير المسجلين حاليًا. يرجى الاتصال بالمسؤول. login.oauth2RequestNotFound=لم يتم العثور على طلب التفويض login.oauth2InvalidUserInfoResponse=استجابة معلومات المستخدم غير صالحة login.oauth2invalidRequest=طلب غير صالح diff --git a/src/main/resources/messages_az_AZ.properties b/src/main/resources/messages_az_AZ.properties index 523412a20..f3d692a5c 100644 --- a/src/main/resources/messages_az_AZ.properties +++ b/src/main/resources/messages_az_AZ.properties @@ -572,8 +572,8 @@ login.invalid=Etibarsız istifadəçi adı və ya şifr. login.locked=Sizin hesabınız kilidlənmişdir. login.signinTitle=Zəhmət olmasa, daxil olun login.ssoSignIn=Single Sign-on vasitəsilə daxil olun -login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create İstifadəçisi Deaktivləşdirilmişdir -login.oauth2AdminBlockedUser=Qeydiyyatdan keçməmiş istifadəçilərin qeydiyyatı və daxil olması hal-hazırda bloklanmışdır. Zəhmət olmasa, administratorla əlaqə saxlayın. +login.oAuth2AutoCreateDisabled=OAUTH2 Auto-Create İstifadəçisi Deaktivləşdirilmişdir +login.oAuth2AdminBlockedUser=Qeydiyyatdan keçməmiş istifadəçilərin qeydiyyatı və daxil olması hal-hazırda bloklanmışdır. Zəhmət olmasa, administratorla əlaqə saxlayın. login.oauth2RequestNotFound=Təsdiqlənmə sorğusu tapılmadı login.oauth2InvalidUserInfoResponse=Yanlış İstifadəçi Məlumatı Cavabı login.oauth2invalidRequest=Etibarsız Sorğu diff --git a/src/main/resources/messages_bg_BG.properties b/src/main/resources/messages_bg_BG.properties index ac5bd1b10..16f756a8b 100644 --- a/src/main/resources/messages_bg_BG.properties +++ b/src/main/resources/messages_bg_BG.properties @@ -572,8 +572,8 @@ login.invalid=Невалидно потребителско име или пар login.locked=Вашият акаунт е заключен. login.signinTitle=Моля впишете се login.ssoSignIn=Влизане чрез еднократно влизане -login.oauth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано -login.oauth2AdminBlockedUser=Регистрацията или влизането на нерегистрирани потребители в момента е блокирано. Моля, свържете се с администратора. +login.oAuth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано +login.oAuth2AdminBlockedUser=Регистрацията или влизането на нерегистрирани потребители в момента е блокирано. Моля, свържете се с администратора. login.oauth2RequestNotFound=Заявката за оторизация не е намерена login.oauth2InvalidUserInfoResponse=Невалидна информация за потребителя login.oauth2invalidRequest=Невалидна заявка diff --git a/src/main/resources/messages_ca_CA.properties b/src/main/resources/messages_ca_CA.properties index da1b2a381..708126d05 100644 --- a/src/main/resources/messages_ca_CA.properties +++ b/src/main/resources/messages_ca_CA.properties @@ -572,8 +572,8 @@ login.invalid=Nom d'usuari/contrasenya no vàlid login.locked=Compte bloquejat login.signinTitle=Autenticat login.ssoSignIn=Inicia sessió mitjançant inici de sessió únic -login.oauth2AutoCreateDisabled=La creació automàtica d'usuaris OAUTH2 està desactivada -login.oauth2AdminBlockedUser=El registre o inici de sessió d'usuaris no registrats està actualment bloquejat. Si us plau, contacta amb l'administrador. +login.oAuth2AutoCreateDisabled=La creació automàtica d'usuaris OAUTH2 està desactivada +login.oAuth2AdminBlockedUser=El registre o inici de sessió d'usuaris no registrats està actualment bloquejat. Si us plau, contacta amb l'administrador. login.oauth2RequestNotFound=Sol·licitud d'autorització no trobada login.oauth2InvalidUserInfoResponse=Resposta d'informació d'usuari no vàlida login.oauth2invalidRequest=Sol·licitud no vàlida diff --git a/src/main/resources/messages_cs_CZ.properties b/src/main/resources/messages_cs_CZ.properties index 1eda28ec1..27801cbf7 100644 --- a/src/main/resources/messages_cs_CZ.properties +++ b/src/main/resources/messages_cs_CZ.properties @@ -572,8 +572,8 @@ login.invalid=Neplatné uživatelské jméno nebo heslo. login.locked=Váš účet byl uzamčen. login.signinTitle=Prosím přihlaste se login.ssoSignIn=Přihlásit se přes Single Sign-on -login.oauth2AutoCreateDisabled=Automatické vytváření OAUTH2 uživatelů je zakázáno -login.oauth2AdminBlockedUser=Registrace nebo přihlášení neregistrovaných uživatelů je momentálně blokováno. Kontaktujte prosím správce. +login.oAuth2AutoCreateDisabled=Automatické vytváření OAUTH2 uživatelů je zakázáno +login.oAuth2AdminBlockedUser=Registrace nebo přihlášení neregistrovaných uživatelů je momentálně blokováno. Kontaktujte prosím správce. login.oauth2RequestNotFound=Požadavek na autorizaci nebyl nalezen login.oauth2InvalidUserInfoResponse=Neplatná odpověď s informacemi o uživateli login.oauth2invalidRequest=Neplatný požadavek diff --git a/src/main/resources/messages_da_DK.properties b/src/main/resources/messages_da_DK.properties index 77e7d87d9..b3019041b 100644 --- a/src/main/resources/messages_da_DK.properties +++ b/src/main/resources/messages_da_DK.properties @@ -572,8 +572,8 @@ login.invalid=Ugyldigt brugernavn eller adgangskode. login.locked=Din konto er blevet låst. login.signinTitle=Log venligst ind login.ssoSignIn=Log ind via Single Sign-on -login.oauth2AutoCreateDisabled=OAUTH2 Auto-Opret Bruger Deaktiveret -login.oauth2AdminBlockedUser=Registrering eller login af ikke-registrerede brugere er i øjeblikket blokeret. Kontakt venligst administratoren. +login.oAuth2AutoCreateDisabled=OAUTH2 Auto-Opret Bruger Deaktiveret +login.oAuth2AdminBlockedUser=Registrering eller login af ikke-registrerede brugere er i øjeblikket blokeret. Kontakt venligst administratoren. login.oauth2RequestNotFound=Autorisationsanmodning ikke fundet login.oauth2InvalidUserInfoResponse=Ugyldigt Brugerinfo Svar login.oauth2invalidRequest=Ugyldig Anmodning diff --git a/src/main/resources/messages_de_DE.properties b/src/main/resources/messages_de_DE.properties index e743ee76c..113ec027e 100644 --- a/src/main/resources/messages_de_DE.properties +++ b/src/main/resources/messages_de_DE.properties @@ -572,8 +572,8 @@ login.invalid=Benutzername oder Passwort ungültig. login.locked=Ihr Konto wurde gesperrt. login.signinTitle=Bitte melden Sie sich an. login.ssoSignIn=Anmeldung per Single Sign-On -login.oauth2AutoCreateDisabled=OAUTH2 Benutzer automatisch erstellen deaktiviert -login.oauth2AdminBlockedUser=Die Registrierung bzw. das anmelden von nicht registrierten Benutzern ist derzeit gesperrt. Bitte wenden Sie sich an den Administrator. +login.oAuth2AutoCreateDisabled=OAUTH2 Benutzer automatisch erstellen deaktiviert +login.oAuth2AdminBlockedUser=Die Registrierung bzw. das anmelden von nicht registrierten Benutzern ist derzeit gesperrt. Bitte wenden Sie sich an den Administrator. login.oauth2RequestNotFound=Autorisierungsanfrage nicht gefunden login.oauth2InvalidUserInfoResponse=Ungültige Benutzerinformationsantwort login.oauth2invalidRequest=ungültige Anfrage diff --git a/src/main/resources/messages_el_GR.properties b/src/main/resources/messages_el_GR.properties index 257f56115..90d63a8f3 100644 --- a/src/main/resources/messages_el_GR.properties +++ b/src/main/resources/messages_el_GR.properties @@ -572,8 +572,8 @@ login.invalid=Μη έγκυρο όνομα χρήστη ή κωδικός. login.locked=Ο λογαριασμός σας έχει κλειδωθεί. login.signinTitle=Παρακαλώ συνδεθείτε login.ssoSignIn=Σύνδεση μέσω Single Sign-on -login.oauth2AutoCreateDisabled=Η αυτόματη δημιουργία χρήστη OAUTH2 είναι απενεργοποιημένη -login.oauth2AdminBlockedUser=Η εγγραφή ή σύνδεση μη εγγεγραμμένων χρηστών είναι προς το παρόν αποκλεισμένη. Παρακαλώ επικοινωνήστε με τον διαχειριστή. +login.oAuth2AutoCreateDisabled=Η αυτόματη δημιουργία χρήστη OAUTH2 είναι απενεργοποιημένη +login.oAuth2AdminBlockedUser=Η εγγραφή ή σύνδεση μη εγγεγραμμένων χρηστών είναι προς το παρόν αποκλεισμένη. Παρακαλώ επικοινωνήστε με τον διαχειριστή. login.oauth2RequestNotFound=Το αίτημα εξουσιοδότησης δεν βρέθηκε login.oauth2InvalidUserInfoResponse=Μη έγκυρη απόκριση πληροφοριών χρήστη login.oauth2invalidRequest=Μη έγκυρο αίτημα diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index e961f75cc..bd5345635 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -572,8 +572,8 @@ login.invalid=Invalid username or password. login.locked=Your account has been locked. login.signinTitle=Please sign in login.ssoSignIn=Login via Single Sign-on -login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled -login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. +login.oAuth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled +login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. login.oauth2RequestNotFound=Authorization request not found login.oauth2InvalidUserInfoResponse=Invalid User Info Response login.oauth2invalidRequest=Invalid Request diff --git a/src/main/resources/messages_en_US.properties b/src/main/resources/messages_en_US.properties index a3e35ea5e..aaa9f9682 100644 --- a/src/main/resources/messages_en_US.properties +++ b/src/main/resources/messages_en_US.properties @@ -572,8 +572,8 @@ login.invalid=Invalid username or password. login.locked=Your account has been locked. login.signinTitle=Please sign in login.ssoSignIn=Login via Single Sign-on -login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled -login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. +login.oAuth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled +login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. login.oauth2RequestNotFound=Authorization request not found login.oauth2InvalidUserInfoResponse=Invalid User Info Response login.oauth2invalidRequest=Invalid Request diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties index cbf73249a..d16e417a8 100644 --- a/src/main/resources/messages_es_ES.properties +++ b/src/main/resources/messages_es_ES.properties @@ -572,8 +572,8 @@ login.invalid=Nombre de usuario o contraseña erróneos. login.locked=Su cuenta se ha bloqueado. login.signinTitle=Por favor, inicie sesión login.ssoSignIn=Iniciar sesión a través del inicio de sesión único -login.oauth2AutoCreateDisabled=Usuario de creación automática de OAUTH2 DESACTIVADO -login.oauth2AdminBlockedUser=El registro o inicio de sesión de usuarios no registrados está actualmente bloqueado. Por favor, contáctese con el administrador. +login.oAuth2AutoCreateDisabled=Usuario de creación automática de OAUTH2 DESACTIVADO +login.oAuth2AdminBlockedUser=El registro o inicio de sesión de usuarios no registrados está actualmente bloqueado. Por favor, contáctese con el administrador. login.oauth2RequestNotFound=Solicitud de autorización no encontrada login.oauth2InvalidUserInfoResponse=Respuesta de información de usuario no válida login.oauth2invalidRequest=Solicitud no válida diff --git a/src/main/resources/messages_eu_ES.properties b/src/main/resources/messages_eu_ES.properties index 9b47fcc36..e7397d25d 100644 --- a/src/main/resources/messages_eu_ES.properties +++ b/src/main/resources/messages_eu_ES.properties @@ -572,8 +572,8 @@ login.invalid=Okerreko erabiltzaile izena edo pasahitza. login.locked=Zure kontua blokeatu egin da. login.signinTitle=Mesedez, hasi saioa login.ssoSignIn=Hasi saioa Saioa hasteko modu bakarraren bidez -login.oauth2AutoCreateDisabled=OAUTH2 Sortu automatikoki erabiltzailea desgaituta dago -login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. +login.oAuth2AutoCreateDisabled=OAUTH2 Sortu automatikoki erabiltzailea desgaituta dago +login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. login.oauth2RequestNotFound=Authorization request not found login.oauth2InvalidUserInfoResponse=Invalid User Info Response login.oauth2invalidRequest=Invalid Request diff --git a/src/main/resources/messages_fa_IR.properties b/src/main/resources/messages_fa_IR.properties index 124a2f307..13c7be2d4 100644 --- a/src/main/resources/messages_fa_IR.properties +++ b/src/main/resources/messages_fa_IR.properties @@ -572,8 +572,8 @@ login.invalid=نام کاربری یا رمز عبور اشتباه است. login.locked=حساب شما قفل شده است. login.signinTitle=لطفاً وارد شوید login.ssoSignIn=ورود از طریق Single Sign-on -login.oauth2AutoCreateDisabled=ایجاد خودکار کاربر با OAUTH2 غیرفعال است -login.oauth2AdminBlockedUser=ثبت‌نام یا ورود کاربران ثبت‌نشده در حال حاضر مسدود است. لطفاً با مدیر تماس بگیرید. +login.oAuth2AutoCreateDisabled=ایجاد خودکار کاربر با OAUTH2 غیرفعال است +login.oAuth2AdminBlockedUser=ثبت‌نام یا ورود کاربران ثبت‌نشده در حال حاضر مسدود است. لطفاً با مدیر تماس بگیرید. login.oauth2RequestNotFound=درخواست احراز هویت پیدا نشد login.oauth2InvalidUserInfoResponse=پاسخ اطلاعات کاربری نامعتبر است login.oauth2invalidRequest=درخواست نامعتبر diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties index 7ce870d43..d82065a11 100644 --- a/src/main/resources/messages_fr_FR.properties +++ b/src/main/resources/messages_fr_FR.properties @@ -572,8 +572,8 @@ login.invalid=Nom d'utilisateur ou mot de passe invalide. login.locked=Votre compte a été verrouillé. login.signinTitle=Veuillez vous connecter login.ssoSignIn=Se connecter via l'authentification unique -login.oauth2AutoCreateDisabled=OAUTH2 Création automatique d'utilisateur désactivée -login.oauth2AdminBlockedUser=La création ou l'authentification d'utilisateurs non enregistrés est actuellement bloquée. Veuillez contacter l'administrateur. +login.oAuth2AutoCreateDisabled=OAUTH2 Création automatique d'utilisateur désactivée +login.oAuth2AdminBlockedUser=La création ou l'authentification d'utilisateurs non enregistrés est actuellement bloquée. Veuillez contacter l'administrateur. login.oauth2RequestNotFound=Demande d'autorisation introuvable login.oauth2InvalidUserInfoResponse=Réponse contenant les informations de l'utilisateur est invalide login.oauth2invalidRequest=Requête invalide diff --git a/src/main/resources/messages_hi_IN.properties b/src/main/resources/messages_hi_IN.properties index 6e9ad2fcd..0ddabb7ac 100644 --- a/src/main/resources/messages_hi_IN.properties +++ b/src/main/resources/messages_hi_IN.properties @@ -572,8 +572,8 @@ login.invalid=अमान्य उपयोगकर्ता नाम या login.locked=आपका खाता लॉक कर दिया गया है। login.signinTitle=कृपया साइन इन करें login.ssoSignIn=सिंगल साइन-ऑन के माध्यम से लॉगिन करें -login.oauth2AutoCreateDisabled=OAUTH2 स्वतः उपयोगकर्ता निर्माण अक्षम है -login.oauth2AdminBlockedUser=गैर-पंजीकृत उपयोगकर्ताओं का पंजीकरण या लॉगिन वर्तमान में अवरुद्ध है। कृपया व्यवस्थापक से संपर्क करें। +login.oAuth2AutoCreateDisabled=OAUTH2 स्वतः उपयोगकर्ता निर्माण अक्षम है +login.oAuth2AdminBlockedUser=गैर-पंजीकृत उपयोगकर्ताओं का पंजीकरण या लॉगिन वर्तमान में अवरुद्ध है। कृपया व्यवस्थापक से संपर्क करें। login.oauth2RequestNotFound=प्राधिकरण अनुरोध नहीं मिला login.oauth2InvalidUserInfoResponse=अमान्य उपयोगकर्ता जानकारी प्रतिक्रिया login.oauth2invalidRequest=अमान्य अनुरोध diff --git a/src/main/resources/messages_hr_HR.properties b/src/main/resources/messages_hr_HR.properties index a19dd7ef2..3a2482600 100644 --- a/src/main/resources/messages_hr_HR.properties +++ b/src/main/resources/messages_hr_HR.properties @@ -572,8 +572,8 @@ login.invalid=Neispravno korisničko ime ili zaporka. login.locked=Vaš račun je zaključan. login.signinTitle=Molimo vas da se prijavite login.ssoSignIn=Prijavite se putem jedinstvene prijave -login.oauth2AutoCreateDisabled=OAUTH2 automatsko kreiranje korisnika je onemogućeno -login.oauth2AdminBlockedUser=Registracija ili prijava nekadreguiranih korisnika trenutno su blokirane. Molimo Vas da kontaktirate administratora. +login.oAuth2AutoCreateDisabled=OAUTH2 automatsko kreiranje korisnika je onemogućeno +login.oAuth2AdminBlockedUser=Registracija ili prijava nekadreguiranih korisnika trenutno su blokirane. Molimo Vas da kontaktirate administratora. login.oauth2RequestNotFound=Zahtjev za autorizaciju nije pronađen login.oauth2InvalidUserInfoResponse=Nevažeće informacije o korisniku login.oauth2invalidRequest=Neispravan zahtjev diff --git a/src/main/resources/messages_hu_HU.properties b/src/main/resources/messages_hu_HU.properties index 6b97fe387..3ef1d3e4e 100644 --- a/src/main/resources/messages_hu_HU.properties +++ b/src/main/resources/messages_hu_HU.properties @@ -572,8 +572,8 @@ login.invalid=Érvénytelen felhasználónév vagy jelszó. login.locked=A fiókja zárolva van. login.signinTitle=Kérjük, jelentkezzen be login.ssoSignIn=Bejelentkezés egyszeri bejelentkezéssel -login.oauth2AutoCreateDisabled=OAuth2 automatikus felhasználólétrehozás letiltva -login.oauth2AdminBlockedUser=A nem regisztrált felhasználók regisztrációja vagy bejelentkezése jelenleg le van tiltva. Kérjük, forduljon a rendszergazdához. +login.oAuth2AutoCreateDisabled=OAuth2 automatikus felhasználólétrehozás letiltva +login.oAuth2AdminBlockedUser=A nem regisztrált felhasználók regisztrációja vagy bejelentkezése jelenleg le van tiltva. Kérjük, forduljon a rendszergazdához. login.oauth2RequestNotFound=A hitelesítési kérés nem található login.oauth2InvalidUserInfoResponse=Érvénytelen felhasználói információ válasz login.oauth2invalidRequest=Érvénytelen kérés diff --git a/src/main/resources/messages_id_ID.properties b/src/main/resources/messages_id_ID.properties index f0e99b45d..021c5ec2b 100644 --- a/src/main/resources/messages_id_ID.properties +++ b/src/main/resources/messages_id_ID.properties @@ -572,8 +572,8 @@ login.invalid=Nama pengguna atau kata sandi tidak valid. login.locked=Akun Anda telah dikunci. login.signinTitle=Silakan masuk login.ssoSignIn=Masuk melalui Single Sign - on -login.oauth2AutoCreateDisabled=OAUTH2 Buat Otomatis Pengguna Dinonaktifkan -login.oauth2AdminBlockedUser=Registrasi atau login pengguna yang tidak terdaftar saat ini diblokir. Silakan hubungi administrator. +login.oAuth2AutoCreateDisabled=OAUTH2 Buat Otomatis Pengguna Dinonaktifkan +login.oAuth2AdminBlockedUser=Registrasi atau login pengguna yang tidak terdaftar saat ini diblokir. Silakan hubungi administrator. login.oauth2RequestNotFound=Permintaan otorisasi tidak ditemukan login.oauth2InvalidUserInfoResponse=Respons Info Pengguna Tidak Valid login.oauth2invalidRequest=Permintaan Tidak Valid diff --git a/src/main/resources/messages_it_IT.properties b/src/main/resources/messages_it_IT.properties index ba900f535..2dab8f05b 100644 --- a/src/main/resources/messages_it_IT.properties +++ b/src/main/resources/messages_it_IT.properties @@ -572,8 +572,8 @@ login.invalid=Nome utente o password errati. login.locked=Il tuo account è stato bloccato. login.signinTitle=Per favore accedi login.ssoSignIn=Accedi tramite Single Sign-on -login.oauth2AutoCreateDisabled=Creazione automatica utente OAUTH2 DISABILITATA -login.oauth2AdminBlockedUser=La registrazione o l'accesso degli utenti non registrati è attualmente bloccata. Si prega di contattare l'amministratore. +login.oAuth2AutoCreateDisabled=Creazione automatica utente OAUTH2 DISABILITATA +login.oAuth2AdminBlockedUser=La registrazione o l'accesso degli utenti non registrati è attualmente bloccata. Si prega di contattare l'amministratore. login.oauth2RequestNotFound=Richiesta di autorizzazione non trovata login.oauth2InvalidUserInfoResponse=Risposta relativa alle informazioni utente non valida login.oauth2invalidRequest=Richiesta non valida diff --git a/src/main/resources/messages_ja_JP.properties b/src/main/resources/messages_ja_JP.properties index f7c171b7b..e6810b2ca 100644 --- a/src/main/resources/messages_ja_JP.properties +++ b/src/main/resources/messages_ja_JP.properties @@ -572,8 +572,8 @@ login.invalid=ユーザー名かパスワードが無効です。 login.locked=あなたのアカウントはロックされています。 login.signinTitle=サインインしてください login.ssoSignIn=シングルサインオンでログイン -login.oauth2AutoCreateDisabled=OAuth 2自動作成ユーザーが無効 -login.oauth2AdminBlockedUser=現在、未登録ユーザーの登録またはログインはブロックされています。管理者にお問い合わせください。 +login.oAuth2AutoCreateDisabled=OAuth 2自動作成ユーザーが無効 +login.oAuth2AdminBlockedUser=現在、未登録ユーザーの登録またはログインはブロックされています。管理者にお問い合わせください。 login.oauth2RequestNotFound=認証リクエストが見つかりません login.oauth2InvalidUserInfoResponse=無効なユーザー情報の応答 login.oauth2invalidRequest=無効なリクエスト diff --git a/src/main/resources/messages_ko_KR.properties b/src/main/resources/messages_ko_KR.properties index a6f5ba4b4..6049937fb 100644 --- a/src/main/resources/messages_ko_KR.properties +++ b/src/main/resources/messages_ko_KR.properties @@ -572,8 +572,8 @@ login.invalid=사용자 이름 또는 비밀번호가 잘못되었습니다. login.locked=계정이 잠겼습니다. login.signinTitle=로그인해 주세요 login.ssoSignIn=단일 로그인으로 로그인 -login.oauth2AutoCreateDisabled=OAuth2 사용자 자동 생성이 비활성화되었습니다 -login.oauth2AdminBlockedUser=현재 미등록 사용자의 등록 또는 로그인이 차단되어 있습니다. 관리자에게 문의하세요. +login.oAuth2AutoCreateDisabled=OAuth2 사용자 자동 생성이 비활성화되었습니다 +login.oAuth2AdminBlockedUser=현재 미등록 사용자의 등록 또는 로그인이 차단되어 있습니다. 관리자에게 문의하세요. login.oauth2RequestNotFound=인증 요청을 찾을 수 없습니다 login.oauth2InvalidUserInfoResponse=잘못된 사용자 정보 응답 login.oauth2invalidRequest=잘못된 요청 diff --git a/src/main/resources/messages_nl_NL.properties b/src/main/resources/messages_nl_NL.properties index bf073d9a3..2bc49d17c 100644 --- a/src/main/resources/messages_nl_NL.properties +++ b/src/main/resources/messages_nl_NL.properties @@ -572,8 +572,8 @@ login.invalid=Ongeldige gebruikersnaam of wachtwoord. login.locked=Je account is geblokkeerd. login.signinTitle=Gelieve in te loggen login.ssoSignIn=Inloggen via Single Sign-on -login.oauth2AutoCreateDisabled=OAUTH2 Automatisch aanmaken gebruiker uitgeschakeld -login.oauth2AdminBlockedUser=Registratie of inloggen van niet-registreerde gebruikers is helaas momenteel geblokkeerd. Neem contact op met de beheerder. +login.oAuth2AutoCreateDisabled=OAUTH2 Automatisch aanmaken gebruiker uitgeschakeld +login.oAuth2AdminBlockedUser=Registratie of inloggen van niet-registreerde gebruikers is helaas momenteel geblokkeerd. Neem contact op met de beheerder. login.oauth2RequestNotFound=Autorisatieverzoek niet gevonden login.oauth2InvalidUserInfoResponse=Ongeldige reactie op gebruikersinfo login.oauth2invalidRequest=Ongeldig verzoek diff --git a/src/main/resources/messages_no_NB.properties b/src/main/resources/messages_no_NB.properties index 565884c29..474013c61 100644 --- a/src/main/resources/messages_no_NB.properties +++ b/src/main/resources/messages_no_NB.properties @@ -572,8 +572,8 @@ login.invalid=Ugyldig brukernavn eller passord. login.locked=Kontoen din har blitt låst. login.signinTitle=Vennligst logg inn login.ssoSignIn=Logg inn via Enkel Pålogging -login.oauth2AutoCreateDisabled=OAUTH2 Auto-Opretting av bruker deaktivert -login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. +login.oAuth2AutoCreateDisabled=OAUTH2 Auto-Opretting av bruker deaktivert +login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. login.oauth2RequestNotFound=Autentiseringsforespørsel ikke funnet login.oauth2InvalidUserInfoResponse=Ugyldig brukerinforespons login.oauth2invalidRequest=Ugyldig forespørsel diff --git a/src/main/resources/messages_pl_PL.properties b/src/main/resources/messages_pl_PL.properties index c8834bb05..7f3936d5e 100644 --- a/src/main/resources/messages_pl_PL.properties +++ b/src/main/resources/messages_pl_PL.properties @@ -572,8 +572,8 @@ login.invalid=Nieprawidłowe dane logowania login.locked=Konto jest zablokowane login.signinTitle=Zaloguj się login.ssoSignIn=Zaloguj się za pomocą logowania jednokrotnego -login.oauth2AutoCreateDisabled=Wyłączono automatyczne tworzenie użytkownika OAUTH2 -login.oauth2AdminBlockedUser=Rejestracja lub logowanie niezarejestrowanych użytkowników jest obecnie zablokowane. Prosimy o kontakt z administratorem. +login.oAuth2AutoCreateDisabled=Wyłączono automatyczne tworzenie użytkownika OAUTH2 +login.oAuth2AdminBlockedUser=Rejestracja lub logowanie niezarejestrowanych użytkowników jest obecnie zablokowane. Prosimy o kontakt z administratorem. login.oauth2RequestNotFound=Błąd logowania OAuth2 login.oauth2InvalidUserInfoResponse=Niewłaściwe dane logowania login.oauth2invalidRequest=Nieprawidłowe żądanie diff --git a/src/main/resources/messages_pt_BR.properties b/src/main/resources/messages_pt_BR.properties index 79633e4e5..28a5b6f12 100644 --- a/src/main/resources/messages_pt_BR.properties +++ b/src/main/resources/messages_pt_BR.properties @@ -572,8 +572,8 @@ login.invalid=Usuário ou senha inválidos. login.locked=Sua conta foi bloqueada. login.signinTitle=Por favor, inicie a sessão login.ssoSignIn=Iniciar sessão através de login único (SSO) -login.oauth2AutoCreateDisabled=Auto-Criar Usuário OAUTH2 Desativado -login.oauth2AdminBlockedUser=O registro ou login de usuários não registrados está atualmente bloqueado. Entre em contato com o administrador. +login.oAuth2AutoCreateDisabled=Auto-Criar Usuário OAUTH2 Desativado +login.oAuth2AdminBlockedUser=O registro ou login de usuários não registrados está atualmente bloqueado. Entre em contato com o administrador. login.oauth2RequestNotFound=Solicitação de autorização não encontrada login.oauth2InvalidUserInfoResponse=Resposta de informação de usuário inválida login.oauth2invalidRequest=Requisição Inválida diff --git a/src/main/resources/messages_pt_PT.properties b/src/main/resources/messages_pt_PT.properties index 034eb0a79..9e11618e4 100644 --- a/src/main/resources/messages_pt_PT.properties +++ b/src/main/resources/messages_pt_PT.properties @@ -572,8 +572,8 @@ login.invalid=Nome de utilizador ou palavra-passe inválidos. login.locked=A sua conta foi bloqueada. login.signinTitle=Por favor inicie sessão login.ssoSignIn=Login via Single Sign-on -login.oauth2AutoCreateDisabled=Criação Automática de Utilizador OAUTH2 Desativada -login.oauth2AdminBlockedUser=O registo ou login de utilizadores não registados está atualmente bloqueado. Por favor contacte o administrador. +login.oAuth2AutoCreateDisabled=Criação Automática de Utilizador OAUTH2 Desativada +login.oAuth2AdminBlockedUser=O registo ou login de utilizadores não registados está atualmente bloqueado. Por favor contacte o administrador. login.oauth2RequestNotFound=Pedido de autorização não encontrado login.oauth2InvalidUserInfoResponse=Resposta de Informação de Utilizador Inválida login.oauth2invalidRequest=Pedido Inválido diff --git a/src/main/resources/messages_ro_RO.properties b/src/main/resources/messages_ro_RO.properties index fa37b823a..306dc1763 100644 --- a/src/main/resources/messages_ro_RO.properties +++ b/src/main/resources/messages_ro_RO.properties @@ -572,8 +572,8 @@ login.invalid=Nume de utilizator sau parolă invalidă. login.locked=Contul tău a fost blocat. login.signinTitle=Te rugăm să te autentifici login.ssoSignIn=Conectare prin conectare unică -login.oauth2AutoCreateDisabled=OAUTH2 Creare automată utilizator dezactivată -login.oauth2AdminBlockedUser=Înregistrarea sau conectarea utilizatorilor neînregistrați este în prezent blocată. Te rugăm să contactezi administratorul. +login.oAuth2AutoCreateDisabled=OAUTH2 Creare automată utilizator dezactivată +login.oAuth2AdminBlockedUser=Înregistrarea sau conectarea utilizatorilor neînregistrați este în prezent blocată. Te rugăm să contactezi administratorul. login.oauth2RequestNotFound=Cererea de autorizare nu a fost găsită login.oauth2InvalidUserInfoResponse=Răspuns Invalid la Informațiile Utilizatorului login.oauth2invalidRequest=Cerere Invalidă diff --git a/src/main/resources/messages_ru_RU.properties b/src/main/resources/messages_ru_RU.properties index 4ed2cf15b..84c069b14 100644 --- a/src/main/resources/messages_ru_RU.properties +++ b/src/main/resources/messages_ru_RU.properties @@ -572,8 +572,8 @@ login.invalid=Неверное имя пользователя или парол login.locked=Ваша учетная запись заблокирована. login.signinTitle=Пожалуйста, войдите login.ssoSignIn=Вход через единый вход -login.oauth2AutoCreateDisabled=Автоматическое создание пользователей OAuth2 отключено -login.oauth2AdminBlockedUser=Регистрация или вход незарегистрированных пользователей в настоящее время заблокированы. Обратитесь к администратору. +login.oAuth2AutoCreateDisabled=Автоматическое создание пользователей OAuth2 отключено +login.oAuth2AdminBlockedUser=Регистрация или вход незарегистрированных пользователей в настоящее время заблокированы. Обратитесь к администратору. login.oauth2RequestNotFound=Запрос авторизации не найден login.oauth2InvalidUserInfoResponse=Недействительный ответ с информацией о пользователе login.oauth2invalidRequest=Недействительный запрос diff --git a/src/main/resources/messages_sk_SK.properties b/src/main/resources/messages_sk_SK.properties index 4078b1162..9d867096b 100644 --- a/src/main/resources/messages_sk_SK.properties +++ b/src/main/resources/messages_sk_SK.properties @@ -572,8 +572,8 @@ login.invalid=Neplatné používateľské meno alebo heslo. login.locked=Váš účet bol uzamknutý. login.signinTitle=Prosím, prihláste sa login.ssoSignIn=Prihlásiť sa cez Single Sign-on -login.oauth2AutoCreateDisabled=Vytváranie používateľa cez OAUTH2 je zakázané -login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. +login.oAuth2AutoCreateDisabled=Vytváranie používateľa cez OAUTH2 je zakázané +login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. login.oauth2RequestNotFound=Authorization request not found login.oauth2InvalidUserInfoResponse=Invalid User Info Response login.oauth2invalidRequest=Invalid Request diff --git a/src/main/resources/messages_sl_SI.properties b/src/main/resources/messages_sl_SI.properties index 132f9a3ee..30ebe7c59 100644 --- a/src/main/resources/messages_sl_SI.properties +++ b/src/main/resources/messages_sl_SI.properties @@ -572,8 +572,8 @@ login.invalid=Neveljavno uporabniško ime ali geslo. login.locked=Vaš račun je bil zaklenjen. login.signinTitle=Prosim prijavite se login.ssoSignIn=Prijava prek enotne prijave -login.oauth2AutoCreateDisabled=OAUTH2 Samodejno ustvarjanje uporabnika onemogočeno -login.oauth2AdminBlockedUser=Registracija ali prijava neregistriranih uporabnikov je trenutno blokirana. Prosimo kontaktirajte skrbnika. +login.oAuth2AutoCreateDisabled=OAUTH2 Samodejno ustvarjanje uporabnika onemogočeno +login.oAuth2AdminBlockedUser=Registracija ali prijava neregistriranih uporabnikov je trenutno blokirana. Prosimo kontaktirajte skrbnika. login.oauth2RequestNotFound=Zahteva za avtorizacijo ni bila najdena login.oauth2InvalidUserInfoResponse=Neveljaven odgovor z informacijami o uporabniku login.oauth2invalidRequest=Neveljavna zahteva diff --git a/src/main/resources/messages_sr_LATN_RS.properties b/src/main/resources/messages_sr_LATN_RS.properties index ad3b7483d..03fc41572 100644 --- a/src/main/resources/messages_sr_LATN_RS.properties +++ b/src/main/resources/messages_sr_LATN_RS.properties @@ -572,8 +572,8 @@ login.invalid=Neispravno korisničko ime ili lozinka. login.locked=Vaš nalog je zaključan. login.signinTitle=Molimo vas da se prijavite login.ssoSignIn=Prijavite se putem jedinstvene prijave -login.oauth2AutoCreateDisabled=OAUTH2 automatsko kreiranje korisnika je onemogućeno -login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. +login.oAuth2AutoCreateDisabled=OAUTH2 automatsko kreiranje korisnika je onemogućeno +login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. login.oauth2RequestNotFound=Authorization request not found login.oauth2InvalidUserInfoResponse=Invalid User Info Response login.oauth2invalidRequest=Invalid Request diff --git a/src/main/resources/messages_sv_SE.properties b/src/main/resources/messages_sv_SE.properties index f887e5fca..e590aa02f 100644 --- a/src/main/resources/messages_sv_SE.properties +++ b/src/main/resources/messages_sv_SE.properties @@ -572,8 +572,8 @@ login.invalid=Ogiltigt användarnamn eller lösenord. login.locked=Ditt konto har låsts. login.signinTitle=Vänligen logga in login.ssoSignIn=Logga in via enkel inloggning -login.oauth2AutoCreateDisabled=OAUTH2 Auto-skapa användare inaktiverad -login.oauth2AdminBlockedUser=Registrering eller inloggning av icke-registrerade användare är för närvarande blockerad. Kontakta administratören. +login.oAuth2AutoCreateDisabled=OAUTH2 Auto-skapa användare inaktiverad +login.oAuth2AdminBlockedUser=Registrering eller inloggning av icke-registrerade användare är för närvarande blockerad. Kontakta administratören. login.oauth2RequestNotFound=Auktoriseringsbegäran hittades inte login.oauth2InvalidUserInfoResponse=Ogiltigt svar på användarinformation login.oauth2invalidRequest=Ogiltig begäran diff --git a/src/main/resources/messages_th_TH.properties b/src/main/resources/messages_th_TH.properties index 00ed546b5..5df9c2b2f 100644 --- a/src/main/resources/messages_th_TH.properties +++ b/src/main/resources/messages_th_TH.properties @@ -572,8 +572,8 @@ login.invalid=ชื่อผู้ใช้หรือรหัสผ่าน login.locked=บัญชีของคุณถูกล็อค login.signinTitle=กรุณาลงชื่อเข้าใช้ login.ssoSignIn=เข้าสู่ระบบด้วย Single Sign-on -login.oauth2AutoCreateDisabled=การสร้างผู้ใช้ OAuth2 อัตโนมัติถูกปิดใช้งาน -login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. +login.oAuth2AutoCreateDisabled=การสร้างผู้ใช้ OAuth2 อัตโนมัติถูกปิดใช้งาน +login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. login.oauth2RequestNotFound=ไม่พบคำขอการอนุญาต login.oauth2InvalidUserInfoResponse=การตอบกลับข้อมูลผู้ใช้ไม่ถูกต้อง login.oauth2invalidRequest=คำขอไม่ถูกต้อง diff --git a/src/main/resources/messages_tr_TR.properties b/src/main/resources/messages_tr_TR.properties index 19bda39d7..e01b78cb3 100644 --- a/src/main/resources/messages_tr_TR.properties +++ b/src/main/resources/messages_tr_TR.properties @@ -572,8 +572,8 @@ login.invalid=Geçersiz kullanıcı adı veya şifre. login.locked=Hesabınız kilitlendi. login.signinTitle=Lütfen giriş yapınız. login.ssoSignIn=Tek Oturum Açma ile Giriş Yap -login.oauth2AutoCreateDisabled=OAUTH2 Otomatik Oluşturma Kullanıcı Devre Dışı Bırakıldı -login.oauth2AdminBlockedUser=Kayıtlı olmayan kullanıcıların kayıt veya giriş yapması şu anda engellenmiştir. Lütfen yöneticiyle iletişime geçin. +login.oAuth2AutoCreateDisabled=OAUTH2 Otomatik Oluşturma Kullanıcı Devre Dışı Bırakıldı +login.oAuth2AdminBlockedUser=Kayıtlı olmayan kullanıcıların kayıt veya giriş yapması şu anda engellenmiştir. Lütfen yöneticiyle iletişime geçin. login.oauth2RequestNotFound=Yetkilendirme isteği bulunamadı login.oauth2InvalidUserInfoResponse=Geçersiz Kullanıcı Bilgisi Yanıtı login.oauth2invalidRequest=Geçersiz İstek diff --git a/src/main/resources/messages_uk_UA.properties b/src/main/resources/messages_uk_UA.properties index 4cf8c65b5..5abae509d 100644 --- a/src/main/resources/messages_uk_UA.properties +++ b/src/main/resources/messages_uk_UA.properties @@ -572,8 +572,8 @@ login.invalid=Недійсне ім'я користувача або парол login.locked=Ваш обліковий запис заблоковано. login.signinTitle=Будь ласка, увійдіть login.ssoSignIn=Увійти через єдиний вхід -login.oauth2AutoCreateDisabled=Автоматичне створення користувача OAUTH2 ВИМКНЕНО -login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. +login.oAuth2AutoCreateDisabled=Автоматичне створення користувача OAUTH2 ВИМКНЕНО +login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. login.oauth2RequestNotFound=Запит на авторизація не знайдено login.oauth2InvalidUserInfoResponse=Недійсна відповідь з інформацією користувача login.oauth2invalidRequest=Недійсний запит diff --git a/src/main/resources/messages_vi_VN.properties b/src/main/resources/messages_vi_VN.properties index b0d333b4b..66accd73a 100644 --- a/src/main/resources/messages_vi_VN.properties +++ b/src/main/resources/messages_vi_VN.properties @@ -572,8 +572,8 @@ login.invalid=Tên đăng nhập hoặc mật khẩu không hợp lệ. login.locked=Tài khoản của bạn đã bị khóa. login.signinTitle=Vui lòng đăng nhập login.ssoSignIn=Đăng nhập qua Single Sign-on -login.oauth2AutoCreateDisabled=Tự động tạo người dùng OAUTH2 bị vô hiệu hóa -login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. +login.oAuth2AutoCreateDisabled=Tự động tạo người dùng OAUTH2 bị vô hiệu hóa +login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. login.oauth2RequestNotFound=Không tìm thấy yêu cầu ủy quyền login.oauth2InvalidUserInfoResponse=Phản hồi thông tin người dùng không hợp lệ login.oauth2invalidRequest=Yêu cầu không hợp lệ diff --git a/src/main/resources/messages_zh_BO.properties b/src/main/resources/messages_zh_BO.properties index 20a463c2e..8fda36d39 100644 --- a/src/main/resources/messages_zh_BO.properties +++ b/src/main/resources/messages_zh_BO.properties @@ -572,8 +572,8 @@ login.invalid=སྤྱོད་མིང་ངམ་གསང་ཚིག་ན login.locked=ཁྱེད་ཀྱི་ཐོ་མཛོད་ཟྭ་རྒྱག་བརྒྱབ་ཟིན། login.signinTitle=ནང་འཛུལ་གནང་རོགས། login.ssoSignIn=གཅིག་གྱུར་ནང་འཛུལ་བརྒྱུད་ནས་ནང་འཛུལ། -login.oauth2AutoCreateDisabled=OAUTH2 རང་འགུལ་སྤྱོད་མཁན་གསར་བཟོ་བཀག་སྡོམ་བྱས་ཟིན། -login.oauth2AdminBlockedUser=ད་ལྟ་ཐོ་འགོད་མ་བྱས་པའི་སྤྱོད་མཁན་གྱི་ཐོ་འགོད་དང་ནང་འཛུལ་བཀག་སྡོམ་བྱས་ཡོད། དོ་དམ་པར་འབྲེལ་བ་གནང་རོགས། +login.oAuth2AutoCreateDisabled=OAUTH2 རང་འགུལ་སྤྱོད་མཁན་གསར་བཟོ་བཀག་སྡོམ་བྱས་ཟིན། +login.oAuth2AdminBlockedUser=ད་ལྟ་ཐོ་འགོད་མ་བྱས་པའི་སྤྱོད་མཁན་གྱི་ཐོ་འགོད་དང་ནང་འཛུལ་བཀག་སྡོམ་བྱས་ཡོད། དོ་དམ་པར་འབྲེལ་བ་གནང་རོགས། login.oauth2RequestNotFound=དབང་སྤྲོད་རེ་ཞུ་རྙེད་མ་བྱུང་། login.oauth2InvalidUserInfoResponse=སྤྱོད་མཁན་གྱི་གནས་ཚུལ་ལན་འདེབས་ནོར་འཁྲུལ། login.oauth2invalidRequest=རེ་ཞུ་ནོར་འཁྲུལ། diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties index ad8880dea..8bdeb4152 100644 --- a/src/main/resources/messages_zh_CN.properties +++ b/src/main/resources/messages_zh_CN.properties @@ -572,8 +572,8 @@ login.invalid=用户名或密码无效。 login.locked=您的账户已被锁定。 login.signinTitle=请登录 login.ssoSignIn=通过单点登录登录 -login.oauth2AutoCreateDisabled=OAuth2 自动创建用户已禁用 -login.oauth2AdminBlockedUser=目前已阻止未注册用户的注册或登录。请联系管理员。 +login.oAuth2AutoCreateDisabled=OAuth2 自动创建用户已禁用 +login.oAuth2AdminBlockedUser=目前已阻止未注册用户的注册或登录。请联系管理员。 login.oauth2RequestNotFound=找不到验证请求 login.oauth2InvalidUserInfoResponse=无效的用户信息响应 login.oauth2invalidRequest=无效请求 diff --git a/src/main/resources/messages_zh_TW.properties b/src/main/resources/messages_zh_TW.properties index 06c61e16a..68e8b8116 100644 --- a/src/main/resources/messages_zh_TW.properties +++ b/src/main/resources/messages_zh_TW.properties @@ -572,8 +572,8 @@ login.invalid=使用者名稱或密碼無效。 login.locked=您的帳號已被鎖定。 login.signinTitle=請登入 login.ssoSignIn=透過 SSO 單一登入 -login.oauth2AutoCreateDisabled=OAuth 2.0 自動建立使用者功能已停用 -login.oauth2AdminBlockedUser=目前不允許未註冊的使用者註冊或登入。請聯絡系統管理員。 +login.oAuth2AutoCreateDisabled=OAuth 2.0 自動建立使用者功能已停用 +login.oAuth2AdminBlockedUser=目前不允許未註冊的使用者註冊或登入。請聯絡系統管理員。 login.oauth2RequestNotFound=找不到驗證請求 login.oauth2InvalidUserInfoResponse=使用者資訊回應無效 login.oauth2invalidRequest=請求無效 diff --git a/src/main/resources/settings.yml.template b/src/main/resources/settings.yml.template index f6e2bb0f5..9ba176e88 100644 --- a/src/main/resources/settings.yml.template +++ b/src/main/resources/settings.yml.template @@ -28,37 +28,38 @@ security: clientId: '' # client ID for Keycloak OAuth2 clientSecret: '' # client secret for Keycloak OAuth2 scopes: openid, profile, email # scopes for Keycloak OAuth2 - useAsUsername: preferred_username # field to use as the username for Keycloak OAuth2 + useAsUsername: preferred_username # field to use as the username for Keycloak OAuth2. Available options are: [email | name | given_name | family_name | preferred_name] google: clientId: '' # client ID for Google OAuth2 clientSecret: '' # client secret for Google OAuth2 - scopes: https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile # scopes for Google OAuth2 - useAsUsername: email # field to use as the username for Google OAuth2 + scopes: email, profile # scopes for Google OAuth2 + useAsUsername: email # field to use as the username for Google OAuth2. Available options are: [email | name | given_name | family_name] github: clientId: '' # client ID for GitHub OAuth2 clientSecret: '' # client secret for GitHub OAuth2 scopes: read:user # scope for GitHub OAuth2 - useAsUsername: login # field to use as the username for GitHub OAuth2 - issuer: '' # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) endpoint - clientId: '' # client ID from your provider - clientSecret: '' # client secret from your provider + useAsUsername: login # field to use as the username for GitHub OAuth2. Available options are: [email | login | name] + issuer: '' # set to any Provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) endpoint + clientId: '' # client ID from your Provider + clientSecret: '' # client secret from your Provider autoCreateUser: true # set to 'true' to allow auto-creation of non-existing users blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin useAsUsername: email # default is 'email'; custom fields can be used as the username scopes: openid, profile, email # specify the scopes for which the application will request permissions - provider: google # set this to your OAuth provider's name, e.g., 'google' or 'keycloak' + provider: google # set this to your OAuth Provider's name, e.g., 'google' or 'keycloak' saml2: enabled: false # Only enabled for paid enterprise clients (enterpriseEdition.enabled must be true) + provider: '' # The name of your Provider autoCreateUser: true # set to 'true' to allow auto-creation of non-existing users blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin - registrationId: stirling - idpMetadataUri: https://dev-XXXXXXXX.okta.com/app/externalKey/sso/saml/metadata - idpSingleLogoutUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/slo/saml - idpSingleLoginUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/sso/saml - idpIssuer: http://www.okta.com/externalKey - idpCert: classpath:okta.crt - privateKey: classpath:saml-private-key.key - spCert: classpath:saml-public-cert.crt + registrationId: stirling # The name of your Service Provider (SP) app name. Should match the name in the path for your SSO & SLO URLs + idpMetadataUri: https://dev-XXXXXXXX.okta.com/app/externalKey/sso/saml/metadata # The uri for your Provider's metadata + idpSingleLoginUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/sso/saml # The URL for initiating SSO. Provided by your Provider + idpSingleLogoutUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/slo/saml # The URL for initiating SLO. Provided by your Provider + idpIssuer: '' # The ID of your Provider + idpCert: classpath:okta.cert # The certificate your Provider will use to authenticate your app's SAML authentication requests. Provided by your Provider + privateKey: classpath:saml-private-key.key # Your private key. Generated from your keypair + spCert: classpath:saml-public-cert.crt # Your signing certificate. Generated from your keypair enterpriseEdition: enabled: false # set to 'true' to enable enterprise edition diff --git a/src/main/resources/templates/account.html b/src/main/resources/templates/account.html index e054bdbd2..ae7c72f9f 100644 --- a/src/main/resources/templates/account.html +++ b/src/main/resources/templates/account.html @@ -34,7 +34,7 @@ - +

Change Username?

@@ -53,7 +53,7 @@ - +

Change Password?

diff --git a/src/main/resources/templates/fragments/navbar.html b/src/main/resources/templates/fragments/navbar.html index 09160ee23..5503f1ae9 100644 --- a/src/main/resources/templates/fragments/navbar.html +++ b/src/main/resources/templates/fragments/navbar.html @@ -253,11 +253,11 @@
- Account Settings
diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html index 919e8dac0..e10ff6758 100644 --- a/src/main/resources/templates/login.html +++ b/src/main/resources/templates/login.html @@ -42,7 +42,7 @@ const runningEE = /*[[${@runningEE}]]*/ false; const SSOAutoLogin = /*[[${@SSOAutoLogin}]]*/ false; const loginMethod = /*[[${loginMethod}]]*/ 'normal'; - const providerList = /*[[${providerlist}]]*/ {}; + const providerList = /*[[${providerList}]]*/ {}; const shouldAutoRedirect = !hasRedirectError && !hasLogout && !hasMessage && @@ -98,14 +98,14 @@ favicon

Stirling-PDF

-
+ -
-
OAuth2: Error Message
+
+
OAuth2: Error Message
@@ -164,7 +164,7 @@