diff --git a/src/main/java/stirling/software/SPDF/config/interfaces/DatabaseInterface.java b/src/main/java/stirling/software/SPDF/config/interfaces/DatabaseInterface.java index 688ce3dcfcd..55107123e99 100644 --- a/src/main/java/stirling/software/SPDF/config/interfaces/DatabaseInterface.java +++ b/src/main/java/stirling/software/SPDF/config/interfaces/DatabaseInterface.java @@ -1,12 +1,13 @@ package stirling.software.SPDF.config.interfaces; -import java.io.IOException; +import java.sql.SQLException; import java.util.List; +import stirling.software.SPDF.model.provider.UnsupportedProviderException; import stirling.software.SPDF.utils.FileInfo; public interface DatabaseInterface { - void exportDatabase() throws IOException; + void exportDatabase() throws SQLException, UnsupportedProviderException; List getBackupList(); } diff --git a/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java b/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java index 83f6158aff3..103181fc4be 100644 --- a/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java +++ b/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java @@ -1,6 +1,6 @@ package stirling.software.SPDF.config.security; -import java.io.IOException; +import java.sql.SQLException; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; @@ -11,6 +11,7 @@ import stirling.software.SPDF.config.interfaces.DatabaseInterface; import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.Role; +import stirling.software.SPDF.model.provider.UnsupportedProviderException; @Slf4j @Component @@ -31,13 +32,13 @@ public void init() { userService.migrateOauth2ToSSO(); initializeInternalApiUser(); - } catch (IllegalArgumentException | IOException e) { + } catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) { log.error("Failed to initialize security setup.", e); System.exit(1); } } - private void initializeAdminUser() throws IOException { + private void initializeAdminUser() throws SQLException, UnsupportedProviderException { String initialUsername = applicationProperties.getSecurity().getInitialLogin().getUsername(); String initialPassword = @@ -55,7 +56,7 @@ private void initializeAdminUser() throws IOException { } } - private void createDefaultAdminUser() throws IOException { + private void createDefaultAdminUser() throws SQLException, UnsupportedProviderException { String defaultUsername = "admin"; String defaultPassword = "stirling"; @@ -65,7 +66,8 @@ private void createDefaultAdminUser() throws IOException { } } - private void initializeInternalApiUser() throws IllegalArgumentException, IOException { + private void initializeInternalApiUser() + throws IllegalArgumentException, SQLException, UnsupportedProviderException { if (!userService.usernameExistsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) { userService.saveUser( Role.INTERNAL_API_USER.getRoleId(), diff --git a/src/main/java/stirling/software/SPDF/config/security/UserService.java b/src/main/java/stirling/software/SPDF/config/security/UserService.java index 2ae4ad136e5..61ddf887629 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserService.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserService.java @@ -1,6 +1,6 @@ package stirling.software.SPDF.config.security; -import java.io.IOException; +import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; @@ -25,11 +25,8 @@ import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface; -import stirling.software.SPDF.model.ApplicationProperties; -import stirling.software.SPDF.model.AuthenticationType; -import stirling.software.SPDF.model.Authority; -import stirling.software.SPDF.model.Role; -import stirling.software.SPDF.model.User; +import stirling.software.SPDF.model.*; +import stirling.software.SPDF.model.provider.UnsupportedProviderException; import stirling.software.SPDF.repository.AuthorityRepository; import stirling.software.SPDF.repository.UserRepository; @@ -64,7 +61,7 @@ public void migrateOauth2ToSSO() { // Handle OAUTH2 login and user auto creation. public boolean processSSOPostLogin(String username, boolean autoCreateUser) - throws IllegalArgumentException, IOException { + throws IllegalArgumentException, SQLException, UnsupportedProviderException { if (!isUsernameValid(username)) { return false; } @@ -151,12 +148,12 @@ public boolean validateApiKeyForUser(String username, String apiKey) { } public void saveUser(String username, AuthenticationType authenticationType) - throws IllegalArgumentException, IOException { + throws IllegalArgumentException, SQLException, UnsupportedProviderException { saveUser(username, authenticationType, Role.USER.getRoleId()); } public void saveUser(String username, AuthenticationType authenticationType, String role) - throws IllegalArgumentException, IOException { + throws IllegalArgumentException, SQLException, UnsupportedProviderException { if (!isUsernameValid(username)) { throw new IllegalArgumentException(getInvalidUsernameMessage()); } @@ -171,7 +168,7 @@ public void saveUser(String username, AuthenticationType authenticationType, Str } public void saveUser(String username, String password) - throws IllegalArgumentException, IOException { + throws IllegalArgumentException, SQLException, UnsupportedProviderException { if (!isUsernameValid(username)) { throw new IllegalArgumentException(getInvalidUsernameMessage()); } @@ -185,7 +182,7 @@ public void saveUser(String username, String password) } public void saveUser(String username, String password, String role, boolean firstLogin) - throws IllegalArgumentException, IOException { + throws IllegalArgumentException, SQLException, UnsupportedProviderException { if (!isUsernameValid(username)) { throw new IllegalArgumentException(getInvalidUsernameMessage()); } @@ -201,7 +198,7 @@ public void saveUser(String username, String password, String role, boolean firs } public void saveUser(String username, String password, String role) - throws IllegalArgumentException, IOException { + throws IllegalArgumentException, SQLException, UnsupportedProviderException { saveUser(username, password, role, false); } @@ -235,7 +232,7 @@ public boolean hasUsers() { } public void updateUserSettings(String username, Map updates) - throws IOException { + throws SQLException, UnsupportedProviderException { Optional userOpt = findByUsernameIgnoreCaseWithSettings(username); if (userOpt.isPresent()) { User user = userOpt.get(); @@ -270,7 +267,7 @@ public Authority findRole(User user) { } public void changeUsername(User user, String newUsername) - throws IllegalArgumentException, IOException { + throws IllegalArgumentException, SQLException, UnsupportedProviderException { if (!isUsernameValid(newUsername)) { throw new IllegalArgumentException(getInvalidUsernameMessage()); } @@ -279,26 +276,30 @@ public void changeUsername(User user, String newUsername) databaseService.exportDatabase(); } - public void changePassword(User user, String newPassword) throws IOException { + public void changePassword(User user, String newPassword) + throws SQLException, UnsupportedProviderException { user.setPassword(passwordEncoder.encode(newPassword)); userRepository.save(user); databaseService.exportDatabase(); } - public void changeFirstUse(User user, boolean firstUse) throws IOException { + public void changeFirstUse(User user, boolean firstUse) + throws SQLException, UnsupportedProviderException { user.setFirstLogin(firstUse); userRepository.save(user); databaseService.exportDatabase(); } - public void changeRole(User user, String newRole) throws IOException { + public void changeRole(User user, String newRole) + throws SQLException, UnsupportedProviderException { Authority userAuthority = this.findRole(user); userAuthority.setAuthority(newRole); authorityRepository.save(userAuthority); databaseService.exportDatabase(); } - public void changeUserEnabled(User user, Boolean enbeled) throws IOException { + public void changeUserEnabled(User user, Boolean enbeled) + throws SQLException, UnsupportedProviderException { user.setEnabled(enbeled); userRepository.save(user); databaseService.exportDatabase(); @@ -391,7 +392,8 @@ public String getCurrentUsername() { } @Transactional - public void syncCustomApiUser(String customApiKey) throws IOException { + public void syncCustomApiUser(String customApiKey) + throws SQLException, UnsupportedProviderException { if (customApiKey == null || customApiKey.trim().length() == 0) { return; } diff --git a/src/main/java/stirling/software/SPDF/config/security/database/DatabaseService.java b/src/main/java/stirling/software/SPDF/config/security/database/DatabaseService.java index 9e22a1cf807..3a0b01d2c22 100644 --- a/src/main/java/stirling/software/SPDF/config/security/database/DatabaseService.java +++ b/src/main/java/stirling/software/SPDF/config/security/database/DatabaseService.java @@ -27,6 +27,7 @@ import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.config.interfaces.DatabaseInterface; +import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.provider.UnsupportedProviderException; import stirling.software.SPDF.utils.FileInfo; @@ -104,7 +105,7 @@ private void importDatabaseFromUI(Path tempTemplatePath) throws IOException { } @Override - public void exportDatabase() { + public void exportDatabase() throws SQLException, UnsupportedProviderException { // Filter and delete old backups if there are more than 5 List filteredBackupList = this.getBackupList().stream() @@ -127,8 +128,10 @@ public void exportDatabase() { log.info("Database export completed: {}", insertOutputFilePath); } catch (SQLException | UnsupportedProviderException e) { log.error("Error during database export: {}", e.getMessage(), e); + throw e; } catch (ScriptException e) { log.error("Error during database export: File {} not found", insertOutputFilePath); + throw e; } } @@ -149,17 +152,26 @@ private static void deleteOldestBackup(List filteredBackupList) { // Retrieves the H2 database version. public String getH2Version() { String version = "Unknown"; - try (Connection conn = databaseConfig.connection()) { - try (Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT H2VERSION() AS version")) { - if (rs.next()) { - version = rs.getString("version"); - log.info("H2 Database Version: {}", version); + + if (databaseConfig + .getApplicationProperties() + .getSystem() + .getDatasource() + .getType() + .equals(ApplicationProperties.Driver.H2.name())) { + try (Connection conn = databaseConfig.connection()) { + try (Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT H2VERSION() AS version")) { + if (rs.next()) { + version = rs.getString("version"); + log.info("H2 Database Version: {}", version); + } } + } catch (SQLException | UnsupportedProviderException e) { + log.error("Error retrieving H2 version: {}", e.getMessage(), e); } - } catch (SQLException | UnsupportedProviderException e) { - log.error("Error retrieving H2 version: {}", e.getMessage(), e); } + return version; } diff --git a/src/main/java/stirling/software/SPDF/config/security/database/ScheduledTasks.java b/src/main/java/stirling/software/SPDF/config/security/database/ScheduledTasks.java index f8d56a91fcd..5d5248ae763 100644 --- a/src/main/java/stirling/software/SPDF/config/security/database/ScheduledTasks.java +++ b/src/main/java/stirling/software/SPDF/config/security/database/ScheduledTasks.java @@ -1,18 +1,20 @@ package stirling.software.SPDF.config.security.database; -import java.io.IOException; +import java.sql.SQLException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +import stirling.software.SPDF.model.provider.UnsupportedProviderException; + @Component public class ScheduledTasks { @Autowired private DatabaseService databaseService; @Scheduled(cron = "0 0 0 * * ?") - public void performBackup() throws IOException { + public void performBackup() throws SQLException, UnsupportedProviderException { databaseService.exportDatabase(); } } diff --git a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java index 9f3f6e35984..ef4ac32477e 100644 --- a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java @@ -1,6 +1,7 @@ package stirling.software.SPDF.config.security.oauth2; import java.io.IOException; +import java.sql.SQLException; import org.springframework.security.authentication.LockedException; import org.springframework.security.core.Authentication; @@ -18,6 +19,7 @@ import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; import stirling.software.SPDF.model.AuthenticationType; +import stirling.software.SPDF.model.provider.UnsupportedProviderException; import stirling.software.SPDF.utils.RequestUriUtils; public class CustomOAuth2AuthenticationSuccessHandler @@ -97,10 +99,8 @@ public void onAuthenticationSuccess( userService.processSSOPostLogin(username, oAuth.getAutoCreateUser()); } response.sendRedirect(contextPath + "/"); - return; - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) { response.sendRedirect(contextPath + "/logout?invalidUsername=true"); - return; } } } diff --git a/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationSuccessHandler.java index faa5e67ee74..2071a5ce076 100644 --- a/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationSuccessHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationSuccessHandler.java @@ -1,6 +1,7 @@ package stirling.software.SPDF.config.security.saml2; import java.io.IOException; +import java.sql.SQLException; import org.springframework.security.authentication.LockedException; import org.springframework.security.core.Authentication; @@ -18,6 +19,7 @@ import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2; import stirling.software.SPDF.model.AuthenticationType; +import stirling.software.SPDF.model.provider.UnsupportedProviderException; import stirling.software.SPDF.utils.RequestUriUtils; @AllArgsConstructor @@ -115,6 +117,9 @@ public void onAuthenticationSuccess( username); response.sendRedirect(contextPath + "/logout?invalidUsername=true"); return; + } catch (SQLException | UnsupportedProviderException e) { + log.error("Error, redirecting to logout", e); + response.sendRedirect(contextPath + "/logout?error=true"); } } } else { diff --git a/src/main/java/stirling/software/SPDF/controller/api/UserController.java b/src/main/java/stirling/software/SPDF/controller/api/UserController.java index c7d19f51810..729529d1e12 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/UserController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/UserController.java @@ -1,7 +1,7 @@ package stirling.software.SPDF.controller.api; -import java.io.IOException; import java.security.Principal; +import java.sql.SQLException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -18,11 +18,7 @@ import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.view.RedirectView; @@ -38,6 +34,7 @@ import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.User; import stirling.software.SPDF.model.api.user.UsernameAndPass; +import stirling.software.SPDF.model.provider.UnsupportedProviderException; @Controller @Tag(name = "User", description = "User APIs") @@ -51,17 +48,17 @@ public class UserController { @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PostMapping("/register") - public String register(@ModelAttribute UsernameAndPass requestModel, Model model) - throws IOException { + public String register(@ModelAttribute UsernameAndPass requestModel, Model model) { if (userService.usernameExistsIgnoreCase(requestModel.getUsername())) { model.addAttribute("error", "Username already exists"); return "register"; } try { userService.saveUser(requestModel.getUsername(), requestModel.getPassword()); - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) { return "redirect:/login?messageType=invalidUsername"; } + return "redirect:/login?registered=true"; } @@ -73,8 +70,7 @@ public RedirectView changeUsername( @RequestParam(name = "newUsername") String newUsername, HttpServletRequest request, HttpServletResponse response, - RedirectAttributes redirectAttributes) - throws IOException { + RedirectAttributes redirectAttributes) { if (!userService.isUsernameValid(newUsername)) { return new RedirectView("/account?messageType=invalidUsername", true); @@ -105,10 +101,10 @@ public RedirectView changeUsername( return new RedirectView("/account?messageType=usernameExists", true); } - if (newUsername != null && newUsername.length() > 0) { + if (newUsername != null && !newUsername.isEmpty()) { try { userService.changeUsername(user, newUsername); - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) { return new RedirectView("/account?messageType=invalidUsername", true); } } @@ -128,7 +124,7 @@ public RedirectView changePasswordOnLogin( HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes) - throws IOException { + throws SQLException, UnsupportedProviderException { if (principal == null) { return new RedirectView("/change-creds?messageType=notAuthenticated", true); } @@ -162,7 +158,7 @@ public RedirectView changePassword( HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes) - throws IOException { + throws SQLException, UnsupportedProviderException { if (principal == null) { return new RedirectView("/account?messageType=notAuthenticated", true); } @@ -190,7 +186,7 @@ public RedirectView changePassword( @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PostMapping("/updateUserSettings") public String updateUserSettings(HttpServletRequest request, Principal principal) - throws IOException { + throws SQLException, UnsupportedProviderException { Map paramMap = request.getParameterMap(); Map updates = new HashMap<>(); @@ -215,7 +211,7 @@ public RedirectView saveUser( @RequestParam(name = "authType") String authType, @RequestParam(name = "forceChange", required = false, defaultValue = "false") boolean forceChange) - throws IllegalArgumentException, IOException { + throws IllegalArgumentException, SQLException, UnsupportedProviderException { if (!userService.isUsernameValid(username)) { return new RedirectView("/addUsers?messageType=invalidUsername", true); @@ -263,7 +259,7 @@ public RedirectView changeRole( @RequestParam(name = "username") String username, @RequestParam(name = "role") String role, Authentication authentication) - throws IOException { + throws SQLException, UnsupportedProviderException { Optional userOpt = userService.findByUsernameIgnoreCase(username); @@ -280,6 +276,7 @@ public RedirectView changeRole( if (currentUsername.equalsIgnoreCase(username)) { return new RedirectView("/addUsers?messageType=downgradeCurrentUser", true); } + try { // Validate the role Role roleEnum = Role.fromString(role); @@ -305,7 +302,7 @@ public RedirectView changeUserEnabled( @PathVariable("username") String username, @RequestParam("enabled") boolean enabled, Authentication authentication) - throws IOException { + throws SQLException, UnsupportedProviderException { Optional userOpt = userService.findByUsernameIgnoreCase(username); diff --git a/src/main/java/stirling/software/SPDF/controller/api/pipeline/ApiDocService.java b/src/main/java/stirling/software/SPDF/controller/api/pipeline/ApiDocService.java index c65c0d8ae8d..c68dfcb971a 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/pipeline/ApiDocService.java +++ b/src/main/java/stirling/software/SPDF/controller/api/pipeline/ApiDocService.java @@ -7,8 +7,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; diff --git a/src/test/java/stirling/software/SPDF/config/security/InitialSecuritySetupTest.java b/src/test/java/stirling/software/SPDF/config/security/InitialSecuritySetupTest.java index e9c5594ef8e..1d024b1e6e4 100644 --- a/src/test/java/stirling/software/SPDF/config/security/InitialSecuritySetupTest.java +++ b/src/test/java/stirling/software/SPDF/config/security/InitialSecuritySetupTest.java @@ -1,7 +1,5 @@ package stirling.software.SPDF.config.security; -import java.io.IOException; -import java.util.Optional; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -10,10 +8,12 @@ import stirling.software.SPDF.config.security.database.DatabaseService; import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.User; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import stirling.software.SPDF.model.provider.UnsupportedProviderException; + +import java.sql.SQLException; +import java.util.Optional; + +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class InitialSecuritySetupTest { @@ -31,7 +31,7 @@ class InitialSecuritySetupTest { private InitialSecuritySetup initialSecuritySetup; @Test - void testInit() throws IOException { + void testInit() throws SQLException, UnsupportedProviderException { String username = "admin"; String password = "stirling"; ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);