Refactoring Provider

This commit is contained in:
DarioGii 2025-01-22 15:14:06 +00:00 committed by Dario Ghunney Ware
parent 81c8b9f152
commit 06a5ba892c
20 changed files with 249 additions and 409 deletions

View File

@ -3,7 +3,7 @@ package stirling.software.SPDF.config.interfaces;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List; import java.util.List;
import stirling.software.SPDF.model.provider.UnsupportedProviderException; import stirling.software.SPDF.model.exception.UnsupportedProviderException;
import stirling.software.SPDF.utils.FileInfo; import stirling.software.SPDF.utils.FileInfo;
public interface DatabaseInterface { public interface DatabaseInterface {

View File

@ -15,7 +15,6 @@ import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuc
import com.coveo.saml.SamlClient; import com.coveo.saml.SamlClient;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
@ -28,8 +27,8 @@ import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrin
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2; import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
import stirling.software.SPDF.model.Provider; import stirling.software.SPDF.model.exception.UnsupportedProviderException;
import stirling.software.SPDF.model.provider.UnsupportedProviderException; import stirling.software.SPDF.model.provider.Provider;
import stirling.software.SPDF.utils.UrlUtils; import stirling.software.SPDF.utils.UrlUtils;
@Slf4j @Slf4j
@ -41,7 +40,7 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
@Override @Override
public void onLogoutSuccess( public void onLogoutSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication authentication) HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException { throws IOException {
if (!response.isCommitted()) { if (!response.isCommitted()) {
// Handle user logout due to disabled account // Handle user logout due to disabled account
@ -60,30 +59,25 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
// Handle SAML2 logout redirection // Handle SAML2 logout redirection
if (authentication instanceof Saml2Authentication) { if (authentication instanceof Saml2Authentication) {
getRedirect_saml2(request, response, authentication); getRedirect_saml2(request, response, authentication);
return;
} }
// Handle OAuth2 logout redirection // Handle OAuth2 logout redirection
else if (authentication instanceof OAuth2AuthenticationToken) { else if (authentication instanceof OAuth2AuthenticationToken) {
getRedirect_oauth2(request, response, authentication); getRedirect_oauth2(request, response, authentication);
return;
} }
// Handle Username/Password logout // Handle Username/Password logout
else if (authentication instanceof UsernamePasswordAuthenticationToken) { else if (authentication instanceof UsernamePasswordAuthenticationToken) {
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true"); getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
return;
} }
// Handle unknown authentication types // Handle unknown authentication types
else { else {
log.error( log.error(
"authentication class unknown: " "authentication class unknown: {}",
+ authentication.getClass().getSimpleName()); authentication.getClass().getSimpleName());
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true"); getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
return;
} }
} else { } else {
// Redirect to login page after logout // Redirect to login page after logout
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true"); getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
return;
} }
} }
} }
@ -164,17 +158,17 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
try { try {
// Get OAuth2 provider details from configuration // Get OAuth2 provider details from configuration
Provider provider = oauth.getClient().get(registrationId); Provider provider = oauth.getClient().get(registrationId);
issuer = provider.getIssuer();
clientId = provider.getClientId();
} catch (UnsupportedProviderException e) { } catch (UnsupportedProviderException e) {
log.error(e.getMessage()); log.error(e.getMessage());
} }
} else { } else {
registrationId = oauth.getProvider() != null ? oauth.getProvider() : ""; registrationId = oauth.getProvider() != null ? oauth.getProvider() : "";
}
issuer = oauth.getIssuer(); issuer = oauth.getIssuer();
clientId = oauth.getClientId(); clientId = oauth.getClientId();
}
String errorMessage = ""; String errorMessage = "";
// Handle different error scenarios during logout // Handle different error scenarios during logout
if (request.getParameter("oauth2AuthenticationErrorWeb") != null) { if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
param = "erroroauth=oauth2AuthenticationErrorWeb"; param = "erroroauth=oauth2AuthenticationErrorWeb";
@ -196,7 +190,7 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
// Redirect based on OAuth2 provider // Redirect based on OAuth2 provider
switch (registrationId.toLowerCase()) { switch (registrationId.toLowerCase()) {
case "keycloak": case "keycloak" -> {
// Add Keycloak specific logout URL if needed // Add Keycloak specific logout URL if needed
String logoutUrl = String logoutUrl =
issuer issuer
@ -207,14 +201,15 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
+ response.encodeRedirectURL(redirect_url); + response.encodeRedirectURL(redirect_url);
log.info("Redirecting to Keycloak logout URL: " + logoutUrl); log.info("Redirecting to Keycloak logout URL: " + logoutUrl);
response.sendRedirect(logoutUrl); response.sendRedirect(logoutUrl);
break; }
case "github": case "github" -> {
// Add GitHub specific logout URL if needed // Add GitHub specific logout URL if needed
// todo: why does the redirect go to github? shouldn't it come to Stirling PDF?
String githubLogoutUrl = "https://github.com/logout"; String githubLogoutUrl = "https://github.com/logout";
log.info("Redirecting to GitHub logout URL: " + githubLogoutUrl); log.info("Redirecting to GitHub logout URL: " + redirect_url);
response.sendRedirect(githubLogoutUrl); response.sendRedirect(redirect_url);
break; }
case "google": case "google" -> {
// Add Google specific logout URL if needed // Add Google specific logout URL if needed
// String googleLogoutUrl = // String googleLogoutUrl =
// "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue=" // "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue="
@ -222,12 +217,12 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
log.info("Google does not have a specific logout URL"); log.info("Google does not have a specific logout URL");
// log.info("Redirecting to Google logout URL: " + googleLogoutUrl); // log.info("Redirecting to Google logout URL: " + googleLogoutUrl);
// response.sendRedirect(googleLogoutUrl); // response.sendRedirect(googleLogoutUrl);
// break; }
default: default -> {
String defaultRedirectUrl = request.getContextPath() + "/login?" + param; String defaultRedirectUrl = request.getContextPath() + "/login?" + param;
log.info("Redirecting to default logout URL: " + defaultRedirectUrl); log.info("Redirecting to default logout URL: {}", defaultRedirectUrl);
response.sendRedirect(defaultRedirectUrl); response.sendRedirect(defaultRedirectUrl);
break; }
} }
} }

View File

@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.interfaces.DatabaseInterface; import stirling.software.SPDF.config.interfaces.DatabaseInterface;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.Role;
import stirling.software.SPDF.model.provider.UnsupportedProviderException; import stirling.software.SPDF.model.exception.UnsupportedProviderException;
@Slf4j @Slf4j
@Component @Component

View File

@ -27,7 +27,7 @@ import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrin
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface; import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
import stirling.software.SPDF.model.*; 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.AuthorityRepository;
import stirling.software.SPDF.repository.UserRepository; import stirling.software.SPDF.repository.UserRepository;

View File

@ -14,7 +14,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.InstallationPathConfig; import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.provider.UnsupportedProviderException; import stirling.software.SPDF.model.exception.UnsupportedProviderException;
@Slf4j @Slf4j
@Getter @Getter

View File

@ -8,7 +8,7 @@ import org.springframework.stereotype.Component;
import stirling.software.SPDF.config.interfaces.DatabaseInterface; import stirling.software.SPDF.config.interfaces.DatabaseInterface;
import stirling.software.SPDF.controller.api.H2SQLCondition; import stirling.software.SPDF.controller.api.H2SQLCondition;
import stirling.software.SPDF.model.provider.UnsupportedProviderException; import stirling.software.SPDF.model.exception.UnsupportedProviderException;
@Component @Component
@Conditional(H2SQLCondition.class) @Conditional(H2SQLCondition.class)

View File

@ -20,7 +20,7 @@ import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.SPDF.model.AuthenticationType; 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; import stirling.software.SPDF.utils.RequestUriUtils;
public class CustomOAuth2AuthenticationSuccessHandler public class CustomOAuth2AuthenticationSuccessHandler

View File

@ -1,5 +1,7 @@
package stirling.software.SPDF.config.security.oauth2; package stirling.software.SPDF.config.security.oauth2;
import static org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -30,11 +32,13 @@ import stirling.software.SPDF.model.provider.GithubProvider;
import stirling.software.SPDF.model.provider.GoogleProvider; import stirling.software.SPDF.model.provider.GoogleProvider;
import stirling.software.SPDF.model.provider.KeycloakProvider; import stirling.software.SPDF.model.provider.KeycloakProvider;
@Configuration
@Slf4j @Slf4j
@Configuration
@ConditionalOnProperty(value = "security.oauth2.enabled", havingValue = "true") @ConditionalOnProperty(value = "security.oauth2.enabled", havingValue = "true")
public class OAuth2Configuration { public class OAuth2Configuration {
public static final String REDIRECT_URI_PATH = "{baseUrl}/login/oauth2/code/";
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
@Lazy private final UserService userService; @Lazy private final UserService userService;
@ -62,13 +66,17 @@ public class OAuth2Configuration {
private Optional<ClientRegistration> googleClientRegistration() { private Optional<ClientRegistration> googleClientRegistration() {
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
if (oauth == null || !oauth.getEnabled()) { if (oauth == null || !oauth.getEnabled()) {
return Optional.empty(); return Optional.empty();
} }
Client client = oauth.getClient(); Client client = oauth.getClient();
if (client == null) { if (client == null) {
return Optional.empty(); return Optional.empty();
} }
GoogleProvider google = client.getGoogle(); GoogleProvider google = client.getGoogle();
return google != null && google.isSettingsValid() return google != null && google.isSettingsValid()
? Optional.of( ? Optional.of(
@ -76,15 +84,13 @@ public class OAuth2Configuration {
.clientId(google.getClientId()) .clientId(google.getClientId())
.clientSecret(google.getClientSecret()) .clientSecret(google.getClientSecret())
.scope(google.getScopes()) .scope(google.getScopes())
.authorizationUri(google.getAuthorizationuri()) .authorizationUri(google.getAuthorizationUri())
.tokenUri(google.getTokenuri()) .tokenUri(google.getTokenUri())
.userInfoUri(google.getUserinfouri()) .userInfoUri(google.getUserinfoUri())
.userNameAttributeName(google.getUseAsUsername()) .userNameAttributeName(google.getUseAsUsername())
.clientName(google.getClientName()) .clientName(google.getClientName())
.redirectUri("{baseUrl}/login/oauth2/code/" + google.getName()) .redirectUri(REDIRECT_URI_PATH + google.getName())
.authorizationGrantType( .authorizationGrantType(AUTHORIZATION_CODE)
org.springframework.security.oauth2.core
.AuthorizationGrantType.AUTHORIZATION_CODE)
.build()) .build())
: Optional.empty(); : Optional.empty();
} }
@ -113,36 +119,33 @@ public class OAuth2Configuration {
} }
private Optional<ClientRegistration> githubClientRegistration() { private Optional<ClientRegistration> githubClientRegistration() {
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); if (isOauthOrClientEmpty()) {
if (oauth == null || !oauth.getEnabled()) {
return Optional.empty(); return Optional.empty();
} }
Client client = oauth.getClient();
if (client == null) { GithubProvider github =
return Optional.empty(); applicationProperties.getSecurity().getOauth2().getClient().getGithub();
}
GithubProvider github = client.getGithub();
return github != null && github.isSettingsValid() return github != null && github.isSettingsValid()
? Optional.of( ? Optional.of(
ClientRegistration.withRegistrationId(github.getName()) ClientRegistration.withRegistrationId(github.getName())
.clientId(github.getClientId()) .clientId(github.getClientId())
.clientSecret(github.getClientSecret()) .clientSecret(github.getClientSecret())
.scope(github.getScopes()) .scope(github.getScopes())
.authorizationUri(github.getAuthorizationuri()) .authorizationUri(github.getAuthorizationUri())
.tokenUri(github.getTokenuri()) .tokenUri(github.getTokenUri())
.userInfoUri(github.getUserinfouri()) .userInfoUri(github.getUserinfoUri())
.userNameAttributeName(github.getUseAsUsername()) .userNameAttributeName(github.getUseAsUsername())
.clientName(github.getClientName()) .clientName(github.getClientName())
.redirectUri("{baseUrl}/login/oauth2/code/" + github.getName()) .redirectUri(REDIRECT_URI_PATH + github.getName())
.authorizationGrantType( .authorizationGrantType(AUTHORIZATION_CODE)
org.springframework.security.oauth2.core
.AuthorizationGrantType.AUTHORIZATION_CODE)
.build()) .build())
: Optional.empty(); : Optional.empty();
} }
private Optional<ClientRegistration> oidcClientRegistration() { private Optional<ClientRegistration> oidcClientRegistration() {
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
if (oauth == null if (oauth == null
|| oauth.getIssuer() == null || oauth.getIssuer() == null
|| oauth.getIssuer().isEmpty() || oauth.getIssuer().isEmpty()
@ -156,6 +159,7 @@ public class OAuth2Configuration {
|| oauth.getUseAsUsername().isEmpty()) { || oauth.getUseAsUsername().isEmpty()) {
return Optional.empty(); return Optional.empty();
} }
return Optional.of( return Optional.of(
ClientRegistrations.fromIssuerLocation(oauth.getIssuer()) ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
.registrationId("oidc") .registrationId("oidc")
@ -164,13 +168,23 @@ public class OAuth2Configuration {
.scope(oauth.getScopes()) .scope(oauth.getScopes())
.userNameAttributeName(oauth.getUseAsUsername()) .userNameAttributeName(oauth.getUseAsUsername())
.clientName("OIDC") .clientName("OIDC")
.redirectUri("{baseUrl}/login/oauth2/code/oidc") .redirectUri(REDIRECT_URI_PATH + "oidc")
.authorizationGrantType( .authorizationGrantType(AUTHORIZATION_CODE)
org.springframework.security.oauth2.core.AuthorizationGrantType
.AUTHORIZATION_CODE)
.build()); .build());
} }
private boolean isOauthOrClientEmpty() {
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
if (oauth == null || !oauth.getEnabled()) {
return false;
}
Client client = oauth.getClient();
return client != null;
}
/* /*
This following function is to grant Authorities to the OAUTH2 user from the values stored in the database. 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. This is required for the internal; 'hasRole()' function to give out the correct role.

View File

@ -21,7 +21,7 @@ import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2; import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
import stirling.software.SPDF.model.AuthenticationType; 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; import stirling.software.SPDF.utils.RequestUriUtils;
@AllArgsConstructor @AllArgsConstructor

View File

@ -36,7 +36,7 @@ import stirling.software.SPDF.model.AuthenticationType;
import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.Role;
import stirling.software.SPDF.model.User; import stirling.software.SPDF.model.User;
import stirling.software.SPDF.model.api.user.UsernameAndPass; import stirling.software.SPDF.model.api.user.UsernameAndPass;
import stirling.software.SPDF.model.provider.UnsupportedProviderException; import stirling.software.SPDF.model.exception.UnsupportedProviderException;
@Controller @Controller
@Tag(name = "User", description = "User APIs") @Tag(name = "User", description = "User APIs")

View File

@ -39,6 +39,7 @@ import stirling.software.SPDF.repository.UserRepository;
@Tag(name = "Account Security", description = "Account Security APIs") @Tag(name = "Account Security", description = "Account Security APIs")
public class AccountWebController { public class AccountWebController {
public static final String OAUTH_2_AUTHORIZATION = "/oauth2/authorization/";
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
private final SessionPersistentRegistry sessionPersistentRegistry; private final SessionPersistentRegistry sessionPersistentRegistry;
@ -67,26 +68,24 @@ public class AccountWebController {
if (oauth != null) { if (oauth != null) {
if (oauth.getEnabled()) { if (oauth.getEnabled()) {
if (oauth.isSettingsValid()) { if (oauth.isSettingsValid()) {
providerList.put("/oauth2/authorization/oidc", oauth.getProvider()); providerList.put(OAUTH_2_AUTHORIZATION + "oidc", oauth.getProvider());
} }
Client client = oauth.getClient(); Client client = oauth.getClient();
if (client != null) { if (client != null) {
GoogleProvider google = client.getGoogle(); GoogleProvider google = client.getGoogle();
if (google.isSettingsValid()) { if (google.isSettingsValid()) {
providerList.put( providerList.put(
"/oauth2/authorization/" + google.getName(), OAUTH_2_AUTHORIZATION + google.getName(), google.getClientName());
google.getClientName());
} }
GithubProvider github = client.getGithub(); GithubProvider github = client.getGithub();
if (github.isSettingsValid()) { if (github.isSettingsValid()) {
providerList.put( providerList.put(
"/oauth2/authorization/" + github.getName(), OAUTH_2_AUTHORIZATION + github.getName(), github.getClientName());
github.getClientName());
} }
KeycloakProvider keycloak = client.getKeycloak(); KeycloakProvider keycloak = client.getKeycloak();
if (keycloak.isSettingsValid()) { if (keycloak.isSettingsValid()) {
providerList.put( providerList.put(
"/oauth2/authorization/" + keycloak.getName(), OAUTH_2_AUTHORIZATION + keycloak.getName(),
keycloak.getClientName()); keycloak.getClientName());
} }
} }
@ -103,7 +102,7 @@ public class AccountWebController {
.removeIf(entry -> entry.getKey() == null || entry.getValue() == null); .removeIf(entry -> entry.getKey() == null || entry.getValue() == null);
model.addAttribute("providerlist", providerList); model.addAttribute("providerlist", providerList);
model.addAttribute("loginMethod", securityProps.getLoginMethod()); 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("altLogin", altLogin);
model.addAttribute("currentPage", "login"); model.addAttribute("currentPage", "login");
String error = request.getParameter("error"); String error = request.getParameter("error");

View File

@ -34,10 +34,11 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.InstallationPathConfig; import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.config.YamlPropertySourceFactory; import stirling.software.SPDF.config.YamlPropertySourceFactory;
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
import stirling.software.SPDF.model.provider.GithubProvider; import stirling.software.SPDF.model.provider.GithubProvider;
import stirling.software.SPDF.model.provider.GoogleProvider; import stirling.software.SPDF.model.provider.GoogleProvider;
import stirling.software.SPDF.model.provider.KeycloakProvider; import stirling.software.SPDF.model.provider.KeycloakProvider;
import stirling.software.SPDF.model.provider.UnsupportedProviderException; import stirling.software.SPDF.model.provider.Provider;
@Configuration @Configuration
@ConfigurationProperties(prefix = "") @ConfigurationProperties(prefix = "")
@ -230,7 +231,7 @@ public class ApplicationProperties {
List<String> scopesList = List<String> scopesList =
Arrays.stream(scopes.split(",")) Arrays.stream(scopes.split(","))
.map(String::trim) .map(String::trim)
.collect(Collectors.toList()); .toList();
this.scopes.addAll(scopesList); this.scopes.addAll(scopesList);
} }
@ -257,18 +258,13 @@ public class ApplicationProperties {
private KeycloakProvider keycloak = new KeycloakProvider(); private KeycloakProvider keycloak = new KeycloakProvider();
public Provider get(String registrationId) throws UnsupportedProviderException { public Provider get(String registrationId) throws UnsupportedProviderException {
switch (registrationId.toLowerCase()) { return switch (registrationId.toLowerCase()) {
case "google": case "google" -> getGoogle();
return getGoogle(); case "github" -> getGithub();
case "github": case "keycloak" -> getKeycloak();
return getGithub(); default -> throw new UnsupportedProviderException(
case "keycloak": "Logout from the provider is not supported. Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues");
return getKeycloak(); };
default:
throw new UnsupportedProviderException(
"Logout from the provider is not supported? Report it at"
+ " https://github.com/Stirling-Tools/Stirling-PDF/issues");
}
} }
} }
} }

View File

@ -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<String> value, String name) {
if (value != null && !value.isEmpty()) {
return true;
}
return false;
}
@Override
public Collection<String> 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'");
}
}

View File

@ -1,26 +0,0 @@
package stirling.software.SPDF.model;
import java.util.Collection;
public interface ProviderInterface {
public Collection<String> 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);
}

View File

@ -1,4 +1,4 @@
package stirling.software.SPDF.model.provider; package stirling.software.SPDF.model.exception;
public class UnsupportedProviderException extends Exception { public class UnsupportedProviderException extends Exception {
public UnsupportedProviderException(String message) { public UnsupportedProviderException(String message) {

View File

@ -1,60 +1,44 @@
package stirling.software.SPDF.model.provider; package stirling.software.SPDF.model.provider;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.stream.Collectors;
import stirling.software.SPDF.model.Provider; import lombok.NoArgsConstructor;
// @Setter
@NoArgsConstructor
public class GithubProvider extends Provider { public class GithubProvider extends Provider {
private static final String authorizationUri = "https://github.com/login/oauth/authorize"; private static final String NAME = "github";
private static final String tokenUri = "https://github.com/login/oauth/access_token"; private static final String CLIENT_NAME = "GitHub";
private static final String userInfoUri = "https://api.github.com/user"; 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";
private String clientId; private String clientId;
private String clientSecret; private String clientSecret;
private Collection<String> scopes = new ArrayList<>(); private Collection<String> scopes = new ArrayList<>();
private String useAsUsername = "login"; private String useAsUsername = "login";
public String getAuthorizationuri() { public GithubProvider(
return authorizationUri; String clientId, String clientSecret, Collection<String> scopes, String useAsUsername) {
} super(null, NAME, CLIENT_NAME, clientId, clientSecret, scopes, useAsUsername);
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; this.clientId = clientId;
}
@Override
public String getClientSecret() {
return this.clientSecret;
}
@Override
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret; this.clientSecret = clientSecret;
this.scopes = scopes;
this.useAsUsername = useAsUsername;
}
public String getAuthorizationUri() {
return AUTHORIZATION_URI;
}
public String getTokenUri() {
return TOKEN_URI;
}
public String getUserinfoUri() {
return USER_INFO_URI;
} }
@Override @Override
@ -66,22 +50,6 @@ public class GithubProvider extends Provider {
return scopes; 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 @Override
public String toString() { public String toString() {
return "GitHub [clientId=" return "GitHub [clientId="
@ -94,21 +62,4 @@ public class GithubProvider extends Provider {
+ 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");
}
} }

View File

@ -1,61 +1,45 @@
package stirling.software.SPDF.model.provider; package stirling.software.SPDF.model.provider;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.stream.Collectors;
import stirling.software.SPDF.model.Provider; import lombok.NoArgsConstructor;
// @Setter
@NoArgsConstructor
public class GoogleProvider extends Provider { public class GoogleProvider extends Provider {
private static final String authorizationUri = "https://accounts.google.com/o/oauth2/v2/auth"; private static final String NAME = "google";
private static final String tokenUri = "https://www.googleapis.com/oauth2/v4/token"; private static final String CLIENT_NAME = "Google";
private static final String userInfoUri = 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"; "https://www.googleapis.com/oauth2/v3/userinfo?alt=json";
private String clientId; private String clientId;
private String clientSecret; private String clientSecret;
private Collection<String> scopes = new ArrayList<>(); private Collection<String> scopes = new ArrayList<>();
private String useAsUsername = "email"; private String useAsUsername = "email";
public String getAuthorizationuri() { public GoogleProvider(
return authorizationUri; String clientId, String clientSecret, Collection<String> scopes, String useAsUsername) {
} super(null, NAME, CLIENT_NAME, clientId, clientSecret, scopes, useAsUsername);
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; this.clientId = clientId;
}
@Override
public String getClientSecret() {
return this.clientSecret;
}
@Override
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret; this.clientSecret = clientSecret;
this.scopes = scopes;
this.useAsUsername = useAsUsername;
}
public String getAuthorizationUri() {
return AUTHORIZATION_URI;
}
public String getTokenUri() {
return TOKEN_URI;
}
public String getUserinfoUri() {
return USER_INFO_URI;
} }
@Override @Override
@ -68,22 +52,6 @@ public class GoogleProvider extends Provider {
return scopes; 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 @Override
public String toString() { public String toString() {
return "Google [clientId=" return "Google [clientId="
@ -96,21 +64,4 @@ public class GoogleProvider extends Provider {
+ 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");
}
} }

View File

@ -1,76 +1,50 @@
package stirling.software.SPDF.model.provider; package stirling.software.SPDF.model.provider;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.stream.Collectors;
import stirling.software.SPDF.model.Provider; import lombok.NoArgsConstructor;
// @Setter
@NoArgsConstructor
public class KeycloakProvider extends Provider { public class KeycloakProvider extends Provider {
private static final String NAME = "keycloak";
private static final String CLIENT_NAME = "Keycloak";
private String issuer; private String issuer;
private String clientId; private String clientId;
private String clientSecret; private String clientSecret;
private Collection<String> scopes = new ArrayList<>(); private Collection<String> scopes;
private String useAsUsername = "email"; private String useAsUsername = "email";
@Override public KeycloakProvider(
public String getIssuer() { String issuer,
return this.issuer; String clientId,
} String clientSecret,
Collection<String> scopes,
@Override String useAsUsername) {
public void setIssuer(String issuer) { super(issuer, NAME, CLIENT_NAME, clientId, clientSecret, scopes, useAsUsername);
this.useAsUsername = useAsUsername;
this.issuer = issuer; this.issuer = issuer;
}
@Override
public String getClientId() {
return this.clientId;
}
@Override
public void setClientId(String clientId) {
this.clientId = clientId; this.clientId = clientId;
}
@Override
public String getClientSecret() {
return this.clientSecret;
}
@Override
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret; this.clientSecret = clientSecret;
this.scopes = scopes;
} }
@Override @Override
public Collection<String> getScopes() { public Collection<String> getScopes() {
var scopes = super.getScopes();
if (scopes == null || scopes.isEmpty()) { if (scopes == null || scopes.isEmpty()) {
scopes = new ArrayList<>(); scopes = new ArrayList<>();
scopes.add("profile"); scopes.add("profile");
scopes.add("email"); scopes.add("email");
} }
return scopes; 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 @Override
public String toString() { public String toString() {
return "Keycloak [issuer=" return "Keycloak [issuer="
@ -78,29 +52,11 @@ public class KeycloakProvider extends Provider {
+ ", clientId=" + ", clientId="
+ clientId + clientId
+ ", clientSecret=" + ", clientSecret="
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL") + (clientSecret != null && !clientSecret.isBlank() ? "MASKED" : "NULL")
+ ", scopes=" + ", scopes="
+ scopes + scopes
+ ", useAsUsername=" + ", useAsUsername="
+ 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");
}
} }

View File

@ -0,0 +1,84 @@
package stirling.software.SPDF.model.provider;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
public abstract class Provider {
private String issuer;
private String name;
private String clientName;
private String clientId;
private String clientSecret;
private Collection<String> scopes;
private String useAsUsername;
public Provider(
String issuer,
String name,
String clientName,
String clientId,
String clientSecret,
Collection<String> scopes,
String useAsUsername) {
this.issuer = issuer;
this.name = name;
this.clientName = clientName;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.scopes = scopes;
this.useAsUsername = !useAsUsername.isBlank() ? useAsUsername : "email";
}
// todo: why are we passing name here if it's not used?
public boolean isSettingsValid() {
return isValid(this.getIssuer(), "issuer")
&& isValid(this.getClientId(), "clientId")
&& isValid(this.getClientSecret(), "clientSecret")
&& isValid(this.getScopes(), "scopes")
&& isValid(this.getUseAsUsername(), "useAsUsername");
}
private boolean isValid(String value, String name) {
return value != null && !value.isBlank();
}
private boolean isValid(Collection<String> value, String name) {
return value != null && !value.isEmpty();
}
public void setIssuer(String issuer) {
this.issuer = issuer;
}
public void setName(String name) {
this.name = name;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public void setScopes(String scopes) {
this.scopes =
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
}
public void setUseAsUsername(String useAsUsername) {
this.useAsUsername = useAsUsername;
}
}

View File

@ -9,7 +9,7 @@ import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.provider.UnsupportedProviderException; import stirling.software.SPDF.model.exception.UnsupportedProviderException;
import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;