mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-14 11:35:03 +00:00
Working on SAML 2
This commit is contained in:
parent
704da399d4
commit
6478674d9b
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import java.security.interfaces.RSAPrivateKey;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@ -14,6 +15,7 @@ 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.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
@ -49,9 +51,8 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
} else if (authentication instanceof OAuth2AuthenticationToken) {
|
} else if (authentication instanceof OAuth2AuthenticationToken) {
|
||||||
// Handle OAuth2 logout redirection
|
// Handle OAuth2 logout redirection
|
||||||
getRedirect_oauth2(request, response, authentication);
|
getRedirect_oauth2(request, response, authentication);
|
||||||
}
|
} 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, LOGOUT_PATH);
|
||||||
} else {
|
} else {
|
||||||
// Handle unknown authentication types
|
// Handle unknown authentication types
|
||||||
@ -90,27 +91,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();
|
||||||
@ -127,7 +108,6 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect for OAuth2 authentication logout
|
|
||||||
private void getRedirect_oauth2(
|
private void getRedirect_oauth2(
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
@ -164,12 +144,45 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
response.sendRedirect(redirectUrl);
|
response.sendRedirect(redirectUrl);
|
||||||
}
|
}
|
||||||
default -> {
|
default -> {
|
||||||
log.info("Redirecting to default logout URL: {}", redirectUrl);
|
String logoutUrl = oauth.getLogoutUrl();
|
||||||
response.sendRedirect(redirectUrl);
|
|
||||||
|
if (StringUtils.isNotBlank(logoutUrl)) {
|
||||||
|
log.info("Redirecting to logout URL: {}", logoutUrl);
|
||||||
|
response.sendRedirect(logoutUrl);
|
||||||
|
} else {
|
||||||
|
log.info("Redirecting to default logout URL: {}", redirectUrl);
|
||||||
|
response.sendRedirect(redirectUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Redirect for OAuth2 authentication logout
|
||||||
|
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 idpUrl = samlConf.getIdpSingleLogoutUrl();
|
||||||
|
|
||||||
|
String idpIssuer = samlConf.getIdpIssuer();
|
||||||
|
|
||||||
|
// Create SamlClient instance for SAML logout
|
||||||
|
return new SamlClient(
|
||||||
|
relyingPartyIdentifier,
|
||||||
|
assertionConsumerServiceUrl,
|
||||||
|
idpUrl,
|
||||||
|
idpIssuer,
|
||||||
|
certificates,
|
||||||
|
SamlClient.SamlIdpBinding.POST);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles different error scenarios during logout. Will return a <code>String</code> containing
|
* Handles different error scenarios during logout. Will return a <code>String</code> containing
|
||||||
* the error request parameter.
|
* the error request parameter.
|
||||||
|
@ -23,6 +23,10 @@ import org.springframework.security.saml2.provider.service.web.authentication.Op
|
|||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||||
|
import org.springframework.security.web.context.DelegatingSecurityContextRepository;
|
||||||
|
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||||
|
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
|
||||||
|
import org.springframework.security.web.context.SecurityContextRepository;
|
||||||
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
|
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
|
||||||
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
|
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
|
||||||
import org.springframework.security.web.savedrequest.NullRequestCache;
|
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||||
@ -51,11 +55,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;
|
||||||
@ -105,10 +105,11 @@ public class SecurityConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http, SecurityContextRepository securityContextRepository) throws Exception {
|
||||||
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 +165,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
|
||||||
@ -234,7 +234,7 @@ public class SecurityConfiguration {
|
|||||||
.
|
.
|
||||||
/*
|
/*
|
||||||
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,14 +258,20 @@ public class SecurityConfiguration {
|
|||||||
.permitAll());
|
.permitAll());
|
||||||
}
|
}
|
||||||
// Handle SAML
|
// Handle SAML
|
||||||
if (applicationProperties.getSecurity().isSaml2Active()) {
|
if (applicationProperties.getSecurity().isSaml2Active() && runningEE) {
|
||||||
// && runningEE
|
|
||||||
// Configure the authentication provider
|
// Configure the authentication provider
|
||||||
OpenSaml4AuthenticationProvider authenticationProvider =
|
OpenSaml4AuthenticationProvider authenticationProvider =
|
||||||
new OpenSaml4AuthenticationProvider();
|
new OpenSaml4AuthenticationProvider();
|
||||||
authenticationProvider.setResponseAuthenticationConverter(
|
authenticationProvider.setResponseAuthenticationConverter(
|
||||||
new CustomSaml2ResponseAuthenticationConverter(userService));
|
new CustomSaml2ResponseAuthenticationConverter(userService));
|
||||||
http.authenticationProvider(authenticationProvider)
|
http.authenticationProvider(authenticationProvider)
|
||||||
|
.securityContext(security ->
|
||||||
|
security.securityContextRepository(
|
||||||
|
new DelegatingSecurityContextRepository(
|
||||||
|
new RequestAttributeSecurityContextRepository(),
|
||||||
|
new HttpSessionSecurityContextRepository())
|
||||||
|
)
|
||||||
|
)
|
||||||
.saml2Login(
|
.saml2Login(
|
||||||
saml2 -> {
|
saml2 -> {
|
||||||
try {
|
try {
|
||||||
@ -284,12 +290,13 @@ public class SecurityConfiguration {
|
|||||||
.authenticationRequestResolver(
|
.authenticationRequestResolver(
|
||||||
saml2AuthenticationRequestResolver);
|
saml2AuthenticationRequestResolver);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error configuring SAML2 login", e);
|
log.error("Error configuring SAML 2 login", e);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} 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 +322,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;
|
||||||
|
@ -373,18 +373,15 @@ 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 =
|
|
||||||
(CustomSaml2AuthenticatedPrincipal) principal;
|
|
||||||
usernameP = saml2User.getName();
|
usernameP = saml2User.getName();
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
usernameP = (String) principal;
|
usernameP = (String) principal;
|
||||||
@ -398,6 +395,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) {
|
||||||
@ -406,8 +404,6 @@ public class UserService implements UserServiceInterface {
|
|||||||
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).getName();
|
||||||
} else if (principal instanceof String) {
|
|
||||||
return (String) principal;
|
|
||||||
} else {
|
} else {
|
||||||
return principal.toString();
|
return principal.toString();
|
||||||
}
|
}
|
||||||
|
@ -50,8 +50,11 @@ 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(
|
||||||
|
"OAuth2 Authentication error: {}",
|
||||||
|
errorCode != null ? errorCode : exception.getMessage(),
|
||||||
|
exception);
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?errorOAuth=" + errorCode);
|
getRedirectStrategy().sendRedirect(request, response, "/login?errorOAuth=" + errorCode);
|
||||||
}
|
}
|
||||||
log.error("Unhandled authentication exception", exception);
|
log.error("Unhandled authentication exception", exception);
|
||||||
|
@ -47,23 +47,31 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
|
|||||||
OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2();
|
OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2();
|
||||||
UsernameAttribute usernameAttribute =
|
UsernameAttribute usernameAttribute =
|
||||||
UsernameAttribute.valueOf(oauth2.getUseAsUsername().toUpperCase());
|
UsernameAttribute.valueOf(oauth2.getUseAsUsername().toUpperCase());
|
||||||
String username = usernameAttribute.getName();
|
String usernameAttributeKey = usernameAttribute.getName();
|
||||||
|
|
||||||
Optional<User> internalUser = userService.findByUsernameIgnoreCase(username);
|
// todo: save user by OIDC ID instead of username
|
||||||
|
Optional<User> internalUser =
|
||||||
|
userService.findByUsernameIgnoreCase(user.getAttribute(usernameAttributeKey));
|
||||||
|
|
||||||
if (internalUser.isPresent()) {
|
if (internalUser.isPresent()) {
|
||||||
if (loginAttemptService.isBlocked(username)) {
|
String internalUsername = internalUser.get().getUsername();
|
||||||
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a new OidcUser with adjusted attributes
|
// Return a new OidcUser with adjusted attributes
|
||||||
return new DefaultOidcUser(
|
return new DefaultOidcUser(
|
||||||
user.getAuthorities(), userRequest.getIdToken(), user.getUserInfo(), username);
|
user.getAuthorities(),
|
||||||
|
userRequest.getIdToken(),
|
||||||
|
user.getUserInfo(),
|
||||||
|
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);
|
||||||
|
@ -94,7 +94,7 @@ public class OAuth2Configuration {
|
|||||||
.clientId(keycloak.getClientId())
|
.clientId(keycloak.getClientId())
|
||||||
.clientSecret(keycloak.getClientSecret())
|
.clientSecret(keycloak.getClientSecret())
|
||||||
.scope(keycloak.getScopes())
|
.scope(keycloak.getScopes())
|
||||||
.userNameAttributeName(keycloak.getUseAsUsername().name())
|
.userNameAttributeName(keycloak.getUseAsUsername().getName())
|
||||||
.clientName(keycloak.getClientName())
|
.clientName(keycloak.getClientName())
|
||||||
.build())
|
.build())
|
||||||
: Optional.empty();
|
: Optional.empty();
|
||||||
@ -125,7 +125,7 @@ public class OAuth2Configuration {
|
|||||||
.authorizationUri(google.getAuthorizationUri())
|
.authorizationUri(google.getAuthorizationUri())
|
||||||
.tokenUri(google.getTokenUri())
|
.tokenUri(google.getTokenUri())
|
||||||
.userInfoUri(google.getUserInfoUri())
|
.userInfoUri(google.getUserInfoUri())
|
||||||
.userNameAttributeName(google.getUseAsUsername().name())
|
.userNameAttributeName(google.getUseAsUsername().getName())
|
||||||
.clientName(google.getClientName())
|
.clientName(google.getClientName())
|
||||||
.redirectUri(REDIRECT_URI_PATH + google.getName())
|
.redirectUri(REDIRECT_URI_PATH + google.getName())
|
||||||
.authorizationGrantType(AUTHORIZATION_CODE)
|
.authorizationGrantType(AUTHORIZATION_CODE)
|
||||||
@ -158,7 +158,7 @@ public class OAuth2Configuration {
|
|||||||
.authorizationUri(github.getAuthorizationUri())
|
.authorizationUri(github.getAuthorizationUri())
|
||||||
.tokenUri(github.getTokenUri())
|
.tokenUri(github.getTokenUri())
|
||||||
.userInfoUri(github.getUserInfoUri())
|
.userInfoUri(github.getUserInfoUri())
|
||||||
.userNameAttributeName(github.getUseAsUsername().name())
|
.userNameAttributeName(github.getUseAsUsername().getName())
|
||||||
.clientName(github.getClientName())
|
.clientName(github.getClientName())
|
||||||
.redirectUri(REDIRECT_URI_PATH + github.getName())
|
.redirectUri(REDIRECT_URI_PATH + github.getName())
|
||||||
.authorizationGrantType(AUTHORIZATION_CODE)
|
.authorizationGrantType(AUTHORIZATION_CODE)
|
||||||
@ -186,6 +186,7 @@ public class OAuth2Configuration {
|
|||||||
oauth.getClientSecret(),
|
oauth.getClientSecret(),
|
||||||
oauth.getScopes(),
|
oauth.getScopes(),
|
||||||
UsernameAttribute.valueOf(oauth.getUseAsUsername().toUpperCase()),
|
UsernameAttribute.valueOf(oauth.getUseAsUsername().toUpperCase()),
|
||||||
|
oauth.getLogoutUrl(),
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null);
|
null);
|
||||||
@ -220,9 +221,7 @@ public class OAuth2Configuration {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(
|
@ConditionalOnProperty(value = "security.oauth2.enabled", havingValue = "true")
|
||||||
value = "security.oauth2.enabled",
|
|
||||||
havingValue = "true")
|
|
||||||
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
||||||
return (authorities) -> {
|
return (authorities) -> {
|
||||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||||
|
@ -8,7 +8,6 @@ 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;
|
||||||
|
|
||||||
@ -22,7 +21,9 @@ 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()
|
||||||
@ -34,6 +35,5 @@ public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthentica
|
|||||||
response,
|
response,
|
||||||
"/login?errorOAuth=not_authentication_provider_found");
|
"/login?errorOAuth=not_authentication_provider_found");
|
||||||
}
|
}
|
||||||
log.error("AuthenticationException: " + exception);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,13 +112,11 @@ public class CustomSaml2AuthenticationSuccessHandler
|
|||||||
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 {
|
||||||
|
@ -21,7 +21,7 @@ import stirling.software.SPDF.model.User;
|
|||||||
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 +61,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,10 +84,8 @@ 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<>();
|
||||||
@ -102,7 +100,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;
|
||||||
@ -39,7 +41,7 @@ public class SAML2Configuration {
|
|||||||
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
||||||
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();
|
||||||
@ -51,15 +53,26 @@ 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);
|
||||||
@ -71,58 +84,93 @@ public class SAML2Configuration {
|
|||||||
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(
|
|
||||||
"Header - {}: {}",
|
|
||||||
headerName,
|
|
||||||
request.getHeader(headerName));
|
|
||||||
});
|
|
||||||
// Log SAML specific parameters
|
|
||||||
log.debug("SAML Request Parameters:");
|
|
||||||
log.debug("SAMLRequest: {}", request.getParameter("SAMLRequest"));
|
|
||||||
log.debug("RelayState: {}", request.getParameter("RelayState"));
|
|
||||||
// Log session debugrmation if exists
|
|
||||||
if (request.getSession(false) != null) {
|
|
||||||
log.debug("Session ID: {}", request.getSession().getId());
|
|
||||||
}
|
|
||||||
// Log any assertions consumer service details if present
|
|
||||||
if (authnRequest.getAssertionConsumerServiceURL() != null) {
|
|
||||||
log.debug(
|
log.debug(
|
||||||
"AssertionConsumerServiceURL: {}",
|
"Retrieving SAML 2 authentication request ID from the current HTTP session {}",
|
||||||
authnRequest.getAssertionConsumerServiceURL());
|
sessionId);
|
||||||
}
|
|
||||||
// Log NameID policy if present
|
String authenticationRequestId = saml2AuthenticationRequest.getId();
|
||||||
if (authnRequest.getNameIDPolicy() != null) {
|
|
||||||
log.debug(
|
if (!authenticationRequestId.isBlank()) {
|
||||||
"NameIDPolicy Format: {}",
|
authnRequest.setID(authenticationRequestId);
|
||||||
authnRequest.getNameIDPolicy().getFormat());
|
} else {
|
||||||
|
log.warn(
|
||||||
|
"No authentication request found for HTTP session {}. Generating new ID",
|
||||||
|
sessionId);
|
||||||
|
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.debug("Generating new authentication request ID");
|
||||||
|
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logAuthnRequestDetails(authnRequest);
|
||||||
|
logHttpRequestDetails(request);
|
||||||
});
|
});
|
||||||
return resolver;
|
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"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,7 +295,7 @@ 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();
|
||||||
@ -307,8 +307,8 @@ public class UserController {
|
|||||||
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,12 @@ 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;
|
||||||
@ -26,11 +31,15 @@ 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.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.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;
|
||||||
@ -74,7 +83,7 @@ public class AccountWebController {
|
|||||||
String firstChar = String.valueOf(oauth.getProvider().charAt(0));
|
String firstChar = String.valueOf(oauth.getProvider().charAt(0));
|
||||||
String clientName =
|
String clientName =
|
||||||
oauth.getProvider().replaceFirst(firstChar, firstChar.toUpperCase());
|
oauth.getProvider().replaceFirst(firstChar, firstChar.toUpperCase());
|
||||||
providerList.put(OAUTH_2_AUTHORIZATION + "oidc", clientName);
|
providerList.put(OAUTH_2_AUTHORIZATION + oauth.getProvider(), clientName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Client client = oauth.getClient();
|
Client client = oauth.getClient();
|
||||||
@ -108,8 +117,16 @@ public class AccountWebController {
|
|||||||
SAML2 saml2 = securityProps.getSaml2();
|
SAML2 saml2 = securityProps.getSaml2();
|
||||||
|
|
||||||
if (securityProps.isSaml2Active()
|
if (securityProps.isSaml2Active()
|
||||||
&& applicationProperties.getSystem().getEnableAlphaFunctionality()) {
|
&& applicationProperties.getSystem().getEnableAlphaFunctionality()
|
||||||
providerList.put("/saml2/authenticate/" + saml2.getRegistrationId(), "SAML 2");
|
&& applicationProperties.getEnterpriseEdition().isEnabled()) {
|
||||||
|
String samlIdp = saml2.getProvider();
|
||||||
|
String saml2AuthenticationPath = "/saml2/authenticate/" + saml2.getRegistrationId();
|
||||||
|
|
||||||
|
if (applicationProperties.getEnterpriseEdition().isSsoAutoLogin()) {
|
||||||
|
return "redirect:login" + saml2AuthenticationPath;
|
||||||
|
} else {
|
||||||
|
providerList.put(saml2AuthenticationPath, samlIdp + " (SAML 2)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove any null keys/values from the providerList
|
// Remove any null keys/values from the providerList
|
||||||
@ -134,7 +151,9 @@ public class AccountWebController {
|
|||||||
|
|
||||||
model.addAttribute("error", error);
|
model.addAttribute("error", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
String errorOAuth = request.getParameter("errorOAuth");
|
String errorOAuth = request.getParameter("errorOAuth");
|
||||||
|
|
||||||
if (errorOAuth != null) {
|
if (errorOAuth != null) {
|
||||||
switch (errorOAuth) {
|
switch (errorOAuth) {
|
||||||
case "oAuth2AutoCreateDisabled" -> errorOAuth = "login.oAuth2AutoCreateDisabled";
|
case "oAuth2AutoCreateDisabled" -> errorOAuth = "login.oAuth2AutoCreateDisabled";
|
||||||
@ -142,19 +161,23 @@ public class AccountWebController {
|
|||||||
case "userAlreadyExistsWeb" -> errorOAuth = "userAlreadyExistsWebMessage";
|
case "userAlreadyExistsWeb" -> errorOAuth = "userAlreadyExistsWebMessage";
|
||||||
case "oAuth2AuthenticationErrorWeb" -> errorOAuth = "login.oauth2InvalidUserType";
|
case "oAuth2AuthenticationErrorWeb" -> errorOAuth = "login.oauth2InvalidUserType";
|
||||||
case "invalid_token_response" -> errorOAuth = "login.oauth2InvalidTokenResponse";
|
case "invalid_token_response" -> errorOAuth = "login.oauth2InvalidTokenResponse";
|
||||||
case "authorization_request_not_found" -> errorOAuth = "login.oauth2RequestNotFound";
|
case "authorization_request_not_found" ->
|
||||||
|
errorOAuth = "login.oauth2RequestNotFound";
|
||||||
case "access_denied" -> errorOAuth = "login.oauth2AccessDenied";
|
case "access_denied" -> errorOAuth = "login.oauth2AccessDenied";
|
||||||
case "invalid_user_info_response" -> errorOAuth = "login.oauth2InvalidUserInfoResponse";
|
case "invalid_user_info_response" ->
|
||||||
|
errorOAuth = "login.oauth2InvalidUserInfoResponse";
|
||||||
case "invalid_request" -> errorOAuth = "login.oauth2invalidRequest";
|
case "invalid_request" -> errorOAuth = "login.oauth2invalidRequest";
|
||||||
case "invalid_id_token" -> errorOAuth = "login.oauth2InvalidIdToken";
|
case "invalid_id_token" -> errorOAuth = "login.oauth2InvalidIdToken";
|
||||||
case "oAuth2AdminBlockedUser" -> errorOAuth = "login.oAuth2AdminBlockedUser";
|
case "oAuth2AdminBlockedUser" -> errorOAuth = "login.oAuth2AdminBlockedUser";
|
||||||
case "userIsDisabled" -> errorOAuth = "login.userIsDisabled";
|
case "userIsDisabled" -> errorOAuth = "login.userIsDisabled";
|
||||||
case "invalid_destination" -> errorOAuth = "login.invalid_destination";
|
case "invalid_destination" -> errorOAuth = "login.invalid_destination";
|
||||||
case "relying_party_registration_not_found" -> errorOAuth = "login.relyingPartyRegistrationNotFound";
|
case "relying_party_registration_not_found" ->
|
||||||
|
errorOAuth = "login.relyingPartyRegistrationNotFound";
|
||||||
// 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" -> errorOAuth = "login.invalid_in_response_to";
|
case "invalid_in_response_to" -> errorOAuth = "login.invalid_in_response_to";
|
||||||
case "not_authentication_provider_found" -> errorOAuth = "login.not_authentication_provider_found";
|
case "not_authentication_provider_found" ->
|
||||||
|
errorOAuth = "login.not_authentication_provider_found";
|
||||||
}
|
}
|
||||||
|
|
||||||
model.addAttribute("errorOAuth", errorOAuth);
|
model.addAttribute("errorOAuth", errorOAuth);
|
||||||
@ -211,13 +234,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);
|
||||||
}
|
}
|
||||||
@ -254,53 +275,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) {
|
||||||
switch (messageType) {
|
deleteMessage =
|
||||||
case "deleteCurrentUser":
|
switch (messageType) {
|
||||||
deleteMessage = "deleteCurrentUserMessage";
|
case "deleteCurrentUser" -> "deleteCurrentUserMessage";
|
||||||
break;
|
case "deleteUsernameExists" -> "deleteUsernameExistsMessage";
|
||||||
case "deleteUsernameExists":
|
default -> null;
|
||||||
deleteMessage = "deleteUsernameExistsMessage";
|
};
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
model.addAttribute("deleteMessage", deleteMessage);
|
model.addAttribute("deleteMessage", deleteMessage);
|
||||||
String addMessage = null;
|
|
||||||
switch (messageType) {
|
String addMessage;
|
||||||
case "usernameExists":
|
addMessage =
|
||||||
addMessage = "usernameExistsMessage";
|
switch (messageType) {
|
||||||
break;
|
case "usernameExists" -> "usernameExistsMessage";
|
||||||
case "invalidUsername":
|
case "invalidUsername" -> "invalidUsernameMessage";
|
||||||
addMessage = "invalidUsernameMessage";
|
case "invalidPassword" -> "invalidPasswordMessage";
|
||||||
break;
|
default -> null;
|
||||||
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) {
|
||||||
switch (messageType) {
|
changeMessage =
|
||||||
case "userNotFound":
|
switch (messageType) {
|
||||||
changeMessage = "userNotFoundMessage";
|
case "userNotFound" -> "userNotFoundMessage";
|
||||||
break;
|
case "downgradeCurrentUser" -> "downgradeCurrentUserMessage";
|
||||||
case "downgradeCurrentUser":
|
case "disabledCurrentUser" -> "disabledCurrentUserMessage";
|
||||||
changeMessage = "downgradeCurrentUserMessage";
|
default -> messageType;
|
||||||
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);
|
||||||
@ -321,39 +330,35 @@ 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;
|
||||||
|
|
||||||
|
// Retrieve username and other attributes and add login attributes to the model
|
||||||
if (principal instanceof UserDetails userDetails) {
|
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 userDetails) {
|
if (principal instanceof OAuth2User userDetails) {
|
||||||
// Retrieve username and other attributes
|
|
||||||
username = userDetails.getName();
|
username = userDetails.getName();
|
||||||
// Add oAuth2 Login attributes to the model
|
|
||||||
model.addAttribute("oAuth2Login", true);
|
model.addAttribute("oAuth2Login", true);
|
||||||
}
|
}
|
||||||
if (principal instanceof CustomSaml2AuthenticatedPrincipal userDetails) {
|
if (principal instanceof CustomSaml2AuthenticatedPrincipal userDetails) {
|
||||||
// Retrieve username and other attributes
|
|
||||||
username = userDetails.getName();
|
username = userDetails.getName();
|
||||||
// Add oAuth2 Login attributes to the model
|
model.addAttribute("saml2Login", true);
|
||||||
model.addAttribute("oAuth2Login", true);
|
|
||||||
}
|
}
|
||||||
if (username != null) {
|
if (username != null) {
|
||||||
// Fetch user details from the database, assuming findByUsername method exists
|
// Fetch user details from the database
|
||||||
Optional<User> user = userRepository.findByUsernameIgnoreCaseWithSettings(username);
|
Optional<User> user = userRepository.findByUsernameIgnoreCaseWithSettings(username);
|
||||||
|
|
||||||
if (!user.isPresent()) {
|
if (user.isEmpty()) {
|
||||||
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";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,7 +372,7 @@ public class AccountWebController {
|
|||||||
case "invalidUsername" -> messageType = "invalidUsernameMessage";
|
case "invalidUsername" -> messageType = "invalidUsernameMessage";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add attributes to the model
|
|
||||||
model.addAttribute("username", username);
|
model.addAttribute("username", username);
|
||||||
model.addAttribute("messageType", messageType);
|
model.addAttribute("messageType", messageType);
|
||||||
model.addAttribute("role", user.get().getRolesAsString());
|
model.addAttribute("role", user.get().getRolesAsString());
|
||||||
@ -390,19 +395,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");
|
||||||
@ -425,7 +423,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 {
|
||||||
|
@ -14,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;
|
||||||
@ -110,7 +109,7 @@ public class ApplicationProperties {
|
|||||||
private int loginAttemptCount;
|
private int loginAttemptCount;
|
||||||
private long loginResetTimeMinutes;
|
private long loginResetTimeMinutes;
|
||||||
private String loginMethod = "all";
|
private String loginMethod = "all";
|
||||||
private String customGlobalAPIKey;
|
private String customGlobalAPIKey; // todo: expose?
|
||||||
|
|
||||||
public Boolean isAltLogin() {
|
public Boolean isAltLogin() {
|
||||||
return saml2.getEnabled() || oauth2.getEnabled();
|
return saml2.getEnabled() || oauth2.getEnabled();
|
||||||
@ -139,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()));
|
||||||
@ -161,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;
|
||||||
@ -198,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()));
|
||||||
@ -228,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)
|
|
||||||
.toList();
|
|
||||||
this.scopes.addAll(scopesList);
|
this.scopes.addAll(scopesList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,7 +265,9 @@ public class ApplicationProperties {
|
|||||||
case "keycloak" -> getKeycloak();
|
case "keycloak" -> getKeycloak();
|
||||||
default ->
|
default ->
|
||||||
throw new UnsupportedProviderException(
|
throw new UnsupportedProviderException(
|
||||||
"Logout from the provider " + registrationId + " is not supported. "
|
"Logout from the provider "
|
||||||
|
+ registrationId
|
||||||
|
+ " is not supported. "
|
||||||
+ "Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues");
|
+ "Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,17 @@ import lombok.Getter;
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public enum UsernameAttribute {
|
public enum UsernameAttribute {
|
||||||
NAME("name"),
|
|
||||||
EMAIL("email"),
|
EMAIL("email"),
|
||||||
GIVEN_NAME("given_name"),
|
|
||||||
PREFERRED_NAME("preferred_name"),
|
|
||||||
PREFERRED_USERNAME("preferred_username"),
|
|
||||||
LOGIN("login"),
|
LOGIN("login"),
|
||||||
|
PROFILE("profile"),
|
||||||
|
NAME("name"),
|
||||||
|
USERNAME("username"),
|
||||||
|
NICKNAME("nickname"),
|
||||||
|
GIVEN_NAME("given_name"),
|
||||||
|
MIDDLE_NAME("middle_name"),
|
||||||
FAMILY_NAME("family_name"),
|
FAMILY_NAME("family_name"),
|
||||||
NICKNAME("nickname");
|
PREFERRED_NAME("preferred_name"),
|
||||||
|
PREFERRED_USERNAME("preferred_username");
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ public class GitHubProvider extends Provider {
|
|||||||
clientSecret,
|
clientSecret,
|
||||||
scopes,
|
scopes,
|
||||||
useAsUsername != null ? useAsUsername : UsernameAttribute.LOGIN,
|
useAsUsername != null ? useAsUsername : UsernameAttribute.LOGIN,
|
||||||
|
null,
|
||||||
AUTHORIZATION_URI,
|
AUTHORIZATION_URI,
|
||||||
TOKEN_URI,
|
TOKEN_URI,
|
||||||
USER_INFO_URI);
|
USER_INFO_URI);
|
||||||
|
@ -29,6 +29,7 @@ public class GoogleProvider extends Provider {
|
|||||||
clientSecret,
|
clientSecret,
|
||||||
scopes,
|
scopes,
|
||||||
useAsUsername,
|
useAsUsername,
|
||||||
|
null,
|
||||||
AUTHORIZATION_URI,
|
AUTHORIZATION_URI,
|
||||||
TOKEN_URI,
|
TOKEN_URI,
|
||||||
USER_INFO_URI);
|
USER_INFO_URI);
|
||||||
|
@ -28,6 +28,7 @@ public class KeycloakProvider extends Provider {
|
|||||||
useAsUsername,
|
useAsUsername,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ import stirling.software.SPDF.model.exception.UnsupportedUsernameAttribute;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class Provider {
|
public class Provider {
|
||||||
|
|
||||||
|
public static final String EXCEPTION_MESSAGE = "The attribute %s is not supported for %s.";
|
||||||
|
|
||||||
private String issuer;
|
private String issuer;
|
||||||
private String name;
|
private String name;
|
||||||
private String clientName;
|
private String clientName;
|
||||||
@ -23,6 +25,7 @@ public class Provider {
|
|||||||
private String clientSecret;
|
private String clientSecret;
|
||||||
private Collection<String> scopes;
|
private Collection<String> scopes;
|
||||||
private UsernameAttribute useAsUsername;
|
private UsernameAttribute useAsUsername;
|
||||||
|
private String logoutUrl;
|
||||||
private String authorizationUri;
|
private String authorizationUri;
|
||||||
private String tokenUri;
|
private String tokenUri;
|
||||||
private String userInfoUri;
|
private String userInfoUri;
|
||||||
@ -35,6 +38,7 @@ public class Provider {
|
|||||||
String clientSecret,
|
String clientSecret,
|
||||||
Collection<String> scopes,
|
Collection<String> scopes,
|
||||||
UsernameAttribute useAsUsername,
|
UsernameAttribute useAsUsername,
|
||||||
|
String logoutUrl,
|
||||||
String authorizationUri,
|
String authorizationUri,
|
||||||
String tokenUri,
|
String tokenUri,
|
||||||
String userInfoUri) {
|
String userInfoUri) {
|
||||||
@ -46,6 +50,7 @@ public class Provider {
|
|||||||
this.scopes = scopes == null ? new ArrayList<>() : scopes;
|
this.scopes = scopes == null ? new ArrayList<>() : scopes;
|
||||||
this.useAsUsername =
|
this.useAsUsername =
|
||||||
useAsUsername != null ? validateUsernameAttribute(useAsUsername) : EMAIL;
|
useAsUsername != null ? validateUsernameAttribute(useAsUsername) : EMAIL;
|
||||||
|
this.logoutUrl = logoutUrl;
|
||||||
this.authorizationUri = authorizationUri;
|
this.authorizationUri = authorizationUri;
|
||||||
this.tokenUri = tokenUri;
|
this.tokenUri = tokenUri;
|
||||||
this.userInfoUri = userInfoUri;
|
this.userInfoUri = userInfoUri;
|
||||||
@ -83,41 +88,29 @@ public class Provider {
|
|||||||
}
|
}
|
||||||
default ->
|
default ->
|
||||||
throw new UnsupportedUsernameAttribute(
|
throw new UnsupportedUsernameAttribute(
|
||||||
"The attribute "
|
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
|
||||||
+ usernameAttribute
|
|
||||||
+ "is not supported for "
|
|
||||||
+ clientName
|
|
||||||
+ ".");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private UsernameAttribute validateGoogleUsernameAttribute(UsernameAttribute usernameAttribute) {
|
private UsernameAttribute validateGoogleUsernameAttribute(UsernameAttribute usernameAttribute) {
|
||||||
switch (usernameAttribute) {
|
switch (usernameAttribute) {
|
||||||
case EMAIL, NAME, GIVEN_NAME, PREFERRED_NAME -> {
|
case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME -> {
|
||||||
return usernameAttribute;
|
return usernameAttribute;
|
||||||
}
|
}
|
||||||
default ->
|
default ->
|
||||||
throw new UnsupportedUsernameAttribute(
|
throw new UnsupportedUsernameAttribute(
|
||||||
"The attribute "
|
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
|
||||||
+ usernameAttribute
|
|
||||||
+ "is not supported for "
|
|
||||||
+ clientName
|
|
||||||
+ ".");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private UsernameAttribute validateGitHubUsernameAttribute(UsernameAttribute usernameAttribute) {
|
private UsernameAttribute validateGitHubUsernameAttribute(UsernameAttribute usernameAttribute) {
|
||||||
switch (usernameAttribute) {
|
switch (usernameAttribute) {
|
||||||
case EMAIL, NAME, LOGIN -> {
|
case LOGIN, EMAIL, NAME -> {
|
||||||
return usernameAttribute;
|
return usernameAttribute;
|
||||||
}
|
}
|
||||||
default ->
|
default ->
|
||||||
throw new UnsupportedUsernameAttribute(
|
throw new UnsupportedUsernameAttribute(
|
||||||
"The attribute "
|
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
|
||||||
+ usernameAttribute
|
|
||||||
+ "is not supported for "
|
|
||||||
+ clientName
|
|
||||||
+ ".");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ security:
|
|||||||
csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production)
|
csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production)
|
||||||
loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1
|
loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1
|
||||||
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
|
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
|
||||||
loginMethod: saml2 # Accepts values like 'all' and 'normal'(only Login with Username/Password), 'oauth2'(only Login with OAuth2) or 'saml2'(only Login with SAML2)
|
loginMethod: all # Accepts values like 'all' and 'normal'(only Login with Username/Password), 'oauth2'(only Login with OAuth2) or 'saml2'(only Login with SAML2)
|
||||||
initialLogin:
|
initialLogin:
|
||||||
username: '' # initial username for the first login
|
username: '' # initial username for the first login
|
||||||
password: '' # initial password for the first login
|
password: '' # initial password for the first login
|
||||||
@ -28,42 +28,44 @@ 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 | 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: email, 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: https://trial-6373896.okta.com/home/okta_flow_sso/0oaok4lk1nVvNBnqK697/alnbibn6b0OPFATt20g7 # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) endpoint
|
||||||
clientId: '' # client ID from your provider
|
clientId: 0oaok4lk4eNm6PtFD697 # client ID from your provider
|
||||||
clientSecret: '' # client secret from your provider
|
clientSecret: lmwlmxFZSJ0miOoRpUAKf2jg8tVPPXhUxgL2VB-b4uJfhnk4sI02YodKWRX8fLSq # client secret from your provider
|
||||||
|
logoutUrl: ''
|
||||||
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: username # 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: okta.users.read, okta.users.read.self, okta.users.manage.self, okta.groups.read # 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: true # Only enabled for paid enterprise clients (enterpriseEdition.enabled must be true)
|
||||||
|
provider: okta
|
||||||
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: stirlingpdf-dario-saml
|
||||||
idpMetadataUri: https://dev-XXXXXXXX.okta.com/app/externalKey/sso/saml/metadata
|
idpMetadataUri: https://trial-6373896.okta.com/app/exkomkf71reALy12X697/sso/saml/metadata # todo: remove
|
||||||
idpSingleLogoutUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/slo/saml
|
idpSingleLoginUrl: https://trial-6373896.okta.com/app/trial-6373896_stirlingpdfsaml2_1/exkoot0g5ipqOF3Bo697/sso/saml # todo: remove
|
||||||
idpSingleLoginUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/sso/saml
|
idpSingleLogoutUrl: https://trial-6373896.okta.com/app/trial-6373896_stirlingpdfsaml2_1/exkoot0g5ipqOF3Bo697/slo/saml # todo: remove
|
||||||
idpIssuer: http://www.okta.com/externalKey
|
idpIssuer: http://www.okta.com/exkoot0g5ipqOF3Bo697
|
||||||
idpCert: classpath:okta.crt
|
idpCert: classpath:okta.cert
|
||||||
privateKey: classpath:saml-private-key.key
|
privateKey: classpath:private_key.key
|
||||||
spCert: classpath:saml-public-cert.crt
|
spCert: classpath:certificate.crt
|
||||||
|
|
||||||
enterpriseEdition:
|
enterpriseEdition:
|
||||||
enabled: false # set to 'true' to enable enterprise edition
|
enabled: true # set to 'true' to enable enterprise edition
|
||||||
key: 00000000-0000-0000-0000-000000000000
|
key: 00000000-0000-0000-0000-000000000000
|
||||||
SSOAutoLogin: false # Enable to auto login to first provided SSO
|
SSOAutoLogin: true # Enable to auto login to first provided SSO
|
||||||
CustomMetadata:
|
CustomMetadata:
|
||||||
autoUpdateMetadata: false # set to 'true' to automatically update metadata with below values
|
autoUpdateMetadata: false # set to 'true' to automatically update metadata with below values
|
||||||
author: username # supports text such as 'John Doe' or types such as username to autopopulate with user's username
|
author: username # supports text such as 'John Doe' or types such as username to autopopulate with user's username
|
||||||
@ -80,7 +82,7 @@ legal:
|
|||||||
system:
|
system:
|
||||||
defaultLocale: en-US # set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
defaultLocale: en-US # set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
||||||
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
|
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
|
||||||
enableAlphaFunctionality: false # set to enable functionality which might need more testing before it fully goes live (this feature might make no changes)
|
enableAlphaFunctionality: true # set to enable functionality which might need more testing before it fully goes live (this feature might make no changes)
|
||||||
showUpdate: false # see when a new update is available
|
showUpdate: false # see when a new update is available
|
||||||
showUpdateOnlyAdmin: false # only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
|
showUpdateOnlyAdmin: false # only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
|
||||||
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template HTML files
|
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template HTML files
|
||||||
|
@ -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>
|
||||||
|
@ -98,7 +98,7 @@
|
|||||||
<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>
|
||||||
|
@ -29,7 +29,6 @@ class ValidatorTest {
|
|||||||
when(provider.getClientId()).thenReturn("clientId");
|
when(provider.getClientId()).thenReturn("clientId");
|
||||||
when(provider.getClientSecret()).thenReturn("clientSecret");
|
when(provider.getClientSecret()).thenReturn("clientSecret");
|
||||||
when(provider.getScopes()).thenReturn(List.of("read:user"));
|
when(provider.getScopes()).thenReturn(List.of("read:user"));
|
||||||
when(provider.getUseAsUsername()).thenReturn(UsernameAttribute.EMAIL);
|
|
||||||
|
|
||||||
assertTrue(Validator.validateProvider(provider));
|
assertTrue(Validator.validateProvider(provider));
|
||||||
}
|
}
|
||||||
@ -44,15 +43,11 @@ class ValidatorTest {
|
|||||||
Provider generic = null;
|
Provider generic = null;
|
||||||
var google = new GoogleProvider(null, "clientSecret", List.of("scope"), UsernameAttribute.EMAIL);
|
var google = new GoogleProvider(null, "clientSecret", List.of("scope"), UsernameAttribute.EMAIL);
|
||||||
var github = new GitHubProvider("clientId", "", List.of("scope"), UsernameAttribute.LOGIN);
|
var github = new GitHubProvider("clientId", "", List.of("scope"), UsernameAttribute.LOGIN);
|
||||||
var keycloak = new KeycloakProvider("issuer", "clientId", "clientSecret", List.of("scope"), UsernameAttribute.EMAIL);
|
|
||||||
|
|
||||||
keycloak.setUseAsUsername(null);
|
|
||||||
|
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
Arguments.of(generic),
|
Arguments.of(generic),
|
||||||
Arguments.of(google),
|
Arguments.of(google),
|
||||||
Arguments.of(github),
|
Arguments.of(github)
|
||||||
Arguments.of(keycloak)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user