mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-22 15:35:03 +00:00
oauth to saml and compare fixes etc
This commit is contained in:
parent
2885fac30d
commit
d20e8f7d54
@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
@ -30,6 +31,7 @@ public class InitialSecuritySetup {
|
||||
initializeAdminUser();
|
||||
} else {
|
||||
databaseBackupHelper.exportDatabase();
|
||||
userService.migrateOauth2ToSSO();
|
||||
}
|
||||
initializeInternalApiUser();
|
||||
}
|
||||
@ -75,4 +77,7 @@ public class InitialSecuritySetup {
|
||||
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -107,13 +107,14 @@ public class SecurityConfiguration {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||
http.csrf(csrf -> csrf.disable());
|
||||
}
|
||||
|
||||
if (loginEnabledValue) {
|
||||
http.addFilterBefore(
|
||||
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||
http.csrf(csrf -> csrf.disable());
|
||||
} else {
|
||||
if (!applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||
CookieCsrfTokenRepository cookieRepo =
|
||||
CookieCsrfTokenRepository.withHttpOnlyFalse();
|
||||
CsrfTokenRequestAttributeHandler requestHandler =
|
||||
@ -268,11 +269,10 @@ public class SecurityConfiguration {
|
||||
try {
|
||||
saml2
|
||||
.loginPage("/saml2")
|
||||
// Add this
|
||||
.relyingPartyRegistrationRepository(relyingPartyRegistrations())
|
||||
.authenticationRequestResolver(new OpenSaml4AuthenticationRequestResolver(
|
||||
relyingPartyRegistrations()
|
||||
))
|
||||
//.authenticationRequestResolver(new OpenSaml4AuthenticationRequestResolver(
|
||||
// relyingPartyRegistrations()
|
||||
// ))
|
||||
.successHandler(
|
||||
new CustomSaml2AuthenticationSuccessHandler(
|
||||
loginAttemptService,
|
||||
@ -284,16 +284,10 @@ public class SecurityConfiguration {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
})
|
||||
.saml2Logout(logout -> logout
|
||||
.logoutUrl("/logout"))
|
||||
;
|
||||
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||
http.csrf(csrf -> csrf.disable());
|
||||
} else {
|
||||
if (!applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||
CookieCsrfTokenRepository cookieRepo =
|
||||
CookieCsrfTokenRepository.withHttpOnlyFalse();
|
||||
CsrfTokenRequestAttributeHandler requestHandler =
|
||||
@ -316,19 +310,6 @@ public class SecurityConfiguration {
|
||||
OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
|
||||
provider.setResponseAuthenticationConverter(
|
||||
new CustomSaml2ResponseAuthenticationConverter(userService));
|
||||
|
||||
provider.setAssertionValidator(token -> {
|
||||
try {
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
// Add 5 minutes clock skew
|
||||
params.put(Saml2ErrorCodes.INVALID_ASSERTION, Duration.ofMinutes(5));
|
||||
ValidationContext context = new ValidationContext(params);
|
||||
return Saml2ResponseValidatorResult.success();
|
||||
} catch (Exception e) {
|
||||
return Saml2ResponseValidatorResult.failure();
|
||||
}
|
||||
});
|
||||
|
||||
return provider;
|
||||
}
|
||||
// Client Registration Repository for OAUTH2 OIDC Login
|
||||
@ -489,7 +470,7 @@ public class SecurityConfiguration {
|
||||
.entityId(samlConf.getIdpIssuer())
|
||||
.singleSignOnServiceLocation(samlConf.getIdpSingleLoginUrl())
|
||||
.verificationX509Credentials(c -> c.add(verificationCredential))
|
||||
.singleSignOnServiceBinding(Saml2MessageBinding.POST) // Add this
|
||||
.singleSignOnServiceBinding(Saml2MessageBinding.POST)
|
||||
.wantAuthnRequestsSigned(true)
|
||||
)
|
||||
.build();
|
||||
|
@ -18,6 +18,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
||||
@ -50,8 +51,17 @@ public class UserService implements UserServiceInterface {
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
|
||||
@Transactional
|
||||
public void migrateOauth2ToSSO() {
|
||||
userRepository.findByAuthenticationTypeIgnoreCase("OAUTH2")
|
||||
.forEach(user -> {
|
||||
user.setAuthenticationType(AuthenticationType.SSO);
|
||||
userRepository.save(user);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle OAUTH2 login and user auto creation.
|
||||
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser)
|
||||
public boolean processSSOPostLogin(String username, boolean autoCreateUser)
|
||||
throws IllegalArgumentException, IOException {
|
||||
if (!isUsernameValid(username)) {
|
||||
return false;
|
||||
@ -61,7 +71,7 @@ public class UserService implements UserServiceInterface {
|
||||
return true;
|
||||
}
|
||||
if (autoCreateUser) {
|
||||
saveUser(username, AuthenticationType.OAUTH2);
|
||||
saveUser(username, AuthenticationType.SSO);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -83,7 +83,7 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
||||
if (userService.usernameExistsIgnoreCase(username)
|
||||
&& userService.hasPassword(username)
|
||||
&& !userService.isAuthenticationTypeByUsername(
|
||||
username, AuthenticationType.OAUTH2)
|
||||
username, AuthenticationType.SSO)
|
||||
&& oAuth.getAutoCreateUser()) {
|
||||
response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
||||
return;
|
||||
@ -95,7 +95,7 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
||||
return;
|
||||
}
|
||||
if (principal instanceof OAuth2User) {
|
||||
userService.processOAuth2PostLogin(username, oAuth.getAutoCreateUser());
|
||||
userService.processSSOPostLogin(username, oAuth.getAutoCreateUser());
|
||||
}
|
||||
response.sendRedirect(contextPath + "/");
|
||||
return;
|
||||
|
@ -63,7 +63,7 @@ public class CustomSaml2AuthenticationSuccessHandler
|
||||
if (userService.usernameExistsIgnoreCase(username)
|
||||
&& userService.hasPassword(username)
|
||||
&& !userService.isAuthenticationTypeByUsername(
|
||||
username, AuthenticationType.OAUTH2)
|
||||
username, AuthenticationType.SSO)
|
||||
&& saml2.getAutoCreateUser()) {
|
||||
response.sendRedirect(
|
||||
contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
||||
@ -76,7 +76,7 @@ public class CustomSaml2AuthenticationSuccessHandler
|
||||
contextPath + "/login?erroroauth=oauth2_admin_blocked_user");
|
||||
return;
|
||||
}
|
||||
userService.processOAuth2PostLogin(username, saml2.getAutoCreateUser());
|
||||
userService.processSSOPostLogin(username, saml2.getAutoCreateUser());
|
||||
response.sendRedirect(contextPath + "/");
|
||||
return;
|
||||
} catch (IllegalArgumentException e) {
|
||||
|
@ -244,8 +244,8 @@ public class UserController {
|
||||
return new RedirectView("/addUsers?messageType=invalidRole", true);
|
||||
}
|
||||
|
||||
if (authType.equalsIgnoreCase(AuthenticationType.OAUTH2.toString())) {
|
||||
userService.saveUser(username, AuthenticationType.OAUTH2, role);
|
||||
if (authType.equalsIgnoreCase(AuthenticationType.SSO.toString())) {
|
||||
userService.saveUser(username, AuthenticationType.SSO, role);
|
||||
} else {
|
||||
if (password.isBlank()) {
|
||||
return new RedirectView("/addUsers?messageType=invalidPassword", true);
|
||||
|
@ -2,5 +2,5 @@ package stirling.software.SPDF.model;
|
||||
|
||||
public enum AuthenticationType {
|
||||
WEB,
|
||||
OAUTH2
|
||||
SSO
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package stirling.software.SPDF.repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
@ -19,4 +20,7 @@ public interface UserRepository extends JpaRepository<User, Long> {
|
||||
Optional<User> findByUsername(String username);
|
||||
|
||||
Optional<User> findByApiKey(String apiKey);
|
||||
|
||||
List<User> findByAuthenticationTypeIgnoreCase(String authenticationType);
|
||||
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ security:
|
||||
csrfDisabled: true # 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
|
||||
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
|
||||
loginMethod: all # 'all' (Login Username/Password and OAuth2[must be enabled and configured]), 'normal'(only Login with Username/Password) or 'oauth2'(only Login with OAuth2)
|
||||
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:
|
||||
username: '' # initial username for the first login
|
||||
password: '' # initial password for the first login
|
||||
@ -42,14 +42,14 @@ security:
|
||||
issuer: '' # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) endpoint
|
||||
clientId: '' # client ID from your provider
|
||||
clientSecret: '' # client secret from your provider
|
||||
autoCreateUser: false # 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
|
||||
useAsUsername: email # default is 'email'; custom fields can be used as the username
|
||||
scopes: openid, profile, email # specify the scopes for which the application will request permissions
|
||||
provider: google # set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
||||
saml2:
|
||||
enabled: false # currently in alpha, not recommended for use yet, enableAlphaFunctionality must be set to true
|
||||
autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users
|
||||
enabled: false # Only enabled for paid enterprise clients (enterpriseEdition.enabled must be true)
|
||||
autoCreateUser: true # set to 'true' to allow auto-creation of non-existing users
|
||||
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
|
||||
registrationId: stirling
|
||||
idpMetadataUri: https://dev-XXXXXXXX.okta.com/app/externalKey/sso/saml/metadata
|
||||
|
@ -83,21 +83,22 @@ function setupFileInput(chooser) {
|
||||
$("#" + elementId).on("change", function (e) {
|
||||
let element = e.target;
|
||||
const isDragAndDrop = e.detail?.source == 'drag-drop';
|
||||
|
||||
if (element instanceof HTMLInputElement && element.hasAttribute("multiple")) {
|
||||
allFiles = isDragAndDrop ? allFiles : [... allFiles, ... element.files];
|
||||
} else {
|
||||
allFiles = Array.from(isDragAndDrop ? allFiles : element.files[0]);
|
||||
}
|
||||
allFiles = isDragAndDrop ? allFiles : [...allFiles, ...element.files];
|
||||
} else {
|
||||
allFiles = isDragAndDrop ? allFiles : [element.files[0]];
|
||||
}
|
||||
|
||||
if (!isDragAndDrop) {
|
||||
let dataTransfer = new DataTransfer();
|
||||
allFiles.forEach(file => dataTransfer.items.add(file));
|
||||
element.files = dataTransfer.files;
|
||||
let dataTransfer = new DataTransfer();
|
||||
allFiles.forEach(file => dataTransfer.items.add(file));
|
||||
element.files = dataTransfer.files;
|
||||
}
|
||||
|
||||
handleFileInputChange(this);
|
||||
this.dispatchEvent(new CustomEvent("file-input-change", { bubbles: true }));
|
||||
});
|
||||
});
|
||||
|
||||
function handleFileInputChange(inputElement) {
|
||||
const files = allFiles;
|
||||
|
@ -189,7 +189,7 @@
|
||||
<label for="authType">Authentication Type</label>
|
||||
<select id="authType" name="authType" class="form-control" required>
|
||||
<option value="web" selected>WEB</option>
|
||||
<option value="oauth2">OAUTH2</option>
|
||||
<option value="sso">SSO</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-check mb-3" id="checkboxContainer">
|
||||
@ -267,7 +267,7 @@
|
||||
var passwordFieldContainer = $('#passwordContainer');
|
||||
var checkboxContainer = $('#checkboxContainer');
|
||||
|
||||
if (authType === 'oauth2') {
|
||||
if (authType === 'sso') {
|
||||
passwordField.removeAttr('required');
|
||||
passwordField.prop('disabled', true).val('');
|
||||
passwordFieldContainer.slideUp('fast');
|
||||
|
@ -59,7 +59,7 @@
|
||||
<link rel="stylesheet" th:href="@{'/css/fileSelect.css'}" th:if="${currentPage != 'home'}">
|
||||
<link rel="stylesheet" th:href="@{'/css/footer.css'}">
|
||||
|
||||
<link rel="preload" href="/fonts/google-symbol.woff2" as="font" type="font/woff2" crossorigin="anonymous">
|
||||
<link rel="preload" th:href="@{'/fonts/google-symbol.woff2'}" as="font" type="font/woff2" crossorigin="anonymous">
|
||||
|
||||
|
||||
<script th:src="@{'/js/thirdParty/fontfaceobserver.standalone.js'}"></script>
|
||||
|
@ -156,7 +156,7 @@
|
||||
resultDiv2.innerHTML = loading;
|
||||
|
||||
// Create a new Worker
|
||||
const worker = new Worker('/js/compare/pdfWorker.js');
|
||||
const worker = new Worker('./js/compare/pdfWorker.js');
|
||||
|
||||
|
||||
// Post messages to the worker
|
||||
|
Loading…
x
Reference in New Issue
Block a user