mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-06 18:30:57 +00:00
SSO Refactoring (#2818)
# Description of Changes * Refactoring of SSO code around OAuth & SAML 2 * Enabling auto-login with SAML 2 via the new `SSOAutoLogin` property * Correcting typos & general cleanup --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [x] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [x] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [x] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [x] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details.
This commit is contained in:
parent
16295c7bb9
commit
4c701b2e69
@ -20,6 +20,7 @@ import stirling.software.SPDF.utils.GeneralUtils;
|
|||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class KeygenLicenseVerifier {
|
public class KeygenLicenseVerifier {
|
||||||
|
// todo: place in config files?
|
||||||
private static final String ACCOUNT_ID = "e5430f69-e834-4ae4-befd-b602aae5f372";
|
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 String BASE_URL = "https://api.keygen.sh/v1/accounts";
|
||||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
@ -68,7 +69,7 @@ public class KeygenLicenseVerifier {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error verifying license: " + e.getMessage());
|
log.error("Error verifying license: {}", e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,10 +96,9 @@ public class KeygenLicenseVerifier {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> 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());
|
JsonNode jsonResponse = objectMapper.readTree(response.body());
|
||||||
if (response.statusCode() == 200) {
|
if (response.statusCode() == 200) {
|
||||||
|
|
||||||
JsonNode metaNode = jsonResponse.path("meta");
|
JsonNode metaNode = jsonResponse.path("meta");
|
||||||
boolean isValid = metaNode.path("valid").asBoolean();
|
boolean isValid = metaNode.path("valid").asBoolean();
|
||||||
|
|
||||||
@ -120,7 +120,7 @@ public class KeygenLicenseVerifier {
|
|||||||
log.info(applicationProperties.toString());
|
log.info(applicationProperties.toString());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
log.error("Error validating license. Status code: " + response.statusCode());
|
log.error("Error validating license. Status code: {}", response.statusCode());
|
||||||
}
|
}
|
||||||
return jsonResponse;
|
return jsonResponse;
|
||||||
}
|
}
|
||||||
|
@ -35,10 +35,7 @@ public class AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(
|
@ConditionalOnProperty(name = "system.customHTMLFiles", havingValue = "true")
|
||||||
name = "system.customHTMLFiles",
|
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
public SpringTemplateEngine templateEngine(ResourceLoader resourceLoader) {
|
public SpringTemplateEngine templateEngine(ResourceLoader resourceLoader) {
|
||||||
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
|
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
|
||||||
templateEngine.addTemplateResolver(new FileFallbackTemplateResolver(resourceLoader));
|
templateEngine.addTemplateResolver(new FileFallbackTemplateResolver(resourceLoader));
|
||||||
@ -129,8 +126,8 @@ public class AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ConditionalOnMissingClass("stirling.software.SPDF.config.security.SecurityConfiguration")
|
@ConditionalOnMissingClass("stirling.software.SPDF.config.security.SecurityConfiguration")
|
||||||
@Bean(name = "activSecurity")
|
@Bean(name = "activeSecurity")
|
||||||
public boolean missingActivSecurity() {
|
public boolean missingActiveSecurity() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ public class CleanUrlInterceptor implements HandlerInterceptor {
|
|||||||
"endpoints",
|
"endpoints",
|
||||||
"logout",
|
"logout",
|
||||||
"error",
|
"error",
|
||||||
"erroroauth",
|
"errorOAuth",
|
||||||
"file",
|
"file",
|
||||||
"messageType",
|
"messageType",
|
||||||
"infoMessage");
|
"infoMessage");
|
||||||
|
@ -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 {
|
||||||
|
@ -69,7 +69,7 @@ public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationF
|
|||||||
}
|
}
|
||||||
if (exception instanceof BadCredentialsException
|
if (exception instanceof BadCredentialsException
|
||||||
|| exception instanceof UsernameNotFoundException) {
|
|| exception instanceof UsernameNotFoundException) {
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?error=badcredentials");
|
getRedirectStrategy().sendRedirect(request, response, "/login?error=badCredentials");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (exception instanceof InternalAuthenticationServiceException
|
if (exception instanceof InternalAuthenticationServiceException
|
||||||
|
@ -14,8 +14,8 @@ import org.springframework.security.saml2.provider.service.authentication.Saml2A
|
|||||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||||
|
|
||||||
import com.coveo.saml.SamlClient;
|
import com.coveo.saml.SamlClient;
|
||||||
|
import com.coveo.saml.SamlException;
|
||||||
|
|
||||||
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,62 +28,43 @@ 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.provider.KeycloakProvider;
|
||||||
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
|
||||||
import stirling.software.SPDF.utils.UrlUtils;
|
import stirling.software.SPDF.utils.UrlUtils;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||||
|
|
||||||
|
public static final String LOGOUT_PATH = "/login?logout=true";
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@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
|
|
||||||
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) {
|
if (authentication != null) {
|
||||||
// Handle SAML2 logout redirection
|
|
||||||
if (authentication instanceof Saml2Authentication) {
|
if (authentication instanceof Saml2Authentication) {
|
||||||
|
// Handle SAML2 logout redirection
|
||||||
getRedirect_saml2(request, response, authentication);
|
getRedirect_saml2(request, response, authentication);
|
||||||
return;
|
} else if (authentication instanceof OAuth2AuthenticationToken) {
|
||||||
}
|
|
||||||
// Handle OAuth2 logout redirection
|
// Handle OAuth2 logout redirection
|
||||||
else if (authentication instanceof OAuth2AuthenticationToken) {
|
|
||||||
getRedirect_oauth2(request, response, authentication);
|
getRedirect_oauth2(request, response, authentication);
|
||||||
return;
|
} else if (authentication instanceof UsernamePasswordAuthenticationToken) {
|
||||||
}
|
|
||||||
// Handle Username/Password logout
|
// Handle Username/Password logout
|
||||||
else if (authentication instanceof UsernamePasswordAuthenticationToken) {
|
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
} else {
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Handle unknown authentication types
|
// Handle unknown authentication types
|
||||||
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, LOGOUT_PATH);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Redirect to login page after logout
|
// Redirect to login page after logout
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
String path = checkForErrors(request);
|
||||||
return;
|
getRedirectStrategy().sendRedirect(request, response, path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,7 +81,7 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
CustomSaml2AuthenticatedPrincipal principal =
|
CustomSaml2AuthenticatedPrincipal principal =
|
||||||
(CustomSaml2AuthenticatedPrincipal) samlAuthentication.getPrincipal();
|
(CustomSaml2AuthenticatedPrincipal) samlAuthentication.getPrincipal();
|
||||||
|
|
||||||
String nameIdValue = principal.getName();
|
String nameIdValue = principal.name();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Read certificate from the resource
|
// Read certificate from the resource
|
||||||
@ -111,27 +92,7 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
certificates.add(certificate);
|
certificates.add(certificate);
|
||||||
|
|
||||||
// Construct URLs required for SAML configuration
|
// Construct URLs required for SAML configuration
|
||||||
String serverUrl =
|
SamlClient samlClient = getSamlClient(registrationId, samlConf, certificates);
|
||||||
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);
|
|
||||||
|
|
||||||
// Read private key for service provider
|
// Read private key for service provider
|
||||||
Resource privateKeyResource = samlConf.getPrivateKey();
|
Resource privateKeyResource = samlConf.getPrivateKey();
|
||||||
@ -143,8 +104,12 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
// Redirect to identity provider for logout
|
// Redirect to identity provider for logout
|
||||||
samlClient.redirectToIdentityProvider(response, null, nameIdValue);
|
samlClient.redirectToIdentityProvider(response, null, nameIdValue);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(nameIdValue, e);
|
log.error(
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
"Error retrieving logout URL from Provider {} for user {}",
|
||||||
|
samlConf.getProvider(),
|
||||||
|
nameIdValue,
|
||||||
|
e);
|
||||||
|
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,87 +117,107 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
private void getRedirect_oauth2(
|
private void getRedirect_oauth2(
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
String param = "logout=true";
|
String registrationId;
|
||||||
String registrationId = null;
|
|
||||||
String issuer = null;
|
|
||||||
String clientId = null;
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
String path = checkForErrors(request);
|
||||||
|
|
||||||
if (authentication instanceof OAuth2AuthenticationToken) {
|
if (authentication instanceof OAuth2AuthenticationToken oauthToken) {
|
||||||
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
|
|
||||||
registrationId = oauthToken.getAuthorizedClientRegistrationId();
|
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 {
|
} else {
|
||||||
registrationId = oauth.getProvider() != null ? oauth.getProvider() : "";
|
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
|
// Redirect based on OAuth2 provider
|
||||||
switch (registrationId.toLowerCase()) {
|
switch (registrationId.toLowerCase()) {
|
||||||
case "keycloak":
|
case "keycloak" -> {
|
||||||
// Add Keycloak specific logout URL if needed
|
KeycloakProvider keycloak = oauth.getClient().getKeycloak();
|
||||||
String logoutUrl =
|
String logoutUrl =
|
||||||
issuer
|
keycloak.getIssuer()
|
||||||
+ "/protocol/openid-connect/logout"
|
+ "/protocol/openid-connect/logout"
|
||||||
+ "?client_id="
|
+ "?client_id="
|
||||||
+ clientId
|
+ keycloak.getClientId()
|
||||||
+ "&post_logout_redirect_uri="
|
+ "&post_logout_redirect_uri="
|
||||||
+ response.encodeRedirectURL(redirect_url);
|
+ response.encodeRedirectURL(redirectUrl);
|
||||||
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", "google" -> {
|
||||||
// Add GitHub specific logout URL if needed
|
log.info(
|
||||||
String githubLogoutUrl = "https://github.com/logout";
|
"No redirect URL for {} available. Redirecting to default logout URL: {}",
|
||||||
log.info("Redirecting to GitHub logout URL: " + githubLogoutUrl);
|
registrationId,
|
||||||
response.sendRedirect(githubLogoutUrl);
|
redirectUrl);
|
||||||
break;
|
response.sendRedirect(redirectUrl);
|
||||||
case "google":
|
}
|
||||||
// Add Google specific logout URL if needed
|
default -> {
|
||||||
// String googleLogoutUrl =
|
log.info("Redirecting to default logout URL: {}", redirectUrl);
|
||||||
// "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue="
|
response.sendRedirect(redirectUrl);
|
||||||
// + 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanitize input to avoid potential security vulnerabilities
|
private static SamlClient getSamlClient(
|
||||||
|
String registrationId, SAML2 samlConf, List<X509Certificate> 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 <code>String</code> containing
|
||||||
|
* the error request parameter.
|
||||||
|
*
|
||||||
|
* @param request the user's <code>HttpServletRequest</code> request.
|
||||||
|
* @return a <code>String</code> 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 <code>
|
||||||
|
* String</code>.
|
||||||
|
*
|
||||||
|
* @return a sanitised <code>String</code>
|
||||||
|
*/
|
||||||
private String sanitizeInput(String input) {
|
private String sanitizeInput(String input) {
|
||||||
return input.replaceAll("[^a-zA-Z0-9 ]", "");
|
return input.replaceAll("[^a-zA-Z0-9 ]", "");
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
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.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
@ -51,11 +51,7 @@ public class SecurityConfiguration {
|
|||||||
|
|
||||||
private final CustomUserDetailsService userDetailsService;
|
private final CustomUserDetailsService userDetailsService;
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
@Qualifier("loginEnabled")
|
|
||||||
private final boolean loginEnabledValue;
|
private final boolean loginEnabledValue;
|
||||||
|
|
||||||
@Qualifier("runningEE")
|
|
||||||
private final boolean runningEE;
|
private final boolean runningEE;
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
@ -109,6 +105,7 @@ public class SecurityConfiguration {
|
|||||||
if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) {
|
if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) {
|
||||||
http.csrf(csrf -> csrf.disable());
|
http.csrf(csrf -> csrf.disable());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loginEnabledValue) {
|
if (loginEnabledValue) {
|
||||||
http.addFilterBefore(
|
http.addFilterBefore(
|
||||||
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
@ -164,8 +161,7 @@ public class SecurityConfiguration {
|
|||||||
.logoutSuccessHandler(
|
.logoutSuccessHandler(
|
||||||
new CustomLogoutSuccessHandler(applicationProperties))
|
new CustomLogoutSuccessHandler(applicationProperties))
|
||||||
.clearAuthentication(true)
|
.clearAuthentication(true)
|
||||||
.invalidateHttpSession( // Invalidate session
|
.invalidateHttpSession(true)
|
||||||
true)
|
|
||||||
.deleteCookies("JSESSIONID", "remember-me"));
|
.deleteCookies("JSESSIONID", "remember-me"));
|
||||||
http.rememberMe(
|
http.rememberMe(
|
||||||
rememberMeConfigurer -> // Use the configurator directly
|
rememberMeConfigurer -> // Use the configurator directly
|
||||||
@ -227,14 +223,14 @@ public class SecurityConfiguration {
|
|||||||
.permitAll());
|
.permitAll());
|
||||||
}
|
}
|
||||||
// Handle OAUTH2 Logins
|
// Handle OAUTH2 Logins
|
||||||
if (applicationProperties.getSecurity().isOauth2Activ()) {
|
if (applicationProperties.getSecurity().isOauth2Active()) {
|
||||||
http.oauth2Login(
|
http.oauth2Login(
|
||||||
oauth2 ->
|
oauth2 ->
|
||||||
oauth2.loginPage("/oauth2")
|
oauth2.loginPage("/oauth2")
|
||||||
.
|
.
|
||||||
/*
|
/*
|
||||||
This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
|
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.
|
is set as true, else login fails with an error message advising the same.
|
||||||
*/
|
*/
|
||||||
successHandler(
|
successHandler(
|
||||||
@ -258,8 +254,7 @@ public class SecurityConfiguration {
|
|||||||
.permitAll());
|
.permitAll());
|
||||||
}
|
}
|
||||||
// Handle SAML
|
// Handle SAML
|
||||||
if (applicationProperties.getSecurity().isSaml2Activ()) {
|
if (applicationProperties.getSecurity().isSaml2Active() && runningEE) {
|
||||||
// && runningEE
|
|
||||||
// Configure the authentication provider
|
// Configure the authentication provider
|
||||||
OpenSaml4AuthenticationProvider authenticationProvider =
|
OpenSaml4AuthenticationProvider authenticationProvider =
|
||||||
new OpenSaml4AuthenticationProvider();
|
new OpenSaml4AuthenticationProvider();
|
||||||
@ -290,6 +285,7 @@ public class SecurityConfiguration {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
log.info("SAML 2 login is not enabled. Using default.");
|
||||||
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||||
}
|
}
|
||||||
return http.build();
|
return http.build();
|
||||||
@ -315,7 +311,7 @@ public class SecurityConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public boolean activSecurity() {
|
public boolean activeSecurity() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
// Use API key to authenticate. This requires you to have an authentication
|
// Use API key to authenticate. This requires you to have an authentication
|
||||||
// provider for API keys.
|
// provider for API keys.
|
||||||
Optional<User> user = userService.getUserByApiKey(apiKey);
|
Optional<User> user = userService.getUserByApiKey(apiKey);
|
||||||
if (!user.isPresent()) {
|
if (user.isEmpty()) {
|
||||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
response.getWriter().write("Invalid API Key.");
|
response.getWriter().write("Invalid API Key.");
|
||||||
return;
|
return;
|
||||||
@ -150,7 +150,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
OAUTH2 oAuth = securityProp.getOauth2();
|
OAUTH2 oAuth = securityProp.getOauth2();
|
||||||
blockRegistration = oAuth != null && oAuth.getBlockRegistration();
|
blockRegistration = oAuth != null && oAuth.getBlockRegistration();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
username = ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
||||||
loginMethod = LoginMethod.SAML2USER;
|
loginMethod = LoginMethod.SAML2USER;
|
||||||
SAML2 saml2 = securityProp.getSaml2();
|
SAML2 saml2 = securityProp.getSaml2();
|
||||||
blockRegistration = saml2 != null && saml2.getBlockRegistration();
|
blockRegistration = saml2 != null && saml2.getBlockRegistration();
|
||||||
@ -177,7 +177,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
if (blockRegistration && !isUserExists) {
|
if (blockRegistration && !isUserExists) {
|
||||||
log.warn("Blocked registration for OAuth2/SAML user: {}", username);
|
log.warn("Blocked registration for OAuth2/SAML user: {}", username);
|
||||||
response.sendRedirect(
|
response.sendRedirect(
|
||||||
request.getContextPath() + "/logout?oauth2_admin_blocked_user=true");
|
request.getContextPath() + "/logout?oAuth2AdminBlockedUser=true");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,7 +193,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
|
|
||||||
// Redirect to logout if credentials are invalid
|
// Redirect to logout if credentials are invalid
|
||||||
if (!isUserExists && notSsoLogin) {
|
if (!isUserExists && notSsoLogin) {
|
||||||
response.sendRedirect(request.getContextPath() + "/logout?badcredentials=true");
|
response.sendRedirect(request.getContextPath() + "/logout?badCredentials=true");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isUserDisabled) {
|
if (isUserDisabled) {
|
||||||
|
@ -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;
|
||||||
|
|
||||||
@ -78,20 +78,18 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle OAUTH2 login and user auto creation.
|
// 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 {
|
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||||
if (!isUsernameValid(username)) {
|
if (!isUsernameValid(username)) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
||||||
if (existingUser.isPresent()) {
|
if (existingUser.isPresent()) {
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
if (autoCreateUser) {
|
if (autoCreateUser) {
|
||||||
saveUser(username, AuthenticationType.SSO);
|
saveUser(username, AuthenticationType.SSO);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Authentication getAuthentication(String apiKey) {
|
public Authentication getAuthentication(String apiKey) {
|
||||||
@ -373,19 +371,16 @@ public class UserService implements UserServiceInterface {
|
|||||||
|
|
||||||
public void invalidateUserSessions(String username) {
|
public void invalidateUserSessions(String username) {
|
||||||
String usernameP = "";
|
String usernameP = "";
|
||||||
|
|
||||||
for (Object principal : sessionRegistry.getAllPrincipals()) {
|
for (Object principal : sessionRegistry.getAllPrincipals()) {
|
||||||
for (SessionInformation sessionsInformation :
|
for (SessionInformation sessionsInformation :
|
||||||
sessionRegistry.getAllSessions(principal, false)) {
|
sessionRegistry.getAllSessions(principal, false)) {
|
||||||
if (principal instanceof UserDetails) {
|
if (principal instanceof UserDetails userDetails) {
|
||||||
UserDetails userDetails = (UserDetails) principal;
|
|
||||||
usernameP = userDetails.getUsername();
|
usernameP = userDetails.getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User oAuth2User) {
|
||||||
OAuth2User oAuth2User = (OAuth2User) principal;
|
|
||||||
usernameP = oAuth2User.getName();
|
usernameP = oAuth2User.getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
||||||
CustomSaml2AuthenticatedPrincipal saml2User =
|
usernameP = saml2User.name();
|
||||||
(CustomSaml2AuthenticatedPrincipal) principal;
|
|
||||||
usernameP = saml2User.getName();
|
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
usernameP = (String) principal;
|
usernameP = (String) principal;
|
||||||
}
|
}
|
||||||
@ -398,6 +393,7 @@ public class UserService implements UserServiceInterface {
|
|||||||
|
|
||||||
public String getCurrentUsername() {
|
public String getCurrentUsername() {
|
||||||
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
|
|
||||||
if (principal instanceof UserDetails) {
|
if (principal instanceof UserDetails) {
|
||||||
return ((UserDetails) principal).getUsername();
|
return ((UserDetails) principal).getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
@ -405,23 +401,32 @@ public class UserService implements UserServiceInterface {
|
|||||||
.getAttribute(
|
.getAttribute(
|
||||||
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
|
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
return ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
return ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
||||||
} else if (principal instanceof String) {
|
|
||||||
return (String) principal;
|
|
||||||
} else {
|
} else {
|
||||||
return principal.toString();
|
return principal.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void syncCustomApiUser(String customApiKey)
|
public void syncCustomApiUser(String customApiKey) {
|
||||||
throws SQLException, UnsupportedProviderException {
|
if (customApiKey == null || customApiKey.trim().isBlank()) {
|
||||||
if (customApiKey == null || customApiKey.trim().length() == 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String username = "CUSTOM_API_USER";
|
String username = "CUSTOM_API_USER";
|
||||||
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
||||||
if (!existingUser.isPresent()) {
|
|
||||||
|
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
|
// Create new user with API role
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setUsername(username);
|
user.setUsername(username);
|
||||||
@ -432,15 +437,12 @@ public class UserService implements UserServiceInterface {
|
|||||||
user.setApiKey(customApiKey);
|
user.setApiKey(customApiKey);
|
||||||
user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user));
|
user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user));
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
databaseService.exportDatabase();
|
databaseService.exportDatabase();
|
||||||
} else {
|
} catch (SQLException | UnsupportedProviderException e) {
|
||||||
// Update API key if it has changed
|
log.error("Error exporting database after synchronising custom API user", e);
|
||||||
User user = existingUser.get();
|
|
||||||
if (!customApiKey.equals(user.getApiKey())) {
|
|
||||||
user.setApiKey(customApiKey);
|
|
||||||
userRepository.save(user);
|
|
||||||
databaseService.exportDatabase();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -29,7 +29,7 @@ public class CustomOAuth2AuthenticationFailureHandler
|
|||||||
|
|
||||||
if (exception instanceof BadCredentialsException) {
|
if (exception instanceof BadCredentialsException) {
|
||||||
log.error("BadCredentialsException", exception);
|
log.error("BadCredentialsException", exception);
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?error=badcredentials");
|
getRedirectStrategy().sendRedirect(request, response, "/login?error=badCredentials");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (exception instanceof DisabledException) {
|
if (exception instanceof DisabledException) {
|
||||||
@ -50,10 +50,12 @@ public class CustomOAuth2AuthenticationFailureHandler
|
|||||||
if (error.getErrorCode().equals("Password must not be null")) {
|
if (error.getErrorCode().equals("Password must not be null")) {
|
||||||
errorCode = "userAlreadyExistsWeb";
|
errorCode = "userAlreadyExistsWeb";
|
||||||
}
|
}
|
||||||
log.error("OAuth2 Authentication error: " + errorCode);
|
|
||||||
log.error("OAuth2AuthenticationException", exception);
|
log.error(
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?erroroauth=" + errorCode);
|
"OAuth2 Authentication error: {}",
|
||||||
return;
|
errorCode != null ? errorCode : exception.getMessage(),
|
||||||
|
exception);
|
||||||
|
getRedirectStrategy().sendRedirect(request, response, "/login?errorOAuth=" + errorCode);
|
||||||
}
|
}
|
||||||
log.error("Unhandled authentication exception", exception);
|
log.error("Unhandled authentication exception", exception);
|
||||||
super.onAuthenticationFailure(request, response, exception);
|
super.onAuthenticationFailure(request, response, exception);
|
||||||
|
@ -20,19 +20,18 @@ 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
|
||||||
extends SavedRequestAwareAuthenticationSuccessHandler {
|
extends SavedRequestAwareAuthenticationSuccessHandler {
|
||||||
|
|
||||||
private LoginAttemptService loginAttemptService;
|
private final LoginAttemptService loginAttemptService;
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
private ApplicationProperties applicationProperties;
|
private final UserService userService;
|
||||||
private UserService userService;
|
|
||||||
|
|
||||||
public CustomOAuth2AuthenticationSuccessHandler(
|
public CustomOAuth2AuthenticationSuccessHandler(
|
||||||
final LoginAttemptService loginAttemptService,
|
LoginAttemptService loginAttemptService,
|
||||||
ApplicationProperties applicationProperties,
|
ApplicationProperties applicationProperties,
|
||||||
UserService userService) {
|
UserService userService) {
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
@ -48,11 +47,9 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
|||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
String username = "";
|
String username = "";
|
||||||
|
|
||||||
if (principal instanceof OAuth2User) {
|
if (principal instanceof OAuth2User oauthUser) {
|
||||||
OAuth2User oauthUser = (OAuth2User) principal;
|
|
||||||
username = oauthUser.getName();
|
username = oauthUser.getName();
|
||||||
} else if (principal instanceof UserDetails) {
|
} else if (principal instanceof UserDetails oauthUser) {
|
||||||
UserDetails oauthUser = (UserDetails) principal;
|
|
||||||
username = oauthUser.getUsername();
|
username = oauthUser.getUsername();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,6 +75,7 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
|||||||
throw new LockedException(
|
throw new LockedException(
|
||||||
"Your account has been locked due to too many failed login attempts.");
|
"Your account has been locked due to too many failed login attempts.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userService.isUserDisabled(username)) {
|
if (userService.isUserDisabled(username)) {
|
||||||
getRedirectStrategy()
|
getRedirectStrategy()
|
||||||
.sendRedirect(request, response, "/logout?userIsDisabled=true");
|
.sendRedirect(request, response, "/logout?userIsDisabled=true");
|
||||||
@ -87,13 +85,14 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
|||||||
&& userService.hasPassword(username)
|
&& userService.hasPassword(username)
|
||||||
&& !userService.isAuthenticationTypeByUsername(username, AuthenticationType.SSO)
|
&& !userService.isAuthenticationTypeByUsername(username, AuthenticationType.SSO)
|
||||||
&& oAuth.getAutoCreateUser()) {
|
&& oAuth.getAutoCreateUser()) {
|
||||||
response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
response.sendRedirect(contextPath + "/logout?oAuth2AuthenticationErrorWeb=true");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (oAuth.getBlockRegistration()
|
if (oAuth.getBlockRegistration()
|
||||||
&& !userService.usernameExistsIgnoreCase(username)) {
|
&& !userService.usernameExistsIgnoreCase(username)) {
|
||||||
response.sendRedirect(contextPath + "/logout?oauth2_admin_blocked_user=true");
|
response.sendRedirect(contextPath + "/logout?oAuth2AdminBlockedUser=true");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (principal instanceof OAuth2User) {
|
if (principal instanceof OAuth2User) {
|
||||||
|
@ -17,19 +17,19 @@ import stirling.software.SPDF.config.security.LoginAttemptService;
|
|||||||
import stirling.software.SPDF.config.security.UserService;
|
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.ApplicationProperties.Security.OAUTH2.Client;
|
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
|
import stirling.software.SPDF.model.UsernameAttribute;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class CustomOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
|
public class CustomOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
|
||||||
|
|
||||||
private final OidcUserService delegate = new OidcUserService();
|
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(
|
public CustomOAuth2UserService(
|
||||||
ApplicationProperties applicationProperties,
|
ApplicationProperties applicationProperties,
|
||||||
@ -42,34 +42,26 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
|
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 {
|
try {
|
||||||
OidcUser user = delegate.loadUser(userRequest);
|
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
|
// todo: save user by OIDC ID instead of username
|
||||||
if (username == null || username.trim().isEmpty()) {
|
Optional<User> internalUser =
|
||||||
throw new IllegalArgumentException(
|
userService.findByUsernameIgnoreCase(user.getAttribute(usernameAttributeKey));
|
||||||
"Claim '" + usernameAttribute + "' cannot be null or empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
Optional<User> duser = userService.findByUsernameIgnoreCase(username);
|
if (internalUser.isPresent()) {
|
||||||
if (duser.isPresent()) {
|
String internalUsername = internalUser.get().getUsername();
|
||||||
if (loginAttemptService.isBlocked(username)) {
|
if (loginAttemptService.isBlocked(internalUsername)) {
|
||||||
throw new LockedException(
|
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");
|
throw new IllegalArgumentException("Password must not be null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,7 +71,7 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
|
|||||||
user.getAuthorities(),
|
user.getAuthorities(),
|
||||||
userRequest.getIdToken(),
|
userRequest.getIdToken(),
|
||||||
user.getUserInfo(),
|
user.getUserInfo(),
|
||||||
usernameAttribute);
|
usernameAttributeKey);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
log.error("Error loading OIDC user: {}", e.getMessage());
|
log.error("Error loading OIDC user: {}", e.getMessage());
|
||||||
throw new OAuth2AuthenticationException(new OAuth2Error(e.getMessage()), e);
|
throw new OAuth2AuthenticationException(new OAuth2Error(e.getMessage()), e);
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
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 static stirling.software.SPDF.utils.validation.Validator.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -26,18 +29,20 @@ 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.OAUTH2.Client;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||||
import stirling.software.SPDF.model.User;
|
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.GoogleProvider;
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||||
|
import stirling.software.SPDF.model.provider.Provider;
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ConditionalOnProperty(
|
@Configuration
|
||||||
value = "security.oauth2.enabled",
|
@ConditionalOnProperty(value = "security.oauth2.enabled", havingValue = "true")
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
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;
|
||||||
|
|
||||||
@ -48,139 +53,175 @@ public class OAuth2Configuration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(
|
@ConditionalOnProperty(value = "security.oauth2.enabled", havingValue = "true")
|
||||||
value = "security.oauth2.enabled",
|
public ClientRegistrationRepository clientRegistrationRepository()
|
||||||
havingValue = "true",
|
throws NoProviderFoundException {
|
||||||
matchIfMissing = false)
|
|
||||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
|
||||||
List<ClientRegistration> registrations = new ArrayList<>();
|
List<ClientRegistration> registrations = new ArrayList<>();
|
||||||
githubClientRegistration().ifPresent(registrations::add);
|
githubClientRegistration().ifPresent(registrations::add);
|
||||||
oidcClientRegistration().ifPresent(registrations::add);
|
oidcClientRegistration().ifPresent(registrations::add);
|
||||||
googleClientRegistration().ifPresent(registrations::add);
|
googleClientRegistration().ifPresent(registrations::add);
|
||||||
keycloakClientRegistration().ifPresent(registrations::add);
|
keycloakClientRegistration().ifPresent(registrations::add);
|
||||||
|
|
||||||
if (registrations.isEmpty()) {
|
if (registrations.isEmpty()) {
|
||||||
log.error("At least one OAuth2 provider must be configured");
|
log.error("No OAuth2 provider registered");
|
||||||
System.exit(1);
|
throw new NoProviderFoundException("At least one OAuth2 provider must be configured.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new InMemoryClientRegistrationRepository(registrations);
|
return new InMemoryClientRegistrationRepository(registrations);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<ClientRegistration> googleClientRegistration() {
|
private Optional<ClientRegistration> keycloakClientRegistration() {
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2();
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
|
if (isOAuth2Enabled(oauth2) || isClientInitialised(oauth2)) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
GoogleProvider google = client.getGoogle();
|
|
||||||
return google != null && google.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistration.withRegistrationId(google.getName())
|
|
||||||
.clientId(google.getClientId())
|
|
||||||
.clientSecret(google.getClientSecret())
|
|
||||||
.scope(google.getScopes())
|
|
||||||
.authorizationUri(google.getAuthorizationuri())
|
|
||||||
.tokenUri(google.getTokenuri())
|
|
||||||
.userInfoUri(google.getUserinfouri())
|
|
||||||
.userNameAttributeName(google.getUseAsUsername())
|
|
||||||
.clientName(google.getClientName())
|
|
||||||
.redirectUri("{baseUrl}/login/oauth2/code/" + google.getName())
|
|
||||||
.authorizationGrantType(
|
|
||||||
org.springframework.security.oauth2.core
|
|
||||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> keycloakClientRegistration() {
|
Client client = oauth2.getClient();
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
KeycloakProvider keycloakClient = client.getKeycloak();
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
Provider keycloak =
|
||||||
return Optional.empty();
|
new KeycloakProvider(
|
||||||
}
|
keycloakClient.getIssuer(),
|
||||||
Client client = oauth.getClient();
|
keycloakClient.getClientId(),
|
||||||
if (client == null) {
|
keycloakClient.getClientSecret(),
|
||||||
return Optional.empty();
|
keycloakClient.getScopes(),
|
||||||
}
|
keycloakClient.getUseAsUsername());
|
||||||
KeycloakProvider keycloak = client.getKeycloak();
|
|
||||||
return keycloak != null && keycloak.isSettingsValid()
|
return validateProvider(keycloak)
|
||||||
? Optional.of(
|
? Optional.of(
|
||||||
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
||||||
.registrationId(keycloak.getName())
|
.registrationId(keycloak.getName())
|
||||||
.clientId(keycloak.getClientId())
|
.clientId(keycloak.getClientId())
|
||||||
.clientSecret(keycloak.getClientSecret())
|
.clientSecret(keycloak.getClientSecret())
|
||||||
.scope(keycloak.getScopes())
|
.scope(keycloak.getScopes())
|
||||||
.userNameAttributeName(keycloak.getUseAsUsername())
|
.userNameAttributeName(keycloak.getUseAsUsername().getName())
|
||||||
.clientName(keycloak.getClientName())
|
.clientName(keycloak.getClientName())
|
||||||
.build())
|
.build())
|
||||||
: Optional.empty();
|
: Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Optional<ClientRegistration> googleClientRegistration() {
|
||||||
|
OAUTH2 oAuth2 = applicationProperties.getSecurity().getOauth2();
|
||||||
|
|
||||||
|
if (isOAuth2Enabled(oAuth2) || isClientInitialised(oAuth2)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Client client = oAuth2.getClient();
|
||||||
|
GoogleProvider googleClient = client.getGoogle();
|
||||||
|
Provider google =
|
||||||
|
new GoogleProvider(
|
||||||
|
googleClient.getClientId(),
|
||||||
|
googleClient.getClientSecret(),
|
||||||
|
googleClient.getScopes(),
|
||||||
|
googleClient.getUseAsUsername());
|
||||||
|
|
||||||
|
return validateProvider(google)
|
||||||
|
? Optional.of(
|
||||||
|
ClientRegistration.withRegistrationId(google.getName())
|
||||||
|
.clientId(google.getClientId())
|
||||||
|
.clientSecret(google.getClientSecret())
|
||||||
|
.scope(google.getScopes())
|
||||||
|
.authorizationUri(google.getAuthorizationUri())
|
||||||
|
.tokenUri(google.getTokenUri())
|
||||||
|
.userInfoUri(google.getUserInfoUri())
|
||||||
|
.userNameAttributeName(google.getUseAsUsername().getName())
|
||||||
|
.clientName(google.getClientName())
|
||||||
|
.redirectUri(REDIRECT_URI_PATH + google.getName())
|
||||||
|
.authorizationGrantType(AUTHORIZATION_CODE)
|
||||||
|
.build())
|
||||||
|
: Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
private Optional<ClientRegistration> githubClientRegistration() {
|
private Optional<ClientRegistration> githubClientRegistration() {
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
OAUTH2 oAuth2 = applicationProperties.getSecurity().getOauth2();
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
|
if (isOAuth2Enabled(oAuth2)) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
Client client = oAuth2.getClient();
|
||||||
return Optional.empty();
|
GitHubProvider githubClient = client.getGithub();
|
||||||
}
|
Provider github =
|
||||||
GithubProvider github = client.getGithub();
|
new GitHubProvider(
|
||||||
return github != null && github.isSettingsValid()
|
githubClient.getClientId(),
|
||||||
|
githubClient.getClientSecret(),
|
||||||
|
githubClient.getScopes(),
|
||||||
|
githubClient.getUseAsUsername());
|
||||||
|
|
||||||
|
return validateProvider(github)
|
||||||
? 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().getName())
|
||||||
.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
|
|
||||||
|| oauth.getIssuer() == null
|
if (isOAuth2Enabled(oauth) || isClientInitialised(oauth)) {
|
||||||
|| 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()) {
|
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
return Optional.of(
|
|
||||||
|
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())
|
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
||||||
.registrationId("oidc")
|
.registrationId(name)
|
||||||
.clientId(oauth.getClientId())
|
.clientId(oidcProvider.getClientId())
|
||||||
.clientSecret(oauth.getClientSecret())
|
.clientSecret(oidcProvider.getClientSecret())
|
||||||
.scope(oauth.getScopes())
|
.scope(oidcProvider.getScopes())
|
||||||
.userNameAttributeName(oauth.getUseAsUsername())
|
.userNameAttributeName(oidcProvider.getUseAsUsername().getName())
|
||||||
.clientName("OIDC")
|
.clientName(clientName)
|
||||||
.build());
|
.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 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.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(
|
@ConditionalOnProperty(value = "security.oauth2.enabled", havingValue = "true")
|
||||||
value = "security.oauth2.enabled",
|
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
||||||
return (authorities) -> {
|
return (authorities) -> {
|
||||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||||
@ -200,13 +241,11 @@ public class OAuth2Configuration {
|
|||||||
(String) oauth2Auth.getAttributes().get(useAsUsername));
|
(String) oauth2Auth.getAttributes().get(useAsUsername));
|
||||||
if (userOpt.isPresent()) {
|
if (userOpt.isPresent()) {
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
if (user != null) {
|
|
||||||
mappedAuthorities.add(
|
mappedAuthorities.add(
|
||||||
new SimpleGrantedAuthority(
|
new SimpleGrantedAuthority(
|
||||||
userService.findRole(user).getAuthority()));
|
userService.findRole(user).getAuthority()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return mappedAuthorities;
|
return mappedAuthorities;
|
||||||
};
|
};
|
||||||
|
@ -13,8 +13,10 @@ import org.bouncycastle.openssl.PEMParser;
|
|||||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||||
import org.bouncycastle.util.io.pem.PemObject;
|
import org.bouncycastle.util.io.pem.PemObject;
|
||||||
import org.bouncycastle.util.io.pem.PemReader;
|
import org.bouncycastle.util.io.pem.PemReader;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
|
||||||
|
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
||||||
public class CertificateUtils {
|
public class CertificateUtils {
|
||||||
|
|
||||||
public static X509Certificate readCertificate(Resource certificateResource) throws Exception {
|
public static X509Certificate readCertificate(Resource certificateResource) throws Exception {
|
||||||
|
@ -4,27 +4,13 @@ import java.io.Serializable;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
|
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<String, List<Object>> attributes, String nameId, List<String> sessionIndexes)
|
||||||
implements Saml2AuthenticatedPrincipal, Serializable {
|
implements Saml2AuthenticatedPrincipal, Serializable {
|
||||||
|
|
||||||
private final String name;
|
|
||||||
private final Map<String, List<Object>> attributes;
|
|
||||||
private final String nameId;
|
|
||||||
private final List<String> sessionIndexes;
|
|
||||||
|
|
||||||
public CustomSaml2AuthenticatedPrincipal(
|
|
||||||
String name,
|
|
||||||
Map<String, List<Object>> attributes,
|
|
||||||
String nameId,
|
|
||||||
List<String> sessionIndexes) {
|
|
||||||
this.name = name;
|
|
||||||
this.attributes = attributes;
|
|
||||||
this.nameId = nameId;
|
|
||||||
this.sessionIndexes = sessionIndexes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return this.name;
|
return this.name;
|
||||||
@ -35,11 +21,4 @@ public class CustomSaml2AuthenticatedPrincipal
|
|||||||
return this.attributes;
|
return this.attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNameId() {
|
|
||||||
return this.nameId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getSessionIndexes() {
|
|
||||||
return this.sessionIndexes;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,20 @@ package stirling.software.SPDF.config.security.saml2;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.security.authentication.ProviderNotFoundException;
|
import org.springframework.security.authentication.ProviderNotFoundException;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.saml2.core.Saml2Error;
|
import org.springframework.security.saml2.core.Saml2Error;
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||||
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
||||||
public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -22,18 +23,19 @@ public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthentica
|
|||||||
HttpServletRequest request,
|
HttpServletRequest request,
|
||||||
HttpServletResponse response,
|
HttpServletResponse response,
|
||||||
AuthenticationException exception)
|
AuthenticationException exception)
|
||||||
throws IOException, ServletException {
|
throws IOException {
|
||||||
|
log.error("Authentication error", exception);
|
||||||
|
|
||||||
if (exception instanceof Saml2AuthenticationException) {
|
if (exception instanceof Saml2AuthenticationException) {
|
||||||
Saml2Error error = ((Saml2AuthenticationException) exception).getSaml2Error();
|
Saml2Error error = ((Saml2AuthenticationException) exception).getSaml2Error();
|
||||||
getRedirectStrategy()
|
getRedirectStrategy()
|
||||||
.sendRedirect(request, response, "/login?erroroauth=" + error.getErrorCode());
|
.sendRedirect(request, response, "/login?errorOAuth=" + error.getErrorCode());
|
||||||
} else if (exception instanceof ProviderNotFoundException) {
|
} else if (exception instanceof ProviderNotFoundException) {
|
||||||
getRedirectStrategy()
|
getRedirectStrategy()
|
||||||
.sendRedirect(
|
.sendRedirect(
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
"/login?erroroauth=not_authentication_provider_found");
|
"/login?errorOAuth=not_authentication_provider_found");
|
||||||
}
|
}
|
||||||
log.error("AuthenticationException: " + exception);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
@ -42,7 +42,7 @@ public class CustomSaml2AuthenticationSuccessHandler
|
|||||||
log.debug("Starting SAML2 authentication success handling");
|
log.debug("Starting SAML2 authentication success handling");
|
||||||
|
|
||||||
if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
String username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
String username = ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
||||||
log.debug("Authenticated principal found for user: {}", username);
|
log.debug("Authenticated principal found for user: {}", username);
|
||||||
|
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = request.getSession(false);
|
||||||
@ -97,7 +97,7 @@ public class CustomSaml2AuthenticationSuccessHandler
|
|||||||
"User {} exists with password but is not SSO user, redirecting to logout",
|
"User {} exists with password but is not SSO user, redirecting to logout",
|
||||||
username);
|
username);
|
||||||
response.sendRedirect(
|
response.sendRedirect(
|
||||||
contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
contextPath + "/logout?oAuth2AuthenticationErrorWeb=true");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,20 +105,18 @@ public class CustomSaml2AuthenticationSuccessHandler
|
|||||||
if (saml2.getBlockRegistration() && !userExists) {
|
if (saml2.getBlockRegistration() && !userExists) {
|
||||||
log.debug("Registration blocked for new user: {}", username);
|
log.debug("Registration blocked for new user: {}", username);
|
||||||
response.sendRedirect(
|
response.sendRedirect(
|
||||||
contextPath + "/login?erroroauth=oauth2_admin_blocked_user");
|
contextPath + "/login?errorOAuth=oAuth2AdminBlockedUser");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log.debug("Processing SSO post-login for user: {}", username);
|
log.debug("Processing SSO post-login for user: {}", username);
|
||||||
userService.processSSOPostLogin(username, saml2.getAutoCreateUser());
|
userService.processSSOPostLogin(username, saml2.getAutoCreateUser());
|
||||||
log.debug("Successfully processed authentication for user: {}", username);
|
log.debug("Successfully processed authentication for user: {}", username);
|
||||||
response.sendRedirect(contextPath + "/");
|
response.sendRedirect(contextPath + "/");
|
||||||
return;
|
|
||||||
} catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) {
|
} catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) {
|
||||||
log.debug(
|
log.debug(
|
||||||
"Invalid username detected for user: {}, redirecting to logout",
|
"Invalid username detected for user: {}, redirecting to logout",
|
||||||
username);
|
username);
|
||||||
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
|
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -7,6 +7,7 @@ import org.opensaml.saml.saml2.core.Assertion;
|
|||||||
import org.opensaml.saml.saml2.core.Attribute;
|
import org.opensaml.saml.saml2.core.Attribute;
|
||||||
import org.opensaml.saml.saml2.core.AttributeStatement;
|
import org.opensaml.saml.saml2.core.AttributeStatement;
|
||||||
import org.opensaml.saml.saml2.core.AuthnStatement;
|
import org.opensaml.saml.saml2.core.AuthnStatement;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
|
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
|
||||||
@ -18,10 +19,11 @@ import stirling.software.SPDF.config.security.UserService;
|
|||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
||||||
public class CustomSaml2ResponseAuthenticationConverter
|
public class CustomSaml2ResponseAuthenticationConverter
|
||||||
implements Converter<ResponseToken, Saml2Authentication> {
|
implements Converter<ResponseToken, Saml2Authentication> {
|
||||||
|
|
||||||
private UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
public CustomSaml2ResponseAuthenticationConverter(UserService userService) {
|
public CustomSaml2ResponseAuthenticationConverter(UserService userService) {
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
@ -61,10 +63,10 @@ public class CustomSaml2ResponseAuthenticationConverter
|
|||||||
Map<String, List<Object>> attributes = extractAttributes(assertion);
|
Map<String, List<Object>> attributes = extractAttributes(assertion);
|
||||||
|
|
||||||
// Debug log with actual values
|
// 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
|
// Try to get username/identifier in order of preference
|
||||||
String userIdentifier = null;
|
String userIdentifier;
|
||||||
if (hasAttribute(attributes, "username")) {
|
if (hasAttribute(attributes, "username")) {
|
||||||
userIdentifier = getFirstAttributeValue(attributes, "username");
|
userIdentifier = getFirstAttributeValue(attributes, "username");
|
||||||
} else if (hasAttribute(attributes, "emailaddress")) {
|
} else if (hasAttribute(attributes, "emailaddress")) {
|
||||||
@ -84,11 +86,9 @@ public class CustomSaml2ResponseAuthenticationConverter
|
|||||||
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_USER");
|
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_USER");
|
||||||
if (userOpt.isPresent()) {
|
if (userOpt.isPresent()) {
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
if (user != null) {
|
|
||||||
simpleGrantedAuthority =
|
simpleGrantedAuthority =
|
||||||
new SimpleGrantedAuthority(userService.findRole(user).getAuthority());
|
new SimpleGrantedAuthority(userService.findRole(user).getAuthority());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
List<String> sessionIndexes = new ArrayList<>();
|
List<String> sessionIndexes = new ArrayList<>();
|
||||||
for (AuthnStatement authnStatement : assertion.getAuthnStatements()) {
|
for (AuthnStatement authnStatement : assertion.getAuthnStatements()) {
|
||||||
@ -102,7 +102,7 @@ public class CustomSaml2ResponseAuthenticationConverter
|
|||||||
return new Saml2Authentication(
|
return new Saml2Authentication(
|
||||||
principal,
|
principal,
|
||||||
responseToken.getToken().getSaml2Response(),
|
responseToken.getToken().getSaml2Response(),
|
||||||
Collections.singletonList(simpleGrantedAuthority));
|
List.of(simpleGrantedAuthority));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasAttribute(Map<String, List<Object>> attributes, String name) {
|
private boolean hasAttribute(Map<String, List<Object>> attributes, String name) {
|
||||||
|
@ -11,10 +11,12 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
|
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.InMemoryRelyingPartyRegistrationRepository;
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
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.RelyingPartyRegistrationRepository;
|
||||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
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 org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
@ -26,27 +28,20 @@ import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
|||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ConditionalOnProperty(
|
@ConditionalOnProperty(value = "security.saml2.enabled", havingValue = "true")
|
||||||
value = "security.saml2.enabled",
|
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
public class SAML2Configuration {
|
public class SAML2Configuration {
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
public SAML2Configuration(ApplicationProperties applicationProperties) {
|
public SAML2Configuration(ApplicationProperties applicationProperties) {
|
||||||
|
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(
|
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
||||||
name = "security.saml2.enabled",
|
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
|
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
|
||||||
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
||||||
X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getidpCert());
|
X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getIdpCert());
|
||||||
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
|
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
|
||||||
Resource privateKeyResource = samlConf.getPrivateKey();
|
Resource privateKeyResource = samlConf.getPrivateKey();
|
||||||
Resource certificateResource = samlConf.getSpCert();
|
Resource certificateResource = samlConf.getSpCert();
|
||||||
@ -58,81 +53,124 @@ public class SAML2Configuration {
|
|||||||
RelyingPartyRegistration rp =
|
RelyingPartyRegistration rp =
|
||||||
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
|
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
|
||||||
.signingX509Credentials(c -> c.add(signingCredential))
|
.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(
|
.assertingPartyMetadata(
|
||||||
metadata ->
|
metadata ->
|
||||||
metadata.entityId(samlConf.getIdpIssuer())
|
metadata.entityId(samlConf.getIdpIssuer())
|
||||||
.singleSignOnServiceLocation(
|
|
||||||
samlConf.getIdpSingleLoginUrl())
|
|
||||||
.verificationX509Credentials(
|
.verificationX509Credentials(
|
||||||
c -> c.add(verificationCredential))
|
c -> c.add(verificationCredential))
|
||||||
.singleSignOnServiceBinding(
|
.singleSignOnServiceBinding(
|
||||||
Saml2MessageBinding.POST)
|
Saml2MessageBinding.POST)
|
||||||
|
.singleSignOnServiceLocation(
|
||||||
|
samlConf.getIdpSingleLoginUrl())
|
||||||
|
.singleLogoutServiceBinding(
|
||||||
|
Saml2MessageBinding.POST)
|
||||||
|
.singleLogoutServiceLocation(
|
||||||
|
samlConf.getIdpSingleLogoutUrl())
|
||||||
.wantAuthnRequestsSigned(true))
|
.wantAuthnRequestsSigned(true))
|
||||||
.build();
|
.build();
|
||||||
return new InMemoryRelyingPartyRegistrationRepository(rp);
|
return new InMemoryRelyingPartyRegistrationRepository(rp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(
|
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
||||||
name = "security.saml2.enabled",
|
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
public OpenSaml4AuthenticationRequestResolver authenticationRequestResolver(
|
public OpenSaml4AuthenticationRequestResolver authenticationRequestResolver(
|
||||||
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
|
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
|
||||||
OpenSaml4AuthenticationRequestResolver resolver =
|
OpenSaml4AuthenticationRequestResolver resolver =
|
||||||
new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository);
|
new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository);
|
||||||
|
|
||||||
resolver.setAuthnRequestCustomizer(
|
resolver.setAuthnRequestCustomizer(
|
||||||
customizer -> {
|
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();
|
HttpServletRequest request = customizer.getRequest();
|
||||||
// Log HTTP request details
|
AuthnRequest authnRequest = customizer.getAuthnRequest();
|
||||||
log.debug("HTTP Request Method: {}", request.getMethod());
|
HttpSessionSaml2AuthenticationRequestRepository requestRepository =
|
||||||
log.debug("Request URI: {}", request.getRequestURI());
|
new HttpSessionSaml2AuthenticationRequestRepository();
|
||||||
log.debug("Request URL: {}", request.getRequestURL().toString());
|
AbstractSaml2AuthenticationRequest saml2AuthenticationRequest =
|
||||||
log.debug("Query String: {}", request.getQueryString());
|
requestRepository.loadAuthenticationRequest(request);
|
||||||
log.debug("Remote Address: {}", request.getRemoteAddr());
|
|
||||||
// Log headers
|
if (saml2AuthenticationRequest != null) {
|
||||||
Collections.list(request.getHeaderNames())
|
String sessionId = request.getSession(false).getId();
|
||||||
.forEach(
|
|
||||||
headerName -> {
|
|
||||||
log.debug(
|
log.debug(
|
||||||
"Header - {}: {}",
|
"Retrieving SAML 2 authentication request ID from the current HTTP session {}",
|
||||||
headerName,
|
sessionId);
|
||||||
request.getHeader(headerName));
|
|
||||||
});
|
String authenticationRequestId = saml2AuthenticationRequest.getId();
|
||||||
// Log SAML specific parameters
|
|
||||||
log.debug("SAML Request Parameters:");
|
if (!authenticationRequestId.isBlank()) {
|
||||||
log.debug("SAMLRequest: {}", request.getParameter("SAMLRequest"));
|
authnRequest.setID(authenticationRequestId);
|
||||||
log.debug("RelayState: {}", request.getParameter("RelayState"));
|
} else {
|
||||||
// Log session debugrmation if exists
|
log.warn(
|
||||||
if (request.getSession(false) != null) {
|
"No authentication request found for HTTP session {}. Generating new ID",
|
||||||
log.debug("Session ID: {}", request.getSession().getId());
|
sessionId);
|
||||||
|
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
|
||||||
}
|
}
|
||||||
// Log any assertions consumer service details if present
|
} else {
|
||||||
if (authnRequest.getAssertionConsumerServiceURL() != null) {
|
log.debug("Generating new authentication request ID");
|
||||||
log.debug(
|
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
|
||||||
"AssertionConsumerServiceURL: {}",
|
|
||||||
authnRequest.getAssertionConsumerServiceURL());
|
|
||||||
}
|
|
||||||
// Log NameID policy if present
|
|
||||||
if (authnRequest.getNameIDPolicy() != null) {
|
|
||||||
log.debug(
|
|
||||||
"NameIDPolicy Format: {}",
|
|
||||||
authnRequest.getNameIDPolicy().getFormat());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logAuthnRequestDetails(authnRequest);
|
||||||
|
logHttpRequestDetails(request);
|
||||||
});
|
});
|
||||||
return resolver;
|
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"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
principalName = ((OAuth2User) principal).getName();
|
principalName = ((OAuth2User) principal).getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
principalName = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
principalName = ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
principalName = (String) principal;
|
principalName = (String) principal;
|
||||||
}
|
}
|
||||||
@ -79,7 +79,7 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
principalName = ((OAuth2User) principal).getName();
|
principalName = ((OAuth2User) principal).getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
principalName = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
principalName = ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
principalName = (String) principal;
|
principalName = (String) principal;
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
@ -126,7 +126,7 @@ public class UserController {
|
|||||||
return new RedirectView("/change-creds?messageType=notAuthenticated", true);
|
return new RedirectView("/change-creds?messageType=notAuthenticated", true);
|
||||||
}
|
}
|
||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
|
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
|
||||||
if (userOpt == null || userOpt.isEmpty()) {
|
if (userOpt.isEmpty()) {
|
||||||
return new RedirectView("/change-creds?messageType=userNotFound", true);
|
return new RedirectView("/change-creds?messageType=userNotFound", true);
|
||||||
}
|
}
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
@ -154,7 +154,7 @@ public class UserController {
|
|||||||
return new RedirectView("/account?messageType=notAuthenticated", true);
|
return new RedirectView("/account?messageType=notAuthenticated", true);
|
||||||
}
|
}
|
||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
|
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
|
||||||
if (userOpt == null || userOpt.isEmpty()) {
|
if (userOpt.isEmpty()) {
|
||||||
return new RedirectView("/account?messageType=userNotFound", true);
|
return new RedirectView("/account?messageType=userNotFound", true);
|
||||||
}
|
}
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
@ -176,7 +176,7 @@ public class UserController {
|
|||||||
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
||||||
updates.put(entry.getKey(), entry.getValue()[0]);
|
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
|
// Assuming you have a method in userService to update the settings for a user
|
||||||
userService.updateUserSettings(principal.getName(), updates);
|
userService.updateUserSettings(principal.getName(), updates);
|
||||||
// Redirect to a page of your choice after updating
|
// Redirect to a page of your choice after updating
|
||||||
@ -199,7 +199,7 @@ public class UserController {
|
|||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
||||||
if (userOpt.isPresent()) {
|
if (userOpt.isPresent()) {
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
if (user != null && user.getUsername().equalsIgnoreCase(username)) {
|
if (user.getUsername().equalsIgnoreCase(username)) {
|
||||||
return new RedirectView("/addUsers?messageType=usernameExists", true);
|
return new RedirectView("/addUsers?messageType=usernameExists", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -276,7 +276,7 @@ public class UserController {
|
|||||||
Authentication authentication)
|
Authentication authentication)
|
||||||
throws SQLException, UnsupportedProviderException {
|
throws SQLException, UnsupportedProviderException {
|
||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
||||||
if (!userOpt.isPresent()) {
|
if (userOpt.isEmpty()) {
|
||||||
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
return new RedirectView("/addUsers?messageType=userNotFound", true);
|
||||||
}
|
}
|
||||||
if (!userService.usernameExistsIgnoreCase(username)) {
|
if (!userService.usernameExistsIgnoreCase(username)) {
|
||||||
@ -295,20 +295,20 @@ public class UserController {
|
|||||||
List<Object> principals = sessionRegistry.getAllPrincipals();
|
List<Object> principals = sessionRegistry.getAllPrincipals();
|
||||||
String userNameP = "";
|
String userNameP = "";
|
||||||
for (Object principal : principals) {
|
for (Object principal : principals) {
|
||||||
List<SessionInformation> sessionsInformations =
|
List<SessionInformation> sessionsInformation =
|
||||||
sessionRegistry.getAllSessions(principal, false);
|
sessionRegistry.getAllSessions(principal, false);
|
||||||
if (principal instanceof UserDetails) {
|
if (principal instanceof UserDetails) {
|
||||||
userNameP = ((UserDetails) principal).getUsername();
|
userNameP = ((UserDetails) principal).getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
userNameP = ((OAuth2User) principal).getName();
|
userNameP = ((OAuth2User) principal).getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
userNameP = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
userNameP = ((CustomSaml2AuthenticatedPrincipal) principal).name();
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
userNameP = (String) principal;
|
userNameP = (String) principal;
|
||||||
}
|
}
|
||||||
if (userNameP.equalsIgnoreCase(username)) {
|
if (userNameP.equalsIgnoreCase(username)) {
|
||||||
for (SessionInformation sessionsInformation : sessionsInformations) {
|
for (SessionInformation sessionInfo : sessionsInformation) {
|
||||||
sessionRegistry.expireSession(sessionsInformation.getSessionId());
|
sessionRegistry.expireSession(sessionInfo.getSessionId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
package stirling.software.SPDF.controller.web;
|
package stirling.software.SPDF.controller.web;
|
||||||
|
|
||||||
|
import static stirling.software.SPDF.utils.validation.Validator.validateProvider;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
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 java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
@ -24,12 +31,16 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
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;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
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.GoogleProvider;
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||||
import stirling.software.SPDF.repository.UserRepository;
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
@ -39,12 +50,12 @@ 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;
|
||||||
|
// Assuming you have a repository for user operations
|
||||||
private final UserRepository // Assuming you have a repository for user operations
|
private final UserRepository userRepository;
|
||||||
userRepository;
|
|
||||||
|
|
||||||
public AccountWebController(
|
public AccountWebController(
|
||||||
ApplicationProperties applicationProperties,
|
ApplicationProperties applicationProperties,
|
||||||
@ -61,132 +72,127 @@ public class AccountWebController {
|
|||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
return "redirect:/";
|
return "redirect:/";
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> providerList = new HashMap<>();
|
Map<String, String> providerList = new HashMap<>();
|
||||||
Security securityProps = applicationProperties.getSecurity();
|
Security securityProps = applicationProperties.getSecurity();
|
||||||
OAUTH2 oauth = securityProps.getOauth2();
|
OAUTH2 oauth = securityProps.getOauth2();
|
||||||
|
|
||||||
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());
|
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();
|
Client client = oauth.getClient();
|
||||||
|
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
GoogleProvider google = client.getGoogle();
|
GoogleProvider google = client.getGoogle();
|
||||||
if (google.isSettingsValid()) {
|
|
||||||
|
if (validateProvider(google)) {
|
||||||
providerList.put(
|
providerList.put(
|
||||||
"/oauth2/authorization/" + google.getName(),
|
OAUTH_2_AUTHORIZATION + google.getName(), google.getClientName());
|
||||||
google.getClientName());
|
|
||||||
}
|
}
|
||||||
GithubProvider github = client.getGithub();
|
|
||||||
if (github.isSettingsValid()) {
|
GitHubProvider github = client.getGithub();
|
||||||
|
|
||||||
|
if (validateProvider(github)) {
|
||||||
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 (validateProvider(keycloak)) {
|
||||||
providerList.put(
|
providerList.put(
|
||||||
"/oauth2/authorization/" + keycloak.getName(),
|
OAUTH_2_AUTHORIZATION + keycloak.getName(),
|
||||||
keycloak.getClientName());
|
keycloak.getClientName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SAML2 saml2 = securityProps.getSaml2();
|
SAML2 saml2 = securityProps.getSaml2();
|
||||||
if (securityProps.isSaml2Activ()
|
|
||||||
&& applicationProperties.getSystem().getEnableAlphaFunctionality()) {
|
if (securityProps.isSaml2Active()
|
||||||
providerList.put("/saml2/authenticate/" + saml2.getRegistrationId(), "SAML 2");
|
&& 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
|
// Remove any null keys/values from the providerList
|
||||||
providerList
|
providerList
|
||||||
.entrySet()
|
.entrySet()
|
||||||
.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");
|
||||||
|
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case "badcredentials":
|
case "badCredentials" -> error = "login.invalid";
|
||||||
error = "login.invalid";
|
case "locked" -> error = "login.locked";
|
||||||
break;
|
case "oauth2AuthenticationError" -> error = "userAlreadyExistsOAuthMessage";
|
||||||
case "locked":
|
|
||||||
error = "login.locked";
|
|
||||||
break;
|
|
||||||
case "oauth2AuthenticationError":
|
|
||||||
error = "userAlreadyExistsOAuthMessage";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model.addAttribute("error", error);
|
model.addAttribute("error", error);
|
||||||
}
|
}
|
||||||
String erroroauth = request.getParameter("erroroauth");
|
|
||||||
if (erroroauth != null) {
|
String errorOAuth = request.getParameter("errorOAuth");
|
||||||
switch (erroroauth) {
|
|
||||||
case "oauth2AutoCreateDisabled":
|
if (errorOAuth != null) {
|
||||||
erroroauth = "login.oauth2AutoCreateDisabled";
|
switch (errorOAuth) {
|
||||||
break;
|
case "oAuth2AutoCreateDisabled" -> errorOAuth = "login.oAuth2AutoCreateDisabled";
|
||||||
case "invalidUsername":
|
case "invalidUsername" -> errorOAuth = "login.invalid";
|
||||||
erroroauth = "login.invalid";
|
case "userAlreadyExistsWeb" -> errorOAuth = "userAlreadyExistsWebMessage";
|
||||||
break;
|
case "oAuth2AuthenticationErrorWeb" -> errorOAuth = "login.oauth2InvalidUserType";
|
||||||
case "userAlreadyExistsWeb":
|
case "invalid_token_response" -> errorOAuth = "login.oauth2InvalidTokenResponse";
|
||||||
erroroauth = "userAlreadyExistsWebMessage";
|
case "authorization_request_not_found" ->
|
||||||
break;
|
errorOAuth = "login.oauth2RequestNotFound";
|
||||||
case "oauth2AuthenticationErrorWeb":
|
case "access_denied" -> errorOAuth = "login.oauth2AccessDenied";
|
||||||
erroroauth = "login.oauth2InvalidUserType";
|
case "invalid_user_info_response" ->
|
||||||
break;
|
errorOAuth = "login.oauth2InvalidUserInfoResponse";
|
||||||
case "invalid_token_response":
|
case "invalid_request" -> errorOAuth = "login.oauth2invalidRequest";
|
||||||
erroroauth = "login.oauth2InvalidTokenResponse";
|
case "invalid_id_token" -> errorOAuth = "login.oauth2InvalidIdToken";
|
||||||
break;
|
case "oAuth2AdminBlockedUser" -> errorOAuth = "login.oAuth2AdminBlockedUser";
|
||||||
case "authorization_request_not_found":
|
case "userIsDisabled" -> errorOAuth = "login.userIsDisabled";
|
||||||
erroroauth = "login.oauth2RequestNotFound";
|
case "invalid_destination" -> errorOAuth = "login.invalid_destination";
|
||||||
break;
|
case "relying_party_registration_not_found" ->
|
||||||
case "access_denied":
|
errorOAuth = "login.relyingPartyRegistrationNotFound";
|
||||||
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;
|
|
||||||
// Valid InResponseTo was not available from the validation context, unable to
|
// Valid InResponseTo was not available from the validation context, unable to
|
||||||
// evaluate
|
// evaluate
|
||||||
case "invalid_in_response_to":
|
case "invalid_in_response_to" -> errorOAuth = "login.invalid_in_response_to";
|
||||||
erroroauth = "login.invalid_in_response_to";
|
case "not_authentication_provider_found" ->
|
||||||
break;
|
errorOAuth = "login.not_authentication_provider_found";
|
||||||
case "not_authentication_provider_found":
|
|
||||||
erroroauth = "login.not_authentication_provider_found";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
model.addAttribute("erroroauth", erroroauth);
|
|
||||||
|
model.addAttribute("errorOAuth", errorOAuth);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.getParameter("messageType") != null) {
|
if (request.getParameter("messageType") != null) {
|
||||||
model.addAttribute("messageType", "changedCredsMessage");
|
model.addAttribute("messageType", "changedCredsMessage");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.getParameter("logout") != null) {
|
if (request.getParameter("logout") != null) {
|
||||||
model.addAttribute("logoutMessage", "You have been logged out.");
|
model.addAttribute("logoutMessage", "You have been logged out.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return "login";
|
return "login";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,13 +236,11 @@ public class AccountWebController {
|
|||||||
.plus(maxInactiveInterval, ChronoUnit.SECONDS);
|
.plus(maxInactiveInterval, ChronoUnit.SECONDS);
|
||||||
if (now.isAfter(expirationTime)) {
|
if (now.isAfter(expirationTime)) {
|
||||||
sessionPersistentRegistry.expireSession(sessionEntity.getSessionId());
|
sessionPersistentRegistry.expireSession(sessionEntity.getSessionId());
|
||||||
hasActiveSession = false;
|
|
||||||
} else {
|
} else {
|
||||||
hasActiveSession = !sessionEntity.isExpired();
|
hasActiveSession = !sessionEntity.isExpired();
|
||||||
}
|
}
|
||||||
lastRequest = sessionEntity.getLastRequest();
|
lastRequest = sessionEntity.getLastRequest();
|
||||||
} else {
|
} else {
|
||||||
hasActiveSession = false;
|
|
||||||
// No session, set default last request time
|
// No session, set default last request time
|
||||||
lastRequest = new Date(0);
|
lastRequest = new Date(0);
|
||||||
}
|
}
|
||||||
@ -273,53 +277,41 @@ public class AccountWebController {
|
|||||||
})
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
String messageType = request.getParameter("messageType");
|
String messageType = request.getParameter("messageType");
|
||||||
String deleteMessage = null;
|
|
||||||
|
String deleteMessage;
|
||||||
if (messageType != null) {
|
if (messageType != null) {
|
||||||
|
deleteMessage =
|
||||||
switch (messageType) {
|
switch (messageType) {
|
||||||
case "deleteCurrentUser":
|
case "deleteCurrentUser" -> "deleteCurrentUserMessage";
|
||||||
deleteMessage = "deleteCurrentUserMessage";
|
case "deleteUsernameExists" -> "deleteUsernameExistsMessage";
|
||||||
break;
|
default -> null;
|
||||||
case "deleteUsernameExists":
|
};
|
||||||
deleteMessage = "deleteUsernameExistsMessage";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
model.addAttribute("deleteMessage", deleteMessage);
|
model.addAttribute("deleteMessage", deleteMessage);
|
||||||
String addMessage = null;
|
|
||||||
|
String addMessage;
|
||||||
|
addMessage =
|
||||||
switch (messageType) {
|
switch (messageType) {
|
||||||
case "usernameExists":
|
case "usernameExists" -> "usernameExistsMessage";
|
||||||
addMessage = "usernameExistsMessage";
|
case "invalidUsername" -> "invalidUsernameMessage";
|
||||||
break;
|
case "invalidPassword" -> "invalidPasswordMessage";
|
||||||
case "invalidUsername":
|
default -> null;
|
||||||
addMessage = "invalidUsernameMessage";
|
};
|
||||||
break;
|
|
||||||
case "invalidPassword":
|
|
||||||
addMessage = "invalidPasswordMessage";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
model.addAttribute("addMessage", addMessage);
|
model.addAttribute("addMessage", addMessage);
|
||||||
}
|
}
|
||||||
String changeMessage = null;
|
|
||||||
|
String changeMessage;
|
||||||
if (messageType != null) {
|
if (messageType != null) {
|
||||||
|
changeMessage =
|
||||||
switch (messageType) {
|
switch (messageType) {
|
||||||
case "userNotFound":
|
case "userNotFound" -> "userNotFoundMessage";
|
||||||
changeMessage = "userNotFoundMessage";
|
case "downgradeCurrentUser" -> "downgradeCurrentUserMessage";
|
||||||
break;
|
case "disabledCurrentUser" -> "disabledCurrentUserMessage";
|
||||||
case "downgradeCurrentUser":
|
default -> messageType;
|
||||||
changeMessage = "downgradeCurrentUserMessage";
|
};
|
||||||
break;
|
|
||||||
case "disabledCurrentUser":
|
|
||||||
changeMessage = "disabledCurrentUserMessage";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
changeMessage = messageType;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
model.addAttribute("changeMessage", changeMessage);
|
model.addAttribute("changeMessage", changeMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
model.addAttribute("users", sortedUsers);
|
model.addAttribute("users", sortedUsers);
|
||||||
model.addAttribute("currentUsername", authentication.getName());
|
model.addAttribute("currentUsername", authentication.getName());
|
||||||
model.addAttribute("roleDetails", roleDetails);
|
model.addAttribute("roleDetails", roleDetails);
|
||||||
@ -340,78 +332,51 @@ public class AccountWebController {
|
|||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
String username = null;
|
String username = null;
|
||||||
if (principal instanceof UserDetails) {
|
|
||||||
// Cast the principal object to UserDetails
|
// Retrieve username and other attributes and add login attributes to the model
|
||||||
UserDetails userDetails = (UserDetails) principal;
|
if (principal instanceof UserDetails userDetails) {
|
||||||
// Retrieve username and other attributes
|
|
||||||
username = userDetails.getUsername();
|
username = userDetails.getUsername();
|
||||||
// Add oAuth2 Login attributes to the model
|
|
||||||
model.addAttribute("oAuth2Login", false);
|
model.addAttribute("oAuth2Login", false);
|
||||||
}
|
}
|
||||||
if (principal instanceof OAuth2User) {
|
if (principal instanceof OAuth2User userDetails) {
|
||||||
// Cast the principal object to OAuth2User
|
username = userDetails.getName();
|
||||||
OAuth2User userDetails = (OAuth2User) principal;
|
|
||||||
// Retrieve username and other attributes
|
|
||||||
username =
|
|
||||||
userDetails.getAttribute(
|
|
||||||
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
|
|
||||||
// Add oAuth2 Login attributes to the model
|
|
||||||
model.addAttribute("oAuth2Login", true);
|
model.addAttribute("oAuth2Login", true);
|
||||||
}
|
}
|
||||||
if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
if (principal instanceof CustomSaml2AuthenticatedPrincipal userDetails) {
|
||||||
// Cast the principal object to OAuth2User
|
username = userDetails.name();
|
||||||
CustomSaml2AuthenticatedPrincipal userDetails =
|
model.addAttribute("saml2Login", true);
|
||||||
(CustomSaml2AuthenticatedPrincipal) principal;
|
|
||||||
// Retrieve username and other attributes
|
|
||||||
username = userDetails.getName();
|
|
||||||
// Add oAuth2 Login attributes to the model
|
|
||||||
model.addAttribute("oAuth2Login", true);
|
|
||||||
}
|
}
|
||||||
if (username != null) {
|
if (username != null) {
|
||||||
// Fetch user details from the database
|
// Fetch user details from the database
|
||||||
Optional<User> user =
|
Optional<User> user = userRepository.findByUsernameIgnoreCaseWithSettings(username);
|
||||||
userRepository
|
|
||||||
.findByUsernameIgnoreCaseWithSettings( // Assuming findByUsername
|
if (user.isEmpty()) {
|
||||||
// method exists
|
|
||||||
username);
|
|
||||||
if (!user.isPresent()) {
|
|
||||||
return "redirect:/error";
|
return "redirect:/error";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert settings map to JSON string
|
// Convert settings map to JSON string
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
String settingsJson;
|
String settingsJson;
|
||||||
try {
|
try {
|
||||||
settingsJson = objectMapper.writeValueAsString(user.get().getSettings());
|
settingsJson = objectMapper.writeValueAsString(user.get().getSettings());
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonProcessingException e) {
|
||||||
// Handle JSON conversion error
|
log.error("Error converting settings map", e);
|
||||||
log.error("exception", e);
|
|
||||||
return "redirect:/error";
|
return "redirect:/error";
|
||||||
}
|
}
|
||||||
|
|
||||||
String messageType = request.getParameter("messageType");
|
String messageType = request.getParameter("messageType");
|
||||||
if (messageType != null) {
|
if (messageType != null) {
|
||||||
switch (messageType) {
|
switch (messageType) {
|
||||||
case "notAuthenticated":
|
case "notAuthenticated" -> messageType = "notAuthenticatedMessage";
|
||||||
messageType = "notAuthenticatedMessage";
|
case "userNotFound" -> messageType = "userNotFoundMessage";
|
||||||
break;
|
case "incorrectPassword" -> messageType = "incorrectPasswordMessage";
|
||||||
case "userNotFound":
|
case "usernameExists" -> messageType = "usernameExistsMessage";
|
||||||
messageType = "userNotFoundMessage";
|
case "invalidUsername" -> messageType = "invalidUsernameMessage";
|
||||||
break;
|
|
||||||
case "incorrectPassword":
|
|
||||||
messageType = "incorrectPasswordMessage";
|
|
||||||
break;
|
|
||||||
case "usernameExists":
|
|
||||||
messageType = "usernameExistsMessage";
|
|
||||||
break;
|
|
||||||
case "invalidUsername":
|
|
||||||
messageType = "invalidUsernameMessage";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
model.addAttribute("messageType", messageType);
|
|
||||||
}
|
}
|
||||||
// Add attributes to the model
|
|
||||||
model.addAttribute("username", username);
|
model.addAttribute("username", username);
|
||||||
|
model.addAttribute("messageType", messageType);
|
||||||
model.addAttribute("role", user.get().getRolesAsString());
|
model.addAttribute("role", user.get().getRolesAsString());
|
||||||
model.addAttribute("settings", settingsJson);
|
model.addAttribute("settings", settingsJson);
|
||||||
model.addAttribute("changeCredsFlag", user.get().isFirstLogin());
|
model.addAttribute("changeCredsFlag", user.get().isFirstLogin());
|
||||||
@ -432,19 +397,12 @@ public class AccountWebController {
|
|||||||
}
|
}
|
||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
if (principal instanceof UserDetails) {
|
if (principal instanceof UserDetails userDetails) {
|
||||||
// Cast the principal object to UserDetails
|
|
||||||
UserDetails userDetails = (UserDetails) principal;
|
|
||||||
// Retrieve username and other attributes
|
|
||||||
String username = userDetails.getUsername();
|
String username = userDetails.getUsername();
|
||||||
// Fetch user details from the database
|
// Fetch user details from the database
|
||||||
Optional<User> user =
|
Optional<User> user = userRepository.findByUsernameIgnoreCase(username);
|
||||||
userRepository
|
if (user.isEmpty()) {
|
||||||
.findByUsernameIgnoreCase( // Assuming findByUsername method exists
|
// Handle error appropriately, example redirection in case of error
|
||||||
username);
|
|
||||||
if (!user.isPresent()) {
|
|
||||||
// Handle error appropriately
|
|
||||||
// Example redirection in case of error
|
|
||||||
return "redirect:/error";
|
return "redirect:/error";
|
||||||
}
|
}
|
||||||
String messageType = request.getParameter("messageType");
|
String messageType = request.getParameter("messageType");
|
||||||
@ -467,7 +425,7 @@ public class AccountWebController {
|
|||||||
}
|
}
|
||||||
model.addAttribute("messageType", messageType);
|
model.addAttribute("messageType", messageType);
|
||||||
}
|
}
|
||||||
// Add attributes to the model
|
|
||||||
model.addAttribute("username", username);
|
model.addAttribute("username", username);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.model;
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
|
import static stirling.software.SPDF.utils.validation.Validator.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -12,7 +14,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@ -34,10 +35,11 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import stirling.software.SPDF.config.InstallationPathConfig;
|
import stirling.software.SPDF.config.InstallationPathConfig;
|
||||||
import stirling.software.SPDF.config.YamlPropertySourceFactory;
|
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.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 = "")
|
||||||
@ -136,13 +138,13 @@ public class ApplicationProperties {
|
|||||||
|| loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString()));
|
|| loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOauth2Activ() {
|
public boolean isOauth2Active() {
|
||||||
return (oauth2 != null
|
return (oauth2 != null
|
||||||
&& oauth2.getEnabled()
|
&& oauth2.getEnabled()
|
||||||
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
|
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSaml2Activ() {
|
public boolean isSaml2Active() {
|
||||||
return (saml2 != null
|
return (saml2 != null
|
||||||
&& saml2.getEnabled()
|
&& saml2.getEnabled()
|
||||||
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
|
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
|
||||||
@ -158,6 +160,7 @@ public class ApplicationProperties {
|
|||||||
@Setter
|
@Setter
|
||||||
@ToString
|
@ToString
|
||||||
public static class SAML2 {
|
public static class SAML2 {
|
||||||
|
private String provider;
|
||||||
private Boolean enabled = false;
|
private Boolean enabled = false;
|
||||||
private Boolean autoCreateUser = false;
|
private Boolean autoCreateUser = false;
|
||||||
private Boolean blockRegistration = false;
|
private Boolean blockRegistration = false;
|
||||||
@ -195,7 +198,7 @@ public class ApplicationProperties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Resource getidpCert() {
|
public Resource getIdpCert() {
|
||||||
if (idpCert == null) return null;
|
if (idpCert == null) return null;
|
||||||
if (idpCert.startsWith("classpath:")) {
|
if (idpCert.startsWith("classpath:")) {
|
||||||
return new ClassPathResource(idpCert.substring("classpath:".length()));
|
return new ClassPathResource(idpCert.substring("classpath:".length()));
|
||||||
@ -225,12 +228,11 @@ public class ApplicationProperties {
|
|||||||
private Collection<String> scopes = new ArrayList<>();
|
private Collection<String> scopes = new ArrayList<>();
|
||||||
private String provider;
|
private String provider;
|
||||||
private Client client = new Client();
|
private Client client = new Client();
|
||||||
|
private String logoutUrl;
|
||||||
|
|
||||||
public void setScopes(String scopes) {
|
public void setScopes(String scopes) {
|
||||||
List<String> scopesList =
|
List<String> scopesList =
|
||||||
Arrays.stream(scopes.split(","))
|
Arrays.stream(scopes.split(",")).map(String::trim).toList();
|
||||||
.map(String::trim)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
this.scopes.addAll(scopesList);
|
this.scopes.addAll(scopesList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,32 +245,31 @@ public class ApplicationProperties {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSettingsValid() {
|
public boolean isSettingsValid() {
|
||||||
return isValid(this.getIssuer(), "issuer")
|
return !isStringEmpty(this.getIssuer())
|
||||||
&& isValid(this.getClientId(), "clientId")
|
&& !isStringEmpty(this.getClientId())
|
||||||
&& isValid(this.getClientSecret(), "clientSecret")
|
&& !isStringEmpty(this.getClientSecret())
|
||||||
&& isValid(this.getScopes(), "scopes")
|
&& !isCollectionEmpty(this.getScopes())
|
||||||
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
&& !isStringEmpty(this.getUseAsUsername());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class Client {
|
public static class Client {
|
||||||
private GoogleProvider google = new GoogleProvider();
|
private GoogleProvider google = new GoogleProvider();
|
||||||
private GithubProvider github = new GithubProvider();
|
private GitHubProvider github = new GitHubProvider();
|
||||||
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 ->
|
||||||
case "keycloak":
|
|
||||||
return getKeycloak();
|
|
||||||
default:
|
|
||||||
throw new UnsupportedProviderException(
|
throw new UnsupportedProviderException(
|
||||||
"Logout from the provider is not supported? Report it at"
|
"Logout from the provider "
|
||||||
+ " https://github.com/Stirling-Tools/Stirling-PDF/issues");
|
+ registrationId
|
||||||
}
|
+ " is not supported. "
|
||||||
|
+ "Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues");
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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'");
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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) {
|
@ -0,0 +1,7 @@
|
|||||||
|
package stirling.software.SPDF.model.exception;
|
||||||
|
|
||||||
|
public class UnsupportedUsernameAttribute extends RuntimeException {
|
||||||
|
public UnsupportedUsernameAttribute(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -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<String> 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<String> getScopes() {
|
||||||
|
Collection<String> 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()
|
||||||
|
+ "]";
|
||||||
|
}
|
||||||
|
}
|
@ -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<String> 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<String> 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");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,116 +1,85 @@
|
|||||||
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;
|
||||||
|
import stirling.software.SPDF.model.UsernameAttribute;
|
||||||
|
|
||||||
|
@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 clientSecret;
|
|
||||||
private Collection<String> scopes = new ArrayList<>();
|
|
||||||
private String useAsUsername = "email";
|
|
||||||
|
|
||||||
public String getAuthorizationuri() {
|
public GoogleProvider(
|
||||||
return authorizationUri;
|
String clientId,
|
||||||
|
String clientSecret,
|
||||||
|
Collection<String> scopes,
|
||||||
|
UsernameAttribute useAsUsername) {
|
||||||
|
super(
|
||||||
|
null,
|
||||||
|
NAME,
|
||||||
|
CLIENT_NAME,
|
||||||
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
scopes,
|
||||||
|
useAsUsername,
|
||||||
|
null,
|
||||||
|
AUTHORIZATION_URI,
|
||||||
|
TOKEN_URI,
|
||||||
|
USER_INFO_URI);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTokenuri() {
|
public String getAuthorizationUri() {
|
||||||
return tokenUri;
|
return AUTHORIZATION_URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUserinfouri() {
|
public String getTokenUri() {
|
||||||
return userInfoUri;
|
return TOKEN_URI;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserinfoUri() {
|
||||||
|
return USER_INFO_URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getIssuer() {
|
public String getName() {
|
||||||
return new String();
|
return NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setIssuer(String issuer) {}
|
public String getClientName() {
|
||||||
|
return CLIENT_NAME;
|
||||||
@Override
|
|
||||||
public String getClientId() {
|
|
||||||
return this.clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setClientId(String clientId) {
|
|
||||||
this.clientId = clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getClientSecret() {
|
|
||||||
return this.clientSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setClientSecret(String clientSecret) {
|
|
||||||
this.clientSecret = clientSecret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<String> getScopes() {
|
public Collection<String> getScopes() {
|
||||||
|
Collection<String> scopes = super.getScopes();
|
||||||
|
|
||||||
if (scopes == null || scopes.isEmpty()) {
|
if (scopes == null || scopes.isEmpty()) {
|
||||||
scopes = new ArrayList<>();
|
scopes = new ArrayList<>();
|
||||||
scopes.add("https://www.googleapis.com/auth/userinfo.email");
|
scopes.add("https://www.googleapis.com/auth/userinfo.email");
|
||||||
scopes.add("https://www.googleapis.com/auth/userinfo.profile");
|
scopes.add("https://www.googleapis.com/auth/userinfo.profile");
|
||||||
}
|
}
|
||||||
|
|
||||||
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="
|
||||||
+ clientId
|
+ getClientId()
|
||||||
+ ", clientSecret="
|
+ ", clientSecret="
|
||||||
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
+ (getClientSecret() != null && !getClientSecret().isEmpty() ? "*****" : "NULL")
|
||||||
+ ", scopes="
|
+ ", scopes="
|
||||||
+ scopes
|
+ getScopes()
|
||||||
+ ", useAsUsername="
|
+ ", 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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,106 +1,72 @@
|
|||||||
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;
|
||||||
|
import stirling.software.SPDF.model.UsernameAttribute;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
public class KeycloakProvider extends Provider {
|
public class KeycloakProvider extends Provider {
|
||||||
|
|
||||||
private String issuer;
|
private static final String NAME = "keycloak";
|
||||||
private String clientId;
|
private static final String CLIENT_NAME = "Keycloak";
|
||||||
private String clientSecret;
|
|
||||||
private Collection<String> scopes = new ArrayList<>();
|
|
||||||
private String useAsUsername = "email";
|
|
||||||
|
|
||||||
@Override
|
public KeycloakProvider(
|
||||||
public String getIssuer() {
|
String issuer,
|
||||||
return this.issuer;
|
String clientId,
|
||||||
|
String clientSecret,
|
||||||
|
Collection<String> scopes,
|
||||||
|
UsernameAttribute useAsUsername) {
|
||||||
|
super(
|
||||||
|
issuer,
|
||||||
|
NAME,
|
||||||
|
CLIENT_NAME,
|
||||||
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
scopes,
|
||||||
|
useAsUsername,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setIssuer(String issuer) {
|
public String getName() {
|
||||||
this.issuer = issuer;
|
return NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getClientId() {
|
public String getClientName() {
|
||||||
return this.clientId;
|
return CLIENT_NAME;
|
||||||
}
|
|
||||||
|
|
||||||
@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
|
@Override
|
||||||
public Collection<String> getScopes() {
|
public Collection<String> getScopes() {
|
||||||
|
Collection<String> 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="
|
||||||
+ issuer
|
+ getIssuer()
|
||||||
+ ", clientId="
|
+ ", clientId="
|
||||||
+ clientId
|
+ getClientId()
|
||||||
+ ", clientSecret="
|
+ ", clientSecret="
|
||||||
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
+ (getClientSecret() != null && !getClientSecret().isBlank() ? "*****" : "NULL")
|
||||||
+ ", scopes="
|
+ ", scopes="
|
||||||
+ scopes
|
+ getScopes()
|
||||||
+ ", useAsUsername="
|
+ ", 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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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<String> 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<String> 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()
|
||||||
|
+ "]";
|
||||||
|
}
|
||||||
|
}
|
@ -7,8 +7,6 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
|
|
||||||
public class UrlUtils {
|
public class UrlUtils {
|
||||||
|
|
||||||
private UrlUtils() {}
|
|
||||||
|
|
||||||
public static String getOrigin(HttpServletRequest request) {
|
public static String getOrigin(HttpServletRequest request) {
|
||||||
String scheme = request.getScheme(); // http or https
|
String scheme = request.getScheme(); // http or https
|
||||||
String serverName = request.getServerName(); // localhost
|
String serverName = request.getServerName(); // localhost
|
||||||
|
@ -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<String> input) {
|
||||||
|
return input == null || input.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
@ -572,8 +572,8 @@ login.invalid=اسم المستخدم أو كلمة المرور غير صالح
|
|||||||
login.locked=تم قفل حسابك.
|
login.locked=تم قفل حسابك.
|
||||||
login.signinTitle=الرجاء تسجيل الدخول
|
login.signinTitle=الرجاء تسجيل الدخول
|
||||||
login.ssoSignIn=تسجيل الدخول عبر تسجيل الدخول الأحادي
|
login.ssoSignIn=تسجيل الدخول عبر تسجيل الدخول الأحادي
|
||||||
login.oauth2AutoCreateDisabled=تم تعطيل الإنشاء التلقائي لمستخدم OAuth2
|
login.oAuth2AutoCreateDisabled=تم تعطيل الإنشاء التلقائي لمستخدم OAuth2
|
||||||
login.oauth2AdminBlockedUser=تم حظر تسجيل أو تسجيل دخول المستخدمين غير المسجلين حاليًا. يرجى الاتصال بالمسؤول.
|
login.oAuth2AdminBlockedUser=تم حظر تسجيل أو تسجيل دخول المستخدمين غير المسجلين حاليًا. يرجى الاتصال بالمسؤول.
|
||||||
login.oauth2RequestNotFound=لم يتم العثور على طلب التفويض
|
login.oauth2RequestNotFound=لم يتم العثور على طلب التفويض
|
||||||
login.oauth2InvalidUserInfoResponse=استجابة معلومات المستخدم غير صالحة
|
login.oauth2InvalidUserInfoResponse=استجابة معلومات المستخدم غير صالحة
|
||||||
login.oauth2invalidRequest=طلب غير صالح
|
login.oauth2invalidRequest=طلب غير صالح
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Etibarsız istifadəçi adı və ya şifr.
|
|||||||
login.locked=Sizin hesabınız kilidlənmişdir.
|
login.locked=Sizin hesabınız kilidlənmişdir.
|
||||||
login.signinTitle=Zəhmət olmasa, daxil olun
|
login.signinTitle=Zəhmət olmasa, daxil olun
|
||||||
login.ssoSignIn=Single Sign-on vasitəsilə daxil olun
|
login.ssoSignIn=Single Sign-on vasitəsilə daxil olun
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create İstifadəçisi Deaktivləşdirilmişdir
|
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.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.oauth2RequestNotFound=Təsdiqlənmə sorğusu tapılmadı
|
||||||
login.oauth2InvalidUserInfoResponse=Yanlış İstifadəçi Məlumatı Cavabı
|
login.oauth2InvalidUserInfoResponse=Yanlış İstifadəçi Məlumatı Cavabı
|
||||||
login.oauth2invalidRequest=Etibarsız Sorğu
|
login.oauth2invalidRequest=Etibarsız Sorğu
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Невалидно потребителско име или пар
|
|||||||
login.locked=Вашият акаунт е заключен.
|
login.locked=Вашият акаунт е заключен.
|
||||||
login.signinTitle=Моля впишете се
|
login.signinTitle=Моля впишете се
|
||||||
login.ssoSignIn=Влизане чрез еднократно влизане
|
login.ssoSignIn=Влизане чрез еднократно влизане
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано
|
login.oAuth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано
|
||||||
login.oauth2AdminBlockedUser=Регистрацията или влизането на нерегистрирани потребители в момента е блокирано. Моля, свържете се с администратора.
|
login.oAuth2AdminBlockedUser=Регистрацията или влизането на нерегистрирани потребители в момента е блокирано. Моля, свържете се с администратора.
|
||||||
login.oauth2RequestNotFound=Заявката за оторизация не е намерена
|
login.oauth2RequestNotFound=Заявката за оторизация не е намерена
|
||||||
login.oauth2InvalidUserInfoResponse=Невалидна информация за потребителя
|
login.oauth2InvalidUserInfoResponse=Невалидна информация за потребителя
|
||||||
login.oauth2invalidRequest=Невалидна заявка
|
login.oauth2invalidRequest=Невалидна заявка
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Nom d'usuari/contrasenya no vàlid
|
|||||||
login.locked=Compte bloquejat
|
login.locked=Compte bloquejat
|
||||||
login.signinTitle=Autenticat
|
login.signinTitle=Autenticat
|
||||||
login.ssoSignIn=Inicia sessió mitjançant inici de sessió únic
|
login.ssoSignIn=Inicia sessió mitjançant inici de sessió únic
|
||||||
login.oauth2AutoCreateDisabled=La creació automàtica d'usuaris OAUTH2 està desactivada
|
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.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.oauth2RequestNotFound=Sol·licitud d'autorització no trobada
|
||||||
login.oauth2InvalidUserInfoResponse=Resposta d'informació d'usuari no vàlida
|
login.oauth2InvalidUserInfoResponse=Resposta d'informació d'usuari no vàlida
|
||||||
login.oauth2invalidRequest=Sol·licitud no vàlida
|
login.oauth2invalidRequest=Sol·licitud no vàlida
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Neplatné uživatelské jméno nebo heslo.
|
|||||||
login.locked=Váš účet byl uzamčen.
|
login.locked=Váš účet byl uzamčen.
|
||||||
login.signinTitle=Prosím přihlaste se
|
login.signinTitle=Prosím přihlaste se
|
||||||
login.ssoSignIn=Přihlásit se přes Single Sign-on
|
login.ssoSignIn=Přihlásit se přes Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=Automatické vytváření OAUTH2 uživatelů je zakázáno
|
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.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.oauth2RequestNotFound=Požadavek na autorizaci nebyl nalezen
|
||||||
login.oauth2InvalidUserInfoResponse=Neplatná odpověď s informacemi o uživateli
|
login.oauth2InvalidUserInfoResponse=Neplatná odpověď s informacemi o uživateli
|
||||||
login.oauth2invalidRequest=Neplatný požadavek
|
login.oauth2invalidRequest=Neplatný požadavek
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Ugyldigt brugernavn eller adgangskode.
|
|||||||
login.locked=Din konto er blevet låst.
|
login.locked=Din konto er blevet låst.
|
||||||
login.signinTitle=Log venligst ind
|
login.signinTitle=Log venligst ind
|
||||||
login.ssoSignIn=Log ind via Single Sign-on
|
login.ssoSignIn=Log ind via Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Opret Bruger Deaktiveret
|
login.oAuth2AutoCreateDisabled=OAUTH2 Auto-Opret Bruger Deaktiveret
|
||||||
login.oauth2AdminBlockedUser=Registrering eller login af ikke-registrerede brugere er i øjeblikket blokeret. Kontakt venligst administratoren.
|
login.oAuth2AdminBlockedUser=Registrering eller login af ikke-registrerede brugere er i øjeblikket blokeret. Kontakt venligst administratoren.
|
||||||
login.oauth2RequestNotFound=Autorisationsanmodning ikke fundet
|
login.oauth2RequestNotFound=Autorisationsanmodning ikke fundet
|
||||||
login.oauth2InvalidUserInfoResponse=Ugyldigt Brugerinfo Svar
|
login.oauth2InvalidUserInfoResponse=Ugyldigt Brugerinfo Svar
|
||||||
login.oauth2invalidRequest=Ugyldig Anmodning
|
login.oauth2invalidRequest=Ugyldig Anmodning
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Benutzername oder Passwort ungültig.
|
|||||||
login.locked=Ihr Konto wurde gesperrt.
|
login.locked=Ihr Konto wurde gesperrt.
|
||||||
login.signinTitle=Bitte melden Sie sich an.
|
login.signinTitle=Bitte melden Sie sich an.
|
||||||
login.ssoSignIn=Anmeldung per Single Sign-On
|
login.ssoSignIn=Anmeldung per Single Sign-On
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Benutzer automatisch erstellen deaktiviert
|
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.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.oauth2RequestNotFound=Autorisierungsanfrage nicht gefunden
|
||||||
login.oauth2InvalidUserInfoResponse=Ungültige Benutzerinformationsantwort
|
login.oauth2InvalidUserInfoResponse=Ungültige Benutzerinformationsantwort
|
||||||
login.oauth2invalidRequest=ungültige Anfrage
|
login.oauth2invalidRequest=ungültige Anfrage
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Μη έγκυρο όνομα χρήστη ή κωδικός.
|
|||||||
login.locked=Ο λογαριασμός σας έχει κλειδωθεί.
|
login.locked=Ο λογαριασμός σας έχει κλειδωθεί.
|
||||||
login.signinTitle=Παρακαλώ συνδεθείτε
|
login.signinTitle=Παρακαλώ συνδεθείτε
|
||||||
login.ssoSignIn=Σύνδεση μέσω Single Sign-on
|
login.ssoSignIn=Σύνδεση μέσω Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=Η αυτόματη δημιουργία χρήστη OAUTH2 είναι απενεργοποιημένη
|
login.oAuth2AutoCreateDisabled=Η αυτόματη δημιουργία χρήστη OAUTH2 είναι απενεργοποιημένη
|
||||||
login.oauth2AdminBlockedUser=Η εγγραφή ή σύνδεση μη εγγεγραμμένων χρηστών είναι προς το παρόν αποκλεισμένη. Παρακαλώ επικοινωνήστε με τον διαχειριστή.
|
login.oAuth2AdminBlockedUser=Η εγγραφή ή σύνδεση μη εγγεγραμμένων χρηστών είναι προς το παρόν αποκλεισμένη. Παρακαλώ επικοινωνήστε με τον διαχειριστή.
|
||||||
login.oauth2RequestNotFound=Το αίτημα εξουσιοδότησης δεν βρέθηκε
|
login.oauth2RequestNotFound=Το αίτημα εξουσιοδότησης δεν βρέθηκε
|
||||||
login.oauth2InvalidUserInfoResponse=Μη έγκυρη απόκριση πληροφοριών χρήστη
|
login.oauth2InvalidUserInfoResponse=Μη έγκυρη απόκριση πληροφοριών χρήστη
|
||||||
login.oauth2invalidRequest=Μη έγκυρο αίτημα
|
login.oauth2invalidRequest=Μη έγκυρο αίτημα
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Invalid username or password.
|
|||||||
login.locked=Your account has been locked.
|
login.locked=Your account has been locked.
|
||||||
login.signinTitle=Please sign in
|
login.signinTitle=Please sign in
|
||||||
login.ssoSignIn=Login via Single Sign-on
|
login.ssoSignIn=Login via Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
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.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Authorization request not found
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
login.oauth2invalidRequest=Invalid Request
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Invalid username or password.
|
|||||||
login.locked=Your account has been locked.
|
login.locked=Your account has been locked.
|
||||||
login.signinTitle=Please sign in
|
login.signinTitle=Please sign in
|
||||||
login.ssoSignIn=Login via Single Sign-on
|
login.ssoSignIn=Login via Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
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.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Authorization request not found
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
login.oauth2invalidRequest=Invalid Request
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Nombre de usuario o contraseña erróneos.
|
|||||||
login.locked=Su cuenta se ha bloqueado.
|
login.locked=Su cuenta se ha bloqueado.
|
||||||
login.signinTitle=Por favor, inicie sesión
|
login.signinTitle=Por favor, inicie sesión
|
||||||
login.ssoSignIn=Iniciar sesión a través del inicio de sesión único
|
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.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.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.oauth2RequestNotFound=Solicitud de autorización no encontrada
|
||||||
login.oauth2InvalidUserInfoResponse=Respuesta de información de usuario no válida
|
login.oauth2InvalidUserInfoResponse=Respuesta de información de usuario no válida
|
||||||
login.oauth2invalidRequest=Solicitud no válida
|
login.oauth2invalidRequest=Solicitud no válida
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Okerreko erabiltzaile izena edo pasahitza.
|
|||||||
login.locked=Zure kontua blokeatu egin da.
|
login.locked=Zure kontua blokeatu egin da.
|
||||||
login.signinTitle=Mesedez, hasi saioa
|
login.signinTitle=Mesedez, hasi saioa
|
||||||
login.ssoSignIn=Hasi saioa Saioa hasteko modu bakarraren bidez
|
login.ssoSignIn=Hasi saioa Saioa hasteko modu bakarraren bidez
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Sortu automatikoki erabiltzailea desgaituta dago
|
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.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Authorization request not found
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
login.oauth2invalidRequest=Invalid Request
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=نام کاربری یا رمز عبور اشتباه است.
|
|||||||
login.locked=حساب شما قفل شده است.
|
login.locked=حساب شما قفل شده است.
|
||||||
login.signinTitle=لطفاً وارد شوید
|
login.signinTitle=لطفاً وارد شوید
|
||||||
login.ssoSignIn=ورود از طریق Single Sign-on
|
login.ssoSignIn=ورود از طریق Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=ایجاد خودکار کاربر با OAUTH2 غیرفعال است
|
login.oAuth2AutoCreateDisabled=ایجاد خودکار کاربر با OAUTH2 غیرفعال است
|
||||||
login.oauth2AdminBlockedUser=ثبتنام یا ورود کاربران ثبتنشده در حال حاضر مسدود است. لطفاً با مدیر تماس بگیرید.
|
login.oAuth2AdminBlockedUser=ثبتنام یا ورود کاربران ثبتنشده در حال حاضر مسدود است. لطفاً با مدیر تماس بگیرید.
|
||||||
login.oauth2RequestNotFound=درخواست احراز هویت پیدا نشد
|
login.oauth2RequestNotFound=درخواست احراز هویت پیدا نشد
|
||||||
login.oauth2InvalidUserInfoResponse=پاسخ اطلاعات کاربری نامعتبر است
|
login.oauth2InvalidUserInfoResponse=پاسخ اطلاعات کاربری نامعتبر است
|
||||||
login.oauth2invalidRequest=درخواست نامعتبر
|
login.oauth2invalidRequest=درخواست نامعتبر
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Nom d'utilisateur ou mot de passe invalide.
|
|||||||
login.locked=Votre compte a été verrouillé.
|
login.locked=Votre compte a été verrouillé.
|
||||||
login.signinTitle=Veuillez vous connecter
|
login.signinTitle=Veuillez vous connecter
|
||||||
login.ssoSignIn=Se connecter via l'authentification unique
|
login.ssoSignIn=Se connecter via l'authentification unique
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Création automatique d'utilisateur désactivée
|
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.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.oauth2RequestNotFound=Demande d'autorisation introuvable
|
||||||
login.oauth2InvalidUserInfoResponse=Réponse contenant les informations de l'utilisateur est invalide
|
login.oauth2InvalidUserInfoResponse=Réponse contenant les informations de l'utilisateur est invalide
|
||||||
login.oauth2invalidRequest=Requête invalide
|
login.oauth2invalidRequest=Requête invalide
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=अमान्य उपयोगकर्ता नाम या
|
|||||||
login.locked=आपका खाता लॉक कर दिया गया है।
|
login.locked=आपका खाता लॉक कर दिया गया है।
|
||||||
login.signinTitle=कृपया साइन इन करें
|
login.signinTitle=कृपया साइन इन करें
|
||||||
login.ssoSignIn=सिंगल साइन-ऑन के माध्यम से लॉगिन करें
|
login.ssoSignIn=सिंगल साइन-ऑन के माध्यम से लॉगिन करें
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 स्वतः उपयोगकर्ता निर्माण अक्षम है
|
login.oAuth2AutoCreateDisabled=OAUTH2 स्वतः उपयोगकर्ता निर्माण अक्षम है
|
||||||
login.oauth2AdminBlockedUser=गैर-पंजीकृत उपयोगकर्ताओं का पंजीकरण या लॉगिन वर्तमान में अवरुद्ध है। कृपया व्यवस्थापक से संपर्क करें।
|
login.oAuth2AdminBlockedUser=गैर-पंजीकृत उपयोगकर्ताओं का पंजीकरण या लॉगिन वर्तमान में अवरुद्ध है। कृपया व्यवस्थापक से संपर्क करें।
|
||||||
login.oauth2RequestNotFound=प्राधिकरण अनुरोध नहीं मिला
|
login.oauth2RequestNotFound=प्राधिकरण अनुरोध नहीं मिला
|
||||||
login.oauth2InvalidUserInfoResponse=अमान्य उपयोगकर्ता जानकारी प्रतिक्रिया
|
login.oauth2InvalidUserInfoResponse=अमान्य उपयोगकर्ता जानकारी प्रतिक्रिया
|
||||||
login.oauth2invalidRequest=अमान्य अनुरोध
|
login.oauth2invalidRequest=अमान्य अनुरोध
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Neispravno korisničko ime ili zaporka.
|
|||||||
login.locked=Vaš račun je zaključan.
|
login.locked=Vaš račun je zaključan.
|
||||||
login.signinTitle=Molimo vas da se prijavite
|
login.signinTitle=Molimo vas da se prijavite
|
||||||
login.ssoSignIn=Prijavite se putem jedinstvene prijave
|
login.ssoSignIn=Prijavite se putem jedinstvene prijave
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 automatsko kreiranje korisnika je onemogućeno
|
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.oAuth2AdminBlockedUser=Registracija ili prijava nekadreguiranih korisnika trenutno su blokirane. Molimo Vas da kontaktirate administratora.
|
||||||
login.oauth2RequestNotFound=Zahtjev za autorizaciju nije pronađen
|
login.oauth2RequestNotFound=Zahtjev za autorizaciju nije pronađen
|
||||||
login.oauth2InvalidUserInfoResponse=Nevažeće informacije o korisniku
|
login.oauth2InvalidUserInfoResponse=Nevažeće informacije o korisniku
|
||||||
login.oauth2invalidRequest=Neispravan zahtjev
|
login.oauth2invalidRequest=Neispravan zahtjev
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Érvénytelen felhasználónév vagy jelszó.
|
|||||||
login.locked=A fiókja zárolva van.
|
login.locked=A fiókja zárolva van.
|
||||||
login.signinTitle=Kérjük, jelentkezzen be
|
login.signinTitle=Kérjük, jelentkezzen be
|
||||||
login.ssoSignIn=Bejelentkezés egyszeri bejelentkezéssel
|
login.ssoSignIn=Bejelentkezés egyszeri bejelentkezéssel
|
||||||
login.oauth2AutoCreateDisabled=OAuth2 automatikus felhasználólétrehozás letiltva
|
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.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.oauth2RequestNotFound=A hitelesítési kérés nem található
|
||||||
login.oauth2InvalidUserInfoResponse=Érvénytelen felhasználói információ válasz
|
login.oauth2InvalidUserInfoResponse=Érvénytelen felhasználói információ válasz
|
||||||
login.oauth2invalidRequest=Érvénytelen kérés
|
login.oauth2invalidRequest=Érvénytelen kérés
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Nama pengguna atau kata sandi tidak valid.
|
|||||||
login.locked=Akun Anda telah dikunci.
|
login.locked=Akun Anda telah dikunci.
|
||||||
login.signinTitle=Silakan masuk
|
login.signinTitle=Silakan masuk
|
||||||
login.ssoSignIn=Masuk melalui Single Sign - on
|
login.ssoSignIn=Masuk melalui Single Sign - on
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Buat Otomatis Pengguna Dinonaktifkan
|
login.oAuth2AutoCreateDisabled=OAUTH2 Buat Otomatis Pengguna Dinonaktifkan
|
||||||
login.oauth2AdminBlockedUser=Registrasi atau login pengguna yang tidak terdaftar saat ini diblokir. Silakan hubungi administrator.
|
login.oAuth2AdminBlockedUser=Registrasi atau login pengguna yang tidak terdaftar saat ini diblokir. Silakan hubungi administrator.
|
||||||
login.oauth2RequestNotFound=Permintaan otorisasi tidak ditemukan
|
login.oauth2RequestNotFound=Permintaan otorisasi tidak ditemukan
|
||||||
login.oauth2InvalidUserInfoResponse=Respons Info Pengguna Tidak Valid
|
login.oauth2InvalidUserInfoResponse=Respons Info Pengguna Tidak Valid
|
||||||
login.oauth2invalidRequest=Permintaan Tidak Valid
|
login.oauth2invalidRequest=Permintaan Tidak Valid
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Nome utente o password errati.
|
|||||||
login.locked=Il tuo account è stato bloccato.
|
login.locked=Il tuo account è stato bloccato.
|
||||||
login.signinTitle=Per favore accedi
|
login.signinTitle=Per favore accedi
|
||||||
login.ssoSignIn=Accedi tramite Single Sign-on
|
login.ssoSignIn=Accedi tramite Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=Creazione automatica utente OAUTH2 DISABILITATA
|
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.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.oauth2RequestNotFound=Richiesta di autorizzazione non trovata
|
||||||
login.oauth2InvalidUserInfoResponse=Risposta relativa alle informazioni utente non valida
|
login.oauth2InvalidUserInfoResponse=Risposta relativa alle informazioni utente non valida
|
||||||
login.oauth2invalidRequest=Richiesta non valida
|
login.oauth2invalidRequest=Richiesta non valida
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=ユーザー名かパスワードが無効です。
|
|||||||
login.locked=あなたのアカウントはロックされています。
|
login.locked=あなたのアカウントはロックされています。
|
||||||
login.signinTitle=サインインしてください
|
login.signinTitle=サインインしてください
|
||||||
login.ssoSignIn=シングルサインオンでログイン
|
login.ssoSignIn=シングルサインオンでログイン
|
||||||
login.oauth2AutoCreateDisabled=OAuth 2自動作成ユーザーが無効
|
login.oAuth2AutoCreateDisabled=OAuth 2自動作成ユーザーが無効
|
||||||
login.oauth2AdminBlockedUser=現在、未登録ユーザーの登録またはログインはブロックされています。管理者にお問い合わせください。
|
login.oAuth2AdminBlockedUser=現在、未登録ユーザーの登録またはログインはブロックされています。管理者にお問い合わせください。
|
||||||
login.oauth2RequestNotFound=認証リクエストが見つかりません
|
login.oauth2RequestNotFound=認証リクエストが見つかりません
|
||||||
login.oauth2InvalidUserInfoResponse=無効なユーザー情報の応答
|
login.oauth2InvalidUserInfoResponse=無効なユーザー情報の応答
|
||||||
login.oauth2invalidRequest=無効なリクエスト
|
login.oauth2invalidRequest=無効なリクエスト
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=사용자 이름 또는 비밀번호가 잘못되었습니다.
|
|||||||
login.locked=계정이 잠겼습니다.
|
login.locked=계정이 잠겼습니다.
|
||||||
login.signinTitle=로그인해 주세요
|
login.signinTitle=로그인해 주세요
|
||||||
login.ssoSignIn=단일 로그인으로 로그인
|
login.ssoSignIn=단일 로그인으로 로그인
|
||||||
login.oauth2AutoCreateDisabled=OAuth2 사용자 자동 생성이 비활성화되었습니다
|
login.oAuth2AutoCreateDisabled=OAuth2 사용자 자동 생성이 비활성화되었습니다
|
||||||
login.oauth2AdminBlockedUser=현재 미등록 사용자의 등록 또는 로그인이 차단되어 있습니다. 관리자에게 문의하세요.
|
login.oAuth2AdminBlockedUser=현재 미등록 사용자의 등록 또는 로그인이 차단되어 있습니다. 관리자에게 문의하세요.
|
||||||
login.oauth2RequestNotFound=인증 요청을 찾을 수 없습니다
|
login.oauth2RequestNotFound=인증 요청을 찾을 수 없습니다
|
||||||
login.oauth2InvalidUserInfoResponse=잘못된 사용자 정보 응답
|
login.oauth2InvalidUserInfoResponse=잘못된 사용자 정보 응답
|
||||||
login.oauth2invalidRequest=잘못된 요청
|
login.oauth2invalidRequest=잘못된 요청
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Ongeldige gebruikersnaam of wachtwoord.
|
|||||||
login.locked=Je account is geblokkeerd.
|
login.locked=Je account is geblokkeerd.
|
||||||
login.signinTitle=Gelieve in te loggen
|
login.signinTitle=Gelieve in te loggen
|
||||||
login.ssoSignIn=Inloggen via Single Sign-on
|
login.ssoSignIn=Inloggen via Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Automatisch aanmaken gebruiker uitgeschakeld
|
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.oAuth2AdminBlockedUser=Registratie of inloggen van niet-registreerde gebruikers is helaas momenteel geblokkeerd. Neem contact op met de beheerder.
|
||||||
login.oauth2RequestNotFound=Autorisatieverzoek niet gevonden
|
login.oauth2RequestNotFound=Autorisatieverzoek niet gevonden
|
||||||
login.oauth2InvalidUserInfoResponse=Ongeldige reactie op gebruikersinfo
|
login.oauth2InvalidUserInfoResponse=Ongeldige reactie op gebruikersinfo
|
||||||
login.oauth2invalidRequest=Ongeldig verzoek
|
login.oauth2invalidRequest=Ongeldig verzoek
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Ugyldig brukernavn eller passord.
|
|||||||
login.locked=Kontoen din har blitt låst.
|
login.locked=Kontoen din har blitt låst.
|
||||||
login.signinTitle=Vennligst logg inn
|
login.signinTitle=Vennligst logg inn
|
||||||
login.ssoSignIn=Logg inn via Enkel Pålogging
|
login.ssoSignIn=Logg inn via Enkel Pålogging
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Opretting av bruker deaktivert
|
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.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Autentiseringsforespørsel ikke funnet
|
login.oauth2RequestNotFound=Autentiseringsforespørsel ikke funnet
|
||||||
login.oauth2InvalidUserInfoResponse=Ugyldig brukerinforespons
|
login.oauth2InvalidUserInfoResponse=Ugyldig brukerinforespons
|
||||||
login.oauth2invalidRequest=Ugyldig forespørsel
|
login.oauth2invalidRequest=Ugyldig forespørsel
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Nieprawidłowe dane logowania
|
|||||||
login.locked=Konto jest zablokowane
|
login.locked=Konto jest zablokowane
|
||||||
login.signinTitle=Zaloguj się
|
login.signinTitle=Zaloguj się
|
||||||
login.ssoSignIn=Zaloguj się za pomocą logowania jednokrotnego
|
login.ssoSignIn=Zaloguj się za pomocą logowania jednokrotnego
|
||||||
login.oauth2AutoCreateDisabled=Wyłączono automatyczne tworzenie użytkownika OAUTH2
|
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.oAuth2AdminBlockedUser=Rejestracja lub logowanie niezarejestrowanych użytkowników jest obecnie zablokowane. Prosimy o kontakt z administratorem.
|
||||||
login.oauth2RequestNotFound=Błąd logowania OAuth2
|
login.oauth2RequestNotFound=Błąd logowania OAuth2
|
||||||
login.oauth2InvalidUserInfoResponse=Niewłaściwe dane logowania
|
login.oauth2InvalidUserInfoResponse=Niewłaściwe dane logowania
|
||||||
login.oauth2invalidRequest=Nieprawidłowe żądanie
|
login.oauth2invalidRequest=Nieprawidłowe żądanie
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Usuário ou senha inválidos.
|
|||||||
login.locked=Sua conta foi bloqueada.
|
login.locked=Sua conta foi bloqueada.
|
||||||
login.signinTitle=Por favor, inicie a sessão
|
login.signinTitle=Por favor, inicie a sessão
|
||||||
login.ssoSignIn=Iniciar sessão através de login único (SSO)
|
login.ssoSignIn=Iniciar sessão através de login único (SSO)
|
||||||
login.oauth2AutoCreateDisabled=Auto-Criar Usuário OAUTH2 Desativado
|
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.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.oauth2RequestNotFound=Solicitação de autorização não encontrada
|
||||||
login.oauth2InvalidUserInfoResponse=Resposta de informação de usuário inválida
|
login.oauth2InvalidUserInfoResponse=Resposta de informação de usuário inválida
|
||||||
login.oauth2invalidRequest=Requisição Inválida
|
login.oauth2invalidRequest=Requisição Inválida
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Nome de utilizador ou palavra-passe inválidos.
|
|||||||
login.locked=A sua conta foi bloqueada.
|
login.locked=A sua conta foi bloqueada.
|
||||||
login.signinTitle=Por favor inicie sessão
|
login.signinTitle=Por favor inicie sessão
|
||||||
login.ssoSignIn=Login via Single Sign-on
|
login.ssoSignIn=Login via Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=Criação Automática de Utilizador OAUTH2 Desativada
|
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.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.oauth2RequestNotFound=Pedido de autorização não encontrado
|
||||||
login.oauth2InvalidUserInfoResponse=Resposta de Informação de Utilizador Inválida
|
login.oauth2InvalidUserInfoResponse=Resposta de Informação de Utilizador Inválida
|
||||||
login.oauth2invalidRequest=Pedido Inválido
|
login.oauth2invalidRequest=Pedido Inválido
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Nume de utilizator sau parolă invalidă.
|
|||||||
login.locked=Contul tău a fost blocat.
|
login.locked=Contul tău a fost blocat.
|
||||||
login.signinTitle=Te rugăm să te autentifici
|
login.signinTitle=Te rugăm să te autentifici
|
||||||
login.ssoSignIn=Conectare prin conectare unică
|
login.ssoSignIn=Conectare prin conectare unică
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Creare automată utilizator dezactivată
|
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.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.oauth2RequestNotFound=Cererea de autorizare nu a fost găsită
|
||||||
login.oauth2InvalidUserInfoResponse=Răspuns Invalid la Informațiile Utilizatorului
|
login.oauth2InvalidUserInfoResponse=Răspuns Invalid la Informațiile Utilizatorului
|
||||||
login.oauth2invalidRequest=Cerere Invalidă
|
login.oauth2invalidRequest=Cerere Invalidă
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Неверное имя пользователя или парол
|
|||||||
login.locked=Ваша учетная запись заблокирована.
|
login.locked=Ваша учетная запись заблокирована.
|
||||||
login.signinTitle=Пожалуйста, войдите
|
login.signinTitle=Пожалуйста, войдите
|
||||||
login.ssoSignIn=Вход через единый вход
|
login.ssoSignIn=Вход через единый вход
|
||||||
login.oauth2AutoCreateDisabled=Автоматическое создание пользователей OAuth2 отключено
|
login.oAuth2AutoCreateDisabled=Автоматическое создание пользователей OAuth2 отключено
|
||||||
login.oauth2AdminBlockedUser=Регистрация или вход незарегистрированных пользователей в настоящее время заблокированы. Обратитесь к администратору.
|
login.oAuth2AdminBlockedUser=Регистрация или вход незарегистрированных пользователей в настоящее время заблокированы. Обратитесь к администратору.
|
||||||
login.oauth2RequestNotFound=Запрос авторизации не найден
|
login.oauth2RequestNotFound=Запрос авторизации не найден
|
||||||
login.oauth2InvalidUserInfoResponse=Недействительный ответ с информацией о пользователе
|
login.oauth2InvalidUserInfoResponse=Недействительный ответ с информацией о пользователе
|
||||||
login.oauth2invalidRequest=Недействительный запрос
|
login.oauth2invalidRequest=Недействительный запрос
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Neplatné používateľské meno alebo heslo.
|
|||||||
login.locked=Váš účet bol uzamknutý.
|
login.locked=Váš účet bol uzamknutý.
|
||||||
login.signinTitle=Prosím, prihláste sa
|
login.signinTitle=Prosím, prihláste sa
|
||||||
login.ssoSignIn=Prihlásiť sa cez Single Sign-on
|
login.ssoSignIn=Prihlásiť sa cez Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=Vytváranie používateľa cez OAUTH2 je zakázané
|
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.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Authorization request not found
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
login.oauth2invalidRequest=Invalid Request
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Neveljavno uporabniško ime ali geslo.
|
|||||||
login.locked=Vaš račun je bil zaklenjen.
|
login.locked=Vaš račun je bil zaklenjen.
|
||||||
login.signinTitle=Prosim prijavite se
|
login.signinTitle=Prosim prijavite se
|
||||||
login.ssoSignIn=Prijava prek enotne prijave
|
login.ssoSignIn=Prijava prek enotne prijave
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Samodejno ustvarjanje uporabnika onemogočeno
|
login.oAuth2AutoCreateDisabled=OAUTH2 Samodejno ustvarjanje uporabnika onemogočeno
|
||||||
login.oauth2AdminBlockedUser=Registracija ali prijava neregistriranih uporabnikov je trenutno blokirana. Prosimo kontaktirajte skrbnika.
|
login.oAuth2AdminBlockedUser=Registracija ali prijava neregistriranih uporabnikov je trenutno blokirana. Prosimo kontaktirajte skrbnika.
|
||||||
login.oauth2RequestNotFound=Zahteva za avtorizacijo ni bila najdena
|
login.oauth2RequestNotFound=Zahteva za avtorizacijo ni bila najdena
|
||||||
login.oauth2InvalidUserInfoResponse=Neveljaven odgovor z informacijami o uporabniku
|
login.oauth2InvalidUserInfoResponse=Neveljaven odgovor z informacijami o uporabniku
|
||||||
login.oauth2invalidRequest=Neveljavna zahteva
|
login.oauth2invalidRequest=Neveljavna zahteva
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Neispravno korisničko ime ili lozinka.
|
|||||||
login.locked=Vaš nalog je zaključan.
|
login.locked=Vaš nalog je zaključan.
|
||||||
login.signinTitle=Molimo vas da se prijavite
|
login.signinTitle=Molimo vas da se prijavite
|
||||||
login.ssoSignIn=Prijavite se putem jedinstvene prijave
|
login.ssoSignIn=Prijavite se putem jedinstvene prijave
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 automatsko kreiranje korisnika je onemogućeno
|
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.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Authorization request not found
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
login.oauth2invalidRequest=Invalid Request
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Ogiltigt användarnamn eller lösenord.
|
|||||||
login.locked=Ditt konto har låsts.
|
login.locked=Ditt konto har låsts.
|
||||||
login.signinTitle=Vänligen logga in
|
login.signinTitle=Vänligen logga in
|
||||||
login.ssoSignIn=Logga in via enkel inloggning
|
login.ssoSignIn=Logga in via enkel inloggning
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-skapa användare inaktiverad
|
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.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.oauth2RequestNotFound=Auktoriseringsbegäran hittades inte
|
||||||
login.oauth2InvalidUserInfoResponse=Ogiltigt svar på användarinformation
|
login.oauth2InvalidUserInfoResponse=Ogiltigt svar på användarinformation
|
||||||
login.oauth2invalidRequest=Ogiltig begäran
|
login.oauth2invalidRequest=Ogiltig begäran
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=ชื่อผู้ใช้หรือรหัสผ่าน
|
|||||||
login.locked=บัญชีของคุณถูกล็อค
|
login.locked=บัญชีของคุณถูกล็อค
|
||||||
login.signinTitle=กรุณาลงชื่อเข้าใช้
|
login.signinTitle=กรุณาลงชื่อเข้าใช้
|
||||||
login.ssoSignIn=เข้าสู่ระบบด้วย Single Sign-on
|
login.ssoSignIn=เข้าสู่ระบบด้วย Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=การสร้างผู้ใช้ OAuth2 อัตโนมัติถูกปิดใช้งาน
|
login.oAuth2AutoCreateDisabled=การสร้างผู้ใช้ OAuth2 อัตโนมัติถูกปิดใช้งาน
|
||||||
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=ไม่พบคำขอการอนุญาต
|
login.oauth2RequestNotFound=ไม่พบคำขอการอนุญาต
|
||||||
login.oauth2InvalidUserInfoResponse=การตอบกลับข้อมูลผู้ใช้ไม่ถูกต้อง
|
login.oauth2InvalidUserInfoResponse=การตอบกลับข้อมูลผู้ใช้ไม่ถูกต้อง
|
||||||
login.oauth2invalidRequest=คำขอไม่ถูกต้อง
|
login.oauth2invalidRequest=คำขอไม่ถูกต้อง
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Geçersiz kullanıcı adı veya şifre.
|
|||||||
login.locked=Hesabınız kilitlendi.
|
login.locked=Hesabınız kilitlendi.
|
||||||
login.signinTitle=Lütfen giriş yapınız.
|
login.signinTitle=Lütfen giriş yapınız.
|
||||||
login.ssoSignIn=Tek Oturum Açma ile Giriş Yap
|
login.ssoSignIn=Tek Oturum Açma ile Giriş Yap
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Otomatik Oluşturma Kullanıcı Devre Dışı Bırakıldı
|
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.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.oauth2RequestNotFound=Yetkilendirme isteği bulunamadı
|
||||||
login.oauth2InvalidUserInfoResponse=Geçersiz Kullanıcı Bilgisi Yanıtı
|
login.oauth2InvalidUserInfoResponse=Geçersiz Kullanıcı Bilgisi Yanıtı
|
||||||
login.oauth2invalidRequest=Geçersiz İstek
|
login.oauth2invalidRequest=Geçersiz İstek
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=Недійсне ім'я користувача або парол
|
|||||||
login.locked=Ваш обліковий запис заблоковано.
|
login.locked=Ваш обліковий запис заблоковано.
|
||||||
login.signinTitle=Будь ласка, увійдіть
|
login.signinTitle=Будь ласка, увійдіть
|
||||||
login.ssoSignIn=Увійти через єдиний вхід
|
login.ssoSignIn=Увійти через єдиний вхід
|
||||||
login.oauth2AutoCreateDisabled=Автоматичне створення користувача OAUTH2 ВИМКНЕНО
|
login.oAuth2AutoCreateDisabled=Автоматичне створення користувача OAUTH2 ВИМКНЕНО
|
||||||
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
login.oAuth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Запит на авторизація не знайдено
|
login.oauth2RequestNotFound=Запит на авторизація не знайдено
|
||||||
login.oauth2InvalidUserInfoResponse=Недійсна відповідь з інформацією користувача
|
login.oauth2InvalidUserInfoResponse=Недійсна відповідь з інформацією користувача
|
||||||
login.oauth2invalidRequest=Недійсний запит
|
login.oauth2invalidRequest=Недійсний запит
|
||||||
|
@ -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.locked=Tài khoản của bạn đã bị khóa.
|
||||||
login.signinTitle=Vui lòng đăng nhập
|
login.signinTitle=Vui lòng đăng nhập
|
||||||
login.ssoSignIn=Đăng nhập qua Single Sign-on
|
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.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.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.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.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ệ
|
login.oauth2invalidRequest=Yêu cầu không hợp lệ
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=སྤྱོད་མིང་ངམ་གསང་ཚིག་ན
|
|||||||
login.locked=ཁྱེད་ཀྱི་ཐོ་མཛོད་ཟྭ་རྒྱག་བརྒྱབ་ཟིན།
|
login.locked=ཁྱེད་ཀྱི་ཐོ་མཛོད་ཟྭ་རྒྱག་བརྒྱབ་ཟིན།
|
||||||
login.signinTitle=ནང་འཛུལ་གནང་རོགས།
|
login.signinTitle=ནང་འཛུལ་གནང་རོགས།
|
||||||
login.ssoSignIn=གཅིག་གྱུར་ནང་འཛུལ་བརྒྱུད་ནས་ནང་འཛུལ།
|
login.ssoSignIn=གཅིག་གྱུར་ནང་འཛུལ་བརྒྱུད་ནས་ནང་འཛུལ།
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 རང་འགུལ་སྤྱོད་མཁན་གསར་བཟོ་བཀག་སྡོམ་བྱས་ཟིན།
|
login.oAuth2AutoCreateDisabled=OAUTH2 རང་འགུལ་སྤྱོད་མཁན་གསར་བཟོ་བཀག་སྡོམ་བྱས་ཟིན།
|
||||||
login.oauth2AdminBlockedUser=ད་ལྟ་ཐོ་འགོད་མ་བྱས་པའི་སྤྱོད་མཁན་གྱི་ཐོ་འགོད་དང་ནང་འཛུལ་བཀག་སྡོམ་བྱས་ཡོད། དོ་དམ་པར་འབྲེལ་བ་གནང་རོགས།
|
login.oAuth2AdminBlockedUser=ད་ལྟ་ཐོ་འགོད་མ་བྱས་པའི་སྤྱོད་མཁན་གྱི་ཐོ་འགོད་དང་ནང་འཛུལ་བཀག་སྡོམ་བྱས་ཡོད། དོ་དམ་པར་འབྲེལ་བ་གནང་རོགས།
|
||||||
login.oauth2RequestNotFound=དབང་སྤྲོད་རེ་ཞུ་རྙེད་མ་བྱུང་།
|
login.oauth2RequestNotFound=དབང་སྤྲོད་རེ་ཞུ་རྙེད་མ་བྱུང་།
|
||||||
login.oauth2InvalidUserInfoResponse=སྤྱོད་མཁན་གྱི་གནས་ཚུལ་ལན་འདེབས་ནོར་འཁྲུལ།
|
login.oauth2InvalidUserInfoResponse=སྤྱོད་མཁན་གྱི་གནས་ཚུལ་ལན་འདེབས་ནོར་འཁྲུལ།
|
||||||
login.oauth2invalidRequest=རེ་ཞུ་ནོར་འཁྲུལ།
|
login.oauth2invalidRequest=རེ་ཞུ་ནོར་འཁྲུལ།
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=用户名或密码无效。
|
|||||||
login.locked=您的账户已被锁定。
|
login.locked=您的账户已被锁定。
|
||||||
login.signinTitle=请登录
|
login.signinTitle=请登录
|
||||||
login.ssoSignIn=通过单点登录登录
|
login.ssoSignIn=通过单点登录登录
|
||||||
login.oauth2AutoCreateDisabled=OAuth2 自动创建用户已禁用
|
login.oAuth2AutoCreateDisabled=OAuth2 自动创建用户已禁用
|
||||||
login.oauth2AdminBlockedUser=目前已阻止未注册用户的注册或登录。请联系管理员。
|
login.oAuth2AdminBlockedUser=目前已阻止未注册用户的注册或登录。请联系管理员。
|
||||||
login.oauth2RequestNotFound=找不到验证请求
|
login.oauth2RequestNotFound=找不到验证请求
|
||||||
login.oauth2InvalidUserInfoResponse=无效的用户信息响应
|
login.oauth2InvalidUserInfoResponse=无效的用户信息响应
|
||||||
login.oauth2invalidRequest=无效请求
|
login.oauth2invalidRequest=无效请求
|
||||||
|
@ -572,8 +572,8 @@ login.invalid=使用者名稱或密碼無效。
|
|||||||
login.locked=您的帳號已被鎖定。
|
login.locked=您的帳號已被鎖定。
|
||||||
login.signinTitle=請登入
|
login.signinTitle=請登入
|
||||||
login.ssoSignIn=透過 SSO 單一登入
|
login.ssoSignIn=透過 SSO 單一登入
|
||||||
login.oauth2AutoCreateDisabled=OAuth 2.0 自動建立使用者功能已停用
|
login.oAuth2AutoCreateDisabled=OAuth 2.0 自動建立使用者功能已停用
|
||||||
login.oauth2AdminBlockedUser=目前不允許未註冊的使用者註冊或登入。請聯絡系統管理員。
|
login.oAuth2AdminBlockedUser=目前不允許未註冊的使用者註冊或登入。請聯絡系統管理員。
|
||||||
login.oauth2RequestNotFound=找不到驗證請求
|
login.oauth2RequestNotFound=找不到驗證請求
|
||||||
login.oauth2InvalidUserInfoResponse=使用者資訊回應無效
|
login.oauth2InvalidUserInfoResponse=使用者資訊回應無效
|
||||||
login.oauth2invalidRequest=請求無效
|
login.oauth2invalidRequest=請求無效
|
||||||
|
@ -28,37 +28,38 @@ security:
|
|||||||
clientId: '' # client ID for Keycloak OAuth2
|
clientId: '' # client ID for Keycloak OAuth2
|
||||||
clientSecret: '' # client secret for Keycloak OAuth2
|
clientSecret: '' # client secret for Keycloak OAuth2
|
||||||
scopes: openid, profile, email # scopes 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:
|
google:
|
||||||
clientId: '' # client ID for Google OAuth2
|
clientId: '' # client ID for Google OAuth2
|
||||||
clientSecret: '' # client secret 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
|
scopes: email, profile # scopes for Google OAuth2
|
||||||
useAsUsername: email # field to use as the username for Google OAuth2
|
useAsUsername: email # field to use as the username for Google OAuth2. Available options are: [email | name | given_name | family_name]
|
||||||
github:
|
github:
|
||||||
clientId: '' # client ID for GitHub OAuth2
|
clientId: '' # client ID for GitHub OAuth2
|
||||||
clientSecret: '' # client secret for GitHub OAuth2
|
clientSecret: '' # client secret for GitHub OAuth2
|
||||||
scopes: read:user # scope for GitHub OAuth2
|
scopes: read:user # scope for GitHub OAuth2
|
||||||
useAsUsername: login # field to use as the username for GitHub OAuth2
|
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
|
issuer: '' # set to any Provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) endpoint
|
||||||
clientId: '' # client ID from your provider
|
clientId: '' # client ID from your Provider
|
||||||
clientSecret: '' # client secret from your provider
|
clientSecret: '' # client secret from your Provider
|
||||||
autoCreateUser: true # set to 'true' to allow auto-creation of non-existing users
|
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
|
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
|
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
|
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:
|
saml2:
|
||||||
enabled: false # Only enabled for paid enterprise clients (enterpriseEdition.enabled must be true)
|
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
|
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
|
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
|
||||||
registrationId: stirling
|
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
|
idpMetadataUri: https://dev-XXXXXXXX.okta.com/app/externalKey/sso/saml/metadata # The uri for your Provider's 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 # The URL for initiating SSO. Provided by your Provider
|
||||||
idpSingleLoginUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/sso/saml
|
idpSingleLogoutUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/slo/saml # The URL for initiating SLO. Provided by your Provider
|
||||||
idpIssuer: http://www.okta.com/externalKey
|
idpIssuer: '' # The ID of your Provider
|
||||||
idpCert: classpath:okta.crt
|
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
|
privateKey: classpath:saml-private-key.key # Your private key. Generated from your keypair
|
||||||
spCert: classpath:saml-public-cert.crt
|
spCert: classpath:saml-public-cert.crt # Your signing certificate. Generated from your keypair
|
||||||
|
|
||||||
enterpriseEdition:
|
enterpriseEdition:
|
||||||
enabled: false # set to 'true' to enable enterprise edition
|
enabled: false # set to 'true' to enable enterprise edition
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
<!-- Change Username Form -->
|
<!-- Change Username Form -->
|
||||||
<th:block th:if="${!oAuth2Login}">
|
<th:block th:if="not ${oAuth2Login} or not ${saml2Login}">
|
||||||
<h4 th:text="#{account.changeUsername}">Change Username?</h4>
|
<h4 th:text="#{account.changeUsername}">Change Username?</h4>
|
||||||
<form id="formsavechangeusername" class="bg-card mt-4 mb-4" th:action="@{'/api/v1/user/change-username'}" method="post">
|
<form id="formsavechangeusername" class="bg-card mt-4 mb-4" th:action="@{'/api/v1/user/change-username'}" method="post">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@ -53,7 +53,7 @@
|
|||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
<!-- Change Password Form -->
|
<!-- Change Password Form -->
|
||||||
<th:block th:if="${!oAuth2Login}">
|
<th:block th:if="not ${oAuth2Login} or not ${saml2Login}">
|
||||||
<h4 th:text="#{account.changePassword}">Change Password?</h4>
|
<h4 th:text="#{account.changePassword}">Change Password?</h4>
|
||||||
<form id="formsavechangepassword" class="bg-card mt-4 mb-4" th:action="@{'/api/v1/user/change-password'}" method="post">
|
<form id="formsavechangepassword" class="bg-card mt-4 mb-4" th:action="@{'/api/v1/user/change-password'}" method="post">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
@ -253,11 +253,11 @@
|
|||||||
<label for="cacheInputs" th:text="#{settings.cacheInputs.name}"></label>
|
<label for="cacheInputs" th:text="#{settings.cacheInputs.name}"></label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a th:if="${@loginEnabled and @activSecurity}" th:href="@{'/account'}" class="btn btn-sm btn-outline-primary"
|
<a th:if="${@loginEnabled and @activeSecurity}" th:href="@{'/account'}" class="btn btn-sm btn-outline-primary"
|
||||||
role="button" th:text="#{settings.accountSettings}" target="_blank">Account Settings</a>
|
role="button" th:text="#{settings.accountSettings}" target="_blank">Account Settings</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<a th:if="${@loginEnabled and @activSecurity}" class="btn btn-danger" role="button"
|
<a th:if="${@loginEnabled and @activeSecurity}" class="btn btn-danger" role="button"
|
||||||
th:text="#{settings.signOut}" th:href="@{'/logout'}">Sign Out</a>
|
th:text="#{settings.signOut}" th:href="@{'/logout'}">Sign Out</a>
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}"></button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}"></button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
const runningEE = /*[[${@runningEE}]]*/ false;
|
const runningEE = /*[[${@runningEE}]]*/ false;
|
||||||
const SSOAutoLogin = /*[[${@SSOAutoLogin}]]*/ false;
|
const SSOAutoLogin = /*[[${@SSOAutoLogin}]]*/ false;
|
||||||
const loginMethod = /*[[${loginMethod}]]*/ 'normal';
|
const loginMethod = /*[[${loginMethod}]]*/ 'normal';
|
||||||
const providerList = /*[[${providerlist}]]*/ {};
|
const providerList = /*[[${providerList}]]*/ {};
|
||||||
const shouldAutoRedirect = !hasRedirectError &&
|
const shouldAutoRedirect = !hasRedirectError &&
|
||||||
!hasLogout &&
|
!hasLogout &&
|
||||||
!hasMessage &&
|
!hasMessage &&
|
||||||
@ -98,14 +98,14 @@
|
|||||||
<img class="my-4" th:src="@{'/favicon.svg'}" alt="favicon" width="144" height="144">
|
<img class="my-4" th:src="@{'/favicon.svg'}" alt="favicon" width="144" height="144">
|
||||||
|
|
||||||
<h1 class="h1 mb-3 fw-normal" th:text="${@appName}">Stirling-PDF</h1>
|
<h1 class="h1 mb-3 fw-normal" th:text="${@appName}">Stirling-PDF</h1>
|
||||||
<div th:if="${altLogin} and (${loginMethod} == 'all' or ${loginMethod} == 'oauth2')">
|
<div th:if="${altLogin} and (${loginMethod} == 'all' or ${loginMethod} == 'oauth2' or ${loginMethod} == 'saml2')">
|
||||||
<a href="#" class="w-100 btn btn-lg btn-primary" data-bs-toggle="modal" data-bs-target="#loginsModal" th:text="#{login.ssoSignIn}">Login Via SSO</a>
|
<a href="#" class="w-100 btn btn-lg btn-primary" data-bs-toggle="modal" data-bs-target="#loginsModal" th:text="#{login.ssoSignIn}">Login Via SSO</a>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
<div th:if="${erroroauth}" class="alert alert-danger text-center">
|
<div th:if="${errorOAuth}" class="alert alert-danger text-center">
|
||||||
<div th:if="${erroroauth}" th:text="#{${erroroauth}}">OAuth2: Error Message</div>
|
<div th:if="${errorOAuth}" th:text="#{${errorOAuth}}">OAuth2: Error Message</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div th:if="${error}" class="alert alert-danger text-center">
|
<div th:if="${error}" class="alert alert-danger text-center">
|
||||||
@ -164,7 +164,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3" th:each="provider : ${providerlist}">
|
<div class="mb-3" th:each="provider : ${providerList}">
|
||||||
<a th:href="@{|${provider.key}|}" th:text="${provider.value}" class="w-100 btn btn-lg btn-primary">Login Provider</a>
|
<a th:href="@{|${provider.key}|}" th:text="${provider.value}" class="w-100 btn btn-lg btn-primary">Login Provider</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,262 @@
|
|||||||
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class CustomLogoutSuccessHandlerTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private CustomLogoutSuccessHandler customLogoutSuccessHandler;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccessfulLogout() throws IOException {
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||||
|
String logoutPath = "logout=true";
|
||||||
|
|
||||||
|
when(response.isCommitted()).thenReturn(false);
|
||||||
|
when(request.getContextPath()).thenReturn("");
|
||||||
|
when(response.encodeRedirectURL(logoutPath)).thenReturn(logoutPath);
|
||||||
|
|
||||||
|
customLogoutSuccessHandler.onLogoutSuccess(request, response, null);
|
||||||
|
|
||||||
|
verify(response).sendRedirect(logoutPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccessfulLogoutViaOAuth2() throws IOException {
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||||
|
OAuth2AuthenticationToken oAuth2AuthenticationToken = mock(OAuth2AuthenticationToken.class);
|
||||||
|
ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);
|
||||||
|
ApplicationProperties.Security.OAUTH2 oauth = mock(ApplicationProperties.Security.OAUTH2.class);
|
||||||
|
|
||||||
|
when(response.isCommitted()).thenReturn(false);
|
||||||
|
when(request.getParameter("oAuth2AuthenticationErrorWeb")).thenReturn(null);
|
||||||
|
when(request.getParameter("errorOAuth")).thenReturn(null);
|
||||||
|
when(request.getScheme()).thenReturn("http");
|
||||||
|
when(request.getServerName()).thenReturn("localhost");
|
||||||
|
when(request.getServerPort()).thenReturn(8080);
|
||||||
|
when(request.getContextPath()).thenReturn("");
|
||||||
|
when(applicationProperties.getSecurity()).thenReturn(security);
|
||||||
|
when(security.getOauth2()).thenReturn(oauth);
|
||||||
|
when(oAuth2AuthenticationToken.getAuthorizedClientRegistrationId()).thenReturn("test");
|
||||||
|
|
||||||
|
customLogoutSuccessHandler.onLogoutSuccess(request, response, oAuth2AuthenticationToken);
|
||||||
|
|
||||||
|
verify(response).sendRedirect("http://localhost:8080/login?logout=true");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUserIsDisabledRedirect() throws IOException {
|
||||||
|
String error = "userIsDisabled";
|
||||||
|
String url = "http://localhost:8080";
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||||
|
OAuth2AuthenticationToken authentication = mock(OAuth2AuthenticationToken.class);
|
||||||
|
ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);
|
||||||
|
ApplicationProperties.Security.OAUTH2 oauth = mock(ApplicationProperties.Security.OAUTH2.class);
|
||||||
|
|
||||||
|
when(response.isCommitted()).thenReturn(false);
|
||||||
|
when(request.getParameter("oAuth2AuthenticationErrorWeb")).thenReturn(null);
|
||||||
|
when(request.getParameter("errorOAuth")).thenReturn(null);
|
||||||
|
when(request.getParameter("oAuth2AutoCreateDisabled")).thenReturn(null);
|
||||||
|
when(request.getParameter("oAuth2AdminBlockedUser")).thenReturn(null);
|
||||||
|
when(request.getParameter(error)).thenReturn("true");
|
||||||
|
when(request.getScheme()).thenReturn("http");
|
||||||
|
when(request.getServerName()).thenReturn("localhost");
|
||||||
|
when(request.getServerPort()).thenReturn(8080);
|
||||||
|
when(request.getContextPath()).thenReturn("");
|
||||||
|
when(applicationProperties.getSecurity()).thenReturn(security);
|
||||||
|
when(security.getOauth2()).thenReturn(oauth);
|
||||||
|
when(authentication.getAuthorizedClientRegistrationId()).thenReturn("test");
|
||||||
|
|
||||||
|
customLogoutSuccessHandler.onLogoutSuccess(request, response, authentication);
|
||||||
|
|
||||||
|
verify(response).sendRedirect(url + "/login?errorOAuth=" + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUserAlreadyExistsWebRedirect() throws IOException {
|
||||||
|
String error = "oAuth2AuthenticationErrorWeb";
|
||||||
|
String errorPath = "userAlreadyExistsWeb";
|
||||||
|
String url = "http://localhost:8080";
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||||
|
OAuth2AuthenticationToken authentication = mock(OAuth2AuthenticationToken.class);
|
||||||
|
ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);
|
||||||
|
ApplicationProperties.Security.OAUTH2 oauth = mock(ApplicationProperties.Security.OAUTH2.class);
|
||||||
|
|
||||||
|
when(response.isCommitted()).thenReturn(false);
|
||||||
|
when(request.getParameter(error)).thenReturn("true");
|
||||||
|
when(request.getScheme()).thenReturn("http");
|
||||||
|
when(request.getServerName()).thenReturn("localhost");
|
||||||
|
when(request.getServerPort()).thenReturn(8080);
|
||||||
|
when(request.getContextPath()).thenReturn("");
|
||||||
|
when(applicationProperties.getSecurity()).thenReturn(security);
|
||||||
|
when(security.getOauth2()).thenReturn(oauth);
|
||||||
|
when(authentication.getAuthorizedClientRegistrationId()).thenReturn("test");
|
||||||
|
|
||||||
|
customLogoutSuccessHandler.onLogoutSuccess(request, response, authentication);
|
||||||
|
|
||||||
|
verify(response).sendRedirect(url + "/login?errorOAuth=" + errorPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testErrorOAuthRedirect() throws IOException {
|
||||||
|
String error = "testError";
|
||||||
|
String url = "http://localhost:8080";
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||||
|
OAuth2AuthenticationToken authentication = mock(OAuth2AuthenticationToken.class);
|
||||||
|
ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);
|
||||||
|
ApplicationProperties.Security.OAUTH2 oauth = mock(ApplicationProperties.Security.OAUTH2.class);
|
||||||
|
|
||||||
|
when(response.isCommitted()).thenReturn(false);
|
||||||
|
when(request.getParameter("oAuth2AuthenticationErrorWeb")).thenReturn(null);
|
||||||
|
when(request.getParameter("errorOAuth")).thenReturn("!!!" + error + "!!!");
|
||||||
|
when(request.getScheme()).thenReturn("http");
|
||||||
|
when(request.getServerName()).thenReturn("localhost");
|
||||||
|
when(request.getServerPort()).thenReturn(8080);
|
||||||
|
when(request.getContextPath()).thenReturn("");
|
||||||
|
when(applicationProperties.getSecurity()).thenReturn(security);
|
||||||
|
when(security.getOauth2()).thenReturn(oauth);
|
||||||
|
when(authentication.getAuthorizedClientRegistrationId()).thenReturn("test");
|
||||||
|
|
||||||
|
customLogoutSuccessHandler.onLogoutSuccess(request, response, authentication);
|
||||||
|
|
||||||
|
verify(response).sendRedirect(url + "/login?errorOAuth=" + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testOAuth2AutoCreateDisabled() throws IOException {
|
||||||
|
String error = "oAuth2AutoCreateDisabled";
|
||||||
|
String url = "http://localhost:8080";
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||||
|
OAuth2AuthenticationToken authentication = mock(OAuth2AuthenticationToken.class);
|
||||||
|
ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);
|
||||||
|
ApplicationProperties.Security.OAUTH2 oauth = mock(ApplicationProperties.Security.OAUTH2.class);
|
||||||
|
|
||||||
|
when(response.isCommitted()).thenReturn(false);
|
||||||
|
when(request.getParameter("oAuth2AuthenticationErrorWeb")).thenReturn(null);
|
||||||
|
when(request.getParameter("errorOAuth")).thenReturn(null);
|
||||||
|
when(request.getParameter(error)).thenReturn("true");
|
||||||
|
when(request.getContextPath()).thenReturn(url);
|
||||||
|
when(request.getScheme()).thenReturn("http");
|
||||||
|
when(request.getServerName()).thenReturn("localhost");
|
||||||
|
when(request.getServerPort()).thenReturn(8080);
|
||||||
|
when(request.getContextPath()).thenReturn("");
|
||||||
|
when(applicationProperties.getSecurity()).thenReturn(security);
|
||||||
|
when(security.getOauth2()).thenReturn(oauth);
|
||||||
|
when(authentication.getAuthorizedClientRegistrationId()).thenReturn("test");
|
||||||
|
|
||||||
|
customLogoutSuccessHandler.onLogoutSuccess(request, response, authentication);
|
||||||
|
|
||||||
|
verify(response).sendRedirect(url + "/login?errorOAuth=" + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testOAuth2Error() throws IOException {
|
||||||
|
String error = "test";
|
||||||
|
String url = "http://localhost:8080";
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||||
|
OAuth2AuthenticationToken authentication = mock(OAuth2AuthenticationToken.class);
|
||||||
|
ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);
|
||||||
|
ApplicationProperties.Security.OAUTH2 oauth = mock(ApplicationProperties.Security.OAUTH2.class);
|
||||||
|
|
||||||
|
when(response.isCommitted()).thenReturn(false);
|
||||||
|
when(request.getParameter("oAuth2AuthenticationErrorWeb")).thenReturn(null);
|
||||||
|
when(request.getParameter("errorOAuth")).thenReturn(null);
|
||||||
|
when(request.getParameter("oAuth2AutoCreateDisabled")).thenReturn(null);
|
||||||
|
when(request.getParameter("oAuth2AdminBlockedUser")).thenReturn(null);
|
||||||
|
when(request.getParameter("userIsDisabled")).thenReturn(null);
|
||||||
|
when(request.getParameter("error")).thenReturn("!@$!@£" + error + "£$%^*$");
|
||||||
|
when(request.getScheme()).thenReturn("http");
|
||||||
|
when(request.getServerName()).thenReturn("localhost");
|
||||||
|
when(request.getServerPort()).thenReturn(8080);
|
||||||
|
when(request.getContextPath()).thenReturn("");
|
||||||
|
when(applicationProperties.getSecurity()).thenReturn(security);
|
||||||
|
when(security.getOauth2()).thenReturn(oauth);
|
||||||
|
when(authentication.getAuthorizedClientRegistrationId()).thenReturn("test");
|
||||||
|
|
||||||
|
customLogoutSuccessHandler.onLogoutSuccess(request, response, authentication);
|
||||||
|
|
||||||
|
verify(response).sendRedirect(url + "/login?errorOAuth=" + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testOAuth2BadCredentialsError() throws IOException {
|
||||||
|
String error = "badCredentials";
|
||||||
|
String url = "http://localhost:8080";
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||||
|
OAuth2AuthenticationToken authentication = mock(OAuth2AuthenticationToken.class);
|
||||||
|
ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);
|
||||||
|
ApplicationProperties.Security.OAUTH2 oauth = mock(ApplicationProperties.Security.OAUTH2.class);
|
||||||
|
|
||||||
|
when(response.isCommitted()).thenReturn(false);
|
||||||
|
when(request.getParameter("oAuth2AuthenticationErrorWeb")).thenReturn(null);
|
||||||
|
when(request.getParameter("errorOAuth")).thenReturn(null);
|
||||||
|
when(request.getParameter("oAuth2AutoCreateDisabled")).thenReturn(null);
|
||||||
|
when(request.getParameter("oAuth2AdminBlockedUser")).thenReturn(null);
|
||||||
|
when(request.getParameter("userIsDisabled")).thenReturn(null);
|
||||||
|
when(request.getParameter("error")).thenReturn(null);
|
||||||
|
when(request.getParameter(error)).thenReturn("true");
|
||||||
|
when(request.getScheme()).thenReturn("http");
|
||||||
|
when(request.getServerName()).thenReturn("localhost");
|
||||||
|
when(request.getServerPort()).thenReturn(8080);
|
||||||
|
when(request.getContextPath()).thenReturn("");
|
||||||
|
when(applicationProperties.getSecurity()).thenReturn(security);
|
||||||
|
when(security.getOauth2()).thenReturn(oauth);
|
||||||
|
when(authentication.getAuthorizedClientRegistrationId()).thenReturn("test");
|
||||||
|
|
||||||
|
customLogoutSuccessHandler.onLogoutSuccess(request, response, authentication);
|
||||||
|
|
||||||
|
verify(response).sendRedirect(url + "/login?errorOAuth=" + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testOAuth2AdminBlockedUser() throws IOException {
|
||||||
|
String error = "oAuth2AdminBlockedUser";
|
||||||
|
String url = "http://localhost:8080";
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||||
|
OAuth2AuthenticationToken authentication = mock(OAuth2AuthenticationToken.class);
|
||||||
|
ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);
|
||||||
|
ApplicationProperties.Security.OAUTH2 oauth = mock(ApplicationProperties.Security.OAUTH2.class);
|
||||||
|
|
||||||
|
when(response.isCommitted()).thenReturn(false);
|
||||||
|
when(request.getParameter("oAuth2AuthenticationErrorWeb")).thenReturn(null);
|
||||||
|
when(request.getParameter("errorOAuth")).thenReturn(null);
|
||||||
|
when(request.getParameter("oAuth2AutoCreateDisabled")).thenReturn(null);
|
||||||
|
when(request.getParameter(error)).thenReturn("true");
|
||||||
|
when(request.getScheme()).thenReturn("http");
|
||||||
|
when(request.getServerName()).thenReturn("localhost");
|
||||||
|
when(request.getServerPort()).thenReturn(8080);
|
||||||
|
when(request.getContextPath()).thenReturn("");
|
||||||
|
when(applicationProperties.getSecurity()).thenReturn(security);
|
||||||
|
when(security.getOauth2()).thenReturn(oauth);
|
||||||
|
when(authentication.getAuthorizedClientRegistrationId()).thenReturn("test");
|
||||||
|
|
||||||
|
customLogoutSuccessHandler.onLogoutSuccess(request, response, authentication);
|
||||||
|
|
||||||
|
verify(response).sendRedirect(url + "/login?errorOAuth=" + error);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
package stirling.software.SPDF.utils.validation;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import stirling.software.SPDF.model.UsernameAttribute;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class ValidatorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccessfulValidation() {
|
||||||
|
var provider = mock(GitHubProvider.class);
|
||||||
|
|
||||||
|
when(provider.getClientId()).thenReturn("clientId");
|
||||||
|
when(provider.getClientSecret()).thenReturn("clientSecret");
|
||||||
|
when(provider.getScopes()).thenReturn(List.of("read:user"));
|
||||||
|
|
||||||
|
assertTrue(Validator.validateProvider(provider));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("providerParams")
|
||||||
|
void testUnsuccessfulValidation(Provider provider) {
|
||||||
|
assertFalse(Validator.validateProvider(provider));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<Arguments> providerParams() {
|
||||||
|
Provider generic = null;
|
||||||
|
var google = new GoogleProvider(null, "clientSecret", List.of("scope"), UsernameAttribute.EMAIL);
|
||||||
|
var github = new GitHubProvider("clientId", "", List.of("scope"), UsernameAttribute.LOGIN);
|
||||||
|
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(generic),
|
||||||
|
Arguments.of(google),
|
||||||
|
Arguments.of(github)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user