diff --git a/src/main/java/stirling/software/SPDF/EE/KeygenLicenseVerifier.java b/src/main/java/stirling/software/SPDF/EE/KeygenLicenseVerifier.java index a562cbcbace..ca481532c46 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 @@ @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 boolean verifyLicense(String licenseKey) { 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 @@ private JsonNode validateLicense(String licenseKey, String machineFingerprint) .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 @@ private JsonNode validateLicense(String licenseKey, String machineFingerprint) 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 cb9dbcd8014..62f82a278b7 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 AppConfig(ApplicationProperties applicationProperties) { } @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 boolean isRunningInDockerWithConfig() { } @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 0fb1e26fc99..cc9daff83a2 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 18fbf788308..34b457e8996 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 @@ 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 1a1e2bc31f8..f4f103190d9 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 void onAuthenticationFailure( } 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 4d0b0628097..c455f0ebd8d 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.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.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 @@ private void getRedirect_saml2( CustomSaml2AuthenticatedPrincipal principal = (CustomSaml2AuthenticatedPrincipal) samlAuthentication.getPrincipal(); - String nameIdValue = principal.getName(); + String nameIdValue = principal.name(); try { // Read certificate from the resource @@ -111,27 +92,7 @@ private void getRedirect_saml2( 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 @@ private void getRedirect_saml2( // 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 @@ private void getRedirect_saml2( 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 oauth2Token) { - OAuth2AuthenticationToken oauthToken = oauth2Token; + 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 432e2d2fc60..261fc307ebf 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 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 d140f7492f5..318ca1909ff 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 SecurityFilterChain filterChain(HttpSecurity http) throws Exception { if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) { http.csrf(csrf -> csrf.disable()); } + if (loginEnabledValue) { http.addFilterBefore( userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); @@ -164,8 +161,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .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 SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .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 SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .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 SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .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 PersistentTokenRepository persistentTokenRepository() { } @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 75cffc2f81d..c1c59191264 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 @@ protected void doFilterInternal( // 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; @@ -179,7 +179,7 @@ protected void doFilterInternal( 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; } @@ -195,7 +195,7 @@ protected void doFilterInternal( // 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 cc1401f9fa0..6e30102ed1e 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.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 void migrateOauth2ToSSO() { } // 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,6 +371,7 @@ public boolean isUserDisabled(String username) { public void invalidateUserSessions(String username) { String usernameP = ""; + for (Object principal : sessionRegistry.getAllPrincipals()) { for (SessionInformation sessionsInformation : sessionRegistry.getAllSessions(principal, false)) { @@ -381,9 +380,9 @@ public void invalidateUserSessions(String username) { } else if (principal instanceof OAuth2User oAuth2User) { usernameP = oAuth2User.getName(); } else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) { - usernameP = saml2User.getName(); - } else if (principal instanceof String user) { - usernameP = user; + usernameP = saml2User.name(); + } else if (principal instanceof String) { + usernameP = (String) principal; } if (usernameP.equalsIgnoreCase(username)) { sessionRegistry.expireSession(sessionsInformation.getSessionId()); @@ -409,33 +408,41 @@ public String getCurrentUsername() { } @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 d2b301c0103..704fd013d87 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 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 8597935f500..c318f1d9787 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 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 89d722776c7..41a8c47b9c1 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 void onAuthenticationFailure( 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 void onAuthenticationFailure( if ("Password must not be null".equals(error.getErrorCode())) { 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 1765a4addc5..668772b2efa 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.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; @@ -76,6 +75,7 @@ public void onAuthenticationSuccess( 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"); @@ -85,13 +85,14 @@ public void onAuthenticationSuccess( && 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 2c683b387da..117c9de8fc3 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.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 CustomOAuth2UserService( @Override public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { - OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2(); - String usernameAttribute = oauth2.getUseAsUsername(); - if (usernameAttribute == null || usernameAttribute.trim().isEmpty()) { - Client client = oauth2.getClient(); - if (client != null && client.getKeycloak() != null) { - usernameAttribute = client.getKeycloak().getUseAsUsername(); - } else { - usernameAttribute = "email"; - } - } - try { OidcUser user = delegate.loadUser(userRequest); - String username = user.getUserInfo().getClaimAsString(usernameAttribute); + OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2(); + UsernameAttribute usernameAttribute = + UsernameAttribute.valueOf(oauth2.getUseAsUsername().toUpperCase()); + String usernameAttributeKey = usernameAttribute.getName(); - // Check if the username claim is null or empty - if (username == null || username.trim().isEmpty()) { - throw new IllegalArgumentException( - "Claim '" + usernameAttribute + "' cannot be null or empty"); - } + // todo: save user by OIDC ID instead of username + Optional 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 OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2Authenticatio user.getAuthorities(), userRequest.getIdToken(), user.getUserInfo(), - usernameAttribute); + usernameAttributeKey); } catch (IllegalArgumentException e) { log.error("Error loading OIDC user: {}", e.getMessage()); throw new OAuth2AuthenticationException(new OAuth2Error(e.getMessage()), e); diff --git a/src/main/java/stirling/software/SPDF/config/security/oauth2/OAuth2Configuration.java b/src/main/java/stirling/software/SPDF/config/security/oauth2/OAuth2Configuration.java index 454af5255d5..764c9533c54 100644 --- a/src/main/java/stirling/software/SPDF/config/security/oauth2/OAuth2Configuration.java +++ b/src/main/java/stirling/software/SPDF/config/security/oauth2/OAuth2Configuration.java @@ -1,5 +1,8 @@ package stirling.software.SPDF.config.security.oauth2; +import static org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE; +import static stirling.software.SPDF.utils.validation.Validator.*; + import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -26,18 +29,20 @@ 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.provider.GithubProvider; +import stirling.software.SPDF.model.UsernameAttribute; +import stirling.software.SPDF.model.exception.NoProviderFoundException; +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.Provider; -@Configuration @Slf4j -@ConditionalOnProperty( - value = "security.oauth2.enabled", - havingValue = "true", - matchIfMissing = false) +@Configuration +@ConditionalOnProperty(value = "security.oauth2.enabled", havingValue = "true") public class OAuth2Configuration { + public static final String REDIRECT_URI_PATH = "{baseUrl}/login/oauth2/code/"; + private final ApplicationProperties applicationProperties; @Lazy private final UserService userService; @@ -48,139 +53,175 @@ public OAuth2Configuration( } @Bean - @ConditionalOnProperty( - value = "security.oauth2.enabled", - havingValue = "true", - matchIfMissing = false) - public ClientRegistrationRepository clientRegistrationRepository() { + @ConditionalOnProperty(value = "security.oauth2.enabled", havingValue = "true") + public ClientRegistrationRepository clientRegistrationRepository() + throws NoProviderFoundException { List 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(); + return new InMemoryClientRegistrationRepository(registrations); } private Optional keycloakClientRegistration() { - OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); - if (oauth == null || !oauth.getEnabled()) { - return Optional.empty(); - } - Client client = oauth.getClient(); - if (client == null) { + OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2(); + + if (isOAuth2Enabled(oauth2) || isClientInitialised(oauth2)) { 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 githubClientRegistration() { - OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); - if (oauth == null || !oauth.getEnabled()) { + private Optional googleClientRegistration() { + OAUTH2 oAuth2 = applicationProperties.getSecurity().getOauth2(); + + if (isOAuth2Enabled(oAuth2) || isClientInitialised(oAuth2)) { return Optional.empty(); } - Client client = oauth.getClient(); - if (client == null) { + + 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 oAuth2 = applicationProperties.getSecurity().getOauth2(); + + if (isOAuth2Enabled(oAuth2)) { 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 @@ GrantedAuthoritiesMapper userAuthoritiesMapper() { (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 1bb13d6965e..354e7875096 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.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 efd0dc19917..04a83f4d881 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.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 Map> getAttributes() { 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 df82c40b1b1..884c3bd2e8b 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 @@ 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 void onAuthenticationFailure( HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) - throws IOException, ServletException { - if (exception instanceof Saml2AuthenticationException saml2Exception) { - Saml2Error error = saml2Exception.getSaml2Error(); + 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 e23dc157174..06c97b8aa29 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.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 @@ -98,7 +98,7 @@ public void onAuthenticationSuccess( + " logout", username); response.sendRedirect( - contextPath + "/logout?oauth2AuthenticationErrorWeb=true"); + contextPath + "/logout?oAuth2AuthenticationErrorWeb=true"); return; } @@ -106,20 +106,18 @@ public void onAuthenticationSuccess( 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 47a89414d45..3a576aea57e 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.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.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 Saml2Authentication convert(ResponseToken responseToken) { 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 Saml2Authentication convert(ResponseToken responseToken) { 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 Saml2Authentication convert(ResponseToken responseToken) { 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 bc72df7ad79..a6b487ec64a 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.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 @@ @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 RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exc 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) { - log.debug( - "AssertionConsumerServiceURL: {}", - authnRequest.getAssertionConsumerServiceURL()); - } - // Log NameID policy if present - if (authnRequest.getNameIDPolicy() != 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( - "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/controller/api/UserController.java b/src/main/java/stirling/software/SPDF/controller/api/UserController.java index a648dd6f174..8a811db9ac5 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.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 RedirectView changePasswordOnLogin( 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 RedirectView changePassword( 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 String updateUserSettings(HttpServletRequest request, Principal principal 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 RedirectView saveUser( 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 RedirectView changeUserEnabled( 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,7 +295,7 @@ public RedirectView changeUserEnabled( List principals = sessionRegistry.getAllPrincipals(); String userNameP = ""; for (Object principal : principals) { - List sessionsInformations = + List sessionsInformation = sessionRegistry.getAllSessions(principal, false); if (principal instanceof UserDetails userDetail) { userNameP = userDetail.getUsername(); @@ -307,8 +307,8 @@ public RedirectView changeUserEnabled( userNameP = user; } 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 9fa78c83938..fbb84502311 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 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 @@ @Tag(name = "Account Security", description = "Account Security APIs") public class AccountWebController { - private final ApplicationProperties applicationProperties; + 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 String login(HttpServletRequest request, Model model, Authentication auth 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 String showAddUserForm( .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 String showAddUserForm( }) .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,71 +332,54 @@ public String account(HttpServletRequest request, Model model, Authentication au if (authentication != null && authentication.isAuthenticated()) { Object principal = authentication.getPrincipal(); String username = null; + // Retrieve username and other attributes and add login attributes to the model if (principal instanceof UserDetails userDetail) { - // Retrieve username and other attributes username = userDetail.getUsername(); - // Add oAuth2 Login attributes to the model model.addAttribute("oAuth2Login", false); } if (principal instanceof OAuth2User oauth2User) { - // Retrieve username and other attributes - username = - oauth2User.getAttribute( - applicationProperties.getSecurity().getOauth2().getUseAsUsername()); - // Add oAuth2 Login attributes to the model + username = oauth2User.getName(); model.addAttribute("oAuth2Login", true); } if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) { - // Retrieve username and other attributes username = saml2User.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()); @@ -426,17 +401,12 @@ public String changeCreds( if (authentication != null && authentication.isAuthenticated()) { Object principal = authentication.getPrincipal(); if (principal instanceof UserDetails userDetails) { - // Cast the principal object to UserDetails - // Retrieve username and other attributes + 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"); @@ -459,7 +429,7 @@ public String changeCreds( } 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 0400b3d8bb7..0b4783783cd 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.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 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 boolean isUserPass() { || 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 static class InitialLogin { @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 Resource getSpCert() { } } - 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 static class OAUTH2 { 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 @@ protected boolean isValid(Collection value, String name) { } 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 enum Driver { @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 87f5fa29847..00000000000 --- 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 d0d54827a82..00000000000 --- 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 00000000000..23e098a4985 --- /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 00000000000..162070f3800 --- /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 f9010601555..d0bd8330cb2 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 00000000000..0bf06ee20ff --- /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 00000000000..8ca61094f6f --- /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 afe7fcb77ea..00000000000 --- 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 e43e1327ba7..4cf29c4023d 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; } - @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; + public String getUserinfoUri() { + return USER_INFO_URI; } @Override - public String getClientSecret() { - return this.clientSecret; + public String getName() { + return NAME; } @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; + return scopes; } @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 d715b10380d..6b89e5b1e8c 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"; - - @Override - public String getIssuer() { - return this.issuer; - } - - @Override - public void setIssuer(String issuer) { - this.issuer = issuer; + private static final String NAME = "keycloak"; + private static final String CLIENT_NAME = "Keycloak"; + + 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 String getClientId() { - return this.clientId; - } - - @Override - public void setClientId(String clientId) { - this.clientId = clientId; - } - - @Override - public String getClientSecret() { - return this.clientSecret; + public String getName() { + return NAME; } @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; + return scopes; } @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 00000000000..4e20c15f49c --- /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 d0de88af02f..d4d0d6619bc 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 @@ 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 00000000000..83b906857c4 --- /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 52f5dad1150..a95d56920ff 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=طلب غير صالح @@ -951,6 +951,7 @@ fileToPDF.submit=تحويل إلى PDF compress.title=ضغط compress.header=ضغط ملف PDF compress.credit=تستخدم هذه الخدمة qpdf لضغط / تحسين PDF. +compress.grayscale.label=تطبيق التدرج الرمادي للضغط compress.selectText.1=الوضع اليدوي - من 1 إلى 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=مستوى التحسين: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=تطبيق التدرج الرمادي للضغط diff --git a/src/main/resources/messages_az_AZ.properties b/src/main/resources/messages_az_AZ.properties index 9ceb9b38633..f3d692a5c80 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 @@ -951,6 +951,7 @@ fileToPDF.submit=PDF-ə Çevir compress.title=Sıxışdır compress.header=PDF-i Sıxışdır compress.credit=Bu servis PDF sıxışdırılması/Optimizasiyası üçün Ghostscript istifadə edir. +compress.grayscale.label=Sıxma üçün Boz Rəng Tətbiq Edin compress.selectText.1=Manual Mod - 1-dən 5-ə compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Optimizasiya səviyyəsi: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Sıxma üçün Boz Rəng Tətbiq Edin diff --git a/src/main/resources/messages_bg_BG.properties b/src/main/resources/messages_bg_BG.properties index df835f4fb0a..16f756a8bfd 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=Невалидна заявка @@ -951,6 +951,7 @@ fileToPDF.submit=Преобразуване към PDF compress.title=Компресиране compress.header=Компресиране на PDF compress.credit=Тази услуга използва qpdf за PDF компресиране/оптимизиране. +compress.grayscale.label=Приложи сива скала за компресиране compress.selectText.1=Ръчен режим - от 1 до 5 compress.selectText.1.1=При нива на оптимизация от 6 до 9, в допълнение към общото компресиране на PDF, резолюцията на изображението се намалява, за да се намали допълнително размерът на файла. По-високите нива водят до по-силна компресия на изображенията (до 50% от оригиналния размер), като се постига по-голямо намаляване на размера, но с потенциална загуба на качество на изображенията. compress.selectText.2=Ниво на оптимизация: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Версия validateSignature.cert.keyUsage=Предназначение на ключа за използване validateSignature.cert.selfSigned=Самостоятелно подписан validateSignature.cert.bits=битове -compress.grayscale.label=Приложи сива скала за компресиране diff --git a/src/main/resources/messages_ca_CA.properties b/src/main/resources/messages_ca_CA.properties index ed00145f0c9..708126d05c3 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Converteix a PDF compress.title=Comprimir compress.header=Comprimir PDF compress.credit=Aquest servei utilitza qpdf per a la compressió/optimització de PDF. +compress.grayscale.label=Aplicar escala de grisos per a la compressió compress.selectText.1=Mode manual: de l'1 al 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Nivell d'optimització: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Aplicar escala de grisos per a la compressió diff --git a/src/main/resources/messages_cs_CZ.properties b/src/main/resources/messages_cs_CZ.properties index d3eb1701ec8..27801cbf7c9 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Převést na PDF compress.title=Komprimovat compress.header=Komprimovat PDF compress.credit=Tato služba používá qpdf pro kompresi/optimalizaci PDF. +compress.grayscale.label=Použít stupnici šedi pro kompresi compress.selectText.1=Ruční režim - Od 1 do 5 compress.selectText.1.1=V úrovních optimalizace 6 až 9 je kromě obecné komprese PDF sníženo rozlišení obrázků pro další zmenšení velikosti souboru. Vyšší úrovně vedou k silnější kompresi obrázků (až na 50 % původní velikosti), čímž dosahují většího zmenšení velikosti, ale s potenciální ztrátou kvality obrázků. compress.selectText.2=Úroveň optimalizace: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Verze validateSignature.cert.keyUsage=Použití klíče validateSignature.cert.selfSigned=Podepsaný sám sebou validateSignature.cert.bits=bitů -compress.grayscale.label=Použít stupnici šedi pro kompresi diff --git a/src/main/resources/messages_da_DK.properties b/src/main/resources/messages_da_DK.properties index 795d0b62ee3..b3019041b43 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Konvertér til PDF compress.title=Komprimer compress.header=Komprimer PDF compress.credit=Denne tjeneste bruger qpdf til PDF Komprimering/Optimering. +compress.grayscale.label=Anvend gråskala til komprimering compress.selectText.1=Manuel Tilstand - Fra 1 til 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Optimeringsniveau: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Anvend gråskala til komprimering diff --git a/src/main/resources/messages_de_DE.properties b/src/main/resources/messages_de_DE.properties index 6dfac622542..113ec027ec5 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 @@ -951,6 +951,7 @@ fileToPDF.submit=In PDF konvertieren compress.title=Komprimieren compress.header=PDF komprimieren compress.credit=Dieser Dienst verwendet qpdf für die PDF-Komprimierung/-Optimierung. +compress.grayscale.label=Graustufen für Komprimierung anwenden compress.selectText.1=Manueller Modus – Von 1 bis 5 compress.selectText.1.1=In den Optimierungsstufen 6 bis 9 wird zusätzlich zur allgemeinen PDF-Komprimierung die Bildauflösung reduziert, um die Dateigröße weiter zu verringern. Höhere Stufen führen zu einer stärkeren Bildkomprimierung (bis zu 50 % der Originalgröße), wodurch eine stärkere Größenreduzierung erreicht wird, die jedoch mit einem möglichen Qualitätsverlust der Bilder einhergeht. compress.selectText.2=Optimierungsstufe: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Schlüsselverwendung validateSignature.cert.selfSigned=Selbstsigniert validateSignature.cert.bits=bits -compress.grayscale.label=Graustufen für Komprimierung anwenden diff --git a/src/main/resources/messages_el_GR.properties b/src/main/resources/messages_el_GR.properties index 0bc7ad5bef7..90d63a8f37e 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=Μη έγκυρο αίτημα @@ -951,6 +951,7 @@ fileToPDF.submit=Μετατροπή σε PDF compress.title=Συμπίεση compress.header=Συμπίεση PDF compress.credit=Αυτή η υπηρεσία χρησιμοποιεί qpdf για συμπίεση/βελτιστοποίηση PDF. +compress.grayscale.label=Εφαρμογή κλίμακας του γκρι για συμπίεση compress.selectText.1=Χειροκίνητη λειτουργία - Από 1 έως 5 compress.selectText.1.1=Στα επίπεδα βελτιστοποίησης 6 έως 9, εκτός από τη γενική συμπίεση PDF, η ανάλυση εικόνας μειώνεται για περαιτέρω μείωση του μεγέθους αρχείου. Υψηλότερα επίπεδα οδηγούν σε ισχυρότερη συμπίεση εικόνας (έως και 50% του αρχικού μεγέθους), επιτυγχάνοντας μεγαλύτερη μείωση μεγέθους αλλά με πιθανή απώλεια ποιότητας στις εικόνες. compress.selectText.2=Επίπεδο βελτιστοποίησης: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Έκδοση validateSignature.cert.keyUsage=Χρήση κλειδιού validateSignature.cert.selfSigned=Αυτο-υπογεγραμμένο validateSignature.cert.bits=bits -compress.grayscale.label=Εφαρμογή κλίμακας του γκρι για συμπίεση diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 9c2d0b55abe..bd53456358a 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Convert to PDF compress.title=Compress compress.header=Compress PDF compress.credit=This service uses qpdf for PDF Compress/Optimisation. +compress.grayscale.label=Apply Grayscale for Compression compress.selectText.1=Manual Mode - From 1 to 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Optimisation level: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Apply Grayscale for Compression diff --git a/src/main/resources/messages_en_US.properties b/src/main/resources/messages_en_US.properties index cde27df13ab..aaa9f9682ca 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Convert to PDF compress.title=Compress compress.header=Compress PDF compress.credit=This service uses qpdf for PDF Compress/Optimisation. +compress.grayscale.label=Apply Grayscale for Compression compress.selectText.1=Manual Mode - From 1 to 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Optimization level: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Apply Grayscale for Compression diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties index 216bfd01739..d16e417a856 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Convertir a PDF compress.title=Comprimir compress.header=Comprimir PDF compress.credit=Este servicio utiliza qpdf para compresión/optimización de PDF +compress.grayscale.label=Aplicar escala de grises para compresión compress.selectText.1=Modo manual - De 1 a 5 compress.selectText.1.1=En los niveles de optimización 6 a 9, además de la compresión general de PDF, se reduce la resolución de la imagen para reducir aún más el tamaño del archivo. Los niveles más altos dan como resultado una mayor compresión de la imagen (hasta el 50 % del tamaño original), lo que permite lograr una mayor reducción del tamaño, pero con una posible pérdida de calidad en las imágenes. compress.selectText.2=Nivel de optimización: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Versión validateSignature.cert.keyUsage=Uso de la llave validateSignature.cert.selfSigned=Autofirmado validateSignature.cert.bits=bits -compress.grayscale.label=Aplicar escala de grises para compresión diff --git a/src/main/resources/messages_eu_ES.properties b/src/main/resources/messages_eu_ES.properties index 53fcf428255..e7397d25d35 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 @@ -951,6 +951,7 @@ fileToPDF.submit=PDF bihurtu compress.title=Konprimatu compress.header=PDFa konprimatu compress.credit=Zerbitzu honek qpdf erabiltzen du PDFak komprimatzeko/optimizatzeko +compress.grayscale.label=Aplikatu grisezko eskala konpresiorako compress.selectText.1=Eskuz 1etik 5ra compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Optimizazio maila: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Aplikatu grisezko eskala konpresiorako diff --git a/src/main/resources/messages_fa_IR.properties b/src/main/resources/messages_fa_IR.properties index 0b327f5c843..13c7be2d404 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=درخواست نامعتبر @@ -951,6 +951,7 @@ fileToPDF.submit=تبدیل به PDF compress.title=فشرده‌سازی compress.header=فشرده‌سازی PDF compress.credit=این سرویس از qpdf برای فشرده‌سازی / بهینه‌سازی PDF استفاده می‌کند. +compress.grayscale.label=اعمال مقیاس خاکستری برای فشرده‌سازی compress.selectText.1=حالت دستی - از 1 تا 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=سطح بهینه‌سازی: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=نسخه validateSignature.cert.keyUsage=کاربرد کلید validateSignature.cert.selfSigned=با امضای خود validateSignature.cert.bits=بیت‌ها -compress.grayscale.label=اعمال مقیاس خاکستری برای فشرده‌سازی diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties index 4af67d13302..d82065a119f 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Convertir compress.title=Compresser un PDF compress.header=Compresser un PDF (lorsque c'est possible!) compress.credit=Ce service utilise qpdf pour la compression et l'optimisation des PDF. +compress.grayscale.label=Appliquer l'échelle de gris pour la compression compress.selectText.1=Mode manuel – de 1 à 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Niveau d'optimisation @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Usage de la clé validateSignature.cert.selfSigned=Auto-signé validateSignature.cert.bits=bits -compress.grayscale.label=Appliquer l'échelle de gris pour la compression diff --git a/src/main/resources/messages_ga_IE.properties b/src/main/resources/messages_ga_IE.properties index bbf1131de01..154abd49b24 100644 --- a/src/main/resources/messages_ga_IE.properties +++ b/src/main/resources/messages_ga_IE.properties @@ -951,6 +951,7 @@ fileToPDF.submit=Tiontaigh go PDF compress.title=Comhbhrúigh compress.header=Comhbhrúigh PDF compress.credit=Úsáideann an tseirbhís seo qpdf le haghaidh Comhbhrú/Optimization PDF. +compress.grayscale.label=Cuir Scála Liath i bhFeidhm le Comhbhrú compress.selectText.1=Mód Láimhe - Ó 1 go 5 compress.selectText.1.1=I leibhéil optamaithe 6 go 9, chomh maith le comhbhrú ginearálta PDF, déantar réiteach íomhá a laghdú de réir scála chun méid comhaid a laghdú tuilleadh. Mar thoradh ar leibhéil níos airde tá comhbhrú íomhá níos láidre (suas le 50% den mhéid bunaidh), ag baint amach laghdú méide níos mó ach le caillteanas cáilíochta féideartha in íomhánna. compress.selectText.2=Leibhéal optamaithe: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Leagan validateSignature.cert.keyUsage=Úsáid Eochrach validateSignature.cert.selfSigned=Féin-Sínithe validateSignature.cert.bits=giotáin -compress.grayscale.label=Cuir Scála Liath i bhFeidhm le Comhbhrú diff --git a/src/main/resources/messages_hi_IN.properties b/src/main/resources/messages_hi_IN.properties index 3a5fb5b8ada..0ddabb7ac89 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=अमान्य अनुरोध @@ -951,6 +951,7 @@ fileToPDF.submit=PDF में बदलें compress.title=कम्प्रेस compress.header=PDF कम्प्रेस करें compress.credit=यह सेवा PDF कम्प्रेस/अनुकूलन के लिए qpdf का उपयोग करती है। +compress.grayscale.label=संपीड़न के लिए ग्रेस्केल लागू करें compress.selectText.1=मैनुअल मोड - स्तर 1 से 4 compress.selectText.1.1=अनुकूलन स्तर 6 से 9 में, सामान्य PDF कम्प्रेसन के अतिरिक्त, फ़ाइल आकार को और कम करने के लिए छवि रेज़ोल्यूशन को कम किया जाता है। उच्च स्तर पर छवियों का अधिक कम्प्रेसन होता है (मूल आकार का 50% तक), जिससे आकार में अधिक कमी आती है लेकिन छवियों की गुणवत्ता प्रभावित हो सकती है। compress.selectText.2=अनुकूलन स्तर: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=संस्करण validateSignature.cert.keyUsage=कुंजी उपयोग validateSignature.cert.selfSigned=स्व-हस्ताक्षरित validateSignature.cert.bits=बिट्स -compress.grayscale.label=संपीड़न के लिए ग्रेस्केल लागू करें diff --git a/src/main/resources/messages_hr_HR.properties b/src/main/resources/messages_hr_HR.properties index f56e326e9ed..3a248260023 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Pretvori u PDF compress.title=Komprimirajte compress.header=Komprimirajte PDF compress.credit=Ova usluga koristi qpdf za komprimiranje / optimizaciju PDF-a. +compress.grayscale.label=Primijeni sivinu za kompresiju compress.selectText.1=Ručni režim - Od 1 do 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Nivo optimizacije: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Primijeni sivinu za kompresiju diff --git a/src/main/resources/messages_hu_HU.properties b/src/main/resources/messages_hu_HU.properties index 0a6f21a06af..3ef1d3e4ecc 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Konvertálás PDF-be compress.title=Tömörítés compress.header=PDF tömörítése compress.credit=Ez a szolgáltatás a qpdf használatával végzi a PDF tömörítését/optimalizálását. +compress.grayscale.label=Szürkeárnyalatok alkalmazása tömörítéshez compress.selectText.1=Kézi mód - 1-től 5-ig compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Optimalizálási szint: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Verzió validateSignature.cert.keyUsage=Kulcshasználat validateSignature.cert.selfSigned=Önaláírt validateSignature.cert.bits=bit -compress.grayscale.label=Szürkeárnyalatok alkalmazása tömörítéshez diff --git a/src/main/resources/messages_id_ID.properties b/src/main/resources/messages_id_ID.properties index b869c9d3464..021c5ec2b4b 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Konversi ke PDF compress.title=Kompres compress.header=Kompres PDF compress.credit=Layanan ini menggunakan qpdf untuk Kompresi/Optimalisasi PDF. +compress.grayscale.label=Terapkan Skala Abu-Abu untuk Kompresi compress.selectText.1=Mode Manual - Dari 1 hingga 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Tingkat Optimalisasi: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Terapkan Skala Abu-Abu untuk Kompresi diff --git a/src/main/resources/messages_it_IT.properties b/src/main/resources/messages_it_IT.properties index a0f924ee6ad..2dab8f05b8e 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Converti in PDF compress.title=Comprimi compress.header=Comprimi PDF compress.credit=Questo servizio utilizza qpdf per la compressione/ottimizzazione dei PDF. +compress.grayscale.label=Applica scala di grigio per la compressione compress.selectText.1=Modalità manuale - Da 1 a 5 compress.selectText.1.1=Nei livelli di ottimizzazione da 6 a 9, oltre alla compressione PDF generale, la risoluzione dell'immagine viene ridotta per ridurre ulteriormente le dimensioni del file. Livelli più alti comportano una compressione dell'immagine più forte (fino al 50% delle dimensioni originali), ottenendo una maggiore riduzione delle dimensioni ma con una potenziale perdita di qualità nelle immagini. compress.selectText.2=Livello di ottimizzazione: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Versione validateSignature.cert.keyUsage=Utilizzo della chiave validateSignature.cert.selfSigned=Autofirmato validateSignature.cert.bits=bit -compress.grayscale.label=Applica scala di grigio per la compressione diff --git a/src/main/resources/messages_ja_JP.properties b/src/main/resources/messages_ja_JP.properties index 0f7c85082f7..e6810b2ca32 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=無効なリクエスト @@ -951,6 +951,7 @@ fileToPDF.submit=PDFを変換 compress.title=圧縮 compress.header=PDFを圧縮 compress.credit=本サービスはPDFの圧縮/最適化にqpdfを使用しています。 +compress.grayscale.label=圧縮にグレースケールを適用する compress.selectText.1=手動モード - 1から9 compress.selectText.1.1=最適化レベル6~9では、一般的なPDF圧縮に加えて画像解像度が縮小され、ファイルサイズがさらに縮小されます。レベルが高くなると、画像圧縮が強化され (元のサイズの最大 50%)、サイズはさらに縮小されますが、画像の品質が低下する可能性があります。 compress.selectText.2=品質レベル: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=バージョン validateSignature.cert.keyUsage=キーの使用法 validateSignature.cert.selfSigned=自己署名 validateSignature.cert.bits=ビット -compress.grayscale.label=圧縮にグレースケールを適用する diff --git a/src/main/resources/messages_ko_KR.properties b/src/main/resources/messages_ko_KR.properties index 21a20db0709..6049937fba0 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=잘못된 요청 @@ -951,6 +951,7 @@ fileToPDF.submit=PDF로 변환 compress.title=압축 compress.header=PDF 압축 compress.credit=이 서비스는 PDF 압축/최적화를 위해 qpdf를 사용합니다. +compress.grayscale.label=압축을 위해 그레이스케일 적용 compress.selectText.1=수동 모드 - 1에서 5 compress.selectText.1.1=최적화 레벨 6에서 9에서는 일반적인 PDF 압축 외에도 이미지 해상도가 낮아져 파일 크기가 더욱 감소합니다. 높은 레벨은 더 강력한 이미지 압축(원본 크기의 최대 50%)을 초래하여 더 큰 크기 감소를 달성하지만 이미지 품질이 저하될 수 있습니다. compress.selectText.2=최적화 레벨: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=버전 validateSignature.cert.keyUsage=키 용도 validateSignature.cert.selfSigned=자체 서명 validateSignature.cert.bits=비트 -compress.grayscale.label=압축을 위해 그레이스케일 적용 diff --git a/src/main/resources/messages_nl_NL.properties b/src/main/resources/messages_nl_NL.properties index a3e754a13a7..2bc49d17cbb 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Omzetten naar PDF compress.title=Comprimeren compress.header=PDF comprimeren compress.credit=Deze functie gebruikt qpdf voor PDF Compressie/Optimalisatie. +compress.grayscale.label=Grijsschaal toepassen voor compressie compress.selectText.1=Handmatige modus - Van 1 tot 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Optimalisatieniveau: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Grijsschaal toepassen voor compressie diff --git a/src/main/resources/messages_no_NB.properties b/src/main/resources/messages_no_NB.properties index cfb8dbaea1f..474013c6185 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Konverter til PDF compress.title=Komprimer compress.header=Komprimer PDF compress.credit=Denne tjenesten bruker qpdf for PDF-komprimering/optimisering. +compress.grayscale.label=Bruk gråskala for komprimering compress.selectText.1=Manuell modus - Fra 1 til 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Optimeringsnivå: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Bruk gråskala for komprimering diff --git a/src/main/resources/messages_pl_PL.properties b/src/main/resources/messages_pl_PL.properties index 166fff51f9c..7f3936d5ea4 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Konwertuj na PDF compress.title=Kompresuj compress.header=Kompresuj PDF compress.credit=Ta usługa używa qpdf do kompresji/optymalizacji PDF. +compress.grayscale.label=Zastosuj skalę szarości do kompresji compress.selectText.1=Tryb ręczny - Od 1 do 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Poziom optymalizacji: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Zastosuj skalę szarości do kompresji diff --git a/src/main/resources/messages_pt_BR.properties b/src/main/resources/messages_pt_BR.properties index 73c1e6d8d8c..28a5b6f12a0 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Converter para PDF compress.title=Comprimir compress.header=Comprimir compress.credit=Este serviço usa o Qpdf para compressão/otimização de PDF. +compress.grayscale.label=Aplicar escala de cinza para compressão compress.selectText.1=Modo Manual - De 1 a 9 compress.selectText.1.1=Nos níveis de otimização 6-9, além da compressão normal do PDF, a resolução das imagens são reduzidas, para diminuir ainda mais o tamanho do arquivo. Quanto maior o nível, maior a compressão da imagem (até 50% do tamanho original), resultando em tamanho menor do arquivo, porém com menor qualidade nas imagens. compress.selectText.2=Nível de Otimização: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Versão validateSignature.cert.keyUsage=Uso da chave validateSignature.cert.selfSigned=Autoassinados validateSignature.cert.bits=bits -compress.grayscale.label=Aplicar escala de cinza para compressão diff --git a/src/main/resources/messages_pt_PT.properties b/src/main/resources/messages_pt_PT.properties index d26e33318d9..9e11618e43a 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Converter para PDF compress.title=Comprimir compress.header=Comprimir PDF compress.credit=Este serviço usa qpdf para Compressão/Otimização de PDF. +compress.grayscale.label=Aplicar escala de cinzentos para compressão compress.selectText.1=Modo Manual - De 1 a 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Nível de otimização: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Versão validateSignature.cert.keyUsage=Utilização da Chave validateSignature.cert.selfSigned=Auto-Assinado validateSignature.cert.bits=bits -compress.grayscale.label=Aplicar escala de cinzentos para compressão diff --git a/src/main/resources/messages_ro_RO.properties b/src/main/resources/messages_ro_RO.properties index 6a861e11695..306dc17632e 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ă @@ -951,6 +951,7 @@ fileToPDF.submit=Convertiți în PDF compress.title=Comprimare compress.header=Comprimare PDF compress.credit=Acest serviciu utilizează qpdf pentru comprimarea/optimizarea PDF-urilor. +compress.grayscale.label=Aplicare scală de gri pentru compresie compress.selectText.1=Modul manual - de la 1 la 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Nivel de optimizare: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Aplicare scală de gri pentru compresie diff --git a/src/main/resources/messages_ru_RU.properties b/src/main/resources/messages_ru_RU.properties index f407f0ae210..84c069b14f2 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=Недействительный запрос @@ -951,6 +951,7 @@ fileToPDF.submit=Преобразовать в PDF compress.title=Сжать compress.header=Сжать PDF compress.credit=Этот сервис использует qpdf для сжатия/оптимизации PDF. +compress.grayscale.label=Применить шкалу серого для сжатия compress.selectText.1=Ручной режим - от 1 до 5 compress.selectText.1.1=На уровнях оптимизации от 6 до 9, помимо общего сжатия PDF, разрешение изображений уменьшается для дальнейшего сокращения размера файла. Более высокие уровни приводят к более сильному сжатию изображений (до 50% от исходного размера), обеспечивая большее уменьшение размера, но с возможной потерей качества изображений. compress.selectText.2=Уровень оптимизации: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Версия validateSignature.cert.keyUsage=Использование ключа validateSignature.cert.selfSigned=Самоподписанный validateSignature.cert.bits=бит -compress.grayscale.label=Применить шкалу серого для сжатия diff --git a/src/main/resources/messages_sk_SK.properties b/src/main/resources/messages_sk_SK.properties index 5e4b53e9c0d..9d867096ba4 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Konvertovať do PDF compress.title=Komprimovať compress.header=Komprimovať PDF compress.credit=Táto služba používa qpdf pre kompresiu/optimalizáciu PDF. +compress.grayscale.label=Použiť odtiene šedej na kompresiu compress.selectText.1=Manuálny režim - Od 1 do 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Úroveň optimalizácie: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Použiť odtiene šedej na kompresiu diff --git a/src/main/resources/messages_sl_SI.properties b/src/main/resources/messages_sl_SI.properties index b07753d4247..30ebe7c5900 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Pretvori v PDF compress.title=Stisnite compress.header=Stisnite PDF compress.credit=Ta storitev uporablja qpdf za stiskanje/optimizacijo PDF. +compress.grayscale.label=Uporabi sivinsko lestvico za stiskanje compress.selectText.1=Ročni način - Od 1 do 5 compress.selectText.1.1=Na stopnjah optimizacije od 6 do 9 je poleg splošnega stiskanja PDF ločljivost slike zmanjšana, da se dodatno zmanjša velikost datoteke. Višje ravni povzročijo močnejše stiskanje slike (do 50 % prvotne velikosti), s čimer se doseže večje zmanjšanje velikosti, vendar s potencialno izgubo kakovosti slik. compress.selectText.2=Raven optimizacije: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Različica validateSignature.cert.keyUsage=Uporaba ključa validateSignature.cert.selfSigned=Samopodpisano validateSignature.cert.bits=bits -compress.grayscale.label=Uporabi sivinsko lestvico za stiskanje diff --git a/src/main/resources/messages_sr_LATN_RS.properties b/src/main/resources/messages_sr_LATN_RS.properties index 1edeb8620fe..03fc4157285 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Konvertuj u PDF compress.title=Kompresija compress.header=Kompresuj PDF compress.credit=Ova usluga koristi qpdf za kompresiju / optimizaciju PDF-a. +compress.grayscale.label=Primeni sivinu za kompresiju compress.selectText.1=Ručni režim - Od 1 do 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Nivo optimizacije: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Primeni sivinu za kompresiju diff --git a/src/main/resources/messages_sv_SE.properties b/src/main/resources/messages_sv_SE.properties index 00aac4efc23..e590aa02fb3 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 @@ -951,6 +951,7 @@ fileToPDF.submit=Konvertera till PDF compress.title=Komprimera compress.header=Komprimera PDF compress.credit=Denna tjänst använder qpdf för PDF-komprimering/optimering. +compress.grayscale.label=Tillämpa gråskala för komprimering compress.selectText.1=Manuellt läge - Från 1 till 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Optimeringsnivå: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Tillämpa gråskala för komprimering diff --git a/src/main/resources/messages_th_TH.properties b/src/main/resources/messages_th_TH.properties index 8a9bac5bc35..5df9c2b2fb9 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=คำขอไม่ถูกต้อง @@ -951,6 +951,7 @@ fileToPDF.submit=แปลงเป็น PDF compress.title=บีบอัด compress.header=บีบอัด PDF compress.credit=บริการนี้ใช้ qpdf สำหรับการบีบอัด/การเพิ่มประสิทธิภาพ PDF +compress.grayscale.label=ใช้ระดับสีเทาสำหรับการบีบอัด compress.selectText.1=โหมดแมนนวล - จาก 1 ถึง 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=ระดับการเพิ่มประสิทธิภาพ: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=ใช้ระดับสีเทาสำหรับการบีบอัด diff --git a/src/main/resources/messages_tr_TR.properties b/src/main/resources/messages_tr_TR.properties index 35a6ad5113a..e01b78cb33a 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 @@ -951,6 +951,7 @@ fileToPDF.submit=PDF'e Dönüştür compress.title=Sıkıştır compress.header=PDF'i Sıkıştır compress.credit=Bu hizmet PDF Sıkıştırma/Optimizasyonu için qpdf kullanır. +compress.grayscale.label=Sıkıştırma için Gri Ton Uygula compress.selectText.1=Manuel Mod - 1'den 5'e compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Optimizasyon seviyesi: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Sıkıştırma için Gri Ton Uygula diff --git a/src/main/resources/messages_uk_UA.properties b/src/main/resources/messages_uk_UA.properties index 55deb6fdd5f..5abae509dcf 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=Недійсний запит @@ -951,6 +951,7 @@ fileToPDF.submit=Перетворити у PDF compress.title=Стиснути compress.header=Стиснути PDF compress.credit=Ця служба використовує qpdf для стиснення/оптимізації PDF. +compress.grayscale.label=Застосувати відтінки сірого для стиснення compress.selectText.1=Ручний режим - від 1 до 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Рівень оптимізації: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Застосувати відтінки сірого для стиснення diff --git a/src/main/resources/messages_vi_VN.properties b/src/main/resources/messages_vi_VN.properties index e5aab3f9f0e..66accd73a62 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ệ @@ -951,6 +951,7 @@ fileToPDF.submit=Chuyển đổi sang PDF compress.title=Nén compress.header=Nén PDF compress.credit=Dịch vụ này sử dụng qpdf để Nén/Tối ưu hóa PDF. +compress.grayscale.label=Áp dụng thang độ xám để nén compress.selectText.1=Chế độ thủ công - Từ 1 đến 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Mức độ tối ưu hóa: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=Version validateSignature.cert.keyUsage=Key Usage validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.bits=bits -compress.grayscale.label=Áp dụng thang độ xám để nén diff --git a/src/main/resources/messages_zh_BO.properties b/src/main/resources/messages_zh_BO.properties index ed1269b5a53..8fda36d3905 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=རེ་ཞུ་ནོར་འཁྲུལ། @@ -951,6 +951,7 @@ fileToPDF.submit=PDF ལ་བསྒྱུར་བ། compress.title=སྡུད་སྒྲིལ། compress.header=PDF སྡུད་སྒྲིལ། compress.credit=ཞབས་ཞུ་འདིས་ PDF སྡུད་སྒྲིལ་/ཡར་རྒྱས་གཏོང་བའི་ཆེད་དུ་ qpdf བེད་སྤྱོད་བྱེད་པ། +compress.grayscale.label=应用灰度进行压缩 compress.selectText.1=Manual Mode - From 1 to 4 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=Optimisation level: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=པར་གཞི། validateSignature.cert.keyUsage=ལྡེ་མིག་བེད་སྤྱོད། validateSignature.cert.selfSigned=རང་མིང་རྟགས། validateSignature.cert.bits=གནས། -compress.grayscale.label=应用灰度进行压缩 diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties index 86ae02cbb8a..8bdeb415255 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=无效请求 @@ -951,6 +951,7 @@ fileToPDF.submit=转换为 PDF compress.title=压缩 compress.header=压缩 PDF compress.credit=此服务使用qpdf进行 PDF 压缩/优化。 +compress.grayscale.label=应用灰度进行压缩 compress.selectText.1=手动模式 - 从 1 到 5 compress.selectText.1.1=In optimization levels 6 to 9, in addition to general PDF compression, image resolution is scaled down to further reduce file size. Higher levels result in stronger image compression (up to 50% of the original size), achieving greater size reduction but with potential quality loss in images. compress.selectText.2=优化级别: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=版本 validateSignature.cert.keyUsage=密钥用途 validateSignature.cert.selfSigned=自签名 validateSignature.cert.bits=比特 -compress.grayscale.label=应用灰度进行压缩 diff --git a/src/main/resources/messages_zh_TW.properties b/src/main/resources/messages_zh_TW.properties index 06b591048b1..68e8b81167b 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=請求無效 @@ -951,6 +951,7 @@ fileToPDF.submit=轉換為 PDF compress.title=壓縮 compress.header=壓縮 PDF compress.credit=此服務使用 qpdf 進行 PDF 壓縮/最佳化。 +compress.grayscale.label=應用灰階進行壓縮 compress.selectText.1=手動模式 - 從 1 到 5 compress.selectText.1.1=在最佳化等級 6 到 9 時,除了一般 PDF 壓縮外,圖片解析度也會降低以進一步減少檔案大小。較高的壓縮等級會進行更高強度的圖片壓縮(最多可壓縮到原始大小的 50%),以達到更高的壓縮率,但可能會影響圖片品質。 compress.selectText.2=最佳化等級: @@ -1384,4 +1385,3 @@ validateSignature.cert.version=版本 validateSignature.cert.keyUsage=金鑰用途 validateSignature.cert.selfSigned=自我簽署 validateSignature.cert.bits=位元 -compress.grayscale.label=應用灰階進行壓縮 diff --git a/src/main/resources/settings.yml.template b/src/main/resources/settings.yml.template index f6e2bb0f540..9ba176e8890 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 e054bdbd20e..ae7c72f9f5a 100644 --- a/src/main/resources/templates/account.html +++ b/src/main/resources/templates/account.html @@ -34,7 +34,7 @@

User - +

Change Username?

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

Change Username?

- +

Change Password?

diff --git a/src/main/resources/templates/fragments/navbar.html b/src/main/resources/templates/fragments/navbar.html index 09160ee235f..5503f1ae9fc 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 919e8dac061..e10ff6758df 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 @@