From 036c10fc27e0851bc526b317e741d8972b184a7c Mon Sep 17 00:00:00 2001 From: Ludy Date: Sat, 15 Jun 2024 14:15:09 +0200 Subject: [PATCH] added: Differentiate login methods and more (#1471) - Added Portuguese in the table (README.md) - ApplicationProperties.class diluted, provider outsourced to its own class - Added UnsupportedProviderException to indicate a meaningful error - Closes #1357 - Closes #1238 --- README.md | 1 + .../security/SecurityConfiguration.java | 6 +- .../CustomOAuth2LogoutSuccessHandler.java | 5 +- .../controller/web/AccountWebController.java | 7 +- .../SPDF/model/ApplicationProperties.java | 325 +----------------- .../SPDF/model/provider/GithubProvider.java | 115 +++++++ .../SPDF/model/provider/GoogleProvider.java | 109 ++++++ .../SPDF/model/provider/KeycloakProvider.java | 106 ++++++ .../UnsupportedProviderException.java | 7 + src/main/resources/templates/login.html | 4 +- 10 files changed, 368 insertions(+), 317 deletions(-) create mode 100644 src/main/java/stirling/software/SPDF/model/provider/GithubProvider.java create mode 100644 src/main/java/stirling/software/SPDF/model/provider/GoogleProvider.java create mode 100644 src/main/java/stirling/software/SPDF/model/provider/KeycloakProvider.java create mode 100644 src/main/java/stirling/software/SPDF/model/provider/UnsupportedProviderException.java diff --git a/README.md b/README.md index 6aad4234..f9e37749 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,7 @@ Stirling PDF currently supports 32! | Romanian (Română) (ro_RO) | ![39%](https://geps.dev/progress/39) | | Korean (한국어) (ko_KR) | ![86%](https://geps.dev/progress/86) | | Portuguese Brazilian (Português) (pt_BR) | ![61%](https://geps.dev/progress/61) | +| Portuguese (Português) (pt_PT) | ![61%](https://geps.dev/progress/61) | | Russian (Русский) (ru_RU) | ![86%](https://geps.dev/progress/86) | | Basque (Euskara) (eu_ES) | ![63%](https://geps.dev/progress/63) | | Japanese (日本語) (ja_JP) | ![92%](https://geps.dev/progress/92) | 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 fad0f285..4ecadda6 100644 --- a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java @@ -38,12 +38,12 @@ import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationS import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler; import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService; import stirling.software.SPDF.model.ApplicationProperties; -import stirling.software.SPDF.model.ApplicationProperties.GithubProvider; -import stirling.software.SPDF.model.ApplicationProperties.GoogleProvider; -import stirling.software.SPDF.model.ApplicationProperties.KeycloakProvider; 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.provider.GoogleProvider; +import stirling.software.SPDF.model.provider.KeycloakProvider; import stirling.software.SPDF.repository.JPATokenRepositoryImpl; @Configuration diff --git a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2LogoutSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2LogoutSuccessHandler.java index 9b12b279..0f28025f 100644 --- a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2LogoutSuccessHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2LogoutSuccessHandler.java @@ -16,6 +16,7 @@ import jakarta.servlet.http.HttpSession; import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; import stirling.software.SPDF.model.Provider; +import stirling.software.SPDF.model.provider.UnsupportedProviderException; import stirling.software.SPDF.utils.UrlUtils; public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { @@ -51,8 +52,8 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand Provider provider = oauth.getClient().get(registrationId); issuer = provider.getIssuer(); clientId = provider.getClientId(); - } catch (Exception e) { - logger.error("exception", e); + } catch (UnsupportedProviderException e) { + logger.error(e.getMessage()); } } else { 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 9d67e395..a70ae211 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java @@ -24,14 +24,14 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import stirling.software.SPDF.model.ApplicationProperties; -import stirling.software.SPDF.model.ApplicationProperties.GithubProvider; -import stirling.software.SPDF.model.ApplicationProperties.GoogleProvider; -import stirling.software.SPDF.model.ApplicationProperties.KeycloakProvider; import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client; import stirling.software.SPDF.model.Authority; import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.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; @Controller @@ -74,6 +74,7 @@ public class AccountWebController { } model.addAttribute("providerlist", providerList); + model.addAttribute("loginMethod", applicationProperties.getSecurity().getLoginMethod()); model.addAttribute( "oAuth2Enabled", applicationProperties.getSecurity().getOAUTH2().getEnabled()); diff --git a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java index 13c33d63..99e45776 100644 --- a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java +++ b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java @@ -13,6 +13,10 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import stirling.software.SPDF.config.YamlPropertySourceFactory; +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; @Configuration @ConfigurationProperties(prefix = "") @@ -128,6 +132,15 @@ public class ApplicationProperties { private OAUTH2 oauth2; private int loginAttemptCount; private long loginResetTimeMinutes; + private String loginMethod = "all"; + + public String getLoginMethod() { + return loginMethod; + } + + public void setLoginMethod(String loginMethod) { + this.loginMethod = loginMethod; + } public int getLoginAttemptCount() { return loginAttemptCount; @@ -187,6 +200,8 @@ public class ApplicationProperties { + initialLogin + ", csrfDisabled=" + csrfDisabled + + ", loginMethod=" + + loginMethod + "]"; } @@ -355,7 +370,7 @@ public class ApplicationProperties { private GithubProvider github = new GithubProvider(); private KeycloakProvider keycloak = new KeycloakProvider(); - public Provider get(String registrationId) throws Exception { + public Provider get(String registrationId) throws UnsupportedProviderException { switch (registrationId.toLowerCase()) { case "google": return getGoogle(); @@ -366,7 +381,8 @@ public class ApplicationProperties { default: break; } - throw new Exception("Provider not supported, use custom setting."); + throw new UnsupportedProviderException( + "Logout from the provider is not supported? Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues"); } public GoogleProvider getGoogle() { @@ -407,311 +423,6 @@ public class ApplicationProperties { } } - public static 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 = - "https://www.googleapis.com/oauth2/v3/userinfo?alt=json"; - - public String getAuthorizationuri() { - return authorizationUri; - } - - public String getTokenuri() { - return tokenUri; - } - - public String getUserinfouri() { - return userInfoUri; - } - - private String clientId; - private String clientSecret; - private Collection scopes = new ArrayList<>(); - private String useAsUsername = "email"; - - @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("https://www.googleapis.com/auth/userinfo.email"); - scopes.add("https://www.googleapis.com/auth/userinfo.profile"); - } - return scopes; - } - - @Override - public void setScopes(String scopes) { - this.scopes = - Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList()); - } - - @Override - public String getUseAsUsername() { - return this.useAsUsername; - } - - @Override - public void setUseAsUsername(String useAsUsername) { - this.useAsUsername = useAsUsername; - } - - @Override - public String toString() { - return "Google [clientId=" - + clientId - + ", clientSecret=" - + (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL") - + ", scopes=" - + scopes - + ", useAsUsername=" - + useAsUsername - + "]"; - } - - @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"); - } - } - - public static 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"; - - public String getAuthorizationuri() { - return authorizationUri; - } - - public String getTokenuri() { - return tokenUri; - } - - public String getUserinfouri() { - return userInfoUri; - } - - private String clientId; - private String clientSecret; - private Collection scopes = new ArrayList<>(); - private String useAsUsername = "login"; - - @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"); - } - } - - public static 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; - } - - @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("profile"); - scopes.add("email"); - } - return scopes; - } - - @Override - public void setScopes(String scopes) { - this.scopes = - Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList()); - } - - @Override - public String getUseAsUsername() { - return this.useAsUsername; - } - - @Override - public void setUseAsUsername(String useAsUsername) { - this.useAsUsername = useAsUsername; - } - - @Override - public String toString() { - return "Keycloak [issuer=" - + issuer - + ", clientId=" - + clientId - + ", clientSecret=" - + (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL") - + ", scopes=" - + scopes - + ", useAsUsername=" - + useAsUsername - + "]"; - } - - @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"); - } - } - public static class System { private String defaultLocale; private Boolean googlevisibility; 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 00000000..85fe7258 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/provider/GithubProvider.java @@ -0,0 +1,115 @@ +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"; + + public String getAuthorizationuri() { + return authorizationUri; + } + + public String getTokenuri() { + return tokenUri; + } + + public String getUserinfouri() { + return userInfoUri; + } + + private String clientId; + private String clientSecret; + private Collection scopes = new ArrayList<>(); + private String useAsUsername = "login"; + + @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 new file mode 100644 index 00000000..2863801b --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/provider/GoogleProvider.java @@ -0,0 +1,109 @@ +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 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 = + "https://www.googleapis.com/oauth2/v3/userinfo?alt=json"; + + public String getAuthorizationuri() { + return authorizationUri; + } + + public String getTokenuri() { + return tokenUri; + } + + public String getUserinfouri() { + return userInfoUri; + } + + private String clientId; + private String clientSecret; + private Collection scopes = new ArrayList<>(); + private String useAsUsername = "email"; + + @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("https://www.googleapis.com/auth/userinfo.email"); + scopes.add("https://www.googleapis.com/auth/userinfo.profile"); + } + return scopes; + } + + @Override + public void setScopes(String scopes) { + this.scopes = + Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList()); + } + + @Override + public String getUseAsUsername() { + return this.useAsUsername; + } + + @Override + public void setUseAsUsername(String useAsUsername) { + this.useAsUsername = useAsUsername; + } + + @Override + public String toString() { + return "Google [clientId=" + + clientId + + ", clientSecret=" + + (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL") + + ", scopes=" + + scopes + + ", useAsUsername=" + + useAsUsername + + "]"; + } + + @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 new file mode 100644 index 00000000..d715b103 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/provider/KeycloakProvider.java @@ -0,0 +1,106 @@ +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 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; + } + + @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("profile"); + scopes.add("email"); + } + return scopes; + } + + @Override + public void setScopes(String scopes) { + this.scopes = + Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList()); + } + + @Override + public String getUseAsUsername() { + return this.useAsUsername; + } + + @Override + public void setUseAsUsername(String useAsUsername) { + this.useAsUsername = useAsUsername; + } + + @Override + public String toString() { + return "Keycloak [issuer=" + + issuer + + ", clientId=" + + clientId + + ", clientSecret=" + + (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL") + + ", scopes=" + + scopes + + ", useAsUsername=" + + useAsUsername + + "]"; + } + + @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/UnsupportedProviderException.java b/src/main/java/stirling/software/SPDF/model/provider/UnsupportedProviderException.java new file mode 100644 index 00000000..f9010601 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/provider/UnsupportedProviderException.java @@ -0,0 +1,7 @@ +package stirling.software.SPDF.model.provider; + +public class UnsupportedProviderException extends Exception { + public UnsupportedProviderException(String message) { + super(message); + } +} diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html index ed671179..2832bad2 100644 --- a/src/main/resources/templates/login.html +++ b/src/main/resources/templates/login.html @@ -114,7 +114,7 @@ favicon

Stirling-PDF

-
+
Login Via SSO

@@ -134,7 +134,7 @@
-
+

Please sign in