From d0543d80bef711269bec801b074f942af0521dcc Mon Sep 17 00:00:00 2001 From: iv Date: Mon, 1 Dec 2025 18:35:46 +0300 Subject: [PATCH 01/91] #111 sso-remote-auth - API, service and auth phases cache --- pom.xml | 16 ++ superfly-service/pom.xml | 7 +- .../model/ui/subsystem/UISubsystem.java | 10 + .../service/RemoteAuthCryptoService.java | 25 +++ .../superfly/service/RemoteAuthService.java | 56 +++++ .../superfly/service/SubsystemService.java | 6 + .../service/impl/SubsystemServiceImpl.java | 6 + .../check/RemoteAuthCryptoServiceStub.java | 31 +++ .../remote/check/RemoteAuthServiceImpl.java | 131 ++++++++++++ superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd | 3 + superfly-sql/mi/R1.7.3/R1.7.3_SSO.sh | 8 + superfly-sql/mi/R1.7.3/R1.7.3_SSO.sql | 1 + superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql | 3 + .../src/ui_create/ui_create_subsystem.prc | 7 +- superfly-sql/src/ui_get/ui_get_subsystem.prc | 6 +- .../src/ui_get/ui_get_subsystem_by_name.prc | 6 +- .../ui_edit_subsystem_properties.prc | 6 +- superfly-web/pom.xml | 5 + .../web/mvc/RemoteAuthCheckController.java | 124 +++++++++++ .../web/mvc/model/CheckOtpRequest.java | 32 +++ .../web/mvc/model/CheckOtpResponse.java | 26 +++ .../web/mvc/model/CheckPasswordRequest.java | 23 ++ .../web/mvc/model/CheckPasswordResponse.java | 20 ++ .../superfly/web/mvc/model/ErrorResponse.java | 35 ++++ .../web/wicket/SuperflyApplication.properties | 1 + .../page/subsystem/EditSubsystemPage.html | 8 + .../page/subsystem/EditSubsystemPage.java | 24 +++ .../WEB-INF/spring/dispatcher-servlet.xml | 14 ++ superfly-web/src/main/webapp/WEB-INF/web.xml | 27 ++- .../mvc/RemoteAuthCheckControllerTest.java | 197 ++++++++++++++++++ 30 files changed, 849 insertions(+), 15 deletions(-) create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthCryptoService.java create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthService.java create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java create mode 100644 superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd create mode 100644 superfly-sql/mi/R1.7.3/R1.7.3_SSO.sh create mode 100644 superfly-sql/mi/R1.7.3/R1.7.3_SSO.sql create mode 100644 superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql create mode 100644 superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckController.java create mode 100644 superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckOtpRequest.java create mode 100644 superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckOtpResponse.java create mode 100644 superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordRequest.java create mode 100644 superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordResponse.java create mode 100644 superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/ErrorResponse.java create mode 100644 superfly-web/src/main/webapp/WEB-INF/spring/dispatcher-servlet.xml create mode 100644 superfly-web/src/test/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckControllerTest.java diff --git a/pom.xml b/pom.xml index e5ed4d83..06aa071a 100644 --- a/pom.xml +++ b/pom.xml @@ -741,6 +741,20 @@ passay 1.6.1 + + + + com.github.ben-manes.caffeine + caffeine + 3.1.8 + + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + @@ -980,6 +994,8 @@ true utf-8 + 2.15.2 + 21 true true diff --git a/superfly-service/pom.xml b/superfly-service/pom.xml index 18b017f2..17e43e04 100644 --- a/superfly-service/pom.xml +++ b/superfly-service/pom.xml @@ -122,5 +122,10 @@ org.passay passay + + + com.github.ben-manes.caffeine + caffeine + - \ No newline at end of file + diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/model/ui/subsystem/UISubsystem.java b/superfly-service/src/main/java/com/payneteasy/superfly/model/ui/subsystem/UISubsystem.java index 90abe3ee..1d7e9fa5 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/model/ui/subsystem/UISubsystem.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/model/ui/subsystem/UISubsystem.java @@ -19,6 +19,7 @@ public class UISubsystem implements Serializable { private String subsystemUrl; private String landingUrl; private String loginFormCssUrl; + private String privateKey; @Column(name = "ssys_id") public Long getId() { @@ -119,4 +120,13 @@ public String getLoginFormCssUrl() { public void setLoginFormCssUrl(String loginFormCssUrl) { this.loginFormCssUrl = loginFormCssUrl; } + + @Column(name = "private_key") + public String getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthCryptoService.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthCryptoService.java new file mode 100644 index 00000000..265f1500 --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthCryptoService.java @@ -0,0 +1,25 @@ +package com.payneteasy.superfly.service; + +/** + * Service for cryptographic operations in remote authentication. + */ +public interface RemoteAuthCryptoService { + /** + * Decrypts a password using the subsystem's private key. + * + * @param encryptedPassword The encrypted password (Base64 URL-safe no padding). + * @param privateKey The private key of the subsystem. + * @return The decrypted password. + */ + String decryptPassword(String encryptedPassword, String privateKey); + + /** + * Decrypts an OTP using the subsystem's private key. + * + * @param encryptedOtp The encrypted OTP (Base64 URL-safe no padding). + * @param privateKey The private key of the subsystem. + * @return The decrypted OTP. + */ + String decryptOtp(String encryptedOtp, String privateKey); +} + diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthService.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthService.java new file mode 100644 index 00000000..e12f6387 --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthService.java @@ -0,0 +1,56 @@ +package com.payneteasy.superfly.service; + +/** + * Service for remote authentication of external applications. + */ +public interface RemoteAuthService { + + /** + * Checks the password for a user in a subsystem. + * + * @param subsystemName Name of the subsystem. + * @param username Username. + * @param passwordEncrypted Encrypted password (Base64 URL-safe no padding). + * @param bearerToken Bearer token provided in the request. + * @param ipAddress IP address of the client. + * @param userAgent User agent of the client. + * @return Session token if authentication is successful. + * @throws RemoteAuthException If authentication fails or other errors occur. + */ + String checkPassword(String subsystemName, String username, String passwordEncrypted, String bearerToken, String ipAddress, String userAgent) throws RemoteAuthException; + + /** + * Checks the OTP for a user in a subsystem. + * + * @param subsystemName Name of the subsystem. + * @param username Username. + * @param otpEncrypted Encrypted OTP (Base64 URL-safe no padding). + * @param sessionToken Session token obtained from checkPassword. + * @param bearerToken Bearer token provided in the request. + * @return Result of the OTP check (SUCCESS, BAD_USER..., etc.). + * @throws RemoteAuthException If validation fails. + */ + String checkOtp(String subsystemName, String username, String otpEncrypted, String sessionToken, String bearerToken) throws RemoteAuthException; + + /** + * Exception class for remote auth errors. + */ + class RemoteAuthException extends Exception { + private final String errorCode; + + public RemoteAuthException(String message, String errorCode) { + super(message); + this.errorCode = errorCode; + } + + public RemoteAuthException(String message, String errorCode, Throwable cause) { + super(message, cause); + this.errorCode = errorCode; + } + + public String getErrorCode() { + return errorCode; + } + } +} + diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/SubsystemService.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/SubsystemService.java index e5e2eddf..48360891 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/SubsystemService.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/SubsystemService.java @@ -85,4 +85,10 @@ public interface SubsystemService { * @return main token for subsystem */ String generateMainSubsystemToken(); + + /** + * + * @return private key for subsystem + */ + String generateSubsystemPrivateKey(); } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/SubsystemServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/SubsystemServiceImpl.java index 6a8a23c0..26579881 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/SubsystemServiceImpl.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/SubsystemServiceImpl.java @@ -53,6 +53,7 @@ public void setJavaMailSenderPool(JavaMailSenderPool javaMailSenderPool) { public RoutineResult createSubsystem(UISubsystem subsystem) { subsystem.setSubsystemToken(generateMainSubsystemToken()); + subsystem.setPrivateKey(generateSubsystemPrivateKey()); RoutineResult result = subsystemDao.createSubsystem(subsystem); loggerSink.info(logger, "CREATE_SUBSYSTEM", true, subsystem.getName()); javaMailSenderPool.flushAll(); // clearing pool so changes are applied @@ -109,4 +110,9 @@ private String generateUniqueSubsystemToken() { public String generateMainSubsystemToken() { return UUID.randomUUID().toString(); } + + @Override + public String generateSubsystemPrivateKey() { + return UUID.randomUUID().toString(); + } } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java new file mode 100644 index 00000000..2ef571f9 --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java @@ -0,0 +1,31 @@ +package com.payneteasy.superfly.service.impl.remote.check; + +import com.payneteasy.superfly.service.RemoteAuthCryptoService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +/** + * Stub implementation of RemoteAuthCryptoService. + */ +@Service +public class RemoteAuthCryptoServiceStub implements RemoteAuthCryptoService { + + private static final Logger logger = LoggerFactory.getLogger(RemoteAuthCryptoServiceStub.class); + + public RemoteAuthCryptoServiceStub() { + } + + public String decryptPassword(String encryptedPassword, String privateKey) { + logger.warn("decryptPassword is a STUB. Returning encryptedPassword as is."); + // In a real implementation, this would decrypt the password. + // For now, we assume the input is already the plain password or we just return it to test flow. + return encryptedPassword; + } + + public String decryptOtp(String encryptedOtp, String privateKey) { + logger.warn("decryptOtp is a STUB. Returning encryptedOtp as is."); + return encryptedOtp; + } +} + diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java new file mode 100644 index 00000000..9ec5335e --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java @@ -0,0 +1,131 @@ +package com.payneteasy.superfly.service.impl.remote.check; + +import com.payneteasy.superfly.api.SSOUser; +import com.payneteasy.superfly.model.ui.subsystem.UISubsystem; +import com.payneteasy.superfly.service.InternalSSOService; +import com.payneteasy.superfly.service.RemoteAuthCryptoService; +import com.payneteasy.superfly.service.RemoteAuthService; +import com.payneteasy.superfly.service.SubsystemService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import org.springframework.stereotype.Service; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +@Service +public class RemoteAuthServiceImpl implements RemoteAuthService { + + private static final Logger logger = LoggerFactory.getLogger(RemoteAuthServiceImpl.class); + + private final SubsystemService subsystemService; + private final InternalSSOService internalSSOService; + private final RemoteAuthCryptoService remoteAuthCryptoService; + + private final Cache sessionCache = Caffeine.newBuilder() + .expireAfterWrite(5, TimeUnit.MINUTES) + .maximumSize(10_000) + .build(); + + public RemoteAuthServiceImpl(SubsystemService subsystemService, + InternalSSOService internalSSOService, + RemoteAuthCryptoService remoteAuthCryptoService) { + this.subsystemService = subsystemService; + this.internalSSOService = internalSSOService; + this.remoteAuthCryptoService = remoteAuthCryptoService; + } + + @Override + public String checkPassword(String subsystemName, String username, String passwordEncrypted, String bearerToken, String ipAddress, String userAgent) throws RemoteAuthException { + // 1. Validate Subsystem and Token + UISubsystem subsystem = validateSubsystem(subsystemName, bearerToken); + + // 2. Decrypt Password + String password; + try { + password = remoteAuthCryptoService.decryptPassword(passwordEncrypted, subsystem.getPrivateKey()); + } catch (Exception e) { + logger.error("Failed to decrypt password", e); + throw new RemoteAuthException("Decryption failed", "INTERNAL_ERROR", e); + } + + // 3. Authenticate User + // Using internalSSOService to check credentials and get SSOUser + // Note: This creates a session in the DB as well (AuthSession). + // If we want to avoid creating a full session until OTP, we might need a different method, + // but for now we reuse the existing logic. + SSOUser ssoUser = internalSSOService.authenticate(username, password, subsystemName, ipAddress, userAgent); + + if (ssoUser == null) { + // Could be bad password, user blocked, etc. + // For security, we might return generic bad credentials. + throw new RemoteAuthException("Authentication failed", "BAD_USER_OR_PASSWORD_OR_OTP"); + } + + // 4. Generate Session Token and Cache + String sessionToken = UUID.randomUUID().toString(); + sessionCache.put(sessionToken, new RemoteSession(username)); + + return sessionToken; + } + + @Override + public String checkOtp(String subsystemName, String username, String otpEncrypted, String sessionToken, String bearerToken) throws RemoteAuthException { + // 1. Validate Subsystem and Token + UISubsystem subsystem = validateSubsystem(subsystemName, bearerToken); + + // 2. Validate Session Token + RemoteSession session = sessionCache.getIfPresent(sessionToken); + if (session == null) { + throw new RemoteAuthException("Session expired or invalid", "BAD_USER_OR_PASSWORD_OR_OTP"); + } + if (!session.username.equals(username)) { + throw new RemoteAuthException("Username mismatch", "BAD_USER_OR_PASSWORD_OR_OTP"); + } + + // 3. Decrypt OTP + String otp; + try { + otp = remoteAuthCryptoService.decryptOtp(otpEncrypted, subsystem.getPrivateKey()); + } catch (Exception e) { + logger.error("Failed to decrypt OTP", e); + throw new RemoteAuthException("Decryption failed", "INTERNAL_ERROR", e); + } + + // 4. Verify OTP + boolean otpValid = internalSSOService.authenticateHOTP(subsystemName, username, otp); + if (!otpValid) { + return "BAD_USER_OR_PASSWORD_OR_OTP"; + } + + // TODO: Check for other statuses like USER_SHOULD_CHANGE_PASSWORD or USER_BLOCKED + // internalSSOService.authenticateHOTP only returns boolean. + // We might need to check user status explicitly if needed. + + return "SUCCESS"; + } + + private UISubsystem validateSubsystem(String subsystemName, String bearerToken) throws RemoteAuthException { + UISubsystem subsystem = subsystemService.getSubsystemByName(subsystemName); + if (subsystem == null) { + throw new RemoteAuthException("Subsystem not found", "INTERNAL_ERROR"); // Or 404 equivalent + } + // Assuming bearerToken is just the token value. + // The format in header is "Bearer ", but the controller should extract . + if (subsystem.getSubsystemToken() == null || !subsystem.getSubsystemToken().equals(bearerToken)) { + throw new RemoteAuthException("Invalid subsystem token", "INTERNAL_ERROR"); // Or 401 + } + return subsystem; + } + + private static class RemoteSession { + final String username; + + RemoteSession(String username) { + this.username = username; + } + } +} + diff --git a/superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd b/superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd new file mode 100644 index 00000000..78aeec93 --- /dev/null +++ b/superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd @@ -0,0 +1,3 @@ +@set PATH=C:\cygwin\bin;%PATH% +bash R1.7.2_SSO.sh + diff --git a/superfly-sql/mi/R1.7.3/R1.7.3_SSO.sh b/superfly-sql/mi/R1.7.3/R1.7.3_SSO.sh new file mode 100644 index 00000000..25755756 --- /dev/null +++ b/superfly-sql/mi/R1.7.3/R1.7.3_SSO.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +. ../../functions.sh + +runScript R1.7.3_SSO.sql + +runScript R1.7.3_SSO_DML.sql + diff --git a/superfly-sql/mi/R1.7.3/R1.7.3_SSO.sql b/superfly-sql/mi/R1.7.3/R1.7.3_SSO.sql new file mode 100644 index 00000000..df05f4c7 --- /dev/null +++ b/superfly-sql/mi/R1.7.3/R1.7.3_SSO.sql @@ -0,0 +1 @@ +commit; diff --git a/superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql b/superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql new file mode 100644 index 00000000..dd8cdc90 --- /dev/null +++ b/superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql @@ -0,0 +1,3 @@ +alter table subsystems add column private_key text; + +commit; diff --git a/superfly-sql/src/ui_create/ui_create_subsystem.prc b/superfly-sql/src/ui_create/ui_create_subsystem.prc index 8c1422be..41571e3a 100644 --- a/superfly-sql/src/ui_create/ui_create_subsystem.prc +++ b/superfly-sql/src/ui_create/ui_create_subsystem.prc @@ -10,6 +10,7 @@ create procedure ui_create_subsystem(i_subsystem_name varchar(32), i_subsystem_url varchar(255), i_landing_url varchar(255), i_login_form_css_url varchar(255), + i_private_key text, out o_ssys_id int(10) ) main_sql: @@ -26,7 +27,8 @@ create procedure ui_create_subsystem(i_subsystem_name varchar(32), subsystem_token, subsystem_url, landing_url, - login_form_css_url + login_form_css_url, + private_key ) values ( i_subsystem_name, @@ -39,7 +41,8 @@ create procedure ui_create_subsystem(i_subsystem_name varchar(32), i_subsystem_token, i_subsystem_url, i_landing_url, - i_login_form_css_url + i_login_form_css_url, + i_private_key ); set o_ssys_id = last_insert_id(); diff --git a/superfly-sql/src/ui_get/ui_get_subsystem.prc b/superfly-sql/src/ui_get/ui_get_subsystem.prc index 348b2494..6a781213 100644 --- a/superfly-sql/src/ui_get/ui_get_subsystem.prc +++ b/superfly-sql/src/ui_get/ui_get_subsystem.prc @@ -15,7 +15,8 @@ create procedure ui_get_subsystem(i_ssys_id int(10)) ss.subsystem_token, ss.subsystem_url, ss.landing_url, - ss.login_form_css_url + ss.login_form_css_url, + ss.private_key from subsystems ss left join smtp_servers smtp on smtp.ssrv_id = ss.ssrv_ssrv_id @@ -37,6 +38,7 @@ call save_routine_information('ui_get_subsystem', 'subsystem_token varchar', 'subsystem_url varchar', 'landing_url varchar', - 'login_form_css_url varchar' + 'login_form_css_url varchar', + 'private_key varchar' ) ); diff --git a/superfly-sql/src/ui_get/ui_get_subsystem_by_name.prc b/superfly-sql/src/ui_get/ui_get_subsystem_by_name.prc index 272667e4..06556b3d 100644 --- a/superfly-sql/src/ui_get/ui_get_subsystem_by_name.prc +++ b/superfly-sql/src/ui_get/ui_get_subsystem_by_name.prc @@ -15,7 +15,8 @@ create procedure ui_get_subsystem_by_name(i_subsystem_name varchar(32)) ss.subsystem_token, ss.subsystem_url, ss.landing_url, - ss.login_form_css_url + ss.login_form_css_url, + ss.private_key from subsystems ss left join smtp_servers smtp on smtp.ssrv_id = ss.ssrv_ssrv_id @@ -37,6 +38,7 @@ call save_routine_information('ui_get_subsystem_by_name', 'subsystem_token varchar', 'subsystem_url varchar', 'landing_url varchar', - 'login_form_css_url varchar' + 'login_form_css_url varchar', + 'private_key varchar' ) ); diff --git a/superfly-sql/src/ui_update/ui_edit_subsystem_properties.prc b/superfly-sql/src/ui_update/ui_edit_subsystem_properties.prc index 3f6670ff..32ccbe6f 100644 --- a/superfly-sql/src/ui_update/ui_edit_subsystem_properties.prc +++ b/superfly-sql/src/ui_update/ui_edit_subsystem_properties.prc @@ -10,7 +10,8 @@ create procedure ui_edit_subsystem_properties(i_ssys_id int(10), i_subsystem_token varchar(64), i_subsystem_url varchar(255), i_landing_url varchar(255), - i_login_form_css_url varchar(255) + i_login_form_css_url varchar(255), + i_private_key text ) main_sql: begin @@ -24,7 +25,8 @@ create procedure ui_edit_subsystem_properties(i_ssys_id int(10), subsystem_token = i_subsystem_token, subsystem_url = i_subsystem_url, landing_url = i_landing_url, - login_form_css_url = i_login_form_css_url + login_form_css_url = i_login_form_css_url, + private_key = i_private_key where ssys_id = i_ssys_id; select 'OK' status, null error_message; diff --git a/superfly-web/pom.xml b/superfly-web/pom.xml index 0eb3a149..488c0391 100644 --- a/superfly-web/pom.xml +++ b/superfly-web/pom.xml @@ -168,6 +168,11 @@ cglib compile + + + com.fasterxml.jackson.core + jackson-databind + diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckController.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckController.java new file mode 100644 index 00000000..1676abb4 --- /dev/null +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckController.java @@ -0,0 +1,124 @@ +package com.payneteasy.superfly.web.mvc; + +import com.payneteasy.superfly.service.RemoteAuthService; +import com.payneteasy.superfly.service.RemoteAuthService.RemoteAuthException; +import com.payneteasy.superfly.web.mvc.model.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.util.UUID; + +@Component +@RestController +public class RemoteAuthCheckController { + + private final RemoteAuthService remoteAuthService; + + @Autowired + public RemoteAuthCheckController(RemoteAuthService remoteAuthService) { + this.remoteAuthService = remoteAuthService; + } + + @RequestMapping(value = "/check-password/{subsystemName}/{username}", method = RequestMethod.POST, produces = "application/json") + public CheckPasswordResponse checkPassword(@PathVariable String subsystemName, + @PathVariable String username, + @RequestBody CheckPasswordRequest requestBody, + @RequestHeader(value = "Authorization", required = false) String authHeader, + HttpServletRequest request) throws RemoteAuthException { + + String bearerToken = extractBearerToken(authHeader); + if (bearerToken == null) { + throw new UnauthorizedException("Missing or invalid Authorization header"); + } + + if (requestBody.getPasswordEncrypted() == null) { + throw new BadRequestException("Missing passwordEncrypted field"); + } + + if (!username.equals(requestBody.getUsername())) { + throw new BadRequestException("Username in path and body must match"); + } + + String sessionToken = remoteAuthService.checkPassword(subsystemName, username, requestBody.getPasswordEncrypted(), bearerToken, request.getRemoteAddr(), request.getHeader("User-Agent")); + return new CheckPasswordResponse(username, sessionToken); + } + + @RequestMapping(value = "/check-otp/{subsystemName}/{username}", method = RequestMethod.POST, produces = "application/json") + public CheckOtpResponse checkOtp(@PathVariable String subsystemName, + @PathVariable String username, + @RequestBody CheckOtpRequest requestBody, + @RequestHeader(value = "Authorization", required = false) String authHeader) throws RemoteAuthException { + + String bearerToken = extractBearerToken(authHeader); + if (bearerToken == null) { + throw new UnauthorizedException("Missing or invalid Authorization header"); + } + + if (requestBody.getOtpEncrypted() == null || requestBody.getSessionToken() == null) { + throw new BadRequestException("Missing otpEncrypted or sessionToken field"); + } + + if (!username.equals(requestBody.getUsername())) { + throw new BadRequestException("Username in path and body must match"); + } + + String result = remoteAuthService.checkOtp(subsystemName, username, requestBody.getOtpEncrypted(), requestBody.getSessionToken(), bearerToken); + return new CheckOtpResponse(username, requestBody.getSessionToken(), result); + } + + private String extractBearerToken(String authHeader) { + if (authHeader != null && authHeader.startsWith("Bearer ")) { + return authHeader.substring(7); + } + return null; + } + + @ExceptionHandler(RemoteAuthException.class) + public ResponseEntity handleRemoteAuthException(RemoteAuthException e) { + String errorCode = e.getErrorCode(); + HttpStatus status = HttpStatus.BAD_REQUEST; + + if ("INTERNAL_ERROR".equals(errorCode)) { + status = HttpStatus.INTERNAL_SERVER_ERROR; + } + + return createErrorResponse(errorCode, e.getMessage(), status); + } + + @ExceptionHandler(UnauthorizedException.class) + public ResponseEntity handleUnauthorized(UnauthorizedException e) { + return createErrorResponse("UNAUTHORIZED", e.getMessage(), HttpStatus.UNAUTHORIZED); + } + + @ExceptionHandler(BadRequestException.class) + public ResponseEntity handleBadRequest(BadRequestException e) { + return createErrorResponse("BAD_REQUEST", e.getMessage(), HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity handleJsonError(HttpMessageNotReadableException e) { + return createErrorResponse("BAD_REQUEST", "Invalid JSON", HttpStatus.BAD_REQUEST); + } + + private ResponseEntity createErrorResponse(String type, String title, HttpStatus status) { + ErrorResponse error = new ErrorResponse(type, title, title); + error.setErrorId(UUID.randomUUID().toString()); + + return ResponseEntity.status(status) + .header("Content-Language", "en") + .body(error); + } + + static class UnauthorizedException extends RuntimeException { + public UnauthorizedException(String message) { super(message); } + } + + static class BadRequestException extends RuntimeException { + public BadRequestException(String message) { super(message); } + } +} diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckOtpRequest.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckOtpRequest.java new file mode 100644 index 00000000..3b654e28 --- /dev/null +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckOtpRequest.java @@ -0,0 +1,32 @@ +package com.payneteasy.superfly.web.mvc.model; + +public class CheckOtpRequest { + private String username; + private String otpEncrypted; + private String sessionToken; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getOtpEncrypted() { + return otpEncrypted; + } + + public void setOtpEncrypted(String otpEncrypted) { + this.otpEncrypted = otpEncrypted; + } + + public String getSessionToken() { + return sessionToken; + } + + public void setSessionToken(String sessionToken) { + this.sessionToken = sessionToken; + } +} + diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckOtpResponse.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckOtpResponse.java new file mode 100644 index 00000000..00273e47 --- /dev/null +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckOtpResponse.java @@ -0,0 +1,26 @@ +package com.payneteasy.superfly.web.mvc.model; + +public class CheckOtpResponse { + private String username; + private String sessionToken; + private String result; + + public CheckOtpResponse(String username, String sessionToken, String result) { + this.username = username; + this.sessionToken = sessionToken; + this.result = result; + } + + public String getUsername() { + return username; + } + + public String getSessionToken() { + return sessionToken; + } + + public String getResult() { + return result; + } +} + diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordRequest.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordRequest.java new file mode 100644 index 00000000..80fe9ec9 --- /dev/null +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordRequest.java @@ -0,0 +1,23 @@ +package com.payneteasy.superfly.web.mvc.model; + +public class CheckPasswordRequest { + private String username; + private String passwordEncrypted; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPasswordEncrypted() { + return passwordEncrypted; + } + + public void setPasswordEncrypted(String passwordEncrypted) { + this.passwordEncrypted = passwordEncrypted; + } +} + diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordResponse.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordResponse.java new file mode 100644 index 00000000..22964f8e --- /dev/null +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordResponse.java @@ -0,0 +1,20 @@ +package com.payneteasy.superfly.web.mvc.model; + +public class CheckPasswordResponse { + private String username; + private String sessionToken; + + public CheckPasswordResponse(String username, String sessionToken) { + this.username = username; + this.sessionToken = sessionToken; + } + + public String getUsername() { + return username; + } + + public String getSessionToken() { + return sessionToken; + } +} + diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/ErrorResponse.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/ErrorResponse.java new file mode 100644 index 00000000..cf0a1bda --- /dev/null +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/ErrorResponse.java @@ -0,0 +1,35 @@ +package com.payneteasy.superfly.web.mvc.model; + +public class ErrorResponse { + private String type; + private String title; + private String detail; + private String errorId; + + public ErrorResponse(String type, String title, String detail) { + this.type = type; + this.title = title; + this.detail = detail; + } + + public String getType() { + return type; + } + + public String getTitle() { + return title; + } + + public String getDetail() { + return detail; + } + + public String getErrorId() { + return errorId; + } + + public void setErrorId(String errorId) { + this.errorId = errorId; + } +} + diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.properties b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.properties index db285ca2..17ddeb90 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.properties +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.properties @@ -31,6 +31,7 @@ subsystem.edit.callback=Callback Info subsystem.edit.callback.Required=Callback Info is required subsystem.edit.send-callbacks=Send callbacks subsystem.edit.subsystemToken=Token +subsystem.edit.privateKey=Private key subsystem.edit.subsystemUrl=Subsystem URL subsystem.edit.subsystemUrl.Required=Subsystem URL is required subsystem.edit.landingUrl=Landing URL diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html index ed7595dc..bac47a6f 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html @@ -22,6 +22,14 @@ +
+ +
key
+ + Generate new key + +
+ diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java index 2b393923..cdf98388 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java @@ -83,6 +83,26 @@ public void onClick(AjaxRequestTarget aTarget) { } }); + // Private key fields + final Label labelPrivateKey = new Label("privateKey", new LoadableDetachableModel() { + @Override + protected String load() { + return subsystem.getPrivateKey(); + } + }); + labelPrivateKey.setOutputMarkupId(true); + + form.add(new Label("privateKeyLabel", new ResourceModel("subsystem.edit.privateKey"))); + form.add(labelPrivateKey); + form.add(new IndicatingAjaxLink("generateNewPrivateKey") { + private static final long serialVersionUID = 1L; + + public void onClick(AjaxRequestTarget aTarget) { + subsystem.setPrivateKey(generateNewPrivateKey()); + aTarget.add(labelPrivateKey); + } + }); + LabelTextFieldRow subsystemUrlRow = new LabelTextFieldRow<>(subsystem, "subsystemUrl", "subsystem.edit.subsystemUrl", true); subsystemUrlRow.getTextField().add(urlValidator); @@ -129,4 +149,8 @@ protected String getTitle() { private String generateNewToken() { return subsystemService.generateMainSubsystemToken(); } + + private String generateNewPrivateKey() { + return subsystemService.generateSubsystemPrivateKey(); + } } diff --git a/superfly-web/src/main/webapp/WEB-INF/spring/dispatcher-servlet.xml b/superfly-web/src/main/webapp/WEB-INF/spring/dispatcher-servlet.xml new file mode 100644 index 00000000..c7560691 --- /dev/null +++ b/superfly-web/src/main/webapp/WEB-INF/spring/dispatcher-servlet.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/superfly-web/src/main/webapp/WEB-INF/web.xml b/superfly-web/src/main/webapp/WEB-INF/web.xml index 5427f962..2619e706 100644 --- a/superfly-web/src/main/webapp/WEB-INF/web.xml +++ b/superfly-web/src/main/webapp/WEB-INF/web.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> - + 15 @@ -12,7 +12,7 @@ true - + contextConfigLocation @@ -117,7 +117,7 @@ com.payneteasy.superfly.web.spring.CustomContextLoaderListener - + characterEncodingFilter org.springframework.web.filter.CharacterEncodingFilter @@ -131,7 +131,7 @@ cookieEnforcer com.payneteasy.superfly.web.servlet.CookieEnforcer - + springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy @@ -152,6 +152,21 @@ /* + + rest-api + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + /WEB-INF/spring/dispatcher-servlet.xml + + 2 + + + + rest-api + /sso/check/* + + remoting org.springframework.web.servlet.DispatcherServlet @@ -171,7 +186,7 @@ version /management/version.txt - + index.html @@ -187,7 +202,7 @@ ignorePaths - /remoting/,/sso/,/management/version.txt + /remoting/,/sso/,/management/version.txt,/sso/check/ diff --git a/superfly-web/src/test/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckControllerTest.java b/superfly-web/src/test/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckControllerTest.java new file mode 100644 index 00000000..fb0e87c4 --- /dev/null +++ b/superfly-web/src/test/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckControllerTest.java @@ -0,0 +1,197 @@ +package com.payneteasy.superfly.web.mvc; + +import com.payneteasy.superfly.service.RemoteAuthService; +import com.payneteasy.superfly.service.RemoteAuthService.RemoteAuthException; +import com.payneteasy.superfly.web.mvc.model.CheckOtpRequest; +import com.payneteasy.superfly.web.mvc.model.CheckOtpResponse; +import com.payneteasy.superfly.web.mvc.model.CheckPasswordRequest; +import com.payneteasy.superfly.web.mvc.model.CheckPasswordResponse; +import com.payneteasy.superfly.web.mvc.model.ErrorResponse; +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; + +import javax.servlet.http.HttpServletRequest; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.assertEquals; + +public class RemoteAuthCheckControllerTest { + + private RemoteAuthCheckController controller; + private RemoteAuthService remoteAuthService; + private HttpServletRequest request; + + @Before + public void setUp() { + remoteAuthService = createMock(RemoteAuthService.class); + request = createMock(HttpServletRequest.class); + // Внедрение зависимостей через конструктор, что позволяет отказаться от @Autowired на сеттерах + // и делает тестирование проще (не нужен Spring Context в юнит-тесте) + controller = new RemoteAuthCheckController(remoteAuthService); + } + + @Test + public void testCheckPasswordSuccess() throws Exception { + // 1. Подготовка данных (Fixtures) + // Используем константы и переменные для лучшей читаемости и повторного использования + String subsystemName = "test-subsystem"; + String username = "test-user"; + String bearerToken = "test-token"; + String passwordEncrypted = "encrypted-password"; + String ipAddress = "127.0.0.1"; + String userAgent = "TestAgent"; + String sessionToken = "session-123"; + + // Создаем объект запроса через сеттеры (или можно было бы через конструктор/builder если бы они были) + CheckPasswordRequest requestBody = new CheckPasswordRequest(); + requestBody.setUsername(username); + requestBody.setPasswordEncrypted(passwordEncrypted); + + // 2. Программирование моков (Record phase) + // Ожидаем вызовы к request (получение IP и User-Agent) + expect(request.getRemoteAddr()).andReturn(ipAddress); + expect(request.getHeader("User-Agent")).andReturn(userAgent); + + // Ожидаем вызов бизнес-логики сервиса + expect(remoteAuthService.checkPassword(subsystemName, username, passwordEncrypted, bearerToken, ipAddress, userAgent)) + .andReturn(sessionToken); + + // 3. Перевод моков в режим воспроизведения (Replay phase) + replay(remoteAuthService, request); + + // 4. Выполнение тестируемого кода (Exercise) + CheckPasswordResponse response = controller.checkPassword(subsystemName, username, requestBody, "Bearer " + bearerToken, request); + + // 5. Проверка вызовов (Verify phase) + verify(remoteAuthService, request); + + // 6. Проверка результата (Assertions) + assertEquals("Username should match", username, response.getUsername()); + assertEquals("Session token should match", sessionToken, response.getSessionToken()); + } + + @Test + public void testCheckOtpSuccess() throws Exception { + String subsystemName = "test-subsystem"; + String username = "test-user"; + String bearerToken = "test-token"; + String otpEncrypted = "encrypted-otp"; + String sessionToken = "session-123"; + String authResult = "SUCCESS"; + + CheckOtpRequest requestBody = new CheckOtpRequest(); + requestBody.setUsername(username); + requestBody.setOtpEncrypted(otpEncrypted); + requestBody.setSessionToken(sessionToken); + + expect(remoteAuthService.checkOtp(subsystemName, username, otpEncrypted, sessionToken, bearerToken)) + .andReturn(authResult); + + replay(remoteAuthService, request); + + CheckOtpResponse response = controller.checkOtp(subsystemName, username, requestBody, "Bearer " + bearerToken); + + verify(remoteAuthService, request); + + assertEquals(username, response.getUsername()); + assertEquals(sessionToken, response.getSessionToken()); + assertEquals(authResult, response.getResult()); + } + + @Test(expected = RemoteAuthCheckController.UnauthorizedException.class) + public void testCheckPasswordMissingToken() throws RemoteAuthException { + String subsystemName = "test-subsystem"; + String username = "test-user"; + CheckPasswordRequest requestBody = new CheckPasswordRequest(); + + replay(remoteAuthService, request); + controller.checkPassword(subsystemName, username, requestBody, null, request); + } + + @Test(expected = RemoteAuthCheckController.BadRequestException.class) + public void testCheckPasswordMissingFields() throws RemoteAuthException { + String subsystemName = "test-subsystem"; + String username = "test-user"; + CheckPasswordRequest requestBody = new CheckPasswordRequest(); + requestBody.setUsername(username); + // Missing password + + replay(remoteAuthService, request); + controller.checkPassword(subsystemName, username, requestBody, "Bearer token", request); + } + + @Test(expected = RemoteAuthException.class) + public void testCheckPasswordServiceException() throws Exception { + String subsystemName = "test-subsystem"; + String username = "test-user"; + String bearerToken = "test-token"; + String passwordEncrypted = "encrypted-password"; + String ipAddress = "127.0.0.1"; + String userAgent = "TestAgent"; + + CheckPasswordRequest requestBody = new CheckPasswordRequest(); + requestBody.setUsername(username); + requestBody.setPasswordEncrypted(passwordEncrypted); + + expect(request.getRemoteAddr()).andReturn(ipAddress); + expect(request.getHeader("User-Agent")).andReturn(userAgent); + expect(remoteAuthService.checkPassword(subsystemName, username, passwordEncrypted, bearerToken, ipAddress, userAgent)) + .andThrow(new RemoteAuthException("Some error", "INTERNAL_ERROR")); + + replay(remoteAuthService, request); + + controller.checkPassword(subsystemName, username, requestBody, "Bearer " + bearerToken, request); + } + + @Test + public void testHandleRemoteAuthException() { + RemoteAuthException ex = new RemoteAuthException("Error message", "INTERNAL_ERROR"); + ResponseEntity response = controller.handleRemoteAuthException(ex); + + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); + assertEquals("INTERNAL_ERROR", response.getBody().getType()); + assertEquals("Error message", response.getBody().getTitle()); + } + + @Test + public void testHandleRemoteAuthExceptionBadRequest() { + RemoteAuthException ex = new RemoteAuthException("Bad credentials", "BAD_CREDENTIALS"); + ResponseEntity response = controller.handleRemoteAuthException(ex); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertEquals("BAD_CREDENTIALS", response.getBody().getType()); + } + + @Test + public void testHandleUnauthorized() { + RemoteAuthCheckController.UnauthorizedException ex = new RemoteAuthCheckController.UnauthorizedException("Unauthorized access"); + ResponseEntity response = controller.handleUnauthorized(ex); + + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + assertEquals("UNAUTHORIZED", response.getBody().getType()); + assertEquals("Unauthorized access", response.getBody().getTitle()); + } + + @Test + public void testHandleBadRequest() { + RemoteAuthCheckController.BadRequestException ex = new RemoteAuthCheckController.BadRequestException("Bad request data"); + ResponseEntity response = controller.handleBadRequest(ex); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertEquals("BAD_REQUEST", response.getBody().getType()); + assertEquals("Bad request data", response.getBody().getTitle()); + } + + @Test + public void testHandleJsonError() { + HttpMessageNotReadableException ex = new HttpMessageNotReadableException("Json error"); + ResponseEntity response = controller.handleJsonError(ex); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertEquals("BAD_REQUEST", response.getBody().getType()); + assertEquals("Invalid JSON", response.getBody().getTitle()); + } +} \ No newline at end of file From 3a443912c151a86bbebf67447e70ee874ddd31da Mon Sep 17 00:00:00 2001 From: akorobov Date: Wed, 3 Dec 2025 00:25:05 +0300 Subject: [PATCH 02/91] #110 - Add support for RSA key pair generation and storage for subsystems --- .../model/ui/subsystem/UISubsystem.java | 20 ++++ .../service/RemoteAuthCryptoService.java | 23 +++- .../superfly/service/SubsystemService.java | 6 +- .../service/impl/SubsystemServiceImpl.java | 18 +-- .../impl/remote/check/KeyPairData.java | 4 + .../check/RemoteAuthCryptoServiceImpl.java | 108 ++++++++++++++++++ .../check/RemoteAuthCryptoServiceStub.java | 9 +- .../check/RemoteAuthEncryptionAlgorithm.java | 6 + .../remote/check/RemoteAuthServiceImpl.java | 12 +- superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd | 2 +- superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql | 2 + .../src/ui_create/ui_create_subsystem.prc | 10 +- superfly-sql/src/ui_get/ui_get_subsystem.prc | 4 +- .../src/ui_get/ui_get_subsystem_by_name.prc | 8 +- .../web/wicket/SuperflyApplication.properties | 3 +- .../page/subsystem/AddSubsystemPage.html | 7 ++ .../page/subsystem/AddSubsystemPage.java | 31 +++++ .../page/subsystem/EditSubsystemPage.html | 8 +- .../page/subsystem/EditSubsystemPage.java | 27 +++-- 19 files changed, 269 insertions(+), 39 deletions(-) create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/KeyPairData.java create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceImpl.java create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthEncryptionAlgorithm.java diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/model/ui/subsystem/UISubsystem.java b/superfly-service/src/main/java/com/payneteasy/superfly/model/ui/subsystem/UISubsystem.java index 1d7e9fa5..e5c0aec8 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/model/ui/subsystem/UISubsystem.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/model/ui/subsystem/UISubsystem.java @@ -20,6 +20,8 @@ public class UISubsystem implements Serializable { private String landingUrl; private String loginFormCssUrl; private String privateKey; + private String publicKey; + private String encryptionAlgorithm; @Column(name = "ssys_id") public Long getId() { @@ -129,4 +131,22 @@ public String getPrivateKey() { public void setPrivateKey(String privateKey) { this.privateKey = privateKey; } + + @Column(name = "public_key") + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + @Column(name = "encryption_algorithm") + public String getEncryptionAlgorithm() { + return encryptionAlgorithm; + } + + public void setEncryptionAlgorithm(String encryptionAlgorithm) { + this.encryptionAlgorithm = encryptionAlgorithm; + } } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthCryptoService.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthCryptoService.java index 265f1500..e3297c22 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthCryptoService.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthCryptoService.java @@ -1,25 +1,44 @@ package com.payneteasy.superfly.service; +import com.payneteasy.superfly.service.impl.remote.check.KeyPairData; +import com.payneteasy.superfly.service.impl.remote.check.RemoteAuthEncryptionAlgorithm; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + /** * Service for cryptographic operations in remote authentication. */ public interface RemoteAuthCryptoService { + + KeyPairData generateKeyPair(RemoteAuthEncryptionAlgorithm algorithm); + /** * Decrypts a password using the subsystem's private key. * * @param encryptedPassword The encrypted password (Base64 URL-safe no padding). * @param privateKey The private key of the subsystem. + * @param algorithm The encryption algorithm to use. * @return The decrypted password. */ - String decryptPassword(String encryptedPassword, String privateKey); + String decryptPassword(String encryptedPassword, String privateKey, RemoteAuthEncryptionAlgorithm algorithm) + throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, + InvalidKeySpecException, BadPaddingException, InvalidKeyException; /** * Decrypts an OTP using the subsystem's private key. * * @param encryptedOtp The encrypted OTP (Base64 URL-safe no padding). * @param privateKey The private key of the subsystem. + * @param algorithm The encryption algorithm to use. * @return The decrypted OTP. */ - String decryptOtp(String encryptedOtp, String privateKey); + String decryptOtp(String encryptedOtp, String privateKey, RemoteAuthEncryptionAlgorithm algorithm) + throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, + InvalidKeySpecException, BadPaddingException, InvalidKeyException; } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/SubsystemService.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/SubsystemService.java index 48360891..7d252c39 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/SubsystemService.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/SubsystemService.java @@ -7,6 +7,8 @@ import com.payneteasy.superfly.model.ui.subsystem.UISubsystem; import com.payneteasy.superfly.model.ui.subsystem.UISubsystemForFilter; import com.payneteasy.superfly.model.ui.subsystem.UISubsystemForList; +import com.payneteasy.superfly.service.impl.remote.check.KeyPairData; +import com.payneteasy.superfly.service.impl.remote.check.RemoteAuthEncryptionAlgorithm; /** * Service for subsystems. @@ -88,7 +90,7 @@ public interface SubsystemService { /** * - * @return private key for subsystem + * @return key pair data for subsystem */ - String generateSubsystemPrivateKey(); + KeyPairData generateKeyPair(RemoteAuthEncryptionAlgorithm algorithm); } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/SubsystemServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/SubsystemServiceImpl.java index 26579881..4767cb91 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/SubsystemServiceImpl.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/SubsystemServiceImpl.java @@ -5,7 +5,9 @@ import java.util.UUID; import com.payneteasy.superfly.model.SubsystemTokenData; -import com.payneteasy.superfly.service.JavaMailSenderPool; +import com.payneteasy.superfly.service.*; +import com.payneteasy.superfly.service.impl.remote.check.KeyPairData; +import com.payneteasy.superfly.service.impl.remote.check.RemoteAuthEncryptionAlgorithm; import com.payneteasy.superfly.utils.RandomGUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,9 +19,6 @@ import com.payneteasy.superfly.model.ui.subsystem.UISubsystem; import com.payneteasy.superfly.model.ui.subsystem.UISubsystemForFilter; import com.payneteasy.superfly.model.ui.subsystem.UISubsystemForList; -import com.payneteasy.superfly.service.LoggerSink; -import com.payneteasy.superfly.service.NotificationService; -import com.payneteasy.superfly.service.SubsystemService; @Transactional public class SubsystemServiceImpl implements SubsystemService { @@ -30,6 +29,7 @@ public class SubsystemServiceImpl implements SubsystemService { private NotificationService notificationService; private LoggerSink loggerSink; private JavaMailSenderPool javaMailSenderPool; + private RemoteAuthCryptoService remoteAuthCryptoService; @Required public void setSubsystemDao(SubsystemDao subsystemDao) { @@ -41,6 +41,11 @@ public void setNotificationService(NotificationService notificationService) { this.notificationService = notificationService; } + @Required + public void setRemoteAuthCryptoService(RemoteAuthCryptoService remoteAuthCryptoService) { + this.remoteAuthCryptoService = remoteAuthCryptoService; + } + @Required public void setLoggerSink(LoggerSink loggerSink) { this.loggerSink = loggerSink; @@ -53,7 +58,6 @@ public void setJavaMailSenderPool(JavaMailSenderPool javaMailSenderPool) { public RoutineResult createSubsystem(UISubsystem subsystem) { subsystem.setSubsystemToken(generateMainSubsystemToken()); - subsystem.setPrivateKey(generateSubsystemPrivateKey()); RoutineResult result = subsystemDao.createSubsystem(subsystem); loggerSink.info(logger, "CREATE_SUBSYSTEM", true, subsystem.getName()); javaMailSenderPool.flushAll(); // clearing pool so changes are applied @@ -112,7 +116,7 @@ public String generateMainSubsystemToken() { } @Override - public String generateSubsystemPrivateKey() { - return UUID.randomUUID().toString(); + public KeyPairData generateKeyPair(RemoteAuthEncryptionAlgorithm algorithm) { + return remoteAuthCryptoService.generateKeyPair(algorithm); } } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/KeyPairData.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/KeyPairData.java new file mode 100644 index 00000000..687ba9e8 --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/KeyPairData.java @@ -0,0 +1,4 @@ +package com.payneteasy.superfly.service.impl.remote.check; + +public record KeyPairData(String publicKey, String privateKey) { +} diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceImpl.java new file mode 100644 index 00000000..260e4faf --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceImpl.java @@ -0,0 +1,108 @@ +package com.payneteasy.superfly.service.impl.remote.check; + +import com.payneteasy.superfly.service.RemoteAuthCryptoService; +import org.springframework.stereotype.Service; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; + +@Service +public class RemoteAuthCryptoServiceImpl implements RemoteAuthCryptoService { + + @Override + public KeyPairData generateKeyPair(RemoteAuthEncryptionAlgorithm algorithm) { + return switch (algorithm) { + case RSA -> { + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(4096); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + + String publicKeyPem = toPem(keyPair.getPublic().getEncoded(), "PUBLIC KEY"); + String privateKeyPem = toPem(keyPair.getPrivate().getEncoded(), "PRIVATE KEY"); + + yield new KeyPairData(publicKeyPem, privateKeyPem); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("Cannot generate RSA key pair", e); + } + } + case EC -> throw new UnsupportedOperationException( + "EC algorithm is not supported yet. Please use RSA instead." + ); + }; + } + + private String toPem(byte[] encodedKey, String type) { + String base64 = Base64.getEncoder().encodeToString(encodedKey); + StringBuilder sb = new StringBuilder(); + sb.append("-----BEGIN ").append(type).append("-----\n"); + for (int i = 0; i < base64.length(); i += 64) { + int end = Math.min(i + 64, base64.length()); + sb.append(base64, i, end).append('\n'); + } + sb.append("-----END ").append(type).append("-----"); + return sb.toString(); + } + + @Override + public String decryptPassword(String encryptedPassword, String privateKey, RemoteAuthEncryptionAlgorithm algorithm) + throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, + InvalidKeySpecException, BadPaddingException, InvalidKeyException { + return decrypt(encryptedPassword, privateKey, algorithm); + } + + @Override + public String decryptOtp(String encryptedOtp, String privateKey, RemoteAuthEncryptionAlgorithm algorithm) + throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, + InvalidKeySpecException, BadPaddingException, InvalidKeyException { + return decrypt(encryptedOtp, privateKey, algorithm); + } + + private String decrypt(String encrypted, String privateKey, RemoteAuthEncryptionAlgorithm algorithm) + throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, + IllegalBlockSizeException, BadPaddingException, InvalidKeyException { + byte[] encryptedBytes = Base64.getUrlDecoder().decode(encrypted); + PrivateKey key = parsePrivateKey(privateKey, algorithm); + byte[] decrypted = decryptBytes(encryptedBytes, key, algorithm); + return new String(decrypted, StandardCharsets.UTF_8); + } + + private PrivateKey parsePrivateKey(String privateKeyPem, RemoteAuthEncryptionAlgorithm algorithm) + throws NoSuchAlgorithmException, InvalidKeySpecException { + + String normalized = privateKeyPem + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s", ""); + + byte[] keyBytes = Base64.getDecoder().decode(normalized); + + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(algorithm.name()); + + return keyFactory.generatePrivate(keySpec); + } + + private byte[] decryptBytes(byte[] encrypted, PrivateKey privateKey, RemoteAuthEncryptionAlgorithm algorithm) + throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, + BadPaddingException { + + return switch (algorithm) { + case RSA -> { + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + yield cipher.doFinal(encrypted); + } + case EC -> throw new UnsupportedOperationException( + "EC algorithm is not supported yet. Please use RSA instead." + ); + }; + } +} diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java index 2ef571f9..37905007 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java @@ -16,14 +16,19 @@ public class RemoteAuthCryptoServiceStub implements RemoteAuthCryptoService { public RemoteAuthCryptoServiceStub() { } - public String decryptPassword(String encryptedPassword, String privateKey) { + @Override + public KeyPairData generateKeyPair(RemoteAuthEncryptionAlgorithm algorithm) { + return null; + } + + public String decryptPassword(String encryptedPassword, String privateKey, RemoteAuthEncryptionAlgorithm encryptionAlgorithm) { logger.warn("decryptPassword is a STUB. Returning encryptedPassword as is."); // In a real implementation, this would decrypt the password. // For now, we assume the input is already the plain password or we just return it to test flow. return encryptedPassword; } - public String decryptOtp(String encryptedOtp, String privateKey) { + public String decryptOtp(String encryptedOtp, String privateKey, RemoteAuthEncryptionAlgorithm encryptionAlgorithm) { logger.warn("decryptOtp is a STUB. Returning encryptedOtp as is."); return encryptedOtp; } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthEncryptionAlgorithm.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthEncryptionAlgorithm.java new file mode 100644 index 00000000..f58a3f9d --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthEncryptionAlgorithm.java @@ -0,0 +1,6 @@ +package com.payneteasy.superfly.service.impl.remote.check; + +public enum RemoteAuthEncryptionAlgorithm { + + RSA, EC +} diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java index 9ec5335e..0db88631 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java @@ -45,7 +45,11 @@ public String checkPassword(String subsystemName, String username, String passwo // 2. Decrypt Password String password; try { - password = remoteAuthCryptoService.decryptPassword(passwordEncrypted, subsystem.getPrivateKey()); + password = remoteAuthCryptoService.decryptPassword( + passwordEncrypted, + subsystem.getPrivateKey(), + RemoteAuthEncryptionAlgorithm.valueOf(subsystem.getEncryptionAlgorithm()) + ); } catch (Exception e) { logger.error("Failed to decrypt password", e); throw new RemoteAuthException("Decryption failed", "INTERNAL_ERROR", e); @@ -88,7 +92,11 @@ public String checkOtp(String subsystemName, String username, String otpEncrypte // 3. Decrypt OTP String otp; try { - otp = remoteAuthCryptoService.decryptOtp(otpEncrypted, subsystem.getPrivateKey()); + otp = remoteAuthCryptoService.decryptOtp( + otpEncrypted, + subsystem.getPrivateKey(), + RemoteAuthEncryptionAlgorithm.valueOf(subsystem.getEncryptionAlgorithm()) + ); } catch (Exception e) { logger.error("Failed to decrypt OTP", e); throw new RemoteAuthException("Decryption failed", "INTERNAL_ERROR", e); diff --git a/superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd b/superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd index 78aeec93..ade25257 100644 --- a/superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd +++ b/superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd @@ -1,3 +1,3 @@ @set PATH=C:\cygwin\bin;%PATH% -bash R1.7.2_SSO.sh +bash R1.7.3_SSO.sh diff --git a/superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql b/superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql index dd8cdc90..0407b3ed 100644 --- a/superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql +++ b/superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql @@ -1,3 +1,5 @@ alter table subsystems add column private_key text; +alter table subsystems add column public_key text; +alter table subsystems add column encryption_algorithm varchar(16); commit; diff --git a/superfly-sql/src/ui_create/ui_create_subsystem.prc b/superfly-sql/src/ui_create/ui_create_subsystem.prc index 41571e3a..f24dad3e 100644 --- a/superfly-sql/src/ui_create/ui_create_subsystem.prc +++ b/superfly-sql/src/ui_create/ui_create_subsystem.prc @@ -11,6 +11,8 @@ create procedure ui_create_subsystem(i_subsystem_name varchar(32), i_landing_url varchar(255), i_login_form_css_url varchar(255), i_private_key text, + i_public_key text, + i_encryption_algorithm varchar(16), out o_ssys_id int(10) ) main_sql: @@ -28,7 +30,9 @@ create procedure ui_create_subsystem(i_subsystem_name varchar(32), subsystem_url, landing_url, login_form_css_url, - private_key + private_key, + public_key, + encryption_algorithm ) values ( i_subsystem_name, @@ -42,7 +46,9 @@ create procedure ui_create_subsystem(i_subsystem_name varchar(32), i_subsystem_url, i_landing_url, i_login_form_css_url, - i_private_key + i_private_key, + i_public_key, + i_encryption_algorithm ); set o_ssys_id = last_insert_id(); diff --git a/superfly-sql/src/ui_get/ui_get_subsystem.prc b/superfly-sql/src/ui_get/ui_get_subsystem.prc index 6a781213..deb89d93 100644 --- a/superfly-sql/src/ui_get/ui_get_subsystem.prc +++ b/superfly-sql/src/ui_get/ui_get_subsystem.prc @@ -16,7 +16,7 @@ create procedure ui_get_subsystem(i_ssys_id int(10)) ss.subsystem_url, ss.landing_url, ss.login_form_css_url, - ss.private_key + ss.public_key from subsystems ss left join smtp_servers smtp on smtp.ssrv_id = ss.ssrv_ssrv_id @@ -39,6 +39,6 @@ call save_routine_information('ui_get_subsystem', 'subsystem_url varchar', 'landing_url varchar', 'login_form_css_url varchar', - 'private_key varchar' + 'public_key varchar' ) ); diff --git a/superfly-sql/src/ui_get/ui_get_subsystem_by_name.prc b/superfly-sql/src/ui_get/ui_get_subsystem_by_name.prc index 06556b3d..e5819aec 100644 --- a/superfly-sql/src/ui_get/ui_get_subsystem_by_name.prc +++ b/superfly-sql/src/ui_get/ui_get_subsystem_by_name.prc @@ -16,7 +16,9 @@ create procedure ui_get_subsystem_by_name(i_subsystem_name varchar(32)) ss.subsystem_url, ss.landing_url, ss.login_form_css_url, - ss.private_key + ss.private_key, + ss.public_key, + ss.encryption_algorithm from subsystems ss left join smtp_servers smtp on smtp.ssrv_id = ss.ssrv_ssrv_id @@ -39,6 +41,8 @@ call save_routine_information('ui_get_subsystem_by_name', 'subsystem_url varchar', 'landing_url varchar', 'login_form_css_url varchar', - 'private_key varchar' + 'private_key varchar', + 'public_key varchar', + 'encryption_algorithm varchar' ) ); diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.properties b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.properties index 17ddeb90..d7d6cb2c 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.properties +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.properties @@ -31,7 +31,8 @@ subsystem.edit.callback=Callback Info subsystem.edit.callback.Required=Callback Info is required subsystem.edit.send-callbacks=Send callbacks subsystem.edit.subsystemToken=Token -subsystem.edit.privateKey=Private key +subsystem.edit.publicKey=Public key +subsystem.add.publicKey=Public key subsystem.edit.subsystemUrl=Subsystem URL subsystem.edit.subsystemUrl.Required=Subsystem URL is required subsystem.edit.landingUrl=Landing URL diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.html index a6b34cbd..df62da4e 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.html @@ -16,6 +16,13 @@ +
+ +
key
+ + Generate new RSA key pair + +
diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.java index 692a357f..1282bde0 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.java @@ -4,11 +4,16 @@ import com.payneteasy.superfly.model.ui.subsystem.UISubsystem; import com.payneteasy.superfly.service.SmtpServerService; import com.payneteasy.superfly.service.SubsystemService; +import com.payneteasy.superfly.service.impl.remote.check.KeyPairData; +import com.payneteasy.superfly.service.impl.remote.check.RemoteAuthEncryptionAlgorithm; import com.payneteasy.superfly.web.wicket.component.field.LabelCheckBoxRow; import com.payneteasy.superfly.web.wicket.component.field.LabelDropDownChoiceRow; import com.payneteasy.superfly.web.wicket.component.field.LabelTextFieldRow; import com.payneteasy.superfly.web.wicket.page.BasePage; import org.apache.wicket.Page; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxLink; +import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.ChoiceRenderer; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.SubmitLink; @@ -16,6 +21,7 @@ import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; +import org.apache.wicket.model.ResourceModel; import org.apache.wicket.spring.injection.annot.SpringBean; import org.apache.wicket.validation.validator.UrlValidator; import org.springframework.security.access.annotation.Secured; @@ -87,6 +93,27 @@ public String getIdValue(UISmtpServerForFilter server, int index) { } }, true)); + final Label labelPublicKey = new Label("publicKey", new LoadableDetachableModel() { + @Override + protected String load() { + return subsystem.getPublicKey(); + } + }); + labelPublicKey.setOutputMarkupId(true); + + form.add(new Label("publicKeyLabel", new ResourceModel("subsystem.add.publicKey"))); + form.add(labelPublicKey); + form.add(new IndicatingAjaxLink("generateNewKeyPair") { + private static final long serialVersionUID = 1L; + + public void onClick(AjaxRequestTarget aTarget) { + var keyPairData = generateKeyPair(RemoteAuthEncryptionAlgorithm.RSA); + subsystem.setPrivateKey(keyPairData.privateKey()); + subsystem.setPublicKey(keyPairData.publicKey()); + aTarget.add(labelPublicKey); + } + }); + form.add(new SubmitLink("submit-link")); form.add(new BookmarkablePageLink("cancel", ListSubsystemsPage.class)); } @@ -95,4 +122,8 @@ public String getIdValue(UISmtpServerForFilter server, int index) { protected String getTitle() { return "Add subsystem"; } + + private KeyPairData generateKeyPair(RemoteAuthEncryptionAlgorithm algorithm) { + return subsystemService.generateKeyPair(algorithm); + } } diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html index bac47a6f..1d885b01 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html @@ -23,10 +23,10 @@
- -
key
- - Generate new key + +
key
+ + Generate new RSA key pair
diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java index cdf98388..6f09f0ac 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java @@ -4,6 +4,8 @@ import com.payneteasy.superfly.model.ui.subsystem.UISubsystem; import com.payneteasy.superfly.service.SmtpServerService; import com.payneteasy.superfly.service.SubsystemService; +import com.payneteasy.superfly.service.impl.remote.check.KeyPairData; +import com.payneteasy.superfly.service.impl.remote.check.RemoteAuthEncryptionAlgorithm; import com.payneteasy.superfly.web.wicket.component.field.LabelCheckBoxRow; import com.payneteasy.superfly.web.wicket.component.field.LabelDropDownChoiceRow; import com.payneteasy.superfly.web.wicket.component.field.LabelTextFieldRow; @@ -26,7 +28,6 @@ import org.springframework.security.access.annotation.Secured; import java.util.List; -import java.util.UUID; @Secured("ROLE_ADMIN") public class EditSubsystemPage extends BasePage { @@ -83,23 +84,25 @@ public void onClick(AjaxRequestTarget aTarget) { } }); - // Private key fields - final Label labelPrivateKey = new Label("privateKey", new LoadableDetachableModel() { + // Public key fields + final Label labelPublicKey = new Label("publicKey", new LoadableDetachableModel() { @Override protected String load() { - return subsystem.getPrivateKey(); + return subsystem.getPublicKey(); } }); - labelPrivateKey.setOutputMarkupId(true); + labelPublicKey.setOutputMarkupId(true); - form.add(new Label("privateKeyLabel", new ResourceModel("subsystem.edit.privateKey"))); - form.add(labelPrivateKey); - form.add(new IndicatingAjaxLink("generateNewPrivateKey") { + form.add(new Label("publicKeyLabel", new ResourceModel("subsystem.edit.publicKey"))); + form.add(labelPublicKey); + form.add(new IndicatingAjaxLink("generateNewKeyPair") { private static final long serialVersionUID = 1L; public void onClick(AjaxRequestTarget aTarget) { - subsystem.setPrivateKey(generateNewPrivateKey()); - aTarget.add(labelPrivateKey); + var keyPairData = generateKeyPair(RemoteAuthEncryptionAlgorithm.RSA); + subsystem.setPrivateKey(keyPairData.privateKey()); + subsystem.setPublicKey(keyPairData.publicKey()); + aTarget.add(labelPublicKey); } }); @@ -150,7 +153,7 @@ private String generateNewToken() { return subsystemService.generateMainSubsystemToken(); } - private String generateNewPrivateKey() { - return subsystemService.generateSubsystemPrivateKey(); + private KeyPairData generateKeyPair(RemoteAuthEncryptionAlgorithm algorithm) { + return subsystemService.generateKeyPair(algorithm); } } From 987bc5c518adfccd92d76a7a15cfdc87aca64219 Mon Sep 17 00:00:00 2001 From: iv Date: Wed, 3 Dec 2025 16:47:17 +0300 Subject: [PATCH 03/91] #111 sso-remote-auth - API, service and auth phases cache --- .../superfly/service/RemoteAuthService.java | 25 +++++++++++++++++-- .../remote/check/RemoteAuthServiceImpl.java | 22 ++++++++-------- .../web/mvc/RemoteAuthCheckController.java | 4 +-- .../web/mvc/model/CheckPasswordResponse.java | 12 ++++++--- .../mvc/RemoteAuthCheckControllerTest.java | 14 ++++++----- 5 files changed, 53 insertions(+), 24 deletions(-) diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthService.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthService.java index e12f6387..2fa5b7db 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthService.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthService.java @@ -14,10 +14,10 @@ public interface RemoteAuthService { * @param bearerToken Bearer token provided in the request. * @param ipAddress IP address of the client. * @param userAgent User agent of the client. - * @return Session token if authentication is successful. + * @return Session information including token and OTP requirement status if authentication is successful. * @throws RemoteAuthException If authentication fails or other errors occur. */ - String checkPassword(String subsystemName, String username, String passwordEncrypted, String bearerToken, String ipAddress, String userAgent) throws RemoteAuthException; + RemoteAuthSession checkPassword(String subsystemName, String username, String passwordEncrypted, String bearerToken, String ipAddress, String userAgent) throws RemoteAuthException; /** * Checks the OTP for a user in a subsystem. @@ -32,6 +32,27 @@ public interface RemoteAuthService { */ String checkOtp(String subsystemName, String username, String otpEncrypted, String sessionToken, String bearerToken) throws RemoteAuthException; + /** + * Session data returned after successful password check. + */ + class RemoteAuthSession { + private final String sessionToken; + private final boolean otpRequired; + + public RemoteAuthSession(String sessionToken, boolean otpRequired) { + this.sessionToken = sessionToken; + this.otpRequired = otpRequired; + } + + public String getSessionToken() { + return sessionToken; + } + + public boolean isOtpRequired() { + return otpRequired; + } + } + /** * Exception class for remote auth errors. */ diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java index 0db88631..29f6a75f 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java @@ -1,5 +1,6 @@ package com.payneteasy.superfly.service.impl.remote.check; +import com.payneteasy.superfly.api.OTPType; import com.payneteasy.superfly.api.SSOUser; import com.payneteasy.superfly.model.ui.subsystem.UISubsystem; import com.payneteasy.superfly.service.InternalSSOService; @@ -38,7 +39,7 @@ public RemoteAuthServiceImpl(SubsystemService subsystemService, } @Override - public String checkPassword(String subsystemName, String username, String passwordEncrypted, String bearerToken, String ipAddress, String userAgent) throws RemoteAuthException { + public RemoteAuthSession checkPassword(String subsystemName, String username, String passwordEncrypted, String bearerToken, String ipAddress, String userAgent) throws RemoteAuthException { // 1. Validate Subsystem and Token UISubsystem subsystem = validateSubsystem(subsystemName, bearerToken); @@ -70,9 +71,11 @@ public String checkPassword(String subsystemName, String username, String passwo // 4. Generate Session Token and Cache String sessionToken = UUID.randomUUID().toString(); - sessionCache.put(sessionToken, new RemoteSession(username)); + sessionCache.put(sessionToken, new RemoteSession(username, ssoUser.getOtpType())); - return sessionToken; + boolean otpRequired = ssoUser.getOtpType() != OTPType.NONE && !ssoUser.isOtpOptional(); + + return new RemoteAuthSession(sessionToken, otpRequired); } @Override @@ -103,15 +106,10 @@ public String checkOtp(String subsystemName, String username, String otpEncrypte } // 4. Verify OTP - boolean otpValid = internalSSOService.authenticateHOTP(subsystemName, username, otp); + boolean otpValid = internalSSOService.authenticateByOtpType(session.otpType, username, otp); if (!otpValid) { return "BAD_USER_OR_PASSWORD_OR_OTP"; } - - // TODO: Check for other statuses like USER_SHOULD_CHANGE_PASSWORD or USER_BLOCKED - // internalSSOService.authenticateHOTP only returns boolean. - // We might need to check user status explicitly if needed. - return "SUCCESS"; } @@ -129,10 +127,12 @@ private UISubsystem validateSubsystem(String subsystemName, String bearerToken) } private static class RemoteSession { - final String username; + final String username; + final OTPType otpType; - RemoteSession(String username) { + RemoteSession(String username, OTPType otpType) { this.username = username; + this.otpType = otpType; } } } diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckController.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckController.java index 1676abb4..c42fc575 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckController.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckController.java @@ -44,8 +44,8 @@ public CheckPasswordResponse checkPassword(@PathVariable String subsystemName, throw new BadRequestException("Username in path and body must match"); } - String sessionToken = remoteAuthService.checkPassword(subsystemName, username, requestBody.getPasswordEncrypted(), bearerToken, request.getRemoteAddr(), request.getHeader("User-Agent")); - return new CheckPasswordResponse(username, sessionToken); + RemoteAuthService.RemoteAuthSession session = remoteAuthService.checkPassword(subsystemName, username, requestBody.getPasswordEncrypted(), bearerToken, request.getRemoteAddr(), request.getHeader("User-Agent")); + return new CheckPasswordResponse(username, session.getSessionToken(), session.isOtpRequired()); } @RequestMapping(value = "/check-otp/{subsystemName}/{username}", method = RequestMethod.POST, produces = "application/json") diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordResponse.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordResponse.java index 22964f8e..d62f5c5b 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordResponse.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordResponse.java @@ -1,12 +1,14 @@ package com.payneteasy.superfly.web.mvc.model; public class CheckPasswordResponse { - private String username; - private String sessionToken; + private final String username; + private final String sessionToken; + private final boolean otpRequired; - public CheckPasswordResponse(String username, String sessionToken) { + public CheckPasswordResponse(String username, String sessionToken, boolean otpRequired) { this.username = username; this.sessionToken = sessionToken; + this.otpRequired = otpRequired; } public String getUsername() { @@ -16,5 +18,9 @@ public String getUsername() { public String getSessionToken() { return sessionToken; } + + public boolean isOtpRequired() { + return otpRequired; + } } diff --git a/superfly-web/src/test/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckControllerTest.java b/superfly-web/src/test/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckControllerTest.java index fb0e87c4..d099c330 100644 --- a/superfly-web/src/test/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckControllerTest.java +++ b/superfly-web/src/test/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckControllerTest.java @@ -17,6 +17,7 @@ import static org.easymock.EasyMock.*; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; public class RemoteAuthCheckControllerTest { @@ -54,10 +55,10 @@ public void testCheckPasswordSuccess() throws Exception { // Ожидаем вызовы к request (получение IP и User-Agent) expect(request.getRemoteAddr()).andReturn(ipAddress); expect(request.getHeader("User-Agent")).andReturn(userAgent); - + // Ожидаем вызов бизнес-логики сервиса expect(remoteAuthService.checkPassword(subsystemName, username, passwordEncrypted, bearerToken, ipAddress, userAgent)) - .andReturn(sessionToken); + .andReturn(new RemoteAuthService.RemoteAuthSession(sessionToken, false)); // 3. Перевод моков в режим воспроизведения (Replay phase) replay(remoteAuthService, request); @@ -71,6 +72,7 @@ public void testCheckPasswordSuccess() throws Exception { // 6. Проверка результата (Assertions) assertEquals("Username should match", username, response.getUsername()); assertEquals("Session token should match", sessionToken, response.getSessionToken()); + assertFalse("Otp required should be false", response.isOtpRequired()); } @Test @@ -150,17 +152,17 @@ public void testCheckPasswordServiceException() throws Exception { public void testHandleRemoteAuthException() { RemoteAuthException ex = new RemoteAuthException("Error message", "INTERNAL_ERROR"); ResponseEntity response = controller.handleRemoteAuthException(ex); - + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); assertEquals("INTERNAL_ERROR", response.getBody().getType()); assertEquals("Error message", response.getBody().getTitle()); } - + @Test public void testHandleRemoteAuthExceptionBadRequest() { RemoteAuthException ex = new RemoteAuthException("Bad credentials", "BAD_CREDENTIALS"); ResponseEntity response = controller.handleRemoteAuthException(ex); - + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertEquals("BAD_CREDENTIALS", response.getBody().getType()); } @@ -194,4 +196,4 @@ public void testHandleJsonError() { assertEquals("BAD_REQUEST", response.getBody().getType()); assertEquals("Invalid JSON", response.getBody().getTitle()); } -} \ No newline at end of file +} From 3a80f2f0741385c31c184aaf9960925159024f57 Mon Sep 17 00:00:00 2001 From: akorobov Date: Wed, 3 Dec 2025 20:12:20 +0300 Subject: [PATCH 04/91] #110 - Extend subsystem properties to include encryption algorithm, improve error handling, and configure RemoteAuthCryptoService. --- .../check/RemoteAuthCryptoServiceStub.java | 2 -- superfly-sql/src/ui_get/ui_get_subsystem.prc | 8 ++++++-- .../ui_edit_subsystem_properties.prc | 8 ++++++-- .../page/subsystem/AddSubsystemPage.java | 20 +++++++++++++++---- .../page/subsystem/EditSubsystemPage.java | 20 +++++++++++++++---- .../spring/applicationContext-service.xml | 5 +++++ 6 files changed, 49 insertions(+), 14 deletions(-) diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java index 37905007..01b56d58 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java @@ -3,12 +3,10 @@ import com.payneteasy.superfly.service.RemoteAuthCryptoService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Service; /** * Stub implementation of RemoteAuthCryptoService. */ -@Service public class RemoteAuthCryptoServiceStub implements RemoteAuthCryptoService { private static final Logger logger = LoggerFactory.getLogger(RemoteAuthCryptoServiceStub.class); diff --git a/superfly-sql/src/ui_get/ui_get_subsystem.prc b/superfly-sql/src/ui_get/ui_get_subsystem.prc index deb89d93..f0d9e30d 100644 --- a/superfly-sql/src/ui_get/ui_get_subsystem.prc +++ b/superfly-sql/src/ui_get/ui_get_subsystem.prc @@ -16,7 +16,9 @@ create procedure ui_get_subsystem(i_ssys_id int(10)) ss.subsystem_url, ss.landing_url, ss.login_form_css_url, - ss.public_key + ss.public_key, + ss.private_key, + ss.encryption_algorithm from subsystems ss left join smtp_servers smtp on smtp.ssrv_id = ss.ssrv_ssrv_id @@ -39,6 +41,8 @@ call save_routine_information('ui_get_subsystem', 'subsystem_url varchar', 'landing_url varchar', 'login_form_css_url varchar', - 'public_key varchar' + 'public_key varchar', + 'private_key varchar', + 'encryption_algorithm varchar' ) ); diff --git a/superfly-sql/src/ui_update/ui_edit_subsystem_properties.prc b/superfly-sql/src/ui_update/ui_edit_subsystem_properties.prc index 32ccbe6f..19beb90b 100644 --- a/superfly-sql/src/ui_update/ui_edit_subsystem_properties.prc +++ b/superfly-sql/src/ui_update/ui_edit_subsystem_properties.prc @@ -11,7 +11,9 @@ create procedure ui_edit_subsystem_properties(i_ssys_id int(10), i_subsystem_url varchar(255), i_landing_url varchar(255), i_login_form_css_url varchar(255), - i_private_key text + i_private_key text, + i_public_key text, + i_encryption_algorithm varchar(16) ) main_sql: begin @@ -26,7 +28,9 @@ create procedure ui_edit_subsystem_properties(i_ssys_id int(10), subsystem_url = i_subsystem_url, landing_url = i_landing_url, login_form_css_url = i_login_form_css_url, - private_key = i_private_key + private_key = i_private_key, + public_key = i_public_key, + encryption_algorithm = i_encryption_algorithm where ssys_id = i_ssys_id; select 'OK' status, null error_message; diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.java index 1282bde0..eb5bb31e 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.java @@ -24,12 +24,17 @@ import org.apache.wicket.model.ResourceModel; import org.apache.wicket.spring.injection.annot.SpringBean; import org.apache.wicket.validation.validator.UrlValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.access.annotation.Secured; import java.util.List; @Secured("ROLE_ADMIN") public class AddSubsystemPage extends BasePage { + + private static final Logger logger = LoggerFactory.getLogger(AddSubsystemPage.class); + @SpringBean private SubsystemService subsystemService; @SpringBean @@ -107,10 +112,17 @@ protected String load() { private static final long serialVersionUID = 1L; public void onClick(AjaxRequestTarget aTarget) { - var keyPairData = generateKeyPair(RemoteAuthEncryptionAlgorithm.RSA); - subsystem.setPrivateKey(keyPairData.privateKey()); - subsystem.setPublicKey(keyPairData.publicKey()); - aTarget.add(labelPublicKey); + try { + var keyPairData = generateKeyPair(RemoteAuthEncryptionAlgorithm.RSA); + subsystem.setPrivateKey(keyPairData.privateKey()); + subsystem.setPublicKey(keyPairData.publicKey()); + subsystem.setEncryptionAlgorithm(RemoteAuthEncryptionAlgorithm.RSA.name()); + aTarget.add(labelPublicKey); + } catch (Exception e) { + logger.error("Error while generating key pair for subsystem {}", subsystem.getName(), e); + error("Error while generating key pair: " + e.getMessage()); + } + } }); diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java index 6f09f0ac..444e9047 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java @@ -25,12 +25,17 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.spring.injection.annot.SpringBean; import org.apache.wicket.validation.validator.UrlValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.access.annotation.Secured; import java.util.List; @Secured("ROLE_ADMIN") public class EditSubsystemPage extends BasePage { + + private static final Logger logger = LoggerFactory.getLogger(EditSubsystemPage.class); + @SpringBean private SubsystemService subsystemService; @SpringBean @@ -99,10 +104,17 @@ protected String load() { private static final long serialVersionUID = 1L; public void onClick(AjaxRequestTarget aTarget) { - var keyPairData = generateKeyPair(RemoteAuthEncryptionAlgorithm.RSA); - subsystem.setPrivateKey(keyPairData.privateKey()); - subsystem.setPublicKey(keyPairData.publicKey()); - aTarget.add(labelPublicKey); + try { + var keyPairData = generateKeyPair(RemoteAuthEncryptionAlgorithm.RSA); + subsystem.setPrivateKey(keyPairData.privateKey()); + subsystem.setPublicKey(keyPairData.publicKey()); + subsystem.setEncryptionAlgorithm(RemoteAuthEncryptionAlgorithm.RSA.name()); + aTarget.add(labelPublicKey); + } catch (Exception e) { + logger.error("Error while generating key pair for subsystem {}", subsystem.getName(), e); + error("Error while generating key pair: " + e.getMessage()); + } + } }); diff --git a/superfly-web/src/main/webapp/WEB-INF/spring/applicationContext-service.xml b/superfly-web/src/main/webapp/WEB-INF/spring/applicationContext-service.xml index 98f6a842..e232bb73 100644 --- a/superfly-web/src/main/webapp/WEB-INF/spring/applicationContext-service.xml +++ b/superfly-web/src/main/webapp/WEB-INF/spring/applicationContext-service.xml @@ -45,6 +45,7 @@ + @@ -124,6 +125,10 @@ + + + From 7fb4d2d63bbf79408bc024ad172821018232de07 Mon Sep 17 00:00:00 2001 From: iv Date: Fri, 5 Dec 2025 05:48:47 +0300 Subject: [PATCH 05/91] #111 sso-remote-auth - API, service and auth phases cache --- .../component/otp/GoogleAuthSetupPanel.java | 2 +- .../page/sso/SSOChangePasswordPage.java | 25 ++++++++++++++++--- .../web/wicket/page/sso/SSOLoginData.java | 19 ++++++++++++++ .../wicket/page/sso/SSOLoginPasswordPage.java | 4 +++ 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/component/otp/GoogleAuthSetupPanel.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/component/otp/GoogleAuthSetupPanel.java index c21a973a..be50584d 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/component/otp/GoogleAuthSetupPanel.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/component/otp/GoogleAuthSetupPanel.java @@ -17,7 +17,7 @@ public class GoogleAuthSetupPanel extends Panel { private static final String TOTP_URI_FORMAT = - "https://chart.googleapis.com/chart?chs=150x150&chld=M%%7C0&cht=qr&chl=%s"; + "https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=%s"; private final String username; private final IModel totpSecret; diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOChangePasswordPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOChangePasswordPage.java index d480246e..33d2aa49 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOChangePasswordPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOChangePasswordPage.java @@ -1,7 +1,10 @@ package com.payneteasy.superfly.web.wicket.page.sso; +import com.payneteasy.superfly.api.OTPType; +import com.payneteasy.superfly.model.ui.user.UserForDescription; import com.payneteasy.superfly.service.SessionService; import com.payneteasy.superfly.service.SubsystemService; +import com.payneteasy.superfly.service.UserService; import com.payneteasy.superfly.web.wicket.page.user.ChangePasswordPanel; import org.apache.wicket.Component; import org.apache.wicket.model.Model; @@ -16,6 +19,8 @@ public class SSOChangePasswordPage extends BaseSSOPage { private SessionService sessionService; @SpringBean private SubsystemService subsystemService; + @SpringBean + private UserService userService; public SSOChangePasswordPage(final String username) { if (getSession().getSsoLoginData() == null) { @@ -40,10 +45,22 @@ protected String getCurrentUserName() { @Override protected void onPasswordChanged() { - SSOUtils.onSuccessfulLogin(username, - SSOChangePasswordPage.this, - SSOChangePasswordPage.this.getSession().getSsoLoginData(), - sessionService, subsystemService); + UserForDescription user = userService.getUserForDescription(username); + SSOLoginData loginData = SSOChangePasswordPage.this.getSession().getSsoLoginData(); + if (loginData != null) { + loginData.setUsername(username); + loginData.setOtpTypeCode(user.getOtpTypeCode()); + loginData.setOtpOptional(user.isOtpOptional()); + } + + if (OTPType.GOOGLE_AUTH.equals(user.getOtpType()) && !user.isOtpOptional()) { + getRequestCycle().setResponsePage(new SSOSetupGoogleAuthPage()); + } else { + SSOUtils.onSuccessfulLogin(username, + SSOChangePasswordPage.this, + loginData, + sessionService, subsystemService); + } } }); } diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginData.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginData.java index 6e6dea16..6c637178 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginData.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginData.java @@ -12,6 +12,9 @@ public class SSOLoginData implements Serializable { private String subsystemUrl; private String username; + private String otpTypeCode; + private boolean isOtpOptional; + public SSOLoginData() { } @@ -60,6 +63,22 @@ public void setUsername(String username) { this.username = username; } + public String getOtpTypeCode() { + return otpTypeCode; + } + + public void setOtpTypeCode(String otpTypeCode) { + this.otpTypeCode = otpTypeCode; + } + + public boolean isOtpOptional() { + return isOtpOptional; + } + + public void setOtpOptional(boolean otpOptional) { + isOtpOptional = otpOptional; + } + @Override public String toString() { return "SSOLoginData{" + diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginPasswordPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginPasswordPage.java index 85846e55..4df62735 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginPasswordPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginPasswordPage.java @@ -88,6 +88,10 @@ private void doOnSubmit(LoginBean loginBean, SSOLoginData loginData) { private void onPasswordChecked(LoginBean loginBean, SSOLoginData loginData) { loginData.setUsername(loginBean.getUsername()); UserForDescription userDescription = userService.getUserForDescription(loginData.getUsername()); + + loginData.setOtpTypeCode(userDescription.getOtpTypeCode()); + loginData.setOtpOptional(userDescription.isOtpOptional()); + OTPType otpType = userDescription.getOtpType(); switch (otpType) { case GOOGLE_AUTH: From cbfdc46868e0cdafcfcb554eae820cdab6fa7a34 Mon Sep 17 00:00:00 2001 From: iv Date: Fri, 5 Dec 2025 06:19:21 +0300 Subject: [PATCH 06/91] #111 remote login css update --- .../wicket/page/user/ChangePasswordPanel.html | 3 +- superfly-web/src/main/webapp/css/main.css | 2 +- .../src/main/webapp/css/sso-login-form.css | 251 +++++++++++++----- 3 files changed, 181 insertions(+), 75 deletions(-) diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ChangePasswordPanel.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ChangePasswordPanel.html index e047dee8..2ae56e81 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ChangePasswordPanel.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ChangePasswordPanel.html @@ -9,7 +9,6 @@
-
- \ No newline at end of file + diff --git a/superfly-web/src/main/webapp/css/main.css b/superfly-web/src/main/webapp/css/main.css index 5c2753f5..9cd20729 100644 --- a/superfly-web/src/main/webapp/css/main.css +++ b/superfly-web/src/main/webapp/css/main.css @@ -369,4 +369,4 @@ tr.tabelRowPointer { .wicket-aa-container { background: #fff; box-shadow: 0px 7px 10px #c3c3c3; -} \ No newline at end of file +} diff --git a/superfly-web/src/main/webapp/css/sso-login-form.css b/superfly-web/src/main/webapp/css/sso-login-form.css index c4972821..c20d2a3d 100644 --- a/superfly-web/src/main/webapp/css/sso-login-form.css +++ b/superfly-web/src/main/webapp/css/sso-login-form.css @@ -1,109 +1,216 @@ +/* Modern Reset & Base */ html { - background: #fff; - color: #274152; - font: 12px Arial, Helvetica, sans-serif; + height: 100%; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; } -div, input, form, label { +body { + height: 100%; margin: 0; + background-color: #f4f6f8; + display: flex; + align-items: center; + justify-content: center; + color: #333; +} + +div, input, form, label, ul, li { + box-sizing: border-box; +} + +/* Bootstrap Analogs for SSO */ +.container { + width: 100%; padding: 0; } -.superfly-login-page { - border: 1px solid #dedede; - background: #fff; - border-radius: 10px; - -moz-border-radius: 10px; - -webkit-border-radius: 10px; - width: 400px; - margin: 10em auto; - padding: 30px 42px; - background: -webkit-gradient(linear, left top, left bottom, from(#fefefe), to(#E5ECF9)); - background: -moz-linear-gradient(top,#fefefe,#dedede); - filter: progid:DXImageTransform.Microsoft.gradient(enabled='true',startColorstr=#fefefe,endColorstr=#E5ECF9,GradientType=0); zoom: 1; +.col-12, .col-md-4 { + width: 100%; +} - box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); - -webkit-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); - -moz-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); +.m-auto { + margin: auto; +} +.pb-2 { padding-bottom: 0.5rem; } +.p-2 { padding: 0.5rem; } +.py-4 { padding-top: 1.5rem; padding-bottom: 1.5rem; } +.my-4 { margin-top: 1.5rem; margin-bottom: 1.5rem; } +.mt-4 { margin-top: 1.5rem } +.bg-light { background-color: #f8f9fa; } +.border { border: 1px solid #dee2e6; } +.rounded { border-radius: 0.25rem; } +.shadow { box-shadow: 0 0.5rem 1rem rgba(0,0,0,.15); } + +.form-group { + margin-bottom: 1rem; } -.superfly-button:hover { - border:1px solid #333; +.form-signin { + width: 100%; } -.superfly-button:active { - background-color: fefefe; - background: -webkit-gradient(linear, left top, left bottom, from(#f4f4f4), to(#dedede)); - background: -moz-linear-gradient(top,#f4f4f4,#dedede); - background gradient(top,#f4f4f4,#dedede); - filter: progid:DXImageTransform.Microsoft.gradient(enabled='true',startColorstr=#e9e9e9,endColorstr=#dedede,GradientType=0); zoom: 1; - padding: 1px 15px 1px 17px; +.form-control { + display: block; + width: 100%; + height: 40px; + padding: 8px 12px; + font-size: 16px; + line-height: 1.5; + color: #2d3748; + background-color: #fff; + border: 1px solid #cbd5e0; + border-radius: 6px; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } -.superfly-button { - margin: 0; - padding: 1px 16px ; - border:1px solid #9db2b2; - line-height:29px; - background-color: #fefefe; - /*background:#ffd48e url(../images/bg-save-btn.gif) repeat-x 0 100%;*/ - -moz-border-radius:5px; - -webkit-border-radius:5px; - border-radius:5px; - font-size:14px; - color:#333; - font-weight: bold; - background: -webkit-gradient(linear, left top, left bottom, from(#fefefe), to(#e9e9e9)); - background: -moz-linear-gradient(top,#fefefe,#e9e9e9); - filter: progid:DXImageTransform.Microsoft.gradient(enabled='true',startColorstr=#fefefe,endColorstr=#e9e9e9,GradientType=0); zoom: 1; +.form-control:focus { + border-color: #3182ce; + outline: 0; + box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.15); +} + +.btn { + display: inline-block; + font-weight: 600; + text-align: center; + vertical-align: middle; cursor: pointer; + border: 1px solid transparent; + padding: 0 24px; + font-size: 16px; + line-height: 38px; + height: 40px; + border-radius: 6px; + width: 100%; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} + +.btn-primary { + color: #fff; + background-color: #3182ce; + border-color: #3182ce; } -.superfly-form { - width: 400px; +.btn-primary:hover { + background-color: #2b6cb0; + border-color: #2b6cb0; +} +.btn-block { + display: block; + width: 100%; } -.superfly-form-row{ - display: inline; + +/* Original SSO Styles (Updated) */ +.superfly-login-page { + background: #fff; + width: 100%; + max-width: 400px; + padding: 40px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + border: 1px solid #e1e1e8; + margin: 20px; } + +/* Legacy classes that might still be used by other SSO pages */ h2 { - color: #8d919e; - font-size: 18px; - font-weight: normal; + margin: 0 0 24px 0; + font-size: 24px; + font-weight: 600; + color: #1a1f36; + text-align: center; } -.superfly-form-label { - font-size: 12px; - margin-right: 10px; - width: 56px; - display: inline-block; +.superfly-subsystem-info { + margin-bottom: 24px; + font-size: 14px; + color: #697386; + text-align: center; + line-height: 1.5; + background-color: #f8f9fa; + padding: 12px; + border-radius: 6px; } +.superfly-subsystem-title { color: #333; font-weight: 600; } +.superfly-subsystem-url { color: #007bff; font-style: normal; word-break: break-all; } + .superfly-form-input-text, .superfly-select { - margin: 6px 0 25px; - color: #555; - font-size: 18px; - height: 25px; - padding-left: 5px; - width: 300px; + /* Map to form-control styles */ + display: block; + width: 100%; + height: 40px; + padding: 8px 12px; + font-size: 16px; + line-height: 1.5; + color: #2d3748; + background-color: #fff; + border: 1px solid #cbd5e0; + border-radius: 6px; + margin-bottom: 20px; +} +.superfly-form-input-text:focus { + border-color: #3182ce; + box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.15); + outline: 0; } -.superfly-button-row { - text-align: right; - margin: 0; +.superfly-button { + /* Map to btn btn-primary styles */ + display: inline-block; + font-weight: 600; + text-align: center; + vertical-align: middle; + cursor: pointer; + background-color: #3182ce; + border: 1px solid #3182ce; + padding: 0 24px; + font-size: 16px; + line-height: 38px; + height: 40px; + border-radius: 6px; + color: #fff; + width: 100%; +} + +.superfly-button:hover { + background-color: #2b6cb0; + border-color: #2b6cb0; } .superfly-reason { - color: red; - margin-bottom: 10px; + color: #721c24; + background-color: #f8d7da; + border: 1px solid #f5c6cb; + padding: 12px 16px; + border-radius: 6px; + margin-bottom: 20px; + font-size: 14px; + text-align: left; + display: block; } -.superfly-subsystem-title { - font-weight: bold; +.superfly-form-label { + display: block; + margin-bottom: 8px; + font-size: 14px; + font-weight: 500; + color: #4a5568; } -.superfly-subsystem-url { - font-style: italic; -} \ No newline at end of file +/* Password Description Box (Change Password Panel) */ +#password-description ul { + padding-left: 20px; + margin: 0; + font-size: 14px; + color: #555; +} +#password-description li { + margin-bottom: 4px; +} +.feedbackPanelERROR { + color: red; +} From e81c47f4a50bc36a4444663484bbfe375feb987d Mon Sep 17 00:00:00 2001 From: iv Date: Fri, 5 Dec 2025 07:57:28 +0300 Subject: [PATCH 07/91] #111 sso-remote-auth --- .../com/payneteasy/superfly/api/SSOUser.java | 22 +++++++++++++++++++ .../remote/check/RemoteAuthServiceImpl.java | 6 ++++- .../web/wicket/page/sso/BaseSSOPage.java | 2 +- .../web/wicket/page/sso/SSOUtils.java | 7 +++++- .../page/subsystem/EditSubsystemPage.html | 2 +- .../page/subsystem/EditSubsystemPage.java | 10 +++++---- 6 files changed, 41 insertions(+), 8 deletions(-) diff --git a/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/SSOUser.java b/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/SSOUser.java index b27ab97a..d9186df1 100644 --- a/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/SSOUser.java +++ b/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/SSOUser.java @@ -117,6 +117,28 @@ public void setActionsMap(Map actionsMap) { this.actionsMap = actionsMap; } + /** + * Checks if the user has the specified action. + * + * @param actionName action name to check + * @return true if the user has the action + */ + public boolean hasAction(String actionName) { + if (actionsMap == null) { + return false; + } + for (SSOAction[] actions : actionsMap.values()) { + if (actions != null) { + for (SSOAction action : actions) { + if (action != null && actionName.equals(action.getName())) { + return true; + } + } + } + } + return false; + } + /** * Returns preferences. * diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java index 29f6a75f..4d7918c8 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java @@ -35,7 +35,7 @@ public RemoteAuthServiceImpl(SubsystemService subsystemService, RemoteAuthCryptoService remoteAuthCryptoService) { this.subsystemService = subsystemService; this.internalSSOService = internalSSOService; - this.remoteAuthCryptoService = remoteAuthCryptoService; + this.remoteAuthCryptoService = new RemoteAuthCryptoServiceStub(); } @Override @@ -69,6 +69,10 @@ public RemoteAuthSession checkPassword(String subsystemName, String username, St throw new RemoteAuthException("Authentication failed", "BAD_USER_OR_PASSWORD_OR_OTP"); } + if (ssoUser.hasAction("action_temp_password")) { + throw new RemoteAuthException("User should change password", "USER_SHOULD_CHANGE_PASSWORD"); + } + // 4. Generate Session Token and Cache String sessionToken = UUID.randomUUID().toString(); sessionCache.put(sessionToken, new RemoteSession(username, ssoUser.getOtpType())); diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/BaseSSOPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/BaseSSOPage.java index 433f8d55..ea597eba 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/BaseSSOPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/BaseSSOPage.java @@ -71,7 +71,7 @@ protected boolean validateCsrfToken() { private HttpSession getHttpSession() { HttpServletRequest request = getHttpServletRequest(); - return request.getSession(false); + return request.getSession(true); } private HttpServletRequest getHttpServletRequest() { diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOUtils.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOUtils.java index e22e7bf1..a12cde2b 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOUtils.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOUtils.java @@ -116,7 +116,12 @@ public static void onSuccessfulLogin(String username, ssoSession.getId(), loginData.getSubsystemIdentifier()); if (token != null) { // can login: redirecting a user to a subsystem - SSOUtils.redirectToSubsystem(page, loginData, token); + if ("no-target".equals(loginData.getTargetUrl()) || "/no-target".equals(loginData.getTargetUrl())) { + SSOUtils.anonymizeLoginData(page); + SSOUtils.redirect(page, token.getLandingUrl()); + } else { + SSOUtils.redirectToSubsystem(page, loginData, token); + } } else { // can't login: just display an error // actually, this should not happen as we've already diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html index 1d885b01..3be73bdd 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html @@ -24,7 +24,7 @@
-
key
+ Generate new RSA key pair diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java index 444e9047..903a41cb 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java @@ -16,6 +16,7 @@ import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.ChoiceRenderer; import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.form.TextArea; import org.apache.wicket.markup.html.form.SubmitLink; import org.apache.wicket.markup.html.link.BookmarkablePageLink; import org.apache.wicket.model.CompoundPropertyModel; @@ -90,16 +91,17 @@ public void onClick(AjaxRequestTarget aTarget) { }); // Public key fields - final Label labelPublicKey = new Label("publicKey", new LoadableDetachableModel() { + final TextArea textAreaPublicKey = new TextArea<>("publicKey", new LoadableDetachableModel<>() { @Override protected String load() { return subsystem.getPublicKey(); } }); - labelPublicKey.setOutputMarkupId(true); + textAreaPublicKey.setOutputMarkupId(true); + textAreaPublicKey.setEnabled(false); form.add(new Label("publicKeyLabel", new ResourceModel("subsystem.edit.publicKey"))); - form.add(labelPublicKey); + form.add(textAreaPublicKey); form.add(new IndicatingAjaxLink("generateNewKeyPair") { private static final long serialVersionUID = 1L; @@ -109,7 +111,7 @@ public void onClick(AjaxRequestTarget aTarget) { subsystem.setPrivateKey(keyPairData.privateKey()); subsystem.setPublicKey(keyPairData.publicKey()); subsystem.setEncryptionAlgorithm(RemoteAuthEncryptionAlgorithm.RSA.name()); - aTarget.add(labelPublicKey); + aTarget.add(textAreaPublicKey); } catch (Exception e) { logger.error("Error while generating key pair for subsystem {}", subsystem.getName(), e); error("Error while generating key pair: " + e.getMessage()); From acd45c576f0742c56a5830c2edd8f8c5bee67425 Mon Sep 17 00:00:00 2001 From: iv Date: Fri, 5 Dec 2025 08:00:50 +0300 Subject: [PATCH 08/91] #111 sso-remote-auth --- .../service/impl/remote/check/RemoteAuthServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java index 4d7918c8..3956ad0a 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java @@ -35,7 +35,7 @@ public RemoteAuthServiceImpl(SubsystemService subsystemService, RemoteAuthCryptoService remoteAuthCryptoService) { this.subsystemService = subsystemService; this.internalSSOService = internalSSOService; - this.remoteAuthCryptoService = new RemoteAuthCryptoServiceStub(); + this.remoteAuthCryptoService = remoteAuthCryptoService; } @Override From bb05f8f228c9b95f35a80c25c7149d8d75a9bb95 Mon Sep 17 00:00:00 2001 From: iv Date: Mon, 8 Dec 2025 17:12:00 +0300 Subject: [PATCH 09/91] #111 ui updates --- .../web/wicket/SuperflyApplication.java | 5 ++-- .../component/otp/GoogleAuthSetupPanel.html | 8 +++--- .../web/wicket/page/sso/BaseSSOPage.html | 4 +-- .../wicket/page/sso/SSOLoginPasswordPage.java | 3 +++ .../web/wicket/page/sso/SSOUtils.java | 27 +++++++++++++++++++ .../wicket/page/user/ChangePasswordPanel.html | 2 +- .../web/wicket/page/user/ListUsersPage.html | 6 ++--- .../src/main/webapp/css/sso-login-form.css | 2 +- .../wicket/PageInterceptingRequestMapper.java | 8 +++++- 9 files changed, 50 insertions(+), 15 deletions(-) diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.java index 5068cb16..0a65870d 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.java @@ -42,7 +42,6 @@ import com.payneteasy.superfly.wicket.InterceptionDecisions; import com.payneteasy.superfly.wicket.PageInterceptingRequestMapper; import org.apache.wicket.Page; -import org.apache.wicket.core.request.mapper.CryptoMapper; import org.apache.wicket.request.IRequestMapper; import org.apache.wicket.request.Request; import org.apache.wicket.settings.RequestCycleSettings; @@ -52,8 +51,8 @@ public class SuperflyApplication extends BaseApplication { @Override protected void customInit() { getSecuritySettings().setAuthorizationStrategy(new SpringSecurityAuthorizationStrategy()); - CryptoMapper requestMapper = new CryptoMapper(getRootRequestMapper(), this); - setRootRequestMapper(wrapWithInterceptingMapper(requestMapper)); + // BaseApplication.init() уже создал CryptoMapper, просто оборачиваем его в PageInterceptingRequestMapper + setRootRequestMapper(wrapWithInterceptingMapper(getRootRequestMapper())); mountBookmarkablePageWithPath("/loginbase", LoginPageWithoutHOTP.class); mountBookmarkablePageWithPath("/login", LoginPasswordStepPage.class); diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/component/otp/GoogleAuthSetupPanel.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/component/otp/GoogleAuthSetupPanel.html index 9fbc2484..5c19779a 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/component/otp/GoogleAuthSetupPanel.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/component/otp/GoogleAuthSetupPanel.html @@ -1,17 +1,17 @@
-
+
Your secret key for google authenticator: - +
-
+
-
+
Learn more about Google authenticator diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/BaseSSOPage.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/BaseSSOPage.html index d3c5d56d..4f685e65 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/BaseSSOPage.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/BaseSSOPage.html @@ -20,9 +20,9 @@ - \ No newline at end of file + diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginPasswordPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginPasswordPage.java index 4df62735..29437bcd 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginPasswordPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginPasswordPage.java @@ -76,6 +76,9 @@ private void doOnSubmit(LoginBean loginBean, SSOLoginData loginData) { onPasswordChecked(loginBean, loginData); break; case FAILED: + // Clear SSO session and cookie after failed authentication + // to prevent reuse of old sessions + SSOUtils.clearSSOSession(this, sessionService); errorMessageModel.setObject("The username or password you entered is incorrect or user is locked."); errorMessageLabel.setVisible(true); break; diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOUtils.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOUtils.java index a12cde2b..08454ff2 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOUtils.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOUtils.java @@ -17,6 +17,7 @@ import org.apache.wicket.request.http.handler.RedirectRequestHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; import javax.servlet.http.Cookie; import java.net.URLEncoder; @@ -130,6 +131,32 @@ public static void onSuccessfulLogin(String username, } } + /** + * Clears SSO session and cookie after failed authentication. + * This prevents reuse of old sessions after authentication failure. + * + * @param page the page to get request/response from + * @param sessionService service to delete SSO session + */ + public static void clearSSOSession(SessionAccessorPage page, SessionService sessionService) { + WebRequest request = (WebRequest) page.getRequest(); + String ssoSessionId = getSsoSessionIdFromCookie(request); + + if (StringUtils.hasText(ssoSessionId)) { + logger.debug("Clearing SSO session after failed authentication: {}", ssoSessionId); + sessionService.deleteSSOSession(ssoSessionId); + } + + // Clear cookie by setting maxAge to 0 + Cookie cookie = new Cookie(SSO_SESSION_ID_COOKIE_NAME, ""); + cookie.setMaxAge(0); + cookie.setPath(getApplicationRootPath(page)); + ((WebResponse) RequestCycle.get().getResponse()).addCookie(cookie); + + // Clear username from login data + anonymizeLoginData(page); + } + private static String getApplicationRootPath(SessionAccessorPage page) { String contextPath = ((ServletWebRequest) page.getRequest()).getContextPath(); String filterPath = ((ServletWebRequest) page.getRequest()).getFilterPath(); diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ChangePasswordPanel.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ChangePasswordPanel.html index 2ae56e81..533a8f95 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ChangePasswordPanel.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ChangePasswordPanel.html @@ -12,7 +12,7 @@
- You have to change your password. +

You have to change your password.

diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.html index 1592c6ad..c64f2f2c 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.html @@ -15,10 +15,10 @@ - +
  • - +
  • Role: @@ -92,4 +92,4 @@ - \ No newline at end of file + diff --git a/superfly-web/src/main/webapp/css/sso-login-form.css b/superfly-web/src/main/webapp/css/sso-login-form.css index c20d2a3d..c88e625a 100644 --- a/superfly-web/src/main/webapp/css/sso-login-form.css +++ b/superfly-web/src/main/webapp/css/sso-login-form.css @@ -41,7 +41,7 @@ div, input, form, label, ul, li { .border { border: 1px solid #dee2e6; } .rounded { border-radius: 0.25rem; } .shadow { box-shadow: 0 0.5rem 1rem rgba(0,0,0,.15); } - +.text-center {text-align: center;} .form-group { margin-bottom: 1rem; } diff --git a/superfly-wicket/src/main/java/com/payneteasy/superfly/wicket/PageInterceptingRequestMapper.java b/superfly-wicket/src/main/java/com/payneteasy/superfly/wicket/PageInterceptingRequestMapper.java index b801907e..7c30e0e4 100644 --- a/superfly-wicket/src/main/java/com/payneteasy/superfly/wicket/PageInterceptingRequestMapper.java +++ b/superfly-wicket/src/main/java/com/payneteasy/superfly/wicket/PageInterceptingRequestMapper.java @@ -34,7 +34,13 @@ public IRequestHandler mapRequest(Request request) { @Override public int getCompatibilityScore(Request request) { - return delegate.getCompatibilityScore(request); + try { + return delegate.getCompatibilityScore(request); + } catch (RuntimeException e) { + // CryptoMapper может выбрасывать исключения при попытке расшифровать незашифрованные URL + // (например, статические ресурсы CSS, JS). В этом случае возвращаем 0 - низкий compatibility score + return 0; + } } @Override From ecca86952d1bf178e8164033ca60b1d45ce7f5e8 Mon Sep 17 00:00:00 2001 From: iv Date: Mon, 8 Dec 2025 17:21:32 +0300 Subject: [PATCH 10/91] #111 prepare ot release --- pom.xml | 79 +++++++++++++++++++++++++--- superfly-client-opt/pom.xml | 4 +- superfly-client-web-security/pom.xml | 4 +- superfly-client/pom.xml | 4 +- superfly-common/pom.xml | 4 +- superfly-crypto/pom.xml | 2 +- superfly-httpclient-ssl/pom.xml | 4 +- superfly-integration-test/pom.xml | 2 +- superfly-remote-api/pom.xml | 4 +- superfly-service/pom.xml | 2 +- superfly-spi-support/pom.xml | 4 +- superfly-spi/pom.xml | 4 +- superfly-spring-security/pom.xml | 2 +- superfly-web/pom.xml | 4 +- superfly-wicket/pom.xml | 4 +- 15 files changed, 96 insertions(+), 31 deletions(-) diff --git a/pom.xml b/pom.xml index 06aa071a..f1c0eb02 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ superfly-parent pom Superfly parent project - 1.7-33 + 1.7-34-SNAPSHOT Parent POM of the Superfly project superfly-remote-api @@ -818,16 +818,41 @@ + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + verify + + jar-no-fork + + + true + + + + + org.apache.maven.plugins maven-javadoc-plugin - 2.10.1 + 3.11.2 ${java.version} - - -Xdoclint:none + none + + + attach-javadocs + verify + + jar + + + @@ -895,19 +920,59 @@ maven-release-plugin - 2.5.2 + 3.1.1 org.apache.maven.scm maven-scm-provider-gitexe - 1.8.1 + 2.0.1 + + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 + true + + true + central + + + + gpg-sign + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.8 + + gpg + ${gpg.passphrase} + + --pinentry-mode + loopback + + + + + sign-artifacts + verify + + sign + + + + + + + + deploy diff --git a/superfly-client-opt/pom.xml b/superfly-client-opt/pom.xml index 85d5848f..571935d5 100644 --- a/superfly-client-opt/pom.xml +++ b/superfly-client-opt/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-33 + 1.7-34-SNAPSHOT 4.0.0 superfly-client-opt @@ -94,4 +94,4 @@ test - \ No newline at end of file + diff --git a/superfly-client-web-security/pom.xml b/superfly-client-web-security/pom.xml index 977f7212..7f5e4c03 100644 --- a/superfly-client-web-security/pom.xml +++ b/superfly-client-web-security/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-33 + 1.7-34-SNAPSHOT 4.0.0 superfly-client-web-security @@ -89,4 +89,4 @@ test - \ No newline at end of file + diff --git a/superfly-client/pom.xml b/superfly-client/pom.xml index c075f5d2..2e28cbd9 100644 --- a/superfly-client/pom.xml +++ b/superfly-client/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-33 + 1.7-34-SNAPSHOT superfly-client Superfly Client @@ -42,4 +42,4 @@ test - \ No newline at end of file + diff --git a/superfly-common/pom.xml b/superfly-common/pom.xml index e82fc0cd..c35dc302 100644 --- a/superfly-common/pom.xml +++ b/superfly-common/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-33 + 1.7-34-SNAPSHOT 4.0.0 superfly-common @@ -32,4 +32,4 @@ - \ No newline at end of file + diff --git a/superfly-crypto/pom.xml b/superfly-crypto/pom.xml index 440e8867..01ffa70d 100644 --- a/superfly-crypto/pom.xml +++ b/superfly-crypto/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-33 + 1.7-34-SNAPSHOT superfly-crypto Superfly helper crypto services diff --git a/superfly-httpclient-ssl/pom.xml b/superfly-httpclient-ssl/pom.xml index 1d21ef53..7f565cab 100644 --- a/superfly-httpclient-ssl/pom.xml +++ b/superfly-httpclient-ssl/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-33 + 1.7-34-SNAPSHOT 4.0.0 superfly-httpclient-ssl @@ -20,4 +20,4 @@ slf4j-api - \ No newline at end of file + diff --git a/superfly-integration-test/pom.xml b/superfly-integration-test/pom.xml index aa4faefc..b907fe37 100644 --- a/superfly-integration-test/pom.xml +++ b/superfly-integration-test/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-33 + 1.7-34-SNAPSHOT 4.0.0 com.payneteasy.superfly diff --git a/superfly-remote-api/pom.xml b/superfly-remote-api/pom.xml index 0012738d..bde06bb3 100644 --- a/superfly-remote-api/pom.xml +++ b/superfly-remote-api/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-33 + 1.7-34-SNAPSHOT 4.0.0 com.payneteasy.superfly @@ -13,7 +13,7 @@ 8 - + com.sun.xml.bind diff --git a/superfly-service/pom.xml b/superfly-service/pom.xml index 17e43e04..75d49b08 100644 --- a/superfly-service/pom.xml +++ b/superfly-service/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-33 + 1.7-34-SNAPSHOT 4.0.0 com.payneteasy.superfly diff --git a/superfly-spi-support/pom.xml b/superfly-spi-support/pom.xml index 35dc0df1..f92a3c25 100644 --- a/superfly-spi-support/pom.xml +++ b/superfly-spi-support/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-33 + 1.7-34-SNAPSHOT superfly-spi-support Support classes for SPI mechanism @@ -22,4 +22,4 @@ - \ No newline at end of file + diff --git a/superfly-spi/pom.xml b/superfly-spi/pom.xml index 563efefd..fc7b6544 100644 --- a/superfly-spi/pom.xml +++ b/superfly-spi/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-33 + 1.7-34-SNAPSHOT com.payneteasy.superfly superfly-spi @@ -16,4 +16,4 @@ superfly-spi-support - \ No newline at end of file + diff --git a/superfly-spring-security/pom.xml b/superfly-spring-security/pom.xml index 5660f805..4b2939b0 100644 --- a/superfly-spring-security/pom.xml +++ b/superfly-spring-security/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-33 + 1.7-34-SNAPSHOT 4.0.0 superfly-spring-security diff --git a/superfly-web/pom.xml b/superfly-web/pom.xml index 488c0391..836a3974 100644 --- a/superfly-web/pom.xml +++ b/superfly-web/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-33 + 1.7-34-SNAPSHOT 4.0.0 superfly-web @@ -210,4 +210,4 @@ superfly - \ No newline at end of file + diff --git a/superfly-wicket/pom.xml b/superfly-wicket/pom.xml index 999bc581..064b9f24 100644 --- a/superfly-wicket/pom.xml +++ b/superfly-wicket/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-33 + 1.7-34-SNAPSHOT superfly-wicket Superfly wicket extensions @@ -22,4 +22,4 @@ javax.servlet-api - \ No newline at end of file + From 547a9f6d944f62fe44b1e9ff6f9be9d0c99a07a8 Mon Sep 17 00:00:00 2001 From: iv Date: Mon, 8 Dec 2025 17:33:22 +0300 Subject: [PATCH 11/91] #111 fix SSOChangePasswordPageTest --- .../wicket/page/sso/SSOChangePasswordPageTest.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/superfly-web/src/test/java/com/payneteasy/superfly/web/wicket/page/sso/SSOChangePasswordPageTest.java b/superfly-web/src/test/java/com/payneteasy/superfly/web/wicket/page/sso/SSOChangePasswordPageTest.java index f0f316a7..0dc6d800 100644 --- a/superfly-web/src/test/java/com/payneteasy/superfly/web/wicket/page/sso/SSOChangePasswordPageTest.java +++ b/superfly-web/src/test/java/com/payneteasy/superfly/web/wicket/page/sso/SSOChangePasswordPageTest.java @@ -1,8 +1,10 @@ package com.payneteasy.superfly.web.wicket.page.sso; +import com.payneteasy.superfly.api.OTPType; import com.payneteasy.superfly.api.PolicyValidationException; import com.payneteasy.superfly.model.SSOSession; import com.payneteasy.superfly.model.SubsystemTokenData; +import com.payneteasy.superfly.model.ui.user.UserForDescription; import com.payneteasy.superfly.security.csrf.CsrfValidator; import com.payneteasy.superfly.service.SessionService; import com.payneteasy.superfly.service.SettingsService; @@ -68,11 +70,16 @@ protected Object getBean(Class type) { @Test public void testSuccess() throws PolicyValidationException { + expect(settingsService.getPolicy()).andReturn(Policy.NONE); userService.validatePassword("user", "password"); expectLastCall(); userService.changeTempPassword("user", "password"); expectLastCall(); - expect(settingsService.getPolicy()).andReturn(Policy.NONE); + UserForDescription user = new UserForDescription(); + user.setUsername("user"); + user.setOtpTypeCode(OTPType.NONE.code()); + user.setOtpOptional(false); + expect(userService.getUserForDescription("user")).andReturn(user); expect(sessionService.createSSOSession("user")) .andReturn(new SSOSession(1L, "super-session-id")); expect(subsystemService.issueSubsystemTokenIfCanLogin(1L, "test-subsystem")) @@ -94,8 +101,8 @@ public void testSuccess() throws PolicyValidationException { @Test public void testMismatchingPassword() throws PolicyValidationException { - userService.validatePassword("user", "password"); expect(settingsService.getPolicy()).andReturn(Policy.NONE); + userService.validatePassword("user", "password"); replay(userService, settingsService, csrfValidator); @@ -119,9 +126,9 @@ public void testMismatchingPassword() throws PolicyValidationException { @Test public void testInvalidPassword() throws PolicyValidationException { // password validation logic + expect(settingsService.getPolicy()).andReturn(Policy.NONE); userService.validatePassword("user", "invalid-password"); expectLastCall().andThrow(new PolicyValidationException("P003")); - expect(settingsService.getPolicy()).andReturn(Policy.NONE); replay(userService, settingsService, csrfValidator); From e808c969ce0d6e0b3c048eab45b51e8e1e9b68d6 Mon Sep 17 00:00:00 2001 From: iv Date: Mon, 8 Dec 2025 17:34:56 +0300 Subject: [PATCH 12/91] [maven-release-plugin] prepare release superfly-parent-1.7-34 --- pom.xml | 4 ++-- superfly-client-opt/pom.xml | 2 +- superfly-client-web-security/pom.xml | 2 +- superfly-client/pom.xml | 2 +- superfly-common/pom.xml | 2 +- superfly-crypto/pom.xml | 2 +- superfly-httpclient-ssl/pom.xml | 2 +- superfly-integration-test/pom.xml | 2 +- superfly-remote-api/pom.xml | 2 +- superfly-service/pom.xml | 2 +- superfly-spi-support/pom.xml | 2 +- superfly-spi/pom.xml | 2 +- superfly-spring-security/pom.xml | 2 +- superfly-web/pom.xml | 2 +- superfly-wicket/pom.xml | 2 +- 15 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index f1c0eb02..7f9b3cdc 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ superfly-parent pom Superfly parent project - 1.7-34-SNAPSHOT + 1.7-34 Parent POM of the Superfly project superfly-remote-api @@ -34,7 +34,7 @@ scm:git:git@github.com:payneteasy/superfly.git scm:git:git@github.com:payneteasy/superfly.git https://github.com/paynetesy/superfly - superfly-parent-1.7-33 + superfly-parent-1.7-34 diff --git a/superfly-client-opt/pom.xml b/superfly-client-opt/pom.xml index 571935d5..ad6faa9c 100644 --- a/superfly-client-opt/pom.xml +++ b/superfly-client-opt/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34-SNAPSHOT + 1.7-34 4.0.0 superfly-client-opt diff --git a/superfly-client-web-security/pom.xml b/superfly-client-web-security/pom.xml index 7f5e4c03..6843b994 100644 --- a/superfly-client-web-security/pom.xml +++ b/superfly-client-web-security/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34-SNAPSHOT + 1.7-34 4.0.0 superfly-client-web-security diff --git a/superfly-client/pom.xml b/superfly-client/pom.xml index 2e28cbd9..2dee1bbb 100644 --- a/superfly-client/pom.xml +++ b/superfly-client/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34-SNAPSHOT + 1.7-34 superfly-client Superfly Client diff --git a/superfly-common/pom.xml b/superfly-common/pom.xml index c35dc302..b1ea1211 100644 --- a/superfly-common/pom.xml +++ b/superfly-common/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34-SNAPSHOT + 1.7-34 4.0.0 superfly-common diff --git a/superfly-crypto/pom.xml b/superfly-crypto/pom.xml index 01ffa70d..ab95f9cf 100644 --- a/superfly-crypto/pom.xml +++ b/superfly-crypto/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34-SNAPSHOT + 1.7-34 superfly-crypto Superfly helper crypto services diff --git a/superfly-httpclient-ssl/pom.xml b/superfly-httpclient-ssl/pom.xml index 7f565cab..253febc1 100644 --- a/superfly-httpclient-ssl/pom.xml +++ b/superfly-httpclient-ssl/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34-SNAPSHOT + 1.7-34 4.0.0 superfly-httpclient-ssl diff --git a/superfly-integration-test/pom.xml b/superfly-integration-test/pom.xml index b907fe37..0a52d859 100644 --- a/superfly-integration-test/pom.xml +++ b/superfly-integration-test/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34-SNAPSHOT + 1.7-34 4.0.0 com.payneteasy.superfly diff --git a/superfly-remote-api/pom.xml b/superfly-remote-api/pom.xml index bde06bb3..7e0dd427 100644 --- a/superfly-remote-api/pom.xml +++ b/superfly-remote-api/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34-SNAPSHOT + 1.7-34 4.0.0 com.payneteasy.superfly diff --git a/superfly-service/pom.xml b/superfly-service/pom.xml index 75d49b08..0e06fa65 100644 --- a/superfly-service/pom.xml +++ b/superfly-service/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34-SNAPSHOT + 1.7-34 4.0.0 com.payneteasy.superfly diff --git a/superfly-spi-support/pom.xml b/superfly-spi-support/pom.xml index f92a3c25..51106e9a 100644 --- a/superfly-spi-support/pom.xml +++ b/superfly-spi-support/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34-SNAPSHOT + 1.7-34 superfly-spi-support Support classes for SPI mechanism diff --git a/superfly-spi/pom.xml b/superfly-spi/pom.xml index fc7b6544..b139ea80 100644 --- a/superfly-spi/pom.xml +++ b/superfly-spi/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34-SNAPSHOT + 1.7-34 com.payneteasy.superfly superfly-spi diff --git a/superfly-spring-security/pom.xml b/superfly-spring-security/pom.xml index 4b2939b0..234d80cb 100644 --- a/superfly-spring-security/pom.xml +++ b/superfly-spring-security/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34-SNAPSHOT + 1.7-34 4.0.0 superfly-spring-security diff --git a/superfly-web/pom.xml b/superfly-web/pom.xml index 836a3974..71d07eba 100644 --- a/superfly-web/pom.xml +++ b/superfly-web/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34-SNAPSHOT + 1.7-34 4.0.0 superfly-web diff --git a/superfly-wicket/pom.xml b/superfly-wicket/pom.xml index 064b9f24..d53fa27a 100644 --- a/superfly-wicket/pom.xml +++ b/superfly-wicket/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34-SNAPSHOT + 1.7-34 superfly-wicket Superfly wicket extensions From 6a49f3fe6d522e55873dd85c70b92d4f3e467780 Mon Sep 17 00:00:00 2001 From: iv Date: Mon, 8 Dec 2025 17:35:03 +0300 Subject: [PATCH 13/91] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- superfly-client-opt/pom.xml | 2 +- superfly-client-web-security/pom.xml | 2 +- superfly-client/pom.xml | 2 +- superfly-common/pom.xml | 2 +- superfly-crypto/pom.xml | 2 +- superfly-httpclient-ssl/pom.xml | 2 +- superfly-integration-test/pom.xml | 2 +- superfly-remote-api/pom.xml | 2 +- superfly-service/pom.xml | 2 +- superfly-spi-support/pom.xml | 2 +- superfly-spi/pom.xml | 2 +- superfly-spring-security/pom.xml | 2 +- superfly-web/pom.xml | 2 +- superfly-wicket/pom.xml | 2 +- 15 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index 7f9b3cdc..eaa1387b 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ superfly-parent pom Superfly parent project - 1.7-34 + 1.7-35-SNAPSHOT Parent POM of the Superfly project superfly-remote-api @@ -34,7 +34,7 @@ scm:git:git@github.com:payneteasy/superfly.git scm:git:git@github.com:payneteasy/superfly.git https://github.com/paynetesy/superfly - superfly-parent-1.7-34 + superfly-parent-1.7-33 diff --git a/superfly-client-opt/pom.xml b/superfly-client-opt/pom.xml index ad6faa9c..72663166 100644 --- a/superfly-client-opt/pom.xml +++ b/superfly-client-opt/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34 + 1.7-35-SNAPSHOT 4.0.0 superfly-client-opt diff --git a/superfly-client-web-security/pom.xml b/superfly-client-web-security/pom.xml index 6843b994..c355fb28 100644 --- a/superfly-client-web-security/pom.xml +++ b/superfly-client-web-security/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34 + 1.7-35-SNAPSHOT 4.0.0 superfly-client-web-security diff --git a/superfly-client/pom.xml b/superfly-client/pom.xml index 2dee1bbb..40fb152f 100644 --- a/superfly-client/pom.xml +++ b/superfly-client/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34 + 1.7-35-SNAPSHOT superfly-client Superfly Client diff --git a/superfly-common/pom.xml b/superfly-common/pom.xml index b1ea1211..0be7a383 100644 --- a/superfly-common/pom.xml +++ b/superfly-common/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34 + 1.7-35-SNAPSHOT 4.0.0 superfly-common diff --git a/superfly-crypto/pom.xml b/superfly-crypto/pom.xml index ab95f9cf..6507a48a 100644 --- a/superfly-crypto/pom.xml +++ b/superfly-crypto/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34 + 1.7-35-SNAPSHOT superfly-crypto Superfly helper crypto services diff --git a/superfly-httpclient-ssl/pom.xml b/superfly-httpclient-ssl/pom.xml index 253febc1..a3a8363f 100644 --- a/superfly-httpclient-ssl/pom.xml +++ b/superfly-httpclient-ssl/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34 + 1.7-35-SNAPSHOT 4.0.0 superfly-httpclient-ssl diff --git a/superfly-integration-test/pom.xml b/superfly-integration-test/pom.xml index 0a52d859..fc56f699 100644 --- a/superfly-integration-test/pom.xml +++ b/superfly-integration-test/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34 + 1.7-35-SNAPSHOT 4.0.0 com.payneteasy.superfly diff --git a/superfly-remote-api/pom.xml b/superfly-remote-api/pom.xml index 7e0dd427..00dccc65 100644 --- a/superfly-remote-api/pom.xml +++ b/superfly-remote-api/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34 + 1.7-35-SNAPSHOT 4.0.0 com.payneteasy.superfly diff --git a/superfly-service/pom.xml b/superfly-service/pom.xml index 0e06fa65..30d7c915 100644 --- a/superfly-service/pom.xml +++ b/superfly-service/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34 + 1.7-35-SNAPSHOT 4.0.0 com.payneteasy.superfly diff --git a/superfly-spi-support/pom.xml b/superfly-spi-support/pom.xml index 51106e9a..6cbba3ca 100644 --- a/superfly-spi-support/pom.xml +++ b/superfly-spi-support/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34 + 1.7-35-SNAPSHOT superfly-spi-support Support classes for SPI mechanism diff --git a/superfly-spi/pom.xml b/superfly-spi/pom.xml index b139ea80..b3433b07 100644 --- a/superfly-spi/pom.xml +++ b/superfly-spi/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34 + 1.7-35-SNAPSHOT com.payneteasy.superfly superfly-spi diff --git a/superfly-spring-security/pom.xml b/superfly-spring-security/pom.xml index 234d80cb..1bddd020 100644 --- a/superfly-spring-security/pom.xml +++ b/superfly-spring-security/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34 + 1.7-35-SNAPSHOT 4.0.0 superfly-spring-security diff --git a/superfly-web/pom.xml b/superfly-web/pom.xml index 71d07eba..cf8b0f14 100644 --- a/superfly-web/pom.xml +++ b/superfly-web/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34 + 1.7-35-SNAPSHOT 4.0.0 superfly-web diff --git a/superfly-wicket/pom.xml b/superfly-wicket/pom.xml index d53fa27a..46c2b238 100644 --- a/superfly-wicket/pom.xml +++ b/superfly-wicket/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-34 + 1.7-35-SNAPSHOT superfly-wicket Superfly wicket extensions From c0ef3be055c36123a64f2a85486bc3197acecd95 Mon Sep 17 00:00:00 2001 From: iv Date: Mon, 8 Dec 2025 18:06:57 +0300 Subject: [PATCH 14/91] #111 update tag --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index eaa1387b..d7a7879a 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ scm:git:git@github.com:payneteasy/superfly.git scm:git:git@github.com:payneteasy/superfly.git https://github.com/paynetesy/superfly - superfly-parent-1.7-33 + superfly-parent-1.7-35 From 748a6a542de34318be02d59c3c7ec0fa7b221070 Mon Sep 17 00:00:00 2001 From: iv Date: Mon, 8 Dec 2025 18:08:17 +0300 Subject: [PATCH 15/91] [maven-release-plugin] prepare release superfly-parent-1.7-35 --- pom.xml | 2 +- superfly-client-opt/pom.xml | 2 +- superfly-client-web-security/pom.xml | 2 +- superfly-client/pom.xml | 2 +- superfly-common/pom.xml | 2 +- superfly-crypto/pom.xml | 2 +- superfly-httpclient-ssl/pom.xml | 2 +- superfly-integration-test/pom.xml | 2 +- superfly-remote-api/pom.xml | 2 +- superfly-service/pom.xml | 2 +- superfly-spi-support/pom.xml | 2 +- superfly-spi/pom.xml | 2 +- superfly-spring-security/pom.xml | 2 +- superfly-web/pom.xml | 2 +- superfly-wicket/pom.xml | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index d7a7879a..22cdd59f 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ superfly-parent pom Superfly parent project - 1.7-35-SNAPSHOT + 1.7-35 Parent POM of the Superfly project superfly-remote-api diff --git a/superfly-client-opt/pom.xml b/superfly-client-opt/pom.xml index 72663166..359c9c9e 100644 --- a/superfly-client-opt/pom.xml +++ b/superfly-client-opt/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35-SNAPSHOT + 1.7-35 4.0.0 superfly-client-opt diff --git a/superfly-client-web-security/pom.xml b/superfly-client-web-security/pom.xml index c355fb28..810f96b2 100644 --- a/superfly-client-web-security/pom.xml +++ b/superfly-client-web-security/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35-SNAPSHOT + 1.7-35 4.0.0 superfly-client-web-security diff --git a/superfly-client/pom.xml b/superfly-client/pom.xml index 40fb152f..7005e3b6 100644 --- a/superfly-client/pom.xml +++ b/superfly-client/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35-SNAPSHOT + 1.7-35 superfly-client Superfly Client diff --git a/superfly-common/pom.xml b/superfly-common/pom.xml index 0be7a383..92ed96be 100644 --- a/superfly-common/pom.xml +++ b/superfly-common/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35-SNAPSHOT + 1.7-35 4.0.0 superfly-common diff --git a/superfly-crypto/pom.xml b/superfly-crypto/pom.xml index 6507a48a..18dbd231 100644 --- a/superfly-crypto/pom.xml +++ b/superfly-crypto/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35-SNAPSHOT + 1.7-35 superfly-crypto Superfly helper crypto services diff --git a/superfly-httpclient-ssl/pom.xml b/superfly-httpclient-ssl/pom.xml index a3a8363f..63776d22 100644 --- a/superfly-httpclient-ssl/pom.xml +++ b/superfly-httpclient-ssl/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35-SNAPSHOT + 1.7-35 4.0.0 superfly-httpclient-ssl diff --git a/superfly-integration-test/pom.xml b/superfly-integration-test/pom.xml index fc56f699..9270abd0 100644 --- a/superfly-integration-test/pom.xml +++ b/superfly-integration-test/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35-SNAPSHOT + 1.7-35 4.0.0 com.payneteasy.superfly diff --git a/superfly-remote-api/pom.xml b/superfly-remote-api/pom.xml index 00dccc65..02b26eeb 100644 --- a/superfly-remote-api/pom.xml +++ b/superfly-remote-api/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35-SNAPSHOT + 1.7-35 4.0.0 com.payneteasy.superfly diff --git a/superfly-service/pom.xml b/superfly-service/pom.xml index 30d7c915..99635413 100644 --- a/superfly-service/pom.xml +++ b/superfly-service/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35-SNAPSHOT + 1.7-35 4.0.0 com.payneteasy.superfly diff --git a/superfly-spi-support/pom.xml b/superfly-spi-support/pom.xml index 6cbba3ca..6ba61eed 100644 --- a/superfly-spi-support/pom.xml +++ b/superfly-spi-support/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35-SNAPSHOT + 1.7-35 superfly-spi-support Support classes for SPI mechanism diff --git a/superfly-spi/pom.xml b/superfly-spi/pom.xml index b3433b07..bc674c96 100644 --- a/superfly-spi/pom.xml +++ b/superfly-spi/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35-SNAPSHOT + 1.7-35 com.payneteasy.superfly superfly-spi diff --git a/superfly-spring-security/pom.xml b/superfly-spring-security/pom.xml index 1bddd020..6f465cfa 100644 --- a/superfly-spring-security/pom.xml +++ b/superfly-spring-security/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35-SNAPSHOT + 1.7-35 4.0.0 superfly-spring-security diff --git a/superfly-web/pom.xml b/superfly-web/pom.xml index cf8b0f14..1a67a7fb 100644 --- a/superfly-web/pom.xml +++ b/superfly-web/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35-SNAPSHOT + 1.7-35 4.0.0 superfly-web diff --git a/superfly-wicket/pom.xml b/superfly-wicket/pom.xml index 46c2b238..b307d271 100644 --- a/superfly-wicket/pom.xml +++ b/superfly-wicket/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35-SNAPSHOT + 1.7-35 superfly-wicket Superfly wicket extensions From 3641ccfc18d07a68dc9ad2ed7c7e3f1202a05a9e Mon Sep 17 00:00:00 2001 From: iv Date: Mon, 8 Dec 2025 18:08:22 +0300 Subject: [PATCH 16/91] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- superfly-client-opt/pom.xml | 2 +- superfly-client-web-security/pom.xml | 2 +- superfly-client/pom.xml | 2 +- superfly-common/pom.xml | 2 +- superfly-crypto/pom.xml | 2 +- superfly-httpclient-ssl/pom.xml | 2 +- superfly-integration-test/pom.xml | 2 +- superfly-remote-api/pom.xml | 2 +- superfly-service/pom.xml | 2 +- superfly-spi-support/pom.xml | 2 +- superfly-spi/pom.xml | 2 +- superfly-spring-security/pom.xml | 2 +- superfly-web/pom.xml | 2 +- superfly-wicket/pom.xml | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index 22cdd59f..f080daaf 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ superfly-parent pom Superfly parent project - 1.7-35 + 1.7-36-SNAPSHOT Parent POM of the Superfly project superfly-remote-api diff --git a/superfly-client-opt/pom.xml b/superfly-client-opt/pom.xml index 359c9c9e..716baf06 100644 --- a/superfly-client-opt/pom.xml +++ b/superfly-client-opt/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35 + 1.7-36-SNAPSHOT 4.0.0 superfly-client-opt diff --git a/superfly-client-web-security/pom.xml b/superfly-client-web-security/pom.xml index 810f96b2..7de4834b 100644 --- a/superfly-client-web-security/pom.xml +++ b/superfly-client-web-security/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35 + 1.7-36-SNAPSHOT 4.0.0 superfly-client-web-security diff --git a/superfly-client/pom.xml b/superfly-client/pom.xml index 7005e3b6..908e8bc4 100644 --- a/superfly-client/pom.xml +++ b/superfly-client/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35 + 1.7-36-SNAPSHOT superfly-client Superfly Client diff --git a/superfly-common/pom.xml b/superfly-common/pom.xml index 92ed96be..879beb24 100644 --- a/superfly-common/pom.xml +++ b/superfly-common/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35 + 1.7-36-SNAPSHOT 4.0.0 superfly-common diff --git a/superfly-crypto/pom.xml b/superfly-crypto/pom.xml index 18dbd231..0cec5c11 100644 --- a/superfly-crypto/pom.xml +++ b/superfly-crypto/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35 + 1.7-36-SNAPSHOT superfly-crypto Superfly helper crypto services diff --git a/superfly-httpclient-ssl/pom.xml b/superfly-httpclient-ssl/pom.xml index 63776d22..19ecd803 100644 --- a/superfly-httpclient-ssl/pom.xml +++ b/superfly-httpclient-ssl/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35 + 1.7-36-SNAPSHOT 4.0.0 superfly-httpclient-ssl diff --git a/superfly-integration-test/pom.xml b/superfly-integration-test/pom.xml index 9270abd0..9370e4f5 100644 --- a/superfly-integration-test/pom.xml +++ b/superfly-integration-test/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35 + 1.7-36-SNAPSHOT 4.0.0 com.payneteasy.superfly diff --git a/superfly-remote-api/pom.xml b/superfly-remote-api/pom.xml index 02b26eeb..9128c82b 100644 --- a/superfly-remote-api/pom.xml +++ b/superfly-remote-api/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35 + 1.7-36-SNAPSHOT 4.0.0 com.payneteasy.superfly diff --git a/superfly-service/pom.xml b/superfly-service/pom.xml index 99635413..b3662a56 100644 --- a/superfly-service/pom.xml +++ b/superfly-service/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35 + 1.7-36-SNAPSHOT 4.0.0 com.payneteasy.superfly diff --git a/superfly-spi-support/pom.xml b/superfly-spi-support/pom.xml index 6ba61eed..32a1ecfc 100644 --- a/superfly-spi-support/pom.xml +++ b/superfly-spi-support/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35 + 1.7-36-SNAPSHOT superfly-spi-support Support classes for SPI mechanism diff --git a/superfly-spi/pom.xml b/superfly-spi/pom.xml index bc674c96..5f9601a8 100644 --- a/superfly-spi/pom.xml +++ b/superfly-spi/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35 + 1.7-36-SNAPSHOT com.payneteasy.superfly superfly-spi diff --git a/superfly-spring-security/pom.xml b/superfly-spring-security/pom.xml index 6f465cfa..e64e264b 100644 --- a/superfly-spring-security/pom.xml +++ b/superfly-spring-security/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35 + 1.7-36-SNAPSHOT 4.0.0 superfly-spring-security diff --git a/superfly-web/pom.xml b/superfly-web/pom.xml index 1a67a7fb..73c8b012 100644 --- a/superfly-web/pom.xml +++ b/superfly-web/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35 + 1.7-36-SNAPSHOT 4.0.0 superfly-web diff --git a/superfly-wicket/pom.xml b/superfly-wicket/pom.xml index b307d271..8b85a3f4 100644 --- a/superfly-wicket/pom.xml +++ b/superfly-wicket/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-35 + 1.7-36-SNAPSHOT superfly-wicket Superfly wicket extensions From a3cd5ea5cf999bf49ad33844bdd388ea57c5eb2c Mon Sep 17 00:00:00 2001 From: iv Date: Tue, 9 Dec 2025 23:57:34 +0300 Subject: [PATCH 17/91] #111 disable autocomplete --- .../payneteasy/superfly/web/wicket/page/user/ListUsersPage.html | 2 +- .../payneteasy/superfly/web/wicket/page/user/ListUsersPage.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.html index c64f2f2c..5e4dff0b 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.html @@ -18,7 +18,7 @@
    • - +
    • Role: diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.java index dcfa47c2..0ac39d4f 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.java @@ -71,7 +71,7 @@ public ListUsersPage() { final UserFilters userFilters = new UserFilters(); Form filtersForm = new Form("filters-form"); add(filtersForm); - filtersForm.add(new TextField("username-filter", new PropertyModel(userFilters, "usernamePrefix"))); + filtersForm.add(new TextField("user-filter", new PropertyModel<>(userFilters, "usernamePrefix"))); DropDownChoice roleDropdown = new DropDownChoice( "role-filter" From a4f40b97091a88928abc0961d3ed34a0a67f73fa Mon Sep 17 00:00:00 2001 From: iv Date: Wed, 10 Dec 2025 00:26:05 +0300 Subject: [PATCH 18/91] #111 disable autocomplete --- .../payneteasy/superfly/web/wicket/page/user/ListUsersPage.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.html index 5e4dff0b..dc3b9ab6 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.html @@ -18,7 +18,7 @@
      • - +
      • Role: From 131e0040f2c5342e4c159e416942814481f20ff7 Mon Sep 17 00:00:00 2001 From: Vladimir Kondakov Date: Thu, 26 Mar 2026 11:04:31 +0300 Subject: [PATCH 19/91] fixes #19 - add events for user passwords reset, user block , user suspend --- pom.xml | 22 +++++-- superfly-client-opt/pom.xml | 13 +++- .../client/XmlActionDescriptionCollector.java | 8 +-- .../client/ActionDescriptionRoot.java | 4 +- superfly-remote-api/pom.xml | 10 +-- .../superfly/api/ActionDescription.java | 2 +- .../com/payneteasy/superfly/api/SSOEvent.java | 64 +++++++++++++++++++ .../payneteasy/superfly/api/SSOService.java | 8 +++ superfly-service/pom.xml | 16 +++-- .../com/payneteasy/superfly/dao/EventDao.java | 17 +++++ .../com/payneteasy/superfly/model/Event.java | 57 +++++++++++++++++ .../superfly/model/releasenotes/News.java | 4 +- .../superfly/model/releasenotes/Release.java | 6 +- .../model/releasenotes/ReleaseItem.java | 4 +- .../superfly/service/EventService.java | 14 ++++ .../superfly/service/InternalSSOService.java | 11 ++-- .../service/impl/EventServiceImpl.java | 61 ++++++++++++++++++ .../service/impl/InternalSSOServiceImpl.java | 48 ++++++++++---- .../releasenotes/ReleaseNotesServiceImpl.java | 6 +- .../service/impl/remote/SSOServiceImpl.java | 7 ++ .../PlaintextPasswordEncoderTest.java | 7 +- superfly-sql/mi/R1.7.4/R1.7.4_SSO.cmd | 3 + superfly-sql/mi/R1.7.4/R1.7.4_SSO.sh | 8 +++ superfly-sql/mi/R1.7.4/R1.7.4_SSO.sql | 21 ++++++ superfly-sql/mi/R1.7.4/R1.7.4_SSO_DML.sql | 10 +++ superfly-sql/src/all-proc.sql | 4 ++ superfly-sql/src/int/int_create_event.prc | 16 +++++ superfly-sql/src/stub/reset_password.prc | 8 +++ superfly-sql/src/ui_get/ui_get_events.prc | 23 +++++++ superfly-sql/src/ui_update/ui_lock_user.prc | 7 ++ .../src/ui_update/ui_suspend_user.prc | 9 ++- .../WEB-INF/spring/applicationContext-dao.xml | 4 ++ .../spring/applicationContext-service.xml | 5 ++ 33 files changed, 457 insertions(+), 50 deletions(-) create mode 100644 superfly-remote-api/src/main/java/com/payneteasy/superfly/api/SSOEvent.java create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/dao/EventDao.java create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/model/Event.java create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/service/EventService.java create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/service/impl/EventServiceImpl.java create mode 100644 superfly-sql/mi/R1.7.4/R1.7.4_SSO.cmd create mode 100644 superfly-sql/mi/R1.7.4/R1.7.4_SSO.sh create mode 100644 superfly-sql/mi/R1.7.4/R1.7.4_SSO.sql create mode 100644 superfly-sql/mi/R1.7.4/R1.7.4_SSO_DML.sql create mode 100644 superfly-sql/src/int/int_create_event.prc create mode 100644 superfly-sql/src/ui_get/ui_get_events.prc diff --git a/pom.xml b/pom.xml index f080daaf..9211bc91 100644 --- a/pom.xml +++ b/pom.xml @@ -190,10 +190,18 @@ ${project.version} + + + javax.xml.bind + jaxb-api + 2.3.1 + + + com.sun.xml.bind jaxb-impl - 4.0.4 + 2.3.1 @@ -551,9 +559,15 @@ - javax.mail - mail - 1.4 + com.sun.mail + javax.mail + 1.6.2 + + + javax.activation + activation + + diff --git a/superfly-client-opt/pom.xml b/superfly-client-opt/pom.xml index 716baf06..d6fdcfb1 100644 --- a/superfly-client-opt/pom.xml +++ b/superfly-client-opt/pom.xml @@ -9,10 +9,19 @@ Superfly Client optional support Optional Superfly-related client features (like SSL support) + - com.sun.xml.bind - jaxb-impl + javax.xml.bind + jaxb-api + + + + javax.xml.bind + jaxb-api + + + commons-httpclient diff --git a/superfly-client-opt/src/main/java/com/payneteasy/superfly/client/XmlActionDescriptionCollector.java b/superfly-client-opt/src/main/java/com/payneteasy/superfly/client/XmlActionDescriptionCollector.java index a50d1a7d..38114b2d 100644 --- a/superfly-client-opt/src/main/java/com/payneteasy/superfly/client/XmlActionDescriptionCollector.java +++ b/superfly-client-opt/src/main/java/com/payneteasy/superfly/client/XmlActionDescriptionCollector.java @@ -2,15 +2,15 @@ import com.payneteasy.superfly.api.ActionDescription; import com.payneteasy.superfly.client.exception.CollectionException; -import jakarta.xml.bind.JAXBContext; -import jakarta.xml.bind.JAXBException; -import jakarta.xml.bind.Unmarshaller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Required; import org.springframework.core.io.Resource; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -38,7 +38,7 @@ public List collect() throws CollectionException { JAXBContext jaxbContext = JAXBContext.newInstance(ActionDescriptionRoot.class); Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller(); - ActionDescriptionRoot news = (ActionDescriptionRoot) jaxbUnmarshaller.unmarshal(resource.getFile()); + ActionDescriptionRoot news = (ActionDescriptionRoot) jaxbUnmarshaller.unmarshal(resource.getInputStream()); list = new ArrayList<>(news.getActions()); } catch (JAXBException | IOException e) { throw new RuntimeException(e); diff --git a/superfly-client/src/main/java/com/payneteasy/superfly/client/ActionDescriptionRoot.java b/superfly-client/src/main/java/com/payneteasy/superfly/client/ActionDescriptionRoot.java index 3b0c2e42..2fa99f3e 100644 --- a/superfly-client/src/main/java/com/payneteasy/superfly/client/ActionDescriptionRoot.java +++ b/superfly-client/src/main/java/com/payneteasy/superfly/client/ActionDescriptionRoot.java @@ -1,7 +1,7 @@ package com.payneteasy.superfly.client; -import jakarta.xml.bind.annotation.XmlElement; -import jakarta.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; import java.util.ArrayList; import java.util.List; diff --git a/superfly-remote-api/pom.xml b/superfly-remote-api/pom.xml index 9128c82b..03c6d748 100644 --- a/superfly-remote-api/pom.xml +++ b/superfly-remote-api/pom.xml @@ -15,9 +15,11 @@ - - com.sun.xml.bind - jaxb-impl - + + javax.xml.bind + jaxb-api + + + diff --git a/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/ActionDescription.java b/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/ActionDescription.java index 51addf07..e1ee923a 100644 --- a/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/ActionDescription.java +++ b/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/ActionDescription.java @@ -1,6 +1,6 @@ package com.payneteasy.superfly.api; -import jakarta.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlAttribute; import java.io.Serializable; /** diff --git a/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/SSOEvent.java b/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/SSOEvent.java new file mode 100644 index 00000000..30e3e323 --- /dev/null +++ b/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/SSOEvent.java @@ -0,0 +1,64 @@ +package com.payneteasy.superfly.api; + +import java.io.Serializable; +import java.util.Date; + +/** + * SSO event + */ +public class SSOEvent implements Serializable { + private static final long serialVersionUID = 2939579042187840631L; + private String eventData; + private Long eventId; + private String eventTypeCode; + private Date eventTime; + + public SSOEvent(Long eventId, Date eventTime, String eventTypeCode, String eventData) { + this.eventId = eventId; + this.eventTime = eventTime; + this.eventTypeCode = eventTypeCode; + this.eventData = eventData; + } + + public String getEventData() { + return eventData; + } + + public void setEventData(String eventData) { + this.eventData = eventData; + } + + public Long getEventId() { + return eventId; + } + + public void setEventId(Long eventId) { + this.eventId = eventId; + } + + public String getEventTypeCode() { + return eventTypeCode; + } + + public void setEventTypeCode(String eventTypeCode) { + this.eventTypeCode = eventTypeCode; + } + + public Date getEventTime() { + return eventTime; + } + + public void setEventTime(Date eventTime) { + this.eventTime = eventTime; + } + + @Override + public String toString() { + return "SSOEvent{" + + "eventData='" + eventData + '\'' + + ", eventId=" + eventId + + ", eventTypeCode='" + eventTypeCode + '\'' + + ", eventTime=" + eventTime + + '}'; + } +} diff --git a/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/SSOService.java b/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/SSOService.java index feee2a96..2cd551cc 100644 --- a/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/SSOService.java +++ b/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/SSOService.java @@ -1,5 +1,6 @@ package com.payneteasy.superfly.api; +import java.util.Date; import java.util.List; /** @@ -267,4 +268,11 @@ void resetAndSendOTPTable(String subsystemIdentifier, * @param subsystemHint hint to determine the affected subsystem */ void changeUserRole(String username, String newRole, String subsystemHint); + + /** + * Get events from lastEventTime. + * + * @param lastEventTime lastEventTime + */ + List getEvents(Date lastEventTime, long waitTimeMs); } diff --git a/superfly-service/pom.xml b/superfly-service/pom.xml index b3662a56..62dacf9e 100644 --- a/superfly-service/pom.xml +++ b/superfly-service/pom.xml @@ -32,11 +32,19 @@ superfly-crypto + - com.sun.xml.bind - jaxb-impl + javax.xml.bind + jaxb-api + + + javax.xml.bind + jaxb-api + + + org.springframework @@ -104,8 +112,8 @@ - javax.mail - mail + com.sun.mail + javax.mail diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/dao/EventDao.java b/superfly-service/src/main/java/com/payneteasy/superfly/dao/EventDao.java new file mode 100644 index 00000000..7cbc51c8 --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/dao/EventDao.java @@ -0,0 +1,17 @@ +package com.payneteasy.superfly.dao; + +import com.googlecode.jdbcproc.daofactory.annotation.AStoredProcedure; +import com.payneteasy.superfly.model.Event; + +import java.util.Date; +import java.util.List; + +/** + * DAO to work with events. + */ +public interface EventDao { + + @AStoredProcedure(name = "ui_get_events") + List getEvents(Date lastEventTime); + +} diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/model/Event.java b/superfly-service/src/main/java/com/payneteasy/superfly/model/Event.java new file mode 100644 index 00000000..39d46938 --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/model/Event.java @@ -0,0 +1,57 @@ +package com.payneteasy.superfly.model; + +import javax.persistence.Column; +import java.util.Date; + +public class Event { + private Long eventId; + private Date eventTime; + private String eventTypeCode; + private String eventData; + + @Column(name = "event_id") + public Long getEventId() { + return eventId; + } + + public void setEventId(Long eventId) { + this.eventId = eventId; + } + + @Column(name = "event_time") + public Date getEventTime() { + return eventTime; + } + + public void setEventTime(Date eventTime) { + this.eventTime = eventTime; + } + + @Column(name = "event_type_code") + public String getEventTypeCode() { + return eventTypeCode; + } + + public void setEventTypeCode(String eventTypeCode) { + this.eventTypeCode = eventTypeCode; + } + + @Column(name = "event_data") + public String getEventData() { + return eventData; + } + + public void setEventData(String eventData) { + this.eventData = eventData; + } + + @Override + public String toString() { + return "Event{" + + "eventId=" + eventId + + ", eventTime=" + eventTime + + ", eventTypeCode='" + eventTypeCode + '\'' + + ", eventData='" + eventData + '\'' + + '}'; + } +} diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/model/releasenotes/News.java b/superfly-service/src/main/java/com/payneteasy/superfly/model/releasenotes/News.java index d7a5dc8f..92d973eb 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/model/releasenotes/News.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/model/releasenotes/News.java @@ -1,8 +1,8 @@ package com.payneteasy.superfly.model.releasenotes; -import jakarta.xml.bind.annotation.XmlElement; -import jakarta.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; import java.util.ArrayList; import java.util.List; diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/model/releasenotes/Release.java b/superfly-service/src/main/java/com/payneteasy/superfly/model/releasenotes/Release.java index 2eb6907c..49b5cece 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/model/releasenotes/Release.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/model/releasenotes/Release.java @@ -1,8 +1,8 @@ package com.payneteasy.superfly.model.releasenotes; -import jakarta.xml.bind.annotation.XmlAttribute; -import jakarta.xml.bind.annotation.XmlElement; -import jakarta.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; import java.util.ArrayList; import java.util.List; diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/model/releasenotes/ReleaseItem.java b/superfly-service/src/main/java/com/payneteasy/superfly/model/releasenotes/ReleaseItem.java index ffb1415d..94588665 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/model/releasenotes/ReleaseItem.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/model/releasenotes/ReleaseItem.java @@ -1,7 +1,7 @@ package com.payneteasy.superfly.model.releasenotes; -import jakarta.xml.bind.annotation.XmlAttribute; -import jakarta.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; /** diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/EventService.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/EventService.java new file mode 100644 index 00000000..e7040b54 --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/EventService.java @@ -0,0 +1,14 @@ +package com.payneteasy.superfly.service; + +import com.payneteasy.superfly.model.Event; + +import java.util.Date; +import java.util.List; + +/** + * Service to work with events. + * + */ +public interface EventService { + List getEvents(Date lastEventTime, long waitTime); +} diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/InternalSSOService.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/InternalSSOService.java index 573dc297..4076cef5 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/InternalSSOService.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/InternalSSOService.java @@ -1,20 +1,21 @@ package com.payneteasy.superfly.service; -import java.util.List; - import com.payneteasy.superfly.api.ActionDescription; import com.payneteasy.superfly.api.BadPublicKeyException; -import com.payneteasy.superfly.api.OTPType; import com.payneteasy.superfly.api.MessageSendException; +import com.payneteasy.superfly.api.OTPType; import com.payneteasy.superfly.api.PolicyValidationException; import com.payneteasy.superfly.api.RoleGrantSpecification; +import com.payneteasy.superfly.api.SSOEvent; import com.payneteasy.superfly.api.SSOUser; import com.payneteasy.superfly.api.SSOUserWithActions; -import com.payneteasy.superfly.api.SsoDecryptException; import com.payneteasy.superfly.api.UserExistsException; import com.payneteasy.superfly.model.UserWithStatus; import com.payneteasy.superfly.model.ui.user.UserForDescription; +import java.util.Date; +import java.util.List; + /** * Internal service used to implement SSOService. * @@ -194,4 +195,6 @@ void registerUser(String username, String password, String email, String subsyst void changeUserRole(String username, String newRole, String subsystemIdentifier); boolean hasOtpMasterKey(String username); + + List getEvents(Date lastEventTime, long waitTimeMs); } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/EventServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/EventServiceImpl.java new file mode 100644 index 00000000..b9130b93 --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/EventServiceImpl.java @@ -0,0 +1,61 @@ +package com.payneteasy.superfly.service.impl; + +import com.payneteasy.superfly.dao.EventDao; +import com.payneteasy.superfly.model.Event; +import com.payneteasy.superfly.service.EventService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.annotation.Required; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +@Transactional +public class EventServiceImpl implements EventService, DisposableBean { + + private static final Logger logger = LoggerFactory.getLogger(EventServiceImpl.class); + + + private EventDao eventDao; + private static final int DELAY_TIME_MS = 500; + private final AtomicBoolean isShutdown = new AtomicBoolean(false); + + @Override + public void destroy() throws Exception { + isShutdown.set(true); + } + + @Required + public void setEventDao(EventDao eventDao) { + this.eventDao = eventDao; + } + + @Override + public List getEvents(Date lastEventTime, long waitTimeMs) { + List events = eventDao.getEvents(lastEventTime); + List result = (events !=null ? new ArrayList<>(events) : new ArrayList<>()); + if (result.isEmpty()) { + long now = System.currentTimeMillis(); + long finishTime = now + waitTimeMs; + while ((now < finishTime) && !isShutdown.get()) { + try { + Thread.sleep(DELAY_TIME_MS); + } catch (InterruptedException e) { + logger.error(e.getMessage(), e); + } + final List newEvents = eventDao.getEvents(lastEventTime); + + if (newEvents!=null && !newEvents.isEmpty()) { + result.addAll(newEvents); + break; + } + now = System.currentTimeMillis(); + } + } + return result; + } +} diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/InternalSSOServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/InternalSSOServiceImpl.java index dc100e28..92e51bd6 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/InternalSSOServiceImpl.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/InternalSSOServiceImpl.java @@ -7,6 +7,7 @@ import com.payneteasy.superfly.api.PolicyValidationException; import com.payneteasy.superfly.api.RoleGrantSpecification; import com.payneteasy.superfly.api.SSOAction; +import com.payneteasy.superfly.api.SSOEvent; import com.payneteasy.superfly.api.SSORole; import com.payneteasy.superfly.api.SSOUser; import com.payneteasy.superfly.api.SSOUserWithActions; @@ -18,6 +19,7 @@ import com.payneteasy.superfly.model.AuthAction; import com.payneteasy.superfly.model.AuthRole; import com.payneteasy.superfly.model.AuthSession; +import com.payneteasy.superfly.model.Event; import com.payneteasy.superfly.model.LockoutType; import com.payneteasy.superfly.model.RoutineResult; import com.payneteasy.superfly.model.UserRegisterRequest; @@ -30,6 +32,7 @@ import com.payneteasy.superfly.policy.password.PasswordCheckContext; import com.payneteasy.superfly.register.RegisterUserStrategy; import com.payneteasy.superfly.service.ActionService; +import com.payneteasy.superfly.service.EventService; import com.payneteasy.superfly.service.InternalSSOService; import com.payneteasy.superfly.service.LoggerSink; import com.payneteasy.superfly.service.NotificationService; @@ -48,10 +51,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; @Transactional public class InternalSSOServiceImpl implements InternalSSOService { @@ -72,8 +77,13 @@ public class InternalSSOServiceImpl implements InternalSSOService { private PublicKeyCrypto publicKeyCrypto; private HOTPService hotpService; private Set notSavedActions = Collections.singleton("action_temp_password"); - private AbstractPolicyValidation policyValidation; + private EventService eventService; + + @Required + public void setEventService(EventService eventService) { + this.eventService = eventService; + } @Required public void setPolicyValidation(AbstractPolicyValidation policyValidation) { @@ -153,9 +163,9 @@ public void setNotSavedActions(Set notSavedActions) { public SSOUser authenticate(String username, String password, String subsystemIdentifier, String userIpAddress, String sessionInfo) { SSOUser ssoUser; - String encPassword = passwordEncoder.encode(password, saltSource.getSalt(username)); + String encPassword = passwordEncoder.encode(password, saltSource.getSalt(username)); AuthSession session = userService.authenticate(username, encPassword, - subsystemIdentifier, userIpAddress, sessionInfo); + subsystemIdentifier, userIpAddress, sessionInfo); boolean ok = session != null && session.getSessionId() != null; loggerSink.info(logger, "REMOTE_LOGIN", ok, username); if (ok) { @@ -177,9 +187,9 @@ public boolean checkOtp(SSOUser user, String code) { @Override public SSOUser pseudoAuthenticate(String username, String subsystemIdentifier) { - SSOUser ssoUser; + SSOUser ssoUser; AuthSession session = userService.pseudoAuthenticate(username, subsystemIdentifier); - boolean ok = session != null && session.getSessionId() != null; + boolean ok = session != null && session.getSessionId() != null; loggerSink.info(logger, "REMOTE_PSEUDO_LOGIN", ok, username); if (ok) { ssoUser = buildSSOUser(session); @@ -191,7 +201,7 @@ public SSOUser pseudoAuthenticate(String username, String subsystemIdentifier) { } private SSOUser buildSSOUser(AuthSession session) { - SSOUser ssoUser; + SSOUser ssoUser; List authRoles = session.getRoles(); if (authRoles.size() == 1 && authRoles.get(0).getRoleName() == null) { // actually it's empty @@ -200,7 +210,7 @@ private SSOUser buildSSOUser(AuthSession session) { Map actionsMap = new HashMap<>(authRoles.size()); for (AuthRole authRole : authRoles) { - SSORole ssoRole = new SSORole(authRole.getRoleName()); + SSORole ssoRole = new SSORole(authRole.getRoleName()); SSOAction[] actions = convertToSSOActions(authRole.getActions()); actionsMap.put(ssoRole, actions); } @@ -215,7 +225,7 @@ protected SSOAction[] convertToSSOActions(List authActions) { SSOAction[] actions = new SSOAction[authActions.size()]; for (int i = 0; i < authActions.size(); i++) { AuthAction authAction = authActions.get(i); - SSOAction ssoAction = new SSOAction(authAction.getActionName(), authAction.isLogAction()); + SSOAction ssoAction = new SSOAction(authAction.getActionName(), authAction.isLogAction()); actions[i] = ssoAction; } return actions; @@ -253,8 +263,8 @@ public List getUsersWithActions(String subsystemIdentifier) } public void registerUser(String username, String password, String email, String subsystemIdentifier, - RoleGrantSpecification[] roleGrants, String name, String surname, String secretQuestion, - String secretAnswer, String publicKey,String organization, OTPType otpType) throws UserExistsException, PolicyValidationException, + RoleGrantSpecification[] roleGrants, String name, String surname, String secretQuestion, + String secretAnswer, String publicKey, String organization, OTPType otpType) throws UserExistsException, PolicyValidationException, BadPublicKeyException, MessageSendException { UserRegisterRequest registerUser = new UserRegisterRequest(); @@ -388,9 +398,9 @@ public List getUserStatuses(String userNames) { @Override public SSOUser exchangeSubsystemToken(String subsystemToken) { - SSOUser ssoUser; + SSOUser ssoUser; AuthSession session = userService.exchangeSubsystemToken(subsystemToken); - boolean ok = session != null && session.getSessionId() != null; + boolean ok = session != null && session.getSessionId() != null; loggerSink.info(logger, "EXCHANGE_SUBSYSTEM_TOKEN", ok, session != null ? session.getUsername() : "TOKEN: " + subsystemToken); if (ok) { ssoUser = buildSSOUser(session); @@ -430,4 +440,18 @@ public void changeUserRole(String username, String newRole, String subsystemIden public boolean hasOtpMasterKey(String username) { return userService.getOtpMasterKeyByUsername(username) != null; } + + @Override + public List getEvents(Date lastEventTime, long waitTimeMs) { + List events = eventService.getEvents(lastEventTime, waitTimeMs); + + if (events!=null && !events.isEmpty()) { + logger.info("getEvents call info={}",events); + return events.stream().map(event -> + new SSOEvent(event.getEventId(), event.getEventTime(), event.getEventTypeCode(), event.getEventData()) + ).collect(Collectors.toList()); + } + + return List.of(); + } } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/releasenotes/ReleaseNotesServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/releasenotes/ReleaseNotesServiceImpl.java index 727cbfd8..2e3620f9 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/releasenotes/ReleaseNotesServiceImpl.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/releasenotes/ReleaseNotesServiceImpl.java @@ -8,9 +8,9 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; -import jakarta.xml.bind.JAXBContext; -import jakarta.xml.bind.JAXBException; -import jakarta.xml.bind.Unmarshaller; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; import java.io.IOException; import java.util.Collections; import java.util.List; diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/SSOServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/SSOServiceImpl.java index 247cd543..cab9ecc3 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/SSOServiceImpl.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/SSOServiceImpl.java @@ -16,6 +16,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.List; /** @@ -338,4 +339,10 @@ public void changeUserRole(String username, String newRole) { public void changeUserRole(String username, String newRole, String subsystemHint) { internalSSOService.changeUserRole(username, newRole, obtainSubsystemIdentifier(subsystemHint)); } + + + @Override + public List getEvents(Date lastEventTime, long waitTimeMs) { + return internalSSOService.getEvents(lastEventTime, waitTimeMs); + } } diff --git a/superfly-service/src/test/java/com/payneteasy/superfly/password/PlaintextPasswordEncoderTest.java b/superfly-service/src/test/java/com/payneteasy/superfly/password/PlaintextPasswordEncoderTest.java index 208571f2..8d684517 100644 --- a/superfly-service/src/test/java/com/payneteasy/superfly/password/PlaintextPasswordEncoderTest.java +++ b/superfly-service/src/test/java/com/payneteasy/superfly/password/PlaintextPasswordEncoderTest.java @@ -5,14 +5,17 @@ import static org.junit.Assert.assertEquals; public class PlaintextPasswordEncoderTest { - private PlaintextPasswordEncoder encoder = new PlaintextPasswordEncoder(); + private MessageDigestPasswordEncoder encoder = new MessageDigestPasswordEncoder(); @Test public void testEncode() { - String encoded = encoder.encode("hello", "salt"); + encoder.setAlgorithm("SHA-256"); + String encoded = encoder.encode("64SejJ4DkWdt7cQ&", "04ec699c9ef3b67ab01293bc62274002579bd7cfa72decffc394c8b0597640e7"); assertEquals("hello{salt}", encoded); } + //GKPAXnU9rFux86A& + @Test public void testEncodeWithEmptySalt() { String encoded = encoder.encode("hello", ""); diff --git a/superfly-sql/mi/R1.7.4/R1.7.4_SSO.cmd b/superfly-sql/mi/R1.7.4/R1.7.4_SSO.cmd new file mode 100644 index 00000000..ade25257 --- /dev/null +++ b/superfly-sql/mi/R1.7.4/R1.7.4_SSO.cmd @@ -0,0 +1,3 @@ +@set PATH=C:\cygwin\bin;%PATH% +bash R1.7.3_SSO.sh + diff --git a/superfly-sql/mi/R1.7.4/R1.7.4_SSO.sh b/superfly-sql/mi/R1.7.4/R1.7.4_SSO.sh new file mode 100644 index 00000000..b91a8437 --- /dev/null +++ b/superfly-sql/mi/R1.7.4/R1.7.4_SSO.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +. ../../functions.sh + +runScript R1.7.4_SSO.sql + +runScript R1.7.4_SSO_DML.sql + diff --git a/superfly-sql/mi/R1.7.4/R1.7.4_SSO.sql b/superfly-sql/mi/R1.7.4/R1.7.4_SSO.sql new file mode 100644 index 00000000..25ce87c0 --- /dev/null +++ b/superfly-sql/mi/R1.7.4/R1.7.4_SSO.sql @@ -0,0 +1,21 @@ +drop table if exists event_types; + +create table event_types ( + event_type_id int auto_increment, + event_code varchar(24) not null, + event_name varchar(64) not null, + primary key pk_event_types(event_type_id) +) engine = innodb; + +drop table if exists events; + +create table events ( + event_id bigint auto_increment, + event_time datetime not null, + event_type_id int, + event_data varchar(128) not null, + primary key pk_events(event_id), + constraint fk_event_types foreign key (event_type_id) references event_types (event_type_id) +) engine = innodb; + +commit; diff --git a/superfly-sql/mi/R1.7.4/R1.7.4_SSO_DML.sql b/superfly-sql/mi/R1.7.4/R1.7.4_SSO_DML.sql new file mode 100644 index 00000000..2cb610de --- /dev/null +++ b/superfly-sql/mi/R1.7.4/R1.7.4_SSO_DML.sql @@ -0,0 +1,10 @@ +delete from event_types where event_type_id=1; +insert into event_types (event_type_id, event_code, event_name) + values (1,'PASSWORD_RESET','Password reset'); +delete from event_types where event_type_id=2; +insert into event_types (event_type_id, event_code, event_name) + values (2,'ACCOUNT_LOCK','Account lock'); +delete from event_types where event_type_id=3; +insert into event_types (event_type_id, event_code, event_name) + values (3,'ACCOUNT_SUSPEND','Account suspend'); +commit; diff --git a/superfly-sql/src/all-proc.sql b/superfly-sql/src/all-proc.sql index 9cc18750..31fa3e19 100644 --- a/superfly-sql/src/all-proc.sql +++ b/superfly-sql/src/all-proc.sql @@ -82,6 +82,8 @@ create table mysql_routines_return_arguments ( \. int/int_unlink_user_role_actions.prc +\. int/int_create_event.prc + \. ui_copy/ui_copy_action_properties.prc \. ui_filter/ui_filter_subsystems.prc @@ -218,6 +220,8 @@ create table mysql_routines_return_arguments ( \. ui_get/ui_get_expired_sessions.prc +\. ui_get/ui_get_events.prc + \. ui_update/ui_lock_user.prc \. ui_update/ui_unlock_user.prc diff --git a/superfly-sql/src/int/int_create_event.prc b/superfly-sql/src/int/int_create_event.prc new file mode 100644 index 00000000..1d2bdbcd --- /dev/null +++ b/superfly-sql/src/int/int_create_event.prc @@ -0,0 +1,16 @@ +drop procedure if exists int_create_event; +delimiter $$ +create procedure int_create_event( + i_event_code varchar(32), + i_event_data varchar(128) +) +begin + insert into events (event_time,event_type_id,event_data) + select now() + ,event_type_id + ,i_event_data + from event_types + where event_code=i_event_code limit 1; +end +$$ +delimiter ; diff --git a/superfly-sql/src/stub/reset_password.prc b/superfly-sql/src/stub/reset_password.prc index aa90e8c0..b7d25df9 100644 --- a/superfly-sql/src/stub/reset_password.prc +++ b/superfly-sql/src/stub/reset_password.prc @@ -5,6 +5,12 @@ create procedure reset_password(i_user_id int(10), ) main_sql: begin + declare v_user_name varchar(32); + + select user_name into v_user_name + from users where user_id=i_user_id; + + update users u set u.user_password = coalesce(i_user_password,user_password), @@ -14,6 +20,8 @@ main_sql: u.hotp_logins_failed = null where u.user_id = i_user_id; + call int_create_event('PASSWORD_RESET', v_user_name); + select 'OK' status, null error_message; end $$ diff --git a/superfly-sql/src/ui_get/ui_get_events.prc b/superfly-sql/src/ui_get/ui_get_events.prc new file mode 100644 index 00000000..2aa66528 --- /dev/null +++ b/superfly-sql/src/ui_get/ui_get_events.prc @@ -0,0 +1,23 @@ +drop procedure if exists ui_get_events; +delimiter $$ +create procedure ui_get_events(i_last_event_time datetime) + main_sql: + begin + select e.event_id + ,e.event_time + ,e.event_data + ,et.event_code as event_type_code + from events e + inner join event_types et on et.event_type_id=e.event_type_id + where event_time > i_last_event_time; + end +$$ +delimiter ; +call save_routine_information('ui_get_events', + concat_ws(',', + 'event_id int', + 'event_time datetime', + 'event_data varchar', + 'event_type_code varchar' + ) + ); diff --git a/superfly-sql/src/ui_update/ui_lock_user.prc b/superfly-sql/src/ui_update/ui_lock_user.prc index 94c37f21..e2fbbab4 100644 --- a/superfly-sql/src/ui_update/ui_lock_user.prc +++ b/superfly-sql/src/ui_update/ui_lock_user.prc @@ -3,9 +3,16 @@ delimiter $$ create procedure ui_lock_user(i_user_id int(10)) main_sql: begin + declare v_user_name varchar(32); + update users set is_account_locked = "Y" where user_id = i_user_id; + + select user_name into v_user_name + from users where user_id=i_user_id; + + call int_create_event('ACCOUNT_LOCK', v_user_name); /* update sessions diff --git a/superfly-sql/src/ui_update/ui_suspend_user.prc b/superfly-sql/src/ui_update/ui_suspend_user.prc index e5102bd9..6507fc3c 100644 --- a/superfly-sql/src/ui_update/ui_suspend_user.prc +++ b/superfly-sql/src/ui_update/ui_suspend_user.prc @@ -3,9 +3,16 @@ delimiter $$ create procedure ui_suspend_user(i_user_id int(11)) main_sql: begin + declare v_user_name varchar(32); + update users set is_account_locked = 'Y', is_account_suspended = 'Y' where user_id = i_user_id; - + + select user_name into v_user_name + from users where user_id=i_user_id; + + call int_create_event('ACCOUNT_SUSPEND', v_user_name); + select 'OK' status, null error_message; end $$ diff --git a/superfly-web/src/main/webapp/WEB-INF/spring/applicationContext-dao.xml b/superfly-web/src/main/webapp/WEB-INF/spring/applicationContext-dao.xml index 0c9372ac..ffbd6821 100644 --- a/superfly-web/src/main/webapp/WEB-INF/spring/applicationContext-dao.xml +++ b/superfly-web/src/main/webapp/WEB-INF/spring/applicationContext-dao.xml @@ -63,4 +63,8 @@ + + + + diff --git a/superfly-web/src/main/webapp/WEB-INF/spring/applicationContext-service.xml b/superfly-web/src/main/webapp/WEB-INF/spring/applicationContext-service.xml index e232bb73..62ec1f09 100644 --- a/superfly-web/src/main/webapp/WEB-INF/spring/applicationContext-service.xml +++ b/superfly-web/src/main/webapp/WEB-INF/spring/applicationContext-service.xml @@ -30,6 +30,7 @@ + + + + + From 94a8f1f1b55cfa3810816a7dc587c959377f5233 Mon Sep 17 00:00:00 2001 From: Vladimir Kondakov Date: Mon, 30 Mar 2026 15:08:17 +0300 Subject: [PATCH 20/91] =?UTF-8?q?fixes=20#19=20-=20add=20events=20for=20us?= =?UTF-8?q?er=20passwords=20reset,=20user=20block=20,=20user=20suspend=20-?= =?UTF-8?q?-=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- superfly-client-opt/pom.xml | 8 -------- superfly-sql/mi/R1.7.4/R1.7.4_SSO.cmd | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/superfly-client-opt/pom.xml b/superfly-client-opt/pom.xml index d6fdcfb1..5a887d53 100644 --- a/superfly-client-opt/pom.xml +++ b/superfly-client-opt/pom.xml @@ -14,14 +14,6 @@ javax.xml.bind jaxb-api - - - - javax.xml.bind - jaxb-api - - - commons-httpclient diff --git a/superfly-sql/mi/R1.7.4/R1.7.4_SSO.cmd b/superfly-sql/mi/R1.7.4/R1.7.4_SSO.cmd index ade25257..c333e67d 100644 --- a/superfly-sql/mi/R1.7.4/R1.7.4_SSO.cmd +++ b/superfly-sql/mi/R1.7.4/R1.7.4_SSO.cmd @@ -1,3 +1,3 @@ @set PATH=C:\cygwin\bin;%PATH% -bash R1.7.3_SSO.sh +bash R1.7.4_SSO.sh From 319ae5832a546fe6c3213349cc5c3fe7be757c62 Mon Sep 17 00:00:00 2001 From: Vladimir Kondakov Date: Wed, 1 Apr 2026 11:25:19 +0300 Subject: [PATCH 21/91] =?UTF-8?q?fixes=20#19=20-=20add=20events=20for=20us?= =?UTF-8?q?er=20passwords=20reset,=20user=20block=20,=20user=20suspend=20-?= =?UTF-8?q?-=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/payneteasy/superfly/dao/EventDao.java | 2 +- .../payneteasy/superfly/service/impl/EventServiceImpl.java | 5 +++-- superfly-sql/mi/R1.7.4/R1.7.4_SSO.sql | 1 + superfly-sql/src/ui_get/ui_get_events.prc | 7 +++++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/dao/EventDao.java b/superfly-service/src/main/java/com/payneteasy/superfly/dao/EventDao.java index 7cbc51c8..41634738 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/dao/EventDao.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/dao/EventDao.java @@ -12,6 +12,6 @@ public interface EventDao { @AStoredProcedure(name = "ui_get_events") - List getEvents(Date lastEventTime); + List getEvents(Date lastEventTime, int limit); } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/EventServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/EventServiceImpl.java index b9130b93..73da079f 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/EventServiceImpl.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/EventServiceImpl.java @@ -22,6 +22,7 @@ public class EventServiceImpl implements EventService, DisposableBean { private EventDao eventDao; private static final int DELAY_TIME_MS = 500; + private static final int EVENTS_LIMIT = 200; private final AtomicBoolean isShutdown = new AtomicBoolean(false); @Override @@ -36,7 +37,7 @@ public void setEventDao(EventDao eventDao) { @Override public List getEvents(Date lastEventTime, long waitTimeMs) { - List events = eventDao.getEvents(lastEventTime); + List events = eventDao.getEvents(lastEventTime, EVENTS_LIMIT); List result = (events !=null ? new ArrayList<>(events) : new ArrayList<>()); if (result.isEmpty()) { long now = System.currentTimeMillis(); @@ -47,7 +48,7 @@ public List getEvents(Date lastEventTime, long waitTimeMs) { } catch (InterruptedException e) { logger.error(e.getMessage(), e); } - final List newEvents = eventDao.getEvents(lastEventTime); + final List newEvents = eventDao.getEvents(lastEventTime , EVENTS_LIMIT); if (newEvents!=null && !newEvents.isEmpty()) { result.addAll(newEvents); diff --git a/superfly-sql/mi/R1.7.4/R1.7.4_SSO.sql b/superfly-sql/mi/R1.7.4/R1.7.4_SSO.sql index 25ce87c0..81bfb721 100644 --- a/superfly-sql/mi/R1.7.4/R1.7.4_SSO.sql +++ b/superfly-sql/mi/R1.7.4/R1.7.4_SSO.sql @@ -14,6 +14,7 @@ create table events ( event_time datetime not null, event_type_id int, event_data varchar(128) not null, + index idx_events_event_time (event_time), primary key pk_events(event_id), constraint fk_event_types foreign key (event_type_id) references event_types (event_type_id) ) engine = innodb; diff --git a/superfly-sql/src/ui_get/ui_get_events.prc b/superfly-sql/src/ui_get/ui_get_events.prc index 2aa66528..6e711172 100644 --- a/superfly-sql/src/ui_get/ui_get_events.prc +++ b/superfly-sql/src/ui_get/ui_get_events.prc @@ -1,6 +1,6 @@ drop procedure if exists ui_get_events; delimiter $$ -create procedure ui_get_events(i_last_event_time datetime) +create procedure ui_get_events(i_last_event_time datetime, i_limit int) main_sql: begin select e.event_id @@ -9,7 +9,10 @@ create procedure ui_get_events(i_last_event_time datetime) ,et.event_code as event_type_code from events e inner join event_types et on et.event_type_id=e.event_type_id - where event_time > i_last_event_time; + where event_time > i_last_event_time + order by event_time + limit i_limit + ; end $$ delimiter ; From e39eb852821858c63827e40aeca5c57bcb69fd0f Mon Sep 17 00:00:00 2001 From: akorobov Date: Thu, 2 Apr 2026 14:31:53 +0300 Subject: [PATCH 22/91] #119 - Switch test implementation from `MessageDigestPasswordEncoder` to `PlaintextPasswordEncoder` in `PlaintextPasswordEncoderTest`. --- .../superfly/password/PlaintextPasswordEncoderTest.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/superfly-service/src/test/java/com/payneteasy/superfly/password/PlaintextPasswordEncoderTest.java b/superfly-service/src/test/java/com/payneteasy/superfly/password/PlaintextPasswordEncoderTest.java index 8d684517..6c675470 100644 --- a/superfly-service/src/test/java/com/payneteasy/superfly/password/PlaintextPasswordEncoderTest.java +++ b/superfly-service/src/test/java/com/payneteasy/superfly/password/PlaintextPasswordEncoderTest.java @@ -5,17 +5,15 @@ import static org.junit.Assert.assertEquals; public class PlaintextPasswordEncoderTest { - private MessageDigestPasswordEncoder encoder = new MessageDigestPasswordEncoder(); + + private PlaintextPasswordEncoder encoder = new PlaintextPasswordEncoder(); @Test public void testEncode() { - encoder.setAlgorithm("SHA-256"); - String encoded = encoder.encode("64SejJ4DkWdt7cQ&", "04ec699c9ef3b67ab01293bc62274002579bd7cfa72decffc394c8b0597640e7"); + String encoded = encoder.encode("hello", "salt"); assertEquals("hello{salt}", encoded); } - //GKPAXnU9rFux86A& - @Test public void testEncodeWithEmptySalt() { String encoded = encoder.encode("hello", ""); From 5c90437c25e29f6ed0351f3d16b49da8b17aac58 Mon Sep 17 00:00:00 2001 From: akorobov Date: Thu, 2 Apr 2026 15:18:38 +0300 Subject: [PATCH 23/91] #119 Add missing JAXB dependencies and istack runtime to POM files to ensure compatibility and runtime support. --- pom.xml | 6 ++++++ superfly-client-opt/pom.xml | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/pom.xml b/pom.xml index 9211bc91..d935d861 100644 --- a/pom.xml +++ b/pom.xml @@ -204,6 +204,12 @@ 2.3.1 + + com.sun.istack + istack-commons-runtime + 3.0.12 + + org.springframework diff --git a/superfly-client-opt/pom.xml b/superfly-client-opt/pom.xml index 5a887d53..3eb01306 100644 --- a/superfly-client-opt/pom.xml +++ b/superfly-client-opt/pom.xml @@ -14,6 +14,14 @@ javax.xml.bind jaxb-api + + com.sun.xml.bind + jaxb-impl + + + com.sun.istack + istack-commons-runtime + commons-httpclient From b86210c4446238147df20237bfb9c321dcc157c8 Mon Sep 17 00:00:00 2001 From: akorobov Date: Thu, 2 Apr 2026 15:23:32 +0300 Subject: [PATCH 24/91] [maven-release-plugin] prepare release superfly-parent-1.7-36 --- pom.xml | 4 ++-- superfly-client-opt/pom.xml | 2 +- superfly-client-web-security/pom.xml | 2 +- superfly-client/pom.xml | 2 +- superfly-common/pom.xml | 2 +- superfly-crypto/pom.xml | 2 +- superfly-httpclient-ssl/pom.xml | 2 +- superfly-integration-test/pom.xml | 2 +- superfly-remote-api/pom.xml | 2 +- superfly-service/pom.xml | 2 +- superfly-spi-support/pom.xml | 2 +- superfly-spi/pom.xml | 2 +- superfly-spring-security/pom.xml | 2 +- superfly-web/pom.xml | 2 +- superfly-wicket/pom.xml | 2 +- 15 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index d935d861..69bf0ba4 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ superfly-parent pom Superfly parent project - 1.7-36-SNAPSHOT + 1.7-36 Parent POM of the Superfly project superfly-remote-api @@ -34,7 +34,7 @@ scm:git:git@github.com:payneteasy/superfly.git scm:git:git@github.com:payneteasy/superfly.git https://github.com/paynetesy/superfly - superfly-parent-1.7-35 + superfly-parent-1.7-36 diff --git a/superfly-client-opt/pom.xml b/superfly-client-opt/pom.xml index 3eb01306..70f740c1 100644 --- a/superfly-client-opt/pom.xml +++ b/superfly-client-opt/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36-SNAPSHOT + 1.7-36 4.0.0 superfly-client-opt diff --git a/superfly-client-web-security/pom.xml b/superfly-client-web-security/pom.xml index 7de4834b..991019ed 100644 --- a/superfly-client-web-security/pom.xml +++ b/superfly-client-web-security/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36-SNAPSHOT + 1.7-36 4.0.0 superfly-client-web-security diff --git a/superfly-client/pom.xml b/superfly-client/pom.xml index 908e8bc4..37ed5a9b 100644 --- a/superfly-client/pom.xml +++ b/superfly-client/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36-SNAPSHOT + 1.7-36 superfly-client Superfly Client diff --git a/superfly-common/pom.xml b/superfly-common/pom.xml index 879beb24..7c04443c 100644 --- a/superfly-common/pom.xml +++ b/superfly-common/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36-SNAPSHOT + 1.7-36 4.0.0 superfly-common diff --git a/superfly-crypto/pom.xml b/superfly-crypto/pom.xml index 0cec5c11..aafc8601 100644 --- a/superfly-crypto/pom.xml +++ b/superfly-crypto/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36-SNAPSHOT + 1.7-36 superfly-crypto Superfly helper crypto services diff --git a/superfly-httpclient-ssl/pom.xml b/superfly-httpclient-ssl/pom.xml index 19ecd803..eb4be475 100644 --- a/superfly-httpclient-ssl/pom.xml +++ b/superfly-httpclient-ssl/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36-SNAPSHOT + 1.7-36 4.0.0 superfly-httpclient-ssl diff --git a/superfly-integration-test/pom.xml b/superfly-integration-test/pom.xml index 9370e4f5..17591643 100644 --- a/superfly-integration-test/pom.xml +++ b/superfly-integration-test/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36-SNAPSHOT + 1.7-36 4.0.0 com.payneteasy.superfly diff --git a/superfly-remote-api/pom.xml b/superfly-remote-api/pom.xml index 03c6d748..97fcacae 100644 --- a/superfly-remote-api/pom.xml +++ b/superfly-remote-api/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36-SNAPSHOT + 1.7-36 4.0.0 com.payneteasy.superfly diff --git a/superfly-service/pom.xml b/superfly-service/pom.xml index 62dacf9e..09c48fec 100644 --- a/superfly-service/pom.xml +++ b/superfly-service/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36-SNAPSHOT + 1.7-36 4.0.0 com.payneteasy.superfly diff --git a/superfly-spi-support/pom.xml b/superfly-spi-support/pom.xml index 32a1ecfc..b5ce5e3a 100644 --- a/superfly-spi-support/pom.xml +++ b/superfly-spi-support/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36-SNAPSHOT + 1.7-36 superfly-spi-support Support classes for SPI mechanism diff --git a/superfly-spi/pom.xml b/superfly-spi/pom.xml index 5f9601a8..6f54724e 100644 --- a/superfly-spi/pom.xml +++ b/superfly-spi/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36-SNAPSHOT + 1.7-36 com.payneteasy.superfly superfly-spi diff --git a/superfly-spring-security/pom.xml b/superfly-spring-security/pom.xml index e64e264b..3629a686 100644 --- a/superfly-spring-security/pom.xml +++ b/superfly-spring-security/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36-SNAPSHOT + 1.7-36 4.0.0 superfly-spring-security diff --git a/superfly-web/pom.xml b/superfly-web/pom.xml index 73c8b012..722121b2 100644 --- a/superfly-web/pom.xml +++ b/superfly-web/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36-SNAPSHOT + 1.7-36 4.0.0 superfly-web diff --git a/superfly-wicket/pom.xml b/superfly-wicket/pom.xml index 8b85a3f4..ebaade04 100644 --- a/superfly-wicket/pom.xml +++ b/superfly-wicket/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36-SNAPSHOT + 1.7-36 superfly-wicket Superfly wicket extensions From 47c5b26119687c27edc8e944191bad8160b30449 Mon Sep 17 00:00:00 2001 From: akorobov Date: Thu, 2 Apr 2026 15:23:37 +0300 Subject: [PATCH 25/91] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- superfly-client-opt/pom.xml | 2 +- superfly-client-web-security/pom.xml | 2 +- superfly-client/pom.xml | 2 +- superfly-common/pom.xml | 2 +- superfly-crypto/pom.xml | 2 +- superfly-httpclient-ssl/pom.xml | 2 +- superfly-integration-test/pom.xml | 2 +- superfly-remote-api/pom.xml | 2 +- superfly-service/pom.xml | 2 +- superfly-spi-support/pom.xml | 2 +- superfly-spi/pom.xml | 2 +- superfly-spring-security/pom.xml | 2 +- superfly-web/pom.xml | 2 +- superfly-wicket/pom.xml | 2 +- 15 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index 69bf0ba4..847658fa 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ superfly-parent pom Superfly parent project - 1.7-36 + 1.7-37-SNAPSHOT Parent POM of the Superfly project superfly-remote-api @@ -34,7 +34,7 @@ scm:git:git@github.com:payneteasy/superfly.git scm:git:git@github.com:payneteasy/superfly.git https://github.com/paynetesy/superfly - superfly-parent-1.7-36 + superfly-parent-1.7-35 diff --git a/superfly-client-opt/pom.xml b/superfly-client-opt/pom.xml index 70f740c1..8139fcd6 100644 --- a/superfly-client-opt/pom.xml +++ b/superfly-client-opt/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36 + 1.7-37-SNAPSHOT 4.0.0 superfly-client-opt diff --git a/superfly-client-web-security/pom.xml b/superfly-client-web-security/pom.xml index 991019ed..e22b7768 100644 --- a/superfly-client-web-security/pom.xml +++ b/superfly-client-web-security/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36 + 1.7-37-SNAPSHOT 4.0.0 superfly-client-web-security diff --git a/superfly-client/pom.xml b/superfly-client/pom.xml index 37ed5a9b..cf209d49 100644 --- a/superfly-client/pom.xml +++ b/superfly-client/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36 + 1.7-37-SNAPSHOT superfly-client Superfly Client diff --git a/superfly-common/pom.xml b/superfly-common/pom.xml index 7c04443c..ef458d88 100644 --- a/superfly-common/pom.xml +++ b/superfly-common/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36 + 1.7-37-SNAPSHOT 4.0.0 superfly-common diff --git a/superfly-crypto/pom.xml b/superfly-crypto/pom.xml index aafc8601..471c08ad 100644 --- a/superfly-crypto/pom.xml +++ b/superfly-crypto/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36 + 1.7-37-SNAPSHOT superfly-crypto Superfly helper crypto services diff --git a/superfly-httpclient-ssl/pom.xml b/superfly-httpclient-ssl/pom.xml index eb4be475..26597c5e 100644 --- a/superfly-httpclient-ssl/pom.xml +++ b/superfly-httpclient-ssl/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36 + 1.7-37-SNAPSHOT 4.0.0 superfly-httpclient-ssl diff --git a/superfly-integration-test/pom.xml b/superfly-integration-test/pom.xml index 17591643..c8398d63 100644 --- a/superfly-integration-test/pom.xml +++ b/superfly-integration-test/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36 + 1.7-37-SNAPSHOT 4.0.0 com.payneteasy.superfly diff --git a/superfly-remote-api/pom.xml b/superfly-remote-api/pom.xml index 97fcacae..edc4e727 100644 --- a/superfly-remote-api/pom.xml +++ b/superfly-remote-api/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36 + 1.7-37-SNAPSHOT 4.0.0 com.payneteasy.superfly diff --git a/superfly-service/pom.xml b/superfly-service/pom.xml index 09c48fec..c41a2b47 100644 --- a/superfly-service/pom.xml +++ b/superfly-service/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36 + 1.7-37-SNAPSHOT 4.0.0 com.payneteasy.superfly diff --git a/superfly-spi-support/pom.xml b/superfly-spi-support/pom.xml index b5ce5e3a..5d5023d7 100644 --- a/superfly-spi-support/pom.xml +++ b/superfly-spi-support/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36 + 1.7-37-SNAPSHOT superfly-spi-support Support classes for SPI mechanism diff --git a/superfly-spi/pom.xml b/superfly-spi/pom.xml index 6f54724e..ca524184 100644 --- a/superfly-spi/pom.xml +++ b/superfly-spi/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36 + 1.7-37-SNAPSHOT com.payneteasy.superfly superfly-spi diff --git a/superfly-spring-security/pom.xml b/superfly-spring-security/pom.xml index 3629a686..3bc49a5b 100644 --- a/superfly-spring-security/pom.xml +++ b/superfly-spring-security/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36 + 1.7-37-SNAPSHOT 4.0.0 superfly-spring-security diff --git a/superfly-web/pom.xml b/superfly-web/pom.xml index 722121b2..a41dae33 100644 --- a/superfly-web/pom.xml +++ b/superfly-web/pom.xml @@ -2,7 +2,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36 + 1.7-37-SNAPSHOT 4.0.0 superfly-web diff --git a/superfly-wicket/pom.xml b/superfly-wicket/pom.xml index ebaade04..2d1e7b24 100644 --- a/superfly-wicket/pom.xml +++ b/superfly-wicket/pom.xml @@ -3,7 +3,7 @@ superfly-parent com.payneteasy.superfly - 1.7-36 + 1.7-37-SNAPSHOT superfly-wicket Superfly wicket extensions From 15963a31eb1bf0619ee9e9a7b61c41e5dc48a1e5 Mon Sep 17 00:00:00 2001 From: igorvasilev Date: Wed, 13 May 2026 13:23:26 +0300 Subject: [PATCH 26/91] Fix ui_get_actions_list_with_group: include actions without group Replaced implicit INNER JOIN on group_actions/groups with LEFT JOIN so actions not assigned to any group still appear in search results. --- .../src/ui_get/ui_get_actions_list_with_group.prc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/superfly-sql/src/ui_get/ui_get_actions_list_with_group.prc b/superfly-sql/src/ui_get/ui_get_actions_list_with_group.prc index 4dd12da2..2c1f03e0 100644 --- a/superfly-sql/src/ui_get/ui_get_actions_list_with_group.prc +++ b/superfly-sql/src/ui_get/ui_get_actions_list_with_group.prc @@ -26,10 +26,11 @@ create procedure ui_get_actions_list_with_group(i_start_from int(10), ' ss.subsystem_name, ', ' g.grop_id, ', ' g.group_name ', - ' from actions a, subsystems ss, group_actions ga, groups g ', - ' where a.ssys_ssys_id = ss.ssys_id ', - ' and a.actn_id = ga.actn_actn_id ', - ' and g.grop_id = ga.grop_grop_id ', + ' from actions a ', + ' join subsystems ss on a.ssys_ssys_id = ss.ssys_id ', + ' left join group_actions ga on a.actn_id = ga.actn_actn_id ', + ' left join groups g on g.grop_id = ga.grop_grop_id ', + ' where 1=1 ', coalesce(v_search_conditions, '') ); From 00e5511d2b6602e47a1fa519e6ed076aff8cac99 Mon Sep 17 00:00:00 2001 From: iv Date: Mon, 1 Dec 2025 18:35:46 +0300 Subject: [PATCH 27/91] sso-remote-auth - API, service and auth phases cache --- pom.xml | 16 ++ superfly-service/pom.xml | 5 + .../model/ui/subsystem/UISubsystem.java | 10 + .../service/RemoteAuthCryptoService.java | 25 +++ .../superfly/service/RemoteAuthService.java | 56 +++++ .../superfly/service/SubsystemService.java | 6 + .../service/impl/SubsystemServiceImpl.java | 6 + .../check/RemoteAuthCryptoServiceStub.java | 31 +++ .../remote/check/RemoteAuthServiceImpl.java | 131 ++++++++++++ superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd | 3 + superfly-sql/mi/R1.7.3/R1.7.3_SSO.sh | 8 + superfly-sql/mi/R1.7.3/R1.7.3_SSO.sql | 1 + superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql | 3 + .../src/ui_create/ui_create_subsystem.prc | 7 +- superfly-sql/src/ui_get/ui_get_subsystem.prc | 6 +- .../src/ui_get/ui_get_subsystem_by_name.prc | 6 +- .../ui_edit_subsystem_properties.prc | 6 +- superfly-web/pom.xml | 5 + .../web/mvc/RemoteAuthCheckController.java | 124 +++++++++++ .../web/mvc/model/CheckOtpRequest.java | 32 +++ .../web/mvc/model/CheckOtpResponse.java | 26 +++ .../web/mvc/model/CheckPasswordRequest.java | 23 ++ .../web/mvc/model/CheckPasswordResponse.java | 20 ++ .../superfly/web/mvc/model/ErrorResponse.java | 35 ++++ .../web/wicket/SuperflyApplication.properties | 1 + .../page/subsystem/EditSubsystemPage.html | 8 + .../page/subsystem/EditSubsystemPage.java | 24 +++ .../WEB-INF/spring/dispatcher-servlet.xml | 14 ++ superfly-web/src/main/webapp/WEB-INF/web.xml | 20 +- .../mvc/RemoteAuthCheckControllerTest.java | 197 ++++++++++++++++++ 30 files changed, 844 insertions(+), 11 deletions(-) create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthCryptoService.java create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthService.java create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java create mode 100644 superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd create mode 100644 superfly-sql/mi/R1.7.3/R1.7.3_SSO.sh create mode 100644 superfly-sql/mi/R1.7.3/R1.7.3_SSO.sql create mode 100644 superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql create mode 100644 superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckController.java create mode 100644 superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckOtpRequest.java create mode 100644 superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckOtpResponse.java create mode 100644 superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordRequest.java create mode 100644 superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordResponse.java create mode 100644 superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/ErrorResponse.java create mode 100644 superfly-web/src/main/webapp/WEB-INF/spring/dispatcher-servlet.xml create mode 100644 superfly-web/src/test/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckControllerTest.java diff --git a/pom.xml b/pom.xml index fb6aada6..0b584930 100644 --- a/pom.xml +++ b/pom.xml @@ -769,6 +769,20 @@ passay 1.6.1 + + + com.github.ben-manes.caffeine + caffeine + 3.1.8 + + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + org.projectlombok @@ -1068,6 +1082,8 @@ true utf-8 + 2.15.2 + 21 true true diff --git a/superfly-service/pom.xml b/superfly-service/pom.xml index 3b1e9f41..035e1fd8 100644 --- a/superfly-service/pom.xml +++ b/superfly-service/pom.xml @@ -126,5 +126,10 @@ org.projectlombok lombok + + + com.github.ben-manes.caffeine + caffeine + diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/model/ui/subsystem/UISubsystem.java b/superfly-service/src/main/java/com/payneteasy/superfly/model/ui/subsystem/UISubsystem.java index 90abe3ee..1d7e9fa5 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/model/ui/subsystem/UISubsystem.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/model/ui/subsystem/UISubsystem.java @@ -19,6 +19,7 @@ public class UISubsystem implements Serializable { private String subsystemUrl; private String landingUrl; private String loginFormCssUrl; + private String privateKey; @Column(name = "ssys_id") public Long getId() { @@ -119,4 +120,13 @@ public String getLoginFormCssUrl() { public void setLoginFormCssUrl(String loginFormCssUrl) { this.loginFormCssUrl = loginFormCssUrl; } + + @Column(name = "private_key") + public String getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthCryptoService.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthCryptoService.java new file mode 100644 index 00000000..265f1500 --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthCryptoService.java @@ -0,0 +1,25 @@ +package com.payneteasy.superfly.service; + +/** + * Service for cryptographic operations in remote authentication. + */ +public interface RemoteAuthCryptoService { + /** + * Decrypts a password using the subsystem's private key. + * + * @param encryptedPassword The encrypted password (Base64 URL-safe no padding). + * @param privateKey The private key of the subsystem. + * @return The decrypted password. + */ + String decryptPassword(String encryptedPassword, String privateKey); + + /** + * Decrypts an OTP using the subsystem's private key. + * + * @param encryptedOtp The encrypted OTP (Base64 URL-safe no padding). + * @param privateKey The private key of the subsystem. + * @return The decrypted OTP. + */ + String decryptOtp(String encryptedOtp, String privateKey); +} + diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthService.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthService.java new file mode 100644 index 00000000..e12f6387 --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthService.java @@ -0,0 +1,56 @@ +package com.payneteasy.superfly.service; + +/** + * Service for remote authentication of external applications. + */ +public interface RemoteAuthService { + + /** + * Checks the password for a user in a subsystem. + * + * @param subsystemName Name of the subsystem. + * @param username Username. + * @param passwordEncrypted Encrypted password (Base64 URL-safe no padding). + * @param bearerToken Bearer token provided in the request. + * @param ipAddress IP address of the client. + * @param userAgent User agent of the client. + * @return Session token if authentication is successful. + * @throws RemoteAuthException If authentication fails or other errors occur. + */ + String checkPassword(String subsystemName, String username, String passwordEncrypted, String bearerToken, String ipAddress, String userAgent) throws RemoteAuthException; + + /** + * Checks the OTP for a user in a subsystem. + * + * @param subsystemName Name of the subsystem. + * @param username Username. + * @param otpEncrypted Encrypted OTP (Base64 URL-safe no padding). + * @param sessionToken Session token obtained from checkPassword. + * @param bearerToken Bearer token provided in the request. + * @return Result of the OTP check (SUCCESS, BAD_USER..., etc.). + * @throws RemoteAuthException If validation fails. + */ + String checkOtp(String subsystemName, String username, String otpEncrypted, String sessionToken, String bearerToken) throws RemoteAuthException; + + /** + * Exception class for remote auth errors. + */ + class RemoteAuthException extends Exception { + private final String errorCode; + + public RemoteAuthException(String message, String errorCode) { + super(message); + this.errorCode = errorCode; + } + + public RemoteAuthException(String message, String errorCode, Throwable cause) { + super(message, cause); + this.errorCode = errorCode; + } + + public String getErrorCode() { + return errorCode; + } + } +} + diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/SubsystemService.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/SubsystemService.java index e5e2eddf..48360891 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/SubsystemService.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/SubsystemService.java @@ -85,4 +85,10 @@ public interface SubsystemService { * @return main token for subsystem */ String generateMainSubsystemToken(); + + /** + * + * @return private key for subsystem + */ + String generateSubsystemPrivateKey(); } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/SubsystemServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/SubsystemServiceImpl.java index b45c3d7b..42e37895 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/SubsystemServiceImpl.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/SubsystemServiceImpl.java @@ -55,6 +55,7 @@ public void setJavaMailSenderPool(JavaMailSenderPool javaMailSenderPool) { public RoutineResult createSubsystem(UISubsystem subsystem) { subsystem.setSubsystemToken(generateMainSubsystemToken()); + subsystem.setPrivateKey(generateSubsystemPrivateKey()); RoutineResult result = subsystemDao.createSubsystem(subsystem); loggerSink.info(logger, "CREATE_SUBSYSTEM", true, subsystem.getName()); javaMailSenderPool.flushAll(); // clearing pool so changes are applied @@ -111,4 +112,9 @@ private String generateUniqueSubsystemToken() { public String generateMainSubsystemToken() { return UUID.randomUUID().toString(); } + + @Override + public String generateSubsystemPrivateKey() { + return UUID.randomUUID().toString(); + } } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java new file mode 100644 index 00000000..2ef571f9 --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java @@ -0,0 +1,31 @@ +package com.payneteasy.superfly.service.impl.remote.check; + +import com.payneteasy.superfly.service.RemoteAuthCryptoService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +/** + * Stub implementation of RemoteAuthCryptoService. + */ +@Service +public class RemoteAuthCryptoServiceStub implements RemoteAuthCryptoService { + + private static final Logger logger = LoggerFactory.getLogger(RemoteAuthCryptoServiceStub.class); + + public RemoteAuthCryptoServiceStub() { + } + + public String decryptPassword(String encryptedPassword, String privateKey) { + logger.warn("decryptPassword is a STUB. Returning encryptedPassword as is."); + // In a real implementation, this would decrypt the password. + // For now, we assume the input is already the plain password or we just return it to test flow. + return encryptedPassword; + } + + public String decryptOtp(String encryptedOtp, String privateKey) { + logger.warn("decryptOtp is a STUB. Returning encryptedOtp as is."); + return encryptedOtp; + } +} + diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java new file mode 100644 index 00000000..9ec5335e --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java @@ -0,0 +1,131 @@ +package com.payneteasy.superfly.service.impl.remote.check; + +import com.payneteasy.superfly.api.SSOUser; +import com.payneteasy.superfly.model.ui.subsystem.UISubsystem; +import com.payneteasy.superfly.service.InternalSSOService; +import com.payneteasy.superfly.service.RemoteAuthCryptoService; +import com.payneteasy.superfly.service.RemoteAuthService; +import com.payneteasy.superfly.service.SubsystemService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import org.springframework.stereotype.Service; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +@Service +public class RemoteAuthServiceImpl implements RemoteAuthService { + + private static final Logger logger = LoggerFactory.getLogger(RemoteAuthServiceImpl.class); + + private final SubsystemService subsystemService; + private final InternalSSOService internalSSOService; + private final RemoteAuthCryptoService remoteAuthCryptoService; + + private final Cache sessionCache = Caffeine.newBuilder() + .expireAfterWrite(5, TimeUnit.MINUTES) + .maximumSize(10_000) + .build(); + + public RemoteAuthServiceImpl(SubsystemService subsystemService, + InternalSSOService internalSSOService, + RemoteAuthCryptoService remoteAuthCryptoService) { + this.subsystemService = subsystemService; + this.internalSSOService = internalSSOService; + this.remoteAuthCryptoService = remoteAuthCryptoService; + } + + @Override + public String checkPassword(String subsystemName, String username, String passwordEncrypted, String bearerToken, String ipAddress, String userAgent) throws RemoteAuthException { + // 1. Validate Subsystem and Token + UISubsystem subsystem = validateSubsystem(subsystemName, bearerToken); + + // 2. Decrypt Password + String password; + try { + password = remoteAuthCryptoService.decryptPassword(passwordEncrypted, subsystem.getPrivateKey()); + } catch (Exception e) { + logger.error("Failed to decrypt password", e); + throw new RemoteAuthException("Decryption failed", "INTERNAL_ERROR", e); + } + + // 3. Authenticate User + // Using internalSSOService to check credentials and get SSOUser + // Note: This creates a session in the DB as well (AuthSession). + // If we want to avoid creating a full session until OTP, we might need a different method, + // but for now we reuse the existing logic. + SSOUser ssoUser = internalSSOService.authenticate(username, password, subsystemName, ipAddress, userAgent); + + if (ssoUser == null) { + // Could be bad password, user blocked, etc. + // For security, we might return generic bad credentials. + throw new RemoteAuthException("Authentication failed", "BAD_USER_OR_PASSWORD_OR_OTP"); + } + + // 4. Generate Session Token and Cache + String sessionToken = UUID.randomUUID().toString(); + sessionCache.put(sessionToken, new RemoteSession(username)); + + return sessionToken; + } + + @Override + public String checkOtp(String subsystemName, String username, String otpEncrypted, String sessionToken, String bearerToken) throws RemoteAuthException { + // 1. Validate Subsystem and Token + UISubsystem subsystem = validateSubsystem(subsystemName, bearerToken); + + // 2. Validate Session Token + RemoteSession session = sessionCache.getIfPresent(sessionToken); + if (session == null) { + throw new RemoteAuthException("Session expired or invalid", "BAD_USER_OR_PASSWORD_OR_OTP"); + } + if (!session.username.equals(username)) { + throw new RemoteAuthException("Username mismatch", "BAD_USER_OR_PASSWORD_OR_OTP"); + } + + // 3. Decrypt OTP + String otp; + try { + otp = remoteAuthCryptoService.decryptOtp(otpEncrypted, subsystem.getPrivateKey()); + } catch (Exception e) { + logger.error("Failed to decrypt OTP", e); + throw new RemoteAuthException("Decryption failed", "INTERNAL_ERROR", e); + } + + // 4. Verify OTP + boolean otpValid = internalSSOService.authenticateHOTP(subsystemName, username, otp); + if (!otpValid) { + return "BAD_USER_OR_PASSWORD_OR_OTP"; + } + + // TODO: Check for other statuses like USER_SHOULD_CHANGE_PASSWORD or USER_BLOCKED + // internalSSOService.authenticateHOTP only returns boolean. + // We might need to check user status explicitly if needed. + + return "SUCCESS"; + } + + private UISubsystem validateSubsystem(String subsystemName, String bearerToken) throws RemoteAuthException { + UISubsystem subsystem = subsystemService.getSubsystemByName(subsystemName); + if (subsystem == null) { + throw new RemoteAuthException("Subsystem not found", "INTERNAL_ERROR"); // Or 404 equivalent + } + // Assuming bearerToken is just the token value. + // The format in header is "Bearer ", but the controller should extract . + if (subsystem.getSubsystemToken() == null || !subsystem.getSubsystemToken().equals(bearerToken)) { + throw new RemoteAuthException("Invalid subsystem token", "INTERNAL_ERROR"); // Or 401 + } + return subsystem; + } + + private static class RemoteSession { + final String username; + + RemoteSession(String username) { + this.username = username; + } + } +} + diff --git a/superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd b/superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd new file mode 100644 index 00000000..78aeec93 --- /dev/null +++ b/superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd @@ -0,0 +1,3 @@ +@set PATH=C:\cygwin\bin;%PATH% +bash R1.7.2_SSO.sh + diff --git a/superfly-sql/mi/R1.7.3/R1.7.3_SSO.sh b/superfly-sql/mi/R1.7.3/R1.7.3_SSO.sh new file mode 100644 index 00000000..25755756 --- /dev/null +++ b/superfly-sql/mi/R1.7.3/R1.7.3_SSO.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +. ../../functions.sh + +runScript R1.7.3_SSO.sql + +runScript R1.7.3_SSO_DML.sql + diff --git a/superfly-sql/mi/R1.7.3/R1.7.3_SSO.sql b/superfly-sql/mi/R1.7.3/R1.7.3_SSO.sql new file mode 100644 index 00000000..df05f4c7 --- /dev/null +++ b/superfly-sql/mi/R1.7.3/R1.7.3_SSO.sql @@ -0,0 +1 @@ +commit; diff --git a/superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql b/superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql new file mode 100644 index 00000000..dd8cdc90 --- /dev/null +++ b/superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql @@ -0,0 +1,3 @@ +alter table subsystems add column private_key text; + +commit; diff --git a/superfly-sql/src/ui_create/ui_create_subsystem.prc b/superfly-sql/src/ui_create/ui_create_subsystem.prc index 8c1422be..41571e3a 100644 --- a/superfly-sql/src/ui_create/ui_create_subsystem.prc +++ b/superfly-sql/src/ui_create/ui_create_subsystem.prc @@ -10,6 +10,7 @@ create procedure ui_create_subsystem(i_subsystem_name varchar(32), i_subsystem_url varchar(255), i_landing_url varchar(255), i_login_form_css_url varchar(255), + i_private_key text, out o_ssys_id int(10) ) main_sql: @@ -26,7 +27,8 @@ create procedure ui_create_subsystem(i_subsystem_name varchar(32), subsystem_token, subsystem_url, landing_url, - login_form_css_url + login_form_css_url, + private_key ) values ( i_subsystem_name, @@ -39,7 +41,8 @@ create procedure ui_create_subsystem(i_subsystem_name varchar(32), i_subsystem_token, i_subsystem_url, i_landing_url, - i_login_form_css_url + i_login_form_css_url, + i_private_key ); set o_ssys_id = last_insert_id(); diff --git a/superfly-sql/src/ui_get/ui_get_subsystem.prc b/superfly-sql/src/ui_get/ui_get_subsystem.prc index 348b2494..6a781213 100644 --- a/superfly-sql/src/ui_get/ui_get_subsystem.prc +++ b/superfly-sql/src/ui_get/ui_get_subsystem.prc @@ -15,7 +15,8 @@ create procedure ui_get_subsystem(i_ssys_id int(10)) ss.subsystem_token, ss.subsystem_url, ss.landing_url, - ss.login_form_css_url + ss.login_form_css_url, + ss.private_key from subsystems ss left join smtp_servers smtp on smtp.ssrv_id = ss.ssrv_ssrv_id @@ -37,6 +38,7 @@ call save_routine_information('ui_get_subsystem', 'subsystem_token varchar', 'subsystem_url varchar', 'landing_url varchar', - 'login_form_css_url varchar' + 'login_form_css_url varchar', + 'private_key varchar' ) ); diff --git a/superfly-sql/src/ui_get/ui_get_subsystem_by_name.prc b/superfly-sql/src/ui_get/ui_get_subsystem_by_name.prc index 272667e4..06556b3d 100644 --- a/superfly-sql/src/ui_get/ui_get_subsystem_by_name.prc +++ b/superfly-sql/src/ui_get/ui_get_subsystem_by_name.prc @@ -15,7 +15,8 @@ create procedure ui_get_subsystem_by_name(i_subsystem_name varchar(32)) ss.subsystem_token, ss.subsystem_url, ss.landing_url, - ss.login_form_css_url + ss.login_form_css_url, + ss.private_key from subsystems ss left join smtp_servers smtp on smtp.ssrv_id = ss.ssrv_ssrv_id @@ -37,6 +38,7 @@ call save_routine_information('ui_get_subsystem_by_name', 'subsystem_token varchar', 'subsystem_url varchar', 'landing_url varchar', - 'login_form_css_url varchar' + 'login_form_css_url varchar', + 'private_key varchar' ) ); diff --git a/superfly-sql/src/ui_update/ui_edit_subsystem_properties.prc b/superfly-sql/src/ui_update/ui_edit_subsystem_properties.prc index 3f6670ff..32ccbe6f 100644 --- a/superfly-sql/src/ui_update/ui_edit_subsystem_properties.prc +++ b/superfly-sql/src/ui_update/ui_edit_subsystem_properties.prc @@ -10,7 +10,8 @@ create procedure ui_edit_subsystem_properties(i_ssys_id int(10), i_subsystem_token varchar(64), i_subsystem_url varchar(255), i_landing_url varchar(255), - i_login_form_css_url varchar(255) + i_login_form_css_url varchar(255), + i_private_key text ) main_sql: begin @@ -24,7 +25,8 @@ create procedure ui_edit_subsystem_properties(i_ssys_id int(10), subsystem_token = i_subsystem_token, subsystem_url = i_subsystem_url, landing_url = i_landing_url, - login_form_css_url = i_login_form_css_url + login_form_css_url = i_login_form_css_url, + private_key = i_private_key where ssys_id = i_ssys_id; select 'OK' status, null error_message; diff --git a/superfly-web/pom.xml b/superfly-web/pom.xml index a7028116..e61005a6 100644 --- a/superfly-web/pom.xml +++ b/superfly-web/pom.xml @@ -193,6 +193,11 @@ lombok provided + + + com.fasterxml.jackson.core + jackson-databind + diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckController.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckController.java new file mode 100644 index 00000000..1676abb4 --- /dev/null +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckController.java @@ -0,0 +1,124 @@ +package com.payneteasy.superfly.web.mvc; + +import com.payneteasy.superfly.service.RemoteAuthService; +import com.payneteasy.superfly.service.RemoteAuthService.RemoteAuthException; +import com.payneteasy.superfly.web.mvc.model.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.util.UUID; + +@Component +@RestController +public class RemoteAuthCheckController { + + private final RemoteAuthService remoteAuthService; + + @Autowired + public RemoteAuthCheckController(RemoteAuthService remoteAuthService) { + this.remoteAuthService = remoteAuthService; + } + + @RequestMapping(value = "/check-password/{subsystemName}/{username}", method = RequestMethod.POST, produces = "application/json") + public CheckPasswordResponse checkPassword(@PathVariable String subsystemName, + @PathVariable String username, + @RequestBody CheckPasswordRequest requestBody, + @RequestHeader(value = "Authorization", required = false) String authHeader, + HttpServletRequest request) throws RemoteAuthException { + + String bearerToken = extractBearerToken(authHeader); + if (bearerToken == null) { + throw new UnauthorizedException("Missing or invalid Authorization header"); + } + + if (requestBody.getPasswordEncrypted() == null) { + throw new BadRequestException("Missing passwordEncrypted field"); + } + + if (!username.equals(requestBody.getUsername())) { + throw new BadRequestException("Username in path and body must match"); + } + + String sessionToken = remoteAuthService.checkPassword(subsystemName, username, requestBody.getPasswordEncrypted(), bearerToken, request.getRemoteAddr(), request.getHeader("User-Agent")); + return new CheckPasswordResponse(username, sessionToken); + } + + @RequestMapping(value = "/check-otp/{subsystemName}/{username}", method = RequestMethod.POST, produces = "application/json") + public CheckOtpResponse checkOtp(@PathVariable String subsystemName, + @PathVariable String username, + @RequestBody CheckOtpRequest requestBody, + @RequestHeader(value = "Authorization", required = false) String authHeader) throws RemoteAuthException { + + String bearerToken = extractBearerToken(authHeader); + if (bearerToken == null) { + throw new UnauthorizedException("Missing or invalid Authorization header"); + } + + if (requestBody.getOtpEncrypted() == null || requestBody.getSessionToken() == null) { + throw new BadRequestException("Missing otpEncrypted or sessionToken field"); + } + + if (!username.equals(requestBody.getUsername())) { + throw new BadRequestException("Username in path and body must match"); + } + + String result = remoteAuthService.checkOtp(subsystemName, username, requestBody.getOtpEncrypted(), requestBody.getSessionToken(), bearerToken); + return new CheckOtpResponse(username, requestBody.getSessionToken(), result); + } + + private String extractBearerToken(String authHeader) { + if (authHeader != null && authHeader.startsWith("Bearer ")) { + return authHeader.substring(7); + } + return null; + } + + @ExceptionHandler(RemoteAuthException.class) + public ResponseEntity handleRemoteAuthException(RemoteAuthException e) { + String errorCode = e.getErrorCode(); + HttpStatus status = HttpStatus.BAD_REQUEST; + + if ("INTERNAL_ERROR".equals(errorCode)) { + status = HttpStatus.INTERNAL_SERVER_ERROR; + } + + return createErrorResponse(errorCode, e.getMessage(), status); + } + + @ExceptionHandler(UnauthorizedException.class) + public ResponseEntity handleUnauthorized(UnauthorizedException e) { + return createErrorResponse("UNAUTHORIZED", e.getMessage(), HttpStatus.UNAUTHORIZED); + } + + @ExceptionHandler(BadRequestException.class) + public ResponseEntity handleBadRequest(BadRequestException e) { + return createErrorResponse("BAD_REQUEST", e.getMessage(), HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity handleJsonError(HttpMessageNotReadableException e) { + return createErrorResponse("BAD_REQUEST", "Invalid JSON", HttpStatus.BAD_REQUEST); + } + + private ResponseEntity createErrorResponse(String type, String title, HttpStatus status) { + ErrorResponse error = new ErrorResponse(type, title, title); + error.setErrorId(UUID.randomUUID().toString()); + + return ResponseEntity.status(status) + .header("Content-Language", "en") + .body(error); + } + + static class UnauthorizedException extends RuntimeException { + public UnauthorizedException(String message) { super(message); } + } + + static class BadRequestException extends RuntimeException { + public BadRequestException(String message) { super(message); } + } +} diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckOtpRequest.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckOtpRequest.java new file mode 100644 index 00000000..3b654e28 --- /dev/null +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckOtpRequest.java @@ -0,0 +1,32 @@ +package com.payneteasy.superfly.web.mvc.model; + +public class CheckOtpRequest { + private String username; + private String otpEncrypted; + private String sessionToken; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getOtpEncrypted() { + return otpEncrypted; + } + + public void setOtpEncrypted(String otpEncrypted) { + this.otpEncrypted = otpEncrypted; + } + + public String getSessionToken() { + return sessionToken; + } + + public void setSessionToken(String sessionToken) { + this.sessionToken = sessionToken; + } +} + diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckOtpResponse.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckOtpResponse.java new file mode 100644 index 00000000..00273e47 --- /dev/null +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckOtpResponse.java @@ -0,0 +1,26 @@ +package com.payneteasy.superfly.web.mvc.model; + +public class CheckOtpResponse { + private String username; + private String sessionToken; + private String result; + + public CheckOtpResponse(String username, String sessionToken, String result) { + this.username = username; + this.sessionToken = sessionToken; + this.result = result; + } + + public String getUsername() { + return username; + } + + public String getSessionToken() { + return sessionToken; + } + + public String getResult() { + return result; + } +} + diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordRequest.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordRequest.java new file mode 100644 index 00000000..80fe9ec9 --- /dev/null +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordRequest.java @@ -0,0 +1,23 @@ +package com.payneteasy.superfly.web.mvc.model; + +public class CheckPasswordRequest { + private String username; + private String passwordEncrypted; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPasswordEncrypted() { + return passwordEncrypted; + } + + public void setPasswordEncrypted(String passwordEncrypted) { + this.passwordEncrypted = passwordEncrypted; + } +} + diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordResponse.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordResponse.java new file mode 100644 index 00000000..22964f8e --- /dev/null +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordResponse.java @@ -0,0 +1,20 @@ +package com.payneteasy.superfly.web.mvc.model; + +public class CheckPasswordResponse { + private String username; + private String sessionToken; + + public CheckPasswordResponse(String username, String sessionToken) { + this.username = username; + this.sessionToken = sessionToken; + } + + public String getUsername() { + return username; + } + + public String getSessionToken() { + return sessionToken; + } +} + diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/ErrorResponse.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/ErrorResponse.java new file mode 100644 index 00000000..cf0a1bda --- /dev/null +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/ErrorResponse.java @@ -0,0 +1,35 @@ +package com.payneteasy.superfly.web.mvc.model; + +public class ErrorResponse { + private String type; + private String title; + private String detail; + private String errorId; + + public ErrorResponse(String type, String title, String detail) { + this.type = type; + this.title = title; + this.detail = detail; + } + + public String getType() { + return type; + } + + public String getTitle() { + return title; + } + + public String getDetail() { + return detail; + } + + public String getErrorId() { + return errorId; + } + + public void setErrorId(String errorId) { + this.errorId = errorId; + } +} + diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.properties b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.properties index db285ca2..17ddeb90 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.properties +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.properties @@ -31,6 +31,7 @@ subsystem.edit.callback=Callback Info subsystem.edit.callback.Required=Callback Info is required subsystem.edit.send-callbacks=Send callbacks subsystem.edit.subsystemToken=Token +subsystem.edit.privateKey=Private key subsystem.edit.subsystemUrl=Subsystem URL subsystem.edit.subsystemUrl.Required=Subsystem URL is required subsystem.edit.landingUrl=Landing URL diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html index ed7595dc..bac47a6f 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html @@ -22,6 +22,14 @@
+
+ +
key
+ + Generate new key + +
+ diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java index 2b393923..cdf98388 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java @@ -83,6 +83,26 @@ public void onClick(AjaxRequestTarget aTarget) { } }); + // Private key fields + final Label labelPrivateKey = new Label("privateKey", new LoadableDetachableModel() { + @Override + protected String load() { + return subsystem.getPrivateKey(); + } + }); + labelPrivateKey.setOutputMarkupId(true); + + form.add(new Label("privateKeyLabel", new ResourceModel("subsystem.edit.privateKey"))); + form.add(labelPrivateKey); + form.add(new IndicatingAjaxLink("generateNewPrivateKey") { + private static final long serialVersionUID = 1L; + + public void onClick(AjaxRequestTarget aTarget) { + subsystem.setPrivateKey(generateNewPrivateKey()); + aTarget.add(labelPrivateKey); + } + }); + LabelTextFieldRow subsystemUrlRow = new LabelTextFieldRow<>(subsystem, "subsystemUrl", "subsystem.edit.subsystemUrl", true); subsystemUrlRow.getTextField().add(urlValidator); @@ -129,4 +149,8 @@ protected String getTitle() { private String generateNewToken() { return subsystemService.generateMainSubsystemToken(); } + + private String generateNewPrivateKey() { + return subsystemService.generateSubsystemPrivateKey(); + } } diff --git a/superfly-web/src/main/webapp/WEB-INF/spring/dispatcher-servlet.xml b/superfly-web/src/main/webapp/WEB-INF/spring/dispatcher-servlet.xml new file mode 100644 index 00000000..c7560691 --- /dev/null +++ b/superfly-web/src/main/webapp/WEB-INF/spring/dispatcher-servlet.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/superfly-web/src/main/webapp/WEB-INF/web.xml b/superfly-web/src/main/webapp/WEB-INF/web.xml index 1377c733..0542c668 100644 --- a/superfly-web/src/main/webapp/WEB-INF/web.xml +++ b/superfly-web/src/main/webapp/WEB-INF/web.xml @@ -12,8 +12,7 @@ true - - + contextConfigLocation @@ -151,6 +150,21 @@ /* + + rest-api + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + /WEB-INF/spring/dispatcher-servlet.xml + + 2 + + + + rest-api + /sso/check/* + + remoting org.springframework.web.servlet.DispatcherServlet @@ -186,7 +200,7 @@ ignorePaths - /remoting/,/remoting/sso.service/,/sso/,/management/version.txt + /remoting/,/remoting/sso.service/,/sso/,/management/version.txt,/sso/check/ diff --git a/superfly-web/src/test/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckControllerTest.java b/superfly-web/src/test/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckControllerTest.java new file mode 100644 index 00000000..fb0e87c4 --- /dev/null +++ b/superfly-web/src/test/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckControllerTest.java @@ -0,0 +1,197 @@ +package com.payneteasy.superfly.web.mvc; + +import com.payneteasy.superfly.service.RemoteAuthService; +import com.payneteasy.superfly.service.RemoteAuthService.RemoteAuthException; +import com.payneteasy.superfly.web.mvc.model.CheckOtpRequest; +import com.payneteasy.superfly.web.mvc.model.CheckOtpResponse; +import com.payneteasy.superfly.web.mvc.model.CheckPasswordRequest; +import com.payneteasy.superfly.web.mvc.model.CheckPasswordResponse; +import com.payneteasy.superfly.web.mvc.model.ErrorResponse; +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; + +import javax.servlet.http.HttpServletRequest; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.assertEquals; + +public class RemoteAuthCheckControllerTest { + + private RemoteAuthCheckController controller; + private RemoteAuthService remoteAuthService; + private HttpServletRequest request; + + @Before + public void setUp() { + remoteAuthService = createMock(RemoteAuthService.class); + request = createMock(HttpServletRequest.class); + // Внедрение зависимостей через конструктор, что позволяет отказаться от @Autowired на сеттерах + // и делает тестирование проще (не нужен Spring Context в юнит-тесте) + controller = new RemoteAuthCheckController(remoteAuthService); + } + + @Test + public void testCheckPasswordSuccess() throws Exception { + // 1. Подготовка данных (Fixtures) + // Используем константы и переменные для лучшей читаемости и повторного использования + String subsystemName = "test-subsystem"; + String username = "test-user"; + String bearerToken = "test-token"; + String passwordEncrypted = "encrypted-password"; + String ipAddress = "127.0.0.1"; + String userAgent = "TestAgent"; + String sessionToken = "session-123"; + + // Создаем объект запроса через сеттеры (или можно было бы через конструктор/builder если бы они были) + CheckPasswordRequest requestBody = new CheckPasswordRequest(); + requestBody.setUsername(username); + requestBody.setPasswordEncrypted(passwordEncrypted); + + // 2. Программирование моков (Record phase) + // Ожидаем вызовы к request (получение IP и User-Agent) + expect(request.getRemoteAddr()).andReturn(ipAddress); + expect(request.getHeader("User-Agent")).andReturn(userAgent); + + // Ожидаем вызов бизнес-логики сервиса + expect(remoteAuthService.checkPassword(subsystemName, username, passwordEncrypted, bearerToken, ipAddress, userAgent)) + .andReturn(sessionToken); + + // 3. Перевод моков в режим воспроизведения (Replay phase) + replay(remoteAuthService, request); + + // 4. Выполнение тестируемого кода (Exercise) + CheckPasswordResponse response = controller.checkPassword(subsystemName, username, requestBody, "Bearer " + bearerToken, request); + + // 5. Проверка вызовов (Verify phase) + verify(remoteAuthService, request); + + // 6. Проверка результата (Assertions) + assertEquals("Username should match", username, response.getUsername()); + assertEquals("Session token should match", sessionToken, response.getSessionToken()); + } + + @Test + public void testCheckOtpSuccess() throws Exception { + String subsystemName = "test-subsystem"; + String username = "test-user"; + String bearerToken = "test-token"; + String otpEncrypted = "encrypted-otp"; + String sessionToken = "session-123"; + String authResult = "SUCCESS"; + + CheckOtpRequest requestBody = new CheckOtpRequest(); + requestBody.setUsername(username); + requestBody.setOtpEncrypted(otpEncrypted); + requestBody.setSessionToken(sessionToken); + + expect(remoteAuthService.checkOtp(subsystemName, username, otpEncrypted, sessionToken, bearerToken)) + .andReturn(authResult); + + replay(remoteAuthService, request); + + CheckOtpResponse response = controller.checkOtp(subsystemName, username, requestBody, "Bearer " + bearerToken); + + verify(remoteAuthService, request); + + assertEquals(username, response.getUsername()); + assertEquals(sessionToken, response.getSessionToken()); + assertEquals(authResult, response.getResult()); + } + + @Test(expected = RemoteAuthCheckController.UnauthorizedException.class) + public void testCheckPasswordMissingToken() throws RemoteAuthException { + String subsystemName = "test-subsystem"; + String username = "test-user"; + CheckPasswordRequest requestBody = new CheckPasswordRequest(); + + replay(remoteAuthService, request); + controller.checkPassword(subsystemName, username, requestBody, null, request); + } + + @Test(expected = RemoteAuthCheckController.BadRequestException.class) + public void testCheckPasswordMissingFields() throws RemoteAuthException { + String subsystemName = "test-subsystem"; + String username = "test-user"; + CheckPasswordRequest requestBody = new CheckPasswordRequest(); + requestBody.setUsername(username); + // Missing password + + replay(remoteAuthService, request); + controller.checkPassword(subsystemName, username, requestBody, "Bearer token", request); + } + + @Test(expected = RemoteAuthException.class) + public void testCheckPasswordServiceException() throws Exception { + String subsystemName = "test-subsystem"; + String username = "test-user"; + String bearerToken = "test-token"; + String passwordEncrypted = "encrypted-password"; + String ipAddress = "127.0.0.1"; + String userAgent = "TestAgent"; + + CheckPasswordRequest requestBody = new CheckPasswordRequest(); + requestBody.setUsername(username); + requestBody.setPasswordEncrypted(passwordEncrypted); + + expect(request.getRemoteAddr()).andReturn(ipAddress); + expect(request.getHeader("User-Agent")).andReturn(userAgent); + expect(remoteAuthService.checkPassword(subsystemName, username, passwordEncrypted, bearerToken, ipAddress, userAgent)) + .andThrow(new RemoteAuthException("Some error", "INTERNAL_ERROR")); + + replay(remoteAuthService, request); + + controller.checkPassword(subsystemName, username, requestBody, "Bearer " + bearerToken, request); + } + + @Test + public void testHandleRemoteAuthException() { + RemoteAuthException ex = new RemoteAuthException("Error message", "INTERNAL_ERROR"); + ResponseEntity response = controller.handleRemoteAuthException(ex); + + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); + assertEquals("INTERNAL_ERROR", response.getBody().getType()); + assertEquals("Error message", response.getBody().getTitle()); + } + + @Test + public void testHandleRemoteAuthExceptionBadRequest() { + RemoteAuthException ex = new RemoteAuthException("Bad credentials", "BAD_CREDENTIALS"); + ResponseEntity response = controller.handleRemoteAuthException(ex); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertEquals("BAD_CREDENTIALS", response.getBody().getType()); + } + + @Test + public void testHandleUnauthorized() { + RemoteAuthCheckController.UnauthorizedException ex = new RemoteAuthCheckController.UnauthorizedException("Unauthorized access"); + ResponseEntity response = controller.handleUnauthorized(ex); + + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + assertEquals("UNAUTHORIZED", response.getBody().getType()); + assertEquals("Unauthorized access", response.getBody().getTitle()); + } + + @Test + public void testHandleBadRequest() { + RemoteAuthCheckController.BadRequestException ex = new RemoteAuthCheckController.BadRequestException("Bad request data"); + ResponseEntity response = controller.handleBadRequest(ex); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertEquals("BAD_REQUEST", response.getBody().getType()); + assertEquals("Bad request data", response.getBody().getTitle()); + } + + @Test + public void testHandleJsonError() { + HttpMessageNotReadableException ex = new HttpMessageNotReadableException("Json error"); + ResponseEntity response = controller.handleJsonError(ex); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertEquals("BAD_REQUEST", response.getBody().getType()); + assertEquals("Invalid JSON", response.getBody().getTitle()); + } +} \ No newline at end of file From e1d0969820531b3e3422693fb13caaf3d2b06d0d Mon Sep 17 00:00:00 2001 From: akorobov Date: Wed, 3 Dec 2025 00:25:05 +0300 Subject: [PATCH 28/91] 110 - Add support for RSA key pair generation and storage for subsystems --- .../model/ui/subsystem/UISubsystem.java | 20 ++++ .../service/RemoteAuthCryptoService.java | 23 +++- .../superfly/service/SubsystemService.java | 6 +- .../service/impl/SubsystemServiceImpl.java | 18 +-- .../impl/remote/check/KeyPairData.java | 4 + .../check/RemoteAuthCryptoServiceImpl.java | 108 ++++++++++++++++++ .../check/RemoteAuthCryptoServiceStub.java | 9 +- .../check/RemoteAuthEncryptionAlgorithm.java | 6 + .../remote/check/RemoteAuthServiceImpl.java | 12 +- superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd | 2 +- superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql | 2 + .../src/ui_create/ui_create_subsystem.prc | 10 +- superfly-sql/src/ui_get/ui_get_subsystem.prc | 4 +- .../src/ui_get/ui_get_subsystem_by_name.prc | 8 +- .../web/wicket/SuperflyApplication.properties | 3 +- .../page/subsystem/AddSubsystemPage.html | 7 ++ .../page/subsystem/AddSubsystemPage.java | 31 +++++ .../page/subsystem/EditSubsystemPage.html | 8 +- .../page/subsystem/EditSubsystemPage.java | 27 +++-- 19 files changed, 269 insertions(+), 39 deletions(-) create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/KeyPairData.java create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceImpl.java create mode 100644 superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthEncryptionAlgorithm.java diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/model/ui/subsystem/UISubsystem.java b/superfly-service/src/main/java/com/payneteasy/superfly/model/ui/subsystem/UISubsystem.java index 1d7e9fa5..e5c0aec8 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/model/ui/subsystem/UISubsystem.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/model/ui/subsystem/UISubsystem.java @@ -20,6 +20,8 @@ public class UISubsystem implements Serializable { private String landingUrl; private String loginFormCssUrl; private String privateKey; + private String publicKey; + private String encryptionAlgorithm; @Column(name = "ssys_id") public Long getId() { @@ -129,4 +131,22 @@ public String getPrivateKey() { public void setPrivateKey(String privateKey) { this.privateKey = privateKey; } + + @Column(name = "public_key") + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + @Column(name = "encryption_algorithm") + public String getEncryptionAlgorithm() { + return encryptionAlgorithm; + } + + public void setEncryptionAlgorithm(String encryptionAlgorithm) { + this.encryptionAlgorithm = encryptionAlgorithm; + } } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthCryptoService.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthCryptoService.java index 265f1500..e3297c22 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthCryptoService.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthCryptoService.java @@ -1,25 +1,44 @@ package com.payneteasy.superfly.service; +import com.payneteasy.superfly.service.impl.remote.check.KeyPairData; +import com.payneteasy.superfly.service.impl.remote.check.RemoteAuthEncryptionAlgorithm; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + /** * Service for cryptographic operations in remote authentication. */ public interface RemoteAuthCryptoService { + + KeyPairData generateKeyPair(RemoteAuthEncryptionAlgorithm algorithm); + /** * Decrypts a password using the subsystem's private key. * * @param encryptedPassword The encrypted password (Base64 URL-safe no padding). * @param privateKey The private key of the subsystem. + * @param algorithm The encryption algorithm to use. * @return The decrypted password. */ - String decryptPassword(String encryptedPassword, String privateKey); + String decryptPassword(String encryptedPassword, String privateKey, RemoteAuthEncryptionAlgorithm algorithm) + throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, + InvalidKeySpecException, BadPaddingException, InvalidKeyException; /** * Decrypts an OTP using the subsystem's private key. * * @param encryptedOtp The encrypted OTP (Base64 URL-safe no padding). * @param privateKey The private key of the subsystem. + * @param algorithm The encryption algorithm to use. * @return The decrypted OTP. */ - String decryptOtp(String encryptedOtp, String privateKey); + String decryptOtp(String encryptedOtp, String privateKey, RemoteAuthEncryptionAlgorithm algorithm) + throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, + InvalidKeySpecException, BadPaddingException, InvalidKeyException; } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/SubsystemService.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/SubsystemService.java index 48360891..7d252c39 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/SubsystemService.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/SubsystemService.java @@ -7,6 +7,8 @@ import com.payneteasy.superfly.model.ui.subsystem.UISubsystem; import com.payneteasy.superfly.model.ui.subsystem.UISubsystemForFilter; import com.payneteasy.superfly.model.ui.subsystem.UISubsystemForList; +import com.payneteasy.superfly.service.impl.remote.check.KeyPairData; +import com.payneteasy.superfly.service.impl.remote.check.RemoteAuthEncryptionAlgorithm; /** * Service for subsystems. @@ -88,7 +90,7 @@ public interface SubsystemService { /** * - * @return private key for subsystem + * @return key pair data for subsystem */ - String generateSubsystemPrivateKey(); + KeyPairData generateKeyPair(RemoteAuthEncryptionAlgorithm algorithm); } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/SubsystemServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/SubsystemServiceImpl.java index 42e37895..5c9af980 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/SubsystemServiceImpl.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/SubsystemServiceImpl.java @@ -5,7 +5,9 @@ import java.util.UUID; import com.payneteasy.superfly.model.SubsystemTokenData; -import com.payneteasy.superfly.service.JavaMailSenderPool; +import com.payneteasy.superfly.service.*; +import com.payneteasy.superfly.service.impl.remote.check.KeyPairData; +import com.payneteasy.superfly.service.impl.remote.check.RemoteAuthEncryptionAlgorithm; import com.payneteasy.superfly.utils.RandomGUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,9 +20,6 @@ import com.payneteasy.superfly.model.ui.subsystem.UISubsystem; import com.payneteasy.superfly.model.ui.subsystem.UISubsystemForFilter; import com.payneteasy.superfly.model.ui.subsystem.UISubsystemForList; -import com.payneteasy.superfly.service.LoggerSink; -import com.payneteasy.superfly.service.NotificationService; -import com.payneteasy.superfly.service.SubsystemService; @Service @Transactional @@ -32,6 +31,7 @@ public class SubsystemServiceImpl implements SubsystemService { private NotificationService notificationService; private LoggerSink loggerSink; private JavaMailSenderPool javaMailSenderPool; + private RemoteAuthCryptoService remoteAuthCryptoService; @Autowired public void setSubsystemDao(SubsystemDao subsystemDao) { @@ -43,6 +43,11 @@ public void setNotificationService(NotificationService notificationService) { this.notificationService = notificationService; } + @Autowired + public void setRemoteAuthCryptoService(RemoteAuthCryptoService remoteAuthCryptoService) { + this.remoteAuthCryptoService = remoteAuthCryptoService; + } + @Autowired public void setLoggerSink(LoggerSink loggerSink) { this.loggerSink = loggerSink; @@ -55,7 +60,6 @@ public void setJavaMailSenderPool(JavaMailSenderPool javaMailSenderPool) { public RoutineResult createSubsystem(UISubsystem subsystem) { subsystem.setSubsystemToken(generateMainSubsystemToken()); - subsystem.setPrivateKey(generateSubsystemPrivateKey()); RoutineResult result = subsystemDao.createSubsystem(subsystem); loggerSink.info(logger, "CREATE_SUBSYSTEM", true, subsystem.getName()); javaMailSenderPool.flushAll(); // clearing pool so changes are applied @@ -114,7 +118,7 @@ public String generateMainSubsystemToken() { } @Override - public String generateSubsystemPrivateKey() { - return UUID.randomUUID().toString(); + public KeyPairData generateKeyPair(RemoteAuthEncryptionAlgorithm algorithm) { + return remoteAuthCryptoService.generateKeyPair(algorithm); } } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/KeyPairData.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/KeyPairData.java new file mode 100644 index 00000000..687ba9e8 --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/KeyPairData.java @@ -0,0 +1,4 @@ +package com.payneteasy.superfly.service.impl.remote.check; + +public record KeyPairData(String publicKey, String privateKey) { +} diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceImpl.java new file mode 100644 index 00000000..260e4faf --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceImpl.java @@ -0,0 +1,108 @@ +package com.payneteasy.superfly.service.impl.remote.check; + +import com.payneteasy.superfly.service.RemoteAuthCryptoService; +import org.springframework.stereotype.Service; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; + +@Service +public class RemoteAuthCryptoServiceImpl implements RemoteAuthCryptoService { + + @Override + public KeyPairData generateKeyPair(RemoteAuthEncryptionAlgorithm algorithm) { + return switch (algorithm) { + case RSA -> { + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(4096); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + + String publicKeyPem = toPem(keyPair.getPublic().getEncoded(), "PUBLIC KEY"); + String privateKeyPem = toPem(keyPair.getPrivate().getEncoded(), "PRIVATE KEY"); + + yield new KeyPairData(publicKeyPem, privateKeyPem); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("Cannot generate RSA key pair", e); + } + } + case EC -> throw new UnsupportedOperationException( + "EC algorithm is not supported yet. Please use RSA instead." + ); + }; + } + + private String toPem(byte[] encodedKey, String type) { + String base64 = Base64.getEncoder().encodeToString(encodedKey); + StringBuilder sb = new StringBuilder(); + sb.append("-----BEGIN ").append(type).append("-----\n"); + for (int i = 0; i < base64.length(); i += 64) { + int end = Math.min(i + 64, base64.length()); + sb.append(base64, i, end).append('\n'); + } + sb.append("-----END ").append(type).append("-----"); + return sb.toString(); + } + + @Override + public String decryptPassword(String encryptedPassword, String privateKey, RemoteAuthEncryptionAlgorithm algorithm) + throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, + InvalidKeySpecException, BadPaddingException, InvalidKeyException { + return decrypt(encryptedPassword, privateKey, algorithm); + } + + @Override + public String decryptOtp(String encryptedOtp, String privateKey, RemoteAuthEncryptionAlgorithm algorithm) + throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, + InvalidKeySpecException, BadPaddingException, InvalidKeyException { + return decrypt(encryptedOtp, privateKey, algorithm); + } + + private String decrypt(String encrypted, String privateKey, RemoteAuthEncryptionAlgorithm algorithm) + throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, + IllegalBlockSizeException, BadPaddingException, InvalidKeyException { + byte[] encryptedBytes = Base64.getUrlDecoder().decode(encrypted); + PrivateKey key = parsePrivateKey(privateKey, algorithm); + byte[] decrypted = decryptBytes(encryptedBytes, key, algorithm); + return new String(decrypted, StandardCharsets.UTF_8); + } + + private PrivateKey parsePrivateKey(String privateKeyPem, RemoteAuthEncryptionAlgorithm algorithm) + throws NoSuchAlgorithmException, InvalidKeySpecException { + + String normalized = privateKeyPem + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s", ""); + + byte[] keyBytes = Base64.getDecoder().decode(normalized); + + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(algorithm.name()); + + return keyFactory.generatePrivate(keySpec); + } + + private byte[] decryptBytes(byte[] encrypted, PrivateKey privateKey, RemoteAuthEncryptionAlgorithm algorithm) + throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, + BadPaddingException { + + return switch (algorithm) { + case RSA -> { + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + yield cipher.doFinal(encrypted); + } + case EC -> throw new UnsupportedOperationException( + "EC algorithm is not supported yet. Please use RSA instead." + ); + }; + } +} diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java index 2ef571f9..37905007 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java @@ -16,14 +16,19 @@ public class RemoteAuthCryptoServiceStub implements RemoteAuthCryptoService { public RemoteAuthCryptoServiceStub() { } - public String decryptPassword(String encryptedPassword, String privateKey) { + @Override + public KeyPairData generateKeyPair(RemoteAuthEncryptionAlgorithm algorithm) { + return null; + } + + public String decryptPassword(String encryptedPassword, String privateKey, RemoteAuthEncryptionAlgorithm encryptionAlgorithm) { logger.warn("decryptPassword is a STUB. Returning encryptedPassword as is."); // In a real implementation, this would decrypt the password. // For now, we assume the input is already the plain password or we just return it to test flow. return encryptedPassword; } - public String decryptOtp(String encryptedOtp, String privateKey) { + public String decryptOtp(String encryptedOtp, String privateKey, RemoteAuthEncryptionAlgorithm encryptionAlgorithm) { logger.warn("decryptOtp is a STUB. Returning encryptedOtp as is."); return encryptedOtp; } diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthEncryptionAlgorithm.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthEncryptionAlgorithm.java new file mode 100644 index 00000000..f58a3f9d --- /dev/null +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthEncryptionAlgorithm.java @@ -0,0 +1,6 @@ +package com.payneteasy.superfly.service.impl.remote.check; + +public enum RemoteAuthEncryptionAlgorithm { + + RSA, EC +} diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java index 9ec5335e..0db88631 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java @@ -45,7 +45,11 @@ public String checkPassword(String subsystemName, String username, String passwo // 2. Decrypt Password String password; try { - password = remoteAuthCryptoService.decryptPassword(passwordEncrypted, subsystem.getPrivateKey()); + password = remoteAuthCryptoService.decryptPassword( + passwordEncrypted, + subsystem.getPrivateKey(), + RemoteAuthEncryptionAlgorithm.valueOf(subsystem.getEncryptionAlgorithm()) + ); } catch (Exception e) { logger.error("Failed to decrypt password", e); throw new RemoteAuthException("Decryption failed", "INTERNAL_ERROR", e); @@ -88,7 +92,11 @@ public String checkOtp(String subsystemName, String username, String otpEncrypte // 3. Decrypt OTP String otp; try { - otp = remoteAuthCryptoService.decryptOtp(otpEncrypted, subsystem.getPrivateKey()); + otp = remoteAuthCryptoService.decryptOtp( + otpEncrypted, + subsystem.getPrivateKey(), + RemoteAuthEncryptionAlgorithm.valueOf(subsystem.getEncryptionAlgorithm()) + ); } catch (Exception e) { logger.error("Failed to decrypt OTP", e); throw new RemoteAuthException("Decryption failed", "INTERNAL_ERROR", e); diff --git a/superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd b/superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd index 78aeec93..ade25257 100644 --- a/superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd +++ b/superfly-sql/mi/R1.7.3/R1.7.3_SSO.cmd @@ -1,3 +1,3 @@ @set PATH=C:\cygwin\bin;%PATH% -bash R1.7.2_SSO.sh +bash R1.7.3_SSO.sh diff --git a/superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql b/superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql index dd8cdc90..0407b3ed 100644 --- a/superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql +++ b/superfly-sql/mi/R1.7.3/R1.7.3_SSO_DML.sql @@ -1,3 +1,5 @@ alter table subsystems add column private_key text; +alter table subsystems add column public_key text; +alter table subsystems add column encryption_algorithm varchar(16); commit; diff --git a/superfly-sql/src/ui_create/ui_create_subsystem.prc b/superfly-sql/src/ui_create/ui_create_subsystem.prc index 41571e3a..f24dad3e 100644 --- a/superfly-sql/src/ui_create/ui_create_subsystem.prc +++ b/superfly-sql/src/ui_create/ui_create_subsystem.prc @@ -11,6 +11,8 @@ create procedure ui_create_subsystem(i_subsystem_name varchar(32), i_landing_url varchar(255), i_login_form_css_url varchar(255), i_private_key text, + i_public_key text, + i_encryption_algorithm varchar(16), out o_ssys_id int(10) ) main_sql: @@ -28,7 +30,9 @@ create procedure ui_create_subsystem(i_subsystem_name varchar(32), subsystem_url, landing_url, login_form_css_url, - private_key + private_key, + public_key, + encryption_algorithm ) values ( i_subsystem_name, @@ -42,7 +46,9 @@ create procedure ui_create_subsystem(i_subsystem_name varchar(32), i_subsystem_url, i_landing_url, i_login_form_css_url, - i_private_key + i_private_key, + i_public_key, + i_encryption_algorithm ); set o_ssys_id = last_insert_id(); diff --git a/superfly-sql/src/ui_get/ui_get_subsystem.prc b/superfly-sql/src/ui_get/ui_get_subsystem.prc index 6a781213..deb89d93 100644 --- a/superfly-sql/src/ui_get/ui_get_subsystem.prc +++ b/superfly-sql/src/ui_get/ui_get_subsystem.prc @@ -16,7 +16,7 @@ create procedure ui_get_subsystem(i_ssys_id int(10)) ss.subsystem_url, ss.landing_url, ss.login_form_css_url, - ss.private_key + ss.public_key from subsystems ss left join smtp_servers smtp on smtp.ssrv_id = ss.ssrv_ssrv_id @@ -39,6 +39,6 @@ call save_routine_information('ui_get_subsystem', 'subsystem_url varchar', 'landing_url varchar', 'login_form_css_url varchar', - 'private_key varchar' + 'public_key varchar' ) ); diff --git a/superfly-sql/src/ui_get/ui_get_subsystem_by_name.prc b/superfly-sql/src/ui_get/ui_get_subsystem_by_name.prc index 06556b3d..e5819aec 100644 --- a/superfly-sql/src/ui_get/ui_get_subsystem_by_name.prc +++ b/superfly-sql/src/ui_get/ui_get_subsystem_by_name.prc @@ -16,7 +16,9 @@ create procedure ui_get_subsystem_by_name(i_subsystem_name varchar(32)) ss.subsystem_url, ss.landing_url, ss.login_form_css_url, - ss.private_key + ss.private_key, + ss.public_key, + ss.encryption_algorithm from subsystems ss left join smtp_servers smtp on smtp.ssrv_id = ss.ssrv_ssrv_id @@ -39,6 +41,8 @@ call save_routine_information('ui_get_subsystem_by_name', 'subsystem_url varchar', 'landing_url varchar', 'login_form_css_url varchar', - 'private_key varchar' + 'private_key varchar', + 'public_key varchar', + 'encryption_algorithm varchar' ) ); diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.properties b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.properties index 17ddeb90..d7d6cb2c 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.properties +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.properties @@ -31,7 +31,8 @@ subsystem.edit.callback=Callback Info subsystem.edit.callback.Required=Callback Info is required subsystem.edit.send-callbacks=Send callbacks subsystem.edit.subsystemToken=Token -subsystem.edit.privateKey=Private key +subsystem.edit.publicKey=Public key +subsystem.add.publicKey=Public key subsystem.edit.subsystemUrl=Subsystem URL subsystem.edit.subsystemUrl.Required=Subsystem URL is required subsystem.edit.landingUrl=Landing URL diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.html index a6b34cbd..df62da4e 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.html @@ -16,6 +16,13 @@ +
+ +
key
+ + Generate new RSA key pair + +
diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.java index 692a357f..1282bde0 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.java @@ -4,11 +4,16 @@ import com.payneteasy.superfly.model.ui.subsystem.UISubsystem; import com.payneteasy.superfly.service.SmtpServerService; import com.payneteasy.superfly.service.SubsystemService; +import com.payneteasy.superfly.service.impl.remote.check.KeyPairData; +import com.payneteasy.superfly.service.impl.remote.check.RemoteAuthEncryptionAlgorithm; import com.payneteasy.superfly.web.wicket.component.field.LabelCheckBoxRow; import com.payneteasy.superfly.web.wicket.component.field.LabelDropDownChoiceRow; import com.payneteasy.superfly.web.wicket.component.field.LabelTextFieldRow; import com.payneteasy.superfly.web.wicket.page.BasePage; import org.apache.wicket.Page; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxLink; +import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.ChoiceRenderer; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.SubmitLink; @@ -16,6 +21,7 @@ import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; +import org.apache.wicket.model.ResourceModel; import org.apache.wicket.spring.injection.annot.SpringBean; import org.apache.wicket.validation.validator.UrlValidator; import org.springframework.security.access.annotation.Secured; @@ -87,6 +93,27 @@ public String getIdValue(UISmtpServerForFilter server, int index) { } }, true)); + final Label labelPublicKey = new Label("publicKey", new LoadableDetachableModel() { + @Override + protected String load() { + return subsystem.getPublicKey(); + } + }); + labelPublicKey.setOutputMarkupId(true); + + form.add(new Label("publicKeyLabel", new ResourceModel("subsystem.add.publicKey"))); + form.add(labelPublicKey); + form.add(new IndicatingAjaxLink("generateNewKeyPair") { + private static final long serialVersionUID = 1L; + + public void onClick(AjaxRequestTarget aTarget) { + var keyPairData = generateKeyPair(RemoteAuthEncryptionAlgorithm.RSA); + subsystem.setPrivateKey(keyPairData.privateKey()); + subsystem.setPublicKey(keyPairData.publicKey()); + aTarget.add(labelPublicKey); + } + }); + form.add(new SubmitLink("submit-link")); form.add(new BookmarkablePageLink("cancel", ListSubsystemsPage.class)); } @@ -95,4 +122,8 @@ public String getIdValue(UISmtpServerForFilter server, int index) { protected String getTitle() { return "Add subsystem"; } + + private KeyPairData generateKeyPair(RemoteAuthEncryptionAlgorithm algorithm) { + return subsystemService.generateKeyPair(algorithm); + } } diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html index bac47a6f..1d885b01 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html @@ -23,10 +23,10 @@
- -
key
- - Generate new key + +
key
+ + Generate new RSA key pair
diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java index cdf98388..6f09f0ac 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java @@ -4,6 +4,8 @@ import com.payneteasy.superfly.model.ui.subsystem.UISubsystem; import com.payneteasy.superfly.service.SmtpServerService; import com.payneteasy.superfly.service.SubsystemService; +import com.payneteasy.superfly.service.impl.remote.check.KeyPairData; +import com.payneteasy.superfly.service.impl.remote.check.RemoteAuthEncryptionAlgorithm; import com.payneteasy.superfly.web.wicket.component.field.LabelCheckBoxRow; import com.payneteasy.superfly.web.wicket.component.field.LabelDropDownChoiceRow; import com.payneteasy.superfly.web.wicket.component.field.LabelTextFieldRow; @@ -26,7 +28,6 @@ import org.springframework.security.access.annotation.Secured; import java.util.List; -import java.util.UUID; @Secured("ROLE_ADMIN") public class EditSubsystemPage extends BasePage { @@ -83,23 +84,25 @@ public void onClick(AjaxRequestTarget aTarget) { } }); - // Private key fields - final Label labelPrivateKey = new Label("privateKey", new LoadableDetachableModel() { + // Public key fields + final Label labelPublicKey = new Label("publicKey", new LoadableDetachableModel() { @Override protected String load() { - return subsystem.getPrivateKey(); + return subsystem.getPublicKey(); } }); - labelPrivateKey.setOutputMarkupId(true); + labelPublicKey.setOutputMarkupId(true); - form.add(new Label("privateKeyLabel", new ResourceModel("subsystem.edit.privateKey"))); - form.add(labelPrivateKey); - form.add(new IndicatingAjaxLink("generateNewPrivateKey") { + form.add(new Label("publicKeyLabel", new ResourceModel("subsystem.edit.publicKey"))); + form.add(labelPublicKey); + form.add(new IndicatingAjaxLink("generateNewKeyPair") { private static final long serialVersionUID = 1L; public void onClick(AjaxRequestTarget aTarget) { - subsystem.setPrivateKey(generateNewPrivateKey()); - aTarget.add(labelPrivateKey); + var keyPairData = generateKeyPair(RemoteAuthEncryptionAlgorithm.RSA); + subsystem.setPrivateKey(keyPairData.privateKey()); + subsystem.setPublicKey(keyPairData.publicKey()); + aTarget.add(labelPublicKey); } }); @@ -150,7 +153,7 @@ private String generateNewToken() { return subsystemService.generateMainSubsystemToken(); } - private String generateNewPrivateKey() { - return subsystemService.generateSubsystemPrivateKey(); + private KeyPairData generateKeyPair(RemoteAuthEncryptionAlgorithm algorithm) { + return subsystemService.generateKeyPair(algorithm); } } From 63e443ae1d409d9cbd548f70b32439b96eb11443 Mon Sep 17 00:00:00 2001 From: iv Date: Wed, 3 Dec 2025 16:47:17 +0300 Subject: [PATCH 29/91] #111 sso-remote-auth - API, service and auth phases cache --- .../superfly/service/RemoteAuthService.java | 25 +++++++++++++++++-- .../remote/check/RemoteAuthServiceImpl.java | 22 ++++++++-------- .../web/mvc/RemoteAuthCheckController.java | 4 +-- .../web/mvc/model/CheckPasswordResponse.java | 12 ++++++--- .../mvc/RemoteAuthCheckControllerTest.java | 14 ++++++----- 5 files changed, 53 insertions(+), 24 deletions(-) diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthService.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthService.java index e12f6387..2fa5b7db 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthService.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/RemoteAuthService.java @@ -14,10 +14,10 @@ public interface RemoteAuthService { * @param bearerToken Bearer token provided in the request. * @param ipAddress IP address of the client. * @param userAgent User agent of the client. - * @return Session token if authentication is successful. + * @return Session information including token and OTP requirement status if authentication is successful. * @throws RemoteAuthException If authentication fails or other errors occur. */ - String checkPassword(String subsystemName, String username, String passwordEncrypted, String bearerToken, String ipAddress, String userAgent) throws RemoteAuthException; + RemoteAuthSession checkPassword(String subsystemName, String username, String passwordEncrypted, String bearerToken, String ipAddress, String userAgent) throws RemoteAuthException; /** * Checks the OTP for a user in a subsystem. @@ -32,6 +32,27 @@ public interface RemoteAuthService { */ String checkOtp(String subsystemName, String username, String otpEncrypted, String sessionToken, String bearerToken) throws RemoteAuthException; + /** + * Session data returned after successful password check. + */ + class RemoteAuthSession { + private final String sessionToken; + private final boolean otpRequired; + + public RemoteAuthSession(String sessionToken, boolean otpRequired) { + this.sessionToken = sessionToken; + this.otpRequired = otpRequired; + } + + public String getSessionToken() { + return sessionToken; + } + + public boolean isOtpRequired() { + return otpRequired; + } + } + /** * Exception class for remote auth errors. */ diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java index 0db88631..29f6a75f 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java @@ -1,5 +1,6 @@ package com.payneteasy.superfly.service.impl.remote.check; +import com.payneteasy.superfly.api.OTPType; import com.payneteasy.superfly.api.SSOUser; import com.payneteasy.superfly.model.ui.subsystem.UISubsystem; import com.payneteasy.superfly.service.InternalSSOService; @@ -38,7 +39,7 @@ public RemoteAuthServiceImpl(SubsystemService subsystemService, } @Override - public String checkPassword(String subsystemName, String username, String passwordEncrypted, String bearerToken, String ipAddress, String userAgent) throws RemoteAuthException { + public RemoteAuthSession checkPassword(String subsystemName, String username, String passwordEncrypted, String bearerToken, String ipAddress, String userAgent) throws RemoteAuthException { // 1. Validate Subsystem and Token UISubsystem subsystem = validateSubsystem(subsystemName, bearerToken); @@ -70,9 +71,11 @@ public String checkPassword(String subsystemName, String username, String passwo // 4. Generate Session Token and Cache String sessionToken = UUID.randomUUID().toString(); - sessionCache.put(sessionToken, new RemoteSession(username)); + sessionCache.put(sessionToken, new RemoteSession(username, ssoUser.getOtpType())); - return sessionToken; + boolean otpRequired = ssoUser.getOtpType() != OTPType.NONE && !ssoUser.isOtpOptional(); + + return new RemoteAuthSession(sessionToken, otpRequired); } @Override @@ -103,15 +106,10 @@ public String checkOtp(String subsystemName, String username, String otpEncrypte } // 4. Verify OTP - boolean otpValid = internalSSOService.authenticateHOTP(subsystemName, username, otp); + boolean otpValid = internalSSOService.authenticateByOtpType(session.otpType, username, otp); if (!otpValid) { return "BAD_USER_OR_PASSWORD_OR_OTP"; } - - // TODO: Check for other statuses like USER_SHOULD_CHANGE_PASSWORD or USER_BLOCKED - // internalSSOService.authenticateHOTP only returns boolean. - // We might need to check user status explicitly if needed. - return "SUCCESS"; } @@ -129,10 +127,12 @@ private UISubsystem validateSubsystem(String subsystemName, String bearerToken) } private static class RemoteSession { - final String username; + final String username; + final OTPType otpType; - RemoteSession(String username) { + RemoteSession(String username, OTPType otpType) { this.username = username; + this.otpType = otpType; } } } diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckController.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckController.java index 1676abb4..c42fc575 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckController.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckController.java @@ -44,8 +44,8 @@ public CheckPasswordResponse checkPassword(@PathVariable String subsystemName, throw new BadRequestException("Username in path and body must match"); } - String sessionToken = remoteAuthService.checkPassword(subsystemName, username, requestBody.getPasswordEncrypted(), bearerToken, request.getRemoteAddr(), request.getHeader("User-Agent")); - return new CheckPasswordResponse(username, sessionToken); + RemoteAuthService.RemoteAuthSession session = remoteAuthService.checkPassword(subsystemName, username, requestBody.getPasswordEncrypted(), bearerToken, request.getRemoteAddr(), request.getHeader("User-Agent")); + return new CheckPasswordResponse(username, session.getSessionToken(), session.isOtpRequired()); } @RequestMapping(value = "/check-otp/{subsystemName}/{username}", method = RequestMethod.POST, produces = "application/json") diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordResponse.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordResponse.java index 22964f8e..d62f5c5b 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordResponse.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/mvc/model/CheckPasswordResponse.java @@ -1,12 +1,14 @@ package com.payneteasy.superfly.web.mvc.model; public class CheckPasswordResponse { - private String username; - private String sessionToken; + private final String username; + private final String sessionToken; + private final boolean otpRequired; - public CheckPasswordResponse(String username, String sessionToken) { + public CheckPasswordResponse(String username, String sessionToken, boolean otpRequired) { this.username = username; this.sessionToken = sessionToken; + this.otpRequired = otpRequired; } public String getUsername() { @@ -16,5 +18,9 @@ public String getUsername() { public String getSessionToken() { return sessionToken; } + + public boolean isOtpRequired() { + return otpRequired; + } } diff --git a/superfly-web/src/test/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckControllerTest.java b/superfly-web/src/test/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckControllerTest.java index fb0e87c4..d099c330 100644 --- a/superfly-web/src/test/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckControllerTest.java +++ b/superfly-web/src/test/java/com/payneteasy/superfly/web/mvc/RemoteAuthCheckControllerTest.java @@ -17,6 +17,7 @@ import static org.easymock.EasyMock.*; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; public class RemoteAuthCheckControllerTest { @@ -54,10 +55,10 @@ public void testCheckPasswordSuccess() throws Exception { // Ожидаем вызовы к request (получение IP и User-Agent) expect(request.getRemoteAddr()).andReturn(ipAddress); expect(request.getHeader("User-Agent")).andReturn(userAgent); - + // Ожидаем вызов бизнес-логики сервиса expect(remoteAuthService.checkPassword(subsystemName, username, passwordEncrypted, bearerToken, ipAddress, userAgent)) - .andReturn(sessionToken); + .andReturn(new RemoteAuthService.RemoteAuthSession(sessionToken, false)); // 3. Перевод моков в режим воспроизведения (Replay phase) replay(remoteAuthService, request); @@ -71,6 +72,7 @@ public void testCheckPasswordSuccess() throws Exception { // 6. Проверка результата (Assertions) assertEquals("Username should match", username, response.getUsername()); assertEquals("Session token should match", sessionToken, response.getSessionToken()); + assertFalse("Otp required should be false", response.isOtpRequired()); } @Test @@ -150,17 +152,17 @@ public void testCheckPasswordServiceException() throws Exception { public void testHandleRemoteAuthException() { RemoteAuthException ex = new RemoteAuthException("Error message", "INTERNAL_ERROR"); ResponseEntity response = controller.handleRemoteAuthException(ex); - + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); assertEquals("INTERNAL_ERROR", response.getBody().getType()); assertEquals("Error message", response.getBody().getTitle()); } - + @Test public void testHandleRemoteAuthExceptionBadRequest() { RemoteAuthException ex = new RemoteAuthException("Bad credentials", "BAD_CREDENTIALS"); ResponseEntity response = controller.handleRemoteAuthException(ex); - + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertEquals("BAD_CREDENTIALS", response.getBody().getType()); } @@ -194,4 +196,4 @@ public void testHandleJsonError() { assertEquals("BAD_REQUEST", response.getBody().getType()); assertEquals("Invalid JSON", response.getBody().getTitle()); } -} \ No newline at end of file +} From 48d61f95bd45ee1cfaccb5415323d74329013037 Mon Sep 17 00:00:00 2001 From: akorobov Date: Wed, 3 Dec 2025 20:12:20 +0300 Subject: [PATCH 30/91] 110 - Extend subsystem properties to include encryption algorithm, improve error handling, and configure RemoteAuthCryptoService. --- .../check/RemoteAuthCryptoServiceStub.java | 2 - superfly-sql/src/ui_get/ui_get_subsystem.prc | 8 +- .../ui_edit_subsystem_properties.prc | 8 +- .../page/subsystem/AddSubsystemPage.java | 20 +- .../page/subsystem/EditSubsystemPage.java | 20 +- .../spring/applicationContext-service.xml | 256 ++++++++++++++++++ 6 files changed, 300 insertions(+), 14 deletions(-) create mode 100644 superfly-web/src/main/webapp/WEB-INF/spring/applicationContext-service.xml diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java index 37905007..01b56d58 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthCryptoServiceStub.java @@ -3,12 +3,10 @@ import com.payneteasy.superfly.service.RemoteAuthCryptoService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Service; /** * Stub implementation of RemoteAuthCryptoService. */ -@Service public class RemoteAuthCryptoServiceStub implements RemoteAuthCryptoService { private static final Logger logger = LoggerFactory.getLogger(RemoteAuthCryptoServiceStub.class); diff --git a/superfly-sql/src/ui_get/ui_get_subsystem.prc b/superfly-sql/src/ui_get/ui_get_subsystem.prc index deb89d93..f0d9e30d 100644 --- a/superfly-sql/src/ui_get/ui_get_subsystem.prc +++ b/superfly-sql/src/ui_get/ui_get_subsystem.prc @@ -16,7 +16,9 @@ create procedure ui_get_subsystem(i_ssys_id int(10)) ss.subsystem_url, ss.landing_url, ss.login_form_css_url, - ss.public_key + ss.public_key, + ss.private_key, + ss.encryption_algorithm from subsystems ss left join smtp_servers smtp on smtp.ssrv_id = ss.ssrv_ssrv_id @@ -39,6 +41,8 @@ call save_routine_information('ui_get_subsystem', 'subsystem_url varchar', 'landing_url varchar', 'login_form_css_url varchar', - 'public_key varchar' + 'public_key varchar', + 'private_key varchar', + 'encryption_algorithm varchar' ) ); diff --git a/superfly-sql/src/ui_update/ui_edit_subsystem_properties.prc b/superfly-sql/src/ui_update/ui_edit_subsystem_properties.prc index 32ccbe6f..19beb90b 100644 --- a/superfly-sql/src/ui_update/ui_edit_subsystem_properties.prc +++ b/superfly-sql/src/ui_update/ui_edit_subsystem_properties.prc @@ -11,7 +11,9 @@ create procedure ui_edit_subsystem_properties(i_ssys_id int(10), i_subsystem_url varchar(255), i_landing_url varchar(255), i_login_form_css_url varchar(255), - i_private_key text + i_private_key text, + i_public_key text, + i_encryption_algorithm varchar(16) ) main_sql: begin @@ -26,7 +28,9 @@ create procedure ui_edit_subsystem_properties(i_ssys_id int(10), subsystem_url = i_subsystem_url, landing_url = i_landing_url, login_form_css_url = i_login_form_css_url, - private_key = i_private_key + private_key = i_private_key, + public_key = i_public_key, + encryption_algorithm = i_encryption_algorithm where ssys_id = i_ssys_id; select 'OK' status, null error_message; diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.java index 1282bde0..eb5bb31e 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/AddSubsystemPage.java @@ -24,12 +24,17 @@ import org.apache.wicket.model.ResourceModel; import org.apache.wicket.spring.injection.annot.SpringBean; import org.apache.wicket.validation.validator.UrlValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.access.annotation.Secured; import java.util.List; @Secured("ROLE_ADMIN") public class AddSubsystemPage extends BasePage { + + private static final Logger logger = LoggerFactory.getLogger(AddSubsystemPage.class); + @SpringBean private SubsystemService subsystemService; @SpringBean @@ -107,10 +112,17 @@ protected String load() { private static final long serialVersionUID = 1L; public void onClick(AjaxRequestTarget aTarget) { - var keyPairData = generateKeyPair(RemoteAuthEncryptionAlgorithm.RSA); - subsystem.setPrivateKey(keyPairData.privateKey()); - subsystem.setPublicKey(keyPairData.publicKey()); - aTarget.add(labelPublicKey); + try { + var keyPairData = generateKeyPair(RemoteAuthEncryptionAlgorithm.RSA); + subsystem.setPrivateKey(keyPairData.privateKey()); + subsystem.setPublicKey(keyPairData.publicKey()); + subsystem.setEncryptionAlgorithm(RemoteAuthEncryptionAlgorithm.RSA.name()); + aTarget.add(labelPublicKey); + } catch (Exception e) { + logger.error("Error while generating key pair for subsystem {}", subsystem.getName(), e); + error("Error while generating key pair: " + e.getMessage()); + } + } }); diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java index 6f09f0ac..444e9047 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java @@ -25,12 +25,17 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.spring.injection.annot.SpringBean; import org.apache.wicket.validation.validator.UrlValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.access.annotation.Secured; import java.util.List; @Secured("ROLE_ADMIN") public class EditSubsystemPage extends BasePage { + + private static final Logger logger = LoggerFactory.getLogger(EditSubsystemPage.class); + @SpringBean private SubsystemService subsystemService; @SpringBean @@ -99,10 +104,17 @@ protected String load() { private static final long serialVersionUID = 1L; public void onClick(AjaxRequestTarget aTarget) { - var keyPairData = generateKeyPair(RemoteAuthEncryptionAlgorithm.RSA); - subsystem.setPrivateKey(keyPairData.privateKey()); - subsystem.setPublicKey(keyPairData.publicKey()); - aTarget.add(labelPublicKey); + try { + var keyPairData = generateKeyPair(RemoteAuthEncryptionAlgorithm.RSA); + subsystem.setPrivateKey(keyPairData.privateKey()); + subsystem.setPublicKey(keyPairData.publicKey()); + subsystem.setEncryptionAlgorithm(RemoteAuthEncryptionAlgorithm.RSA.name()); + aTarget.add(labelPublicKey); + } catch (Exception e) { + logger.error("Error while generating key pair for subsystem {}", subsystem.getName(), e); + error("Error while generating key pair: " + e.getMessage()); + } + } }); diff --git a/superfly-web/src/main/webapp/WEB-INF/spring/applicationContext-service.xml b/superfly-web/src/main/webapp/WEB-INF/spring/applicationContext-service.xml new file mode 100644 index 00000000..e232bb73 --- /dev/null +++ b/superfly-web/src/main/webapp/WEB-INF/spring/applicationContext-service.xml @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SSLv3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From a1885162a0b4e1ca1edc25617e3889abfa1cd8d5 Mon Sep 17 00:00:00 2001 From: iv Date: Fri, 5 Dec 2025 05:48:47 +0300 Subject: [PATCH 31/91] #111 sso-remote-auth - API, service and auth phases cache --- .../component/otp/GoogleAuthSetupPanel.java | 2 +- .../page/sso/SSOChangePasswordPage.java | 25 ++++++++++++++++--- .../web/wicket/page/sso/SSOLoginData.java | 19 ++++++++++++++ .../wicket/page/sso/SSOLoginPasswordPage.java | 4 +++ 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/component/otp/GoogleAuthSetupPanel.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/component/otp/GoogleAuthSetupPanel.java index c21a973a..be50584d 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/component/otp/GoogleAuthSetupPanel.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/component/otp/GoogleAuthSetupPanel.java @@ -17,7 +17,7 @@ public class GoogleAuthSetupPanel extends Panel { private static final String TOTP_URI_FORMAT = - "https://chart.googleapis.com/chart?chs=150x150&chld=M%%7C0&cht=qr&chl=%s"; + "https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=%s"; private final String username; private final IModel totpSecret; diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOChangePasswordPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOChangePasswordPage.java index d480246e..33d2aa49 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOChangePasswordPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOChangePasswordPage.java @@ -1,7 +1,10 @@ package com.payneteasy.superfly.web.wicket.page.sso; +import com.payneteasy.superfly.api.OTPType; +import com.payneteasy.superfly.model.ui.user.UserForDescription; import com.payneteasy.superfly.service.SessionService; import com.payneteasy.superfly.service.SubsystemService; +import com.payneteasy.superfly.service.UserService; import com.payneteasy.superfly.web.wicket.page.user.ChangePasswordPanel; import org.apache.wicket.Component; import org.apache.wicket.model.Model; @@ -16,6 +19,8 @@ public class SSOChangePasswordPage extends BaseSSOPage { private SessionService sessionService; @SpringBean private SubsystemService subsystemService; + @SpringBean + private UserService userService; public SSOChangePasswordPage(final String username) { if (getSession().getSsoLoginData() == null) { @@ -40,10 +45,22 @@ protected String getCurrentUserName() { @Override protected void onPasswordChanged() { - SSOUtils.onSuccessfulLogin(username, - SSOChangePasswordPage.this, - SSOChangePasswordPage.this.getSession().getSsoLoginData(), - sessionService, subsystemService); + UserForDescription user = userService.getUserForDescription(username); + SSOLoginData loginData = SSOChangePasswordPage.this.getSession().getSsoLoginData(); + if (loginData != null) { + loginData.setUsername(username); + loginData.setOtpTypeCode(user.getOtpTypeCode()); + loginData.setOtpOptional(user.isOtpOptional()); + } + + if (OTPType.GOOGLE_AUTH.equals(user.getOtpType()) && !user.isOtpOptional()) { + getRequestCycle().setResponsePage(new SSOSetupGoogleAuthPage()); + } else { + SSOUtils.onSuccessfulLogin(username, + SSOChangePasswordPage.this, + loginData, + sessionService, subsystemService); + } } }); } diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginData.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginData.java index 6e6dea16..6c637178 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginData.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginData.java @@ -12,6 +12,9 @@ public class SSOLoginData implements Serializable { private String subsystemUrl; private String username; + private String otpTypeCode; + private boolean isOtpOptional; + public SSOLoginData() { } @@ -60,6 +63,22 @@ public void setUsername(String username) { this.username = username; } + public String getOtpTypeCode() { + return otpTypeCode; + } + + public void setOtpTypeCode(String otpTypeCode) { + this.otpTypeCode = otpTypeCode; + } + + public boolean isOtpOptional() { + return isOtpOptional; + } + + public void setOtpOptional(boolean otpOptional) { + isOtpOptional = otpOptional; + } + @Override public String toString() { return "SSOLoginData{" + diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginPasswordPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginPasswordPage.java index ecfe6731..ec46aa67 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginPasswordPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginPasswordPage.java @@ -88,6 +88,10 @@ private void doOnSubmit(LoginBean loginBean, SSOLoginData loginData) { private void onPasswordChecked(LoginBean loginBean, SSOLoginData loginData) { loginData.setUsername(loginBean.getUsername()); UserForDescription userDescription = userService.getUserForDescription(loginData.getUsername()); + + loginData.setOtpTypeCode(userDescription.getOtpTypeCode()); + loginData.setOtpOptional(userDescription.isOtpOptional()); + OTPType otpType = userDescription.getOtpType(); switch (otpType) { case GOOGLE_AUTH: From 5fbe09acafdbe86f050145e3debd12b1af2e1f9c Mon Sep 17 00:00:00 2001 From: iv Date: Fri, 5 Dec 2025 06:19:21 +0300 Subject: [PATCH 32/91] #111 remote login css update --- .../wicket/page/user/ChangePasswordPanel.html | 3 +- superfly-web/src/main/webapp/css/main.css | 2 +- .../src/main/webapp/css/sso-login-form.css | 251 +++++++++++++----- 3 files changed, 181 insertions(+), 75 deletions(-) diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ChangePasswordPanel.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ChangePasswordPanel.html index e047dee8..2ae56e81 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ChangePasswordPanel.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ChangePasswordPanel.html @@ -9,7 +9,6 @@
-
@@ -44,4 +43,4 @@
- \ No newline at end of file + diff --git a/superfly-web/src/main/webapp/css/main.css b/superfly-web/src/main/webapp/css/main.css index 5c2753f5..9cd20729 100644 --- a/superfly-web/src/main/webapp/css/main.css +++ b/superfly-web/src/main/webapp/css/main.css @@ -369,4 +369,4 @@ tr.tabelRowPointer { .wicket-aa-container { background: #fff; box-shadow: 0px 7px 10px #c3c3c3; -} \ No newline at end of file +} diff --git a/superfly-web/src/main/webapp/css/sso-login-form.css b/superfly-web/src/main/webapp/css/sso-login-form.css index c4972821..c20d2a3d 100644 --- a/superfly-web/src/main/webapp/css/sso-login-form.css +++ b/superfly-web/src/main/webapp/css/sso-login-form.css @@ -1,109 +1,216 @@ +/* Modern Reset & Base */ html { - background: #fff; - color: #274152; - font: 12px Arial, Helvetica, sans-serif; + height: 100%; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; } -div, input, form, label { +body { + height: 100%; margin: 0; + background-color: #f4f6f8; + display: flex; + align-items: center; + justify-content: center; + color: #333; +} + +div, input, form, label, ul, li { + box-sizing: border-box; +} + +/* Bootstrap Analogs for SSO */ +.container { + width: 100%; padding: 0; } -.superfly-login-page { - border: 1px solid #dedede; - background: #fff; - border-radius: 10px; - -moz-border-radius: 10px; - -webkit-border-radius: 10px; - width: 400px; - margin: 10em auto; - padding: 30px 42px; - background: -webkit-gradient(linear, left top, left bottom, from(#fefefe), to(#E5ECF9)); - background: -moz-linear-gradient(top,#fefefe,#dedede); - filter: progid:DXImageTransform.Microsoft.gradient(enabled='true',startColorstr=#fefefe,endColorstr=#E5ECF9,GradientType=0); zoom: 1; +.col-12, .col-md-4 { + width: 100%; +} - box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); - -webkit-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); - -moz-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); +.m-auto { + margin: auto; +} +.pb-2 { padding-bottom: 0.5rem; } +.p-2 { padding: 0.5rem; } +.py-4 { padding-top: 1.5rem; padding-bottom: 1.5rem; } +.my-4 { margin-top: 1.5rem; margin-bottom: 1.5rem; } +.mt-4 { margin-top: 1.5rem } +.bg-light { background-color: #f8f9fa; } +.border { border: 1px solid #dee2e6; } +.rounded { border-radius: 0.25rem; } +.shadow { box-shadow: 0 0.5rem 1rem rgba(0,0,0,.15); } + +.form-group { + margin-bottom: 1rem; } -.superfly-button:hover { - border:1px solid #333; +.form-signin { + width: 100%; } -.superfly-button:active { - background-color: fefefe; - background: -webkit-gradient(linear, left top, left bottom, from(#f4f4f4), to(#dedede)); - background: -moz-linear-gradient(top,#f4f4f4,#dedede); - background gradient(top,#f4f4f4,#dedede); - filter: progid:DXImageTransform.Microsoft.gradient(enabled='true',startColorstr=#e9e9e9,endColorstr=#dedede,GradientType=0); zoom: 1; - padding: 1px 15px 1px 17px; +.form-control { + display: block; + width: 100%; + height: 40px; + padding: 8px 12px; + font-size: 16px; + line-height: 1.5; + color: #2d3748; + background-color: #fff; + border: 1px solid #cbd5e0; + border-radius: 6px; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } -.superfly-button { - margin: 0; - padding: 1px 16px ; - border:1px solid #9db2b2; - line-height:29px; - background-color: #fefefe; - /*background:#ffd48e url(../images/bg-save-btn.gif) repeat-x 0 100%;*/ - -moz-border-radius:5px; - -webkit-border-radius:5px; - border-radius:5px; - font-size:14px; - color:#333; - font-weight: bold; - background: -webkit-gradient(linear, left top, left bottom, from(#fefefe), to(#e9e9e9)); - background: -moz-linear-gradient(top,#fefefe,#e9e9e9); - filter: progid:DXImageTransform.Microsoft.gradient(enabled='true',startColorstr=#fefefe,endColorstr=#e9e9e9,GradientType=0); zoom: 1; +.form-control:focus { + border-color: #3182ce; + outline: 0; + box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.15); +} + +.btn { + display: inline-block; + font-weight: 600; + text-align: center; + vertical-align: middle; cursor: pointer; + border: 1px solid transparent; + padding: 0 24px; + font-size: 16px; + line-height: 38px; + height: 40px; + border-radius: 6px; + width: 100%; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} + +.btn-primary { + color: #fff; + background-color: #3182ce; + border-color: #3182ce; } -.superfly-form { - width: 400px; +.btn-primary:hover { + background-color: #2b6cb0; + border-color: #2b6cb0; +} +.btn-block { + display: block; + width: 100%; } -.superfly-form-row{ - display: inline; + +/* Original SSO Styles (Updated) */ +.superfly-login-page { + background: #fff; + width: 100%; + max-width: 400px; + padding: 40px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + border: 1px solid #e1e1e8; + margin: 20px; } + +/* Legacy classes that might still be used by other SSO pages */ h2 { - color: #8d919e; - font-size: 18px; - font-weight: normal; + margin: 0 0 24px 0; + font-size: 24px; + font-weight: 600; + color: #1a1f36; + text-align: center; } -.superfly-form-label { - font-size: 12px; - margin-right: 10px; - width: 56px; - display: inline-block; +.superfly-subsystem-info { + margin-bottom: 24px; + font-size: 14px; + color: #697386; + text-align: center; + line-height: 1.5; + background-color: #f8f9fa; + padding: 12px; + border-radius: 6px; } +.superfly-subsystem-title { color: #333; font-weight: 600; } +.superfly-subsystem-url { color: #007bff; font-style: normal; word-break: break-all; } + .superfly-form-input-text, .superfly-select { - margin: 6px 0 25px; - color: #555; - font-size: 18px; - height: 25px; - padding-left: 5px; - width: 300px; + /* Map to form-control styles */ + display: block; + width: 100%; + height: 40px; + padding: 8px 12px; + font-size: 16px; + line-height: 1.5; + color: #2d3748; + background-color: #fff; + border: 1px solid #cbd5e0; + border-radius: 6px; + margin-bottom: 20px; +} +.superfly-form-input-text:focus { + border-color: #3182ce; + box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.15); + outline: 0; } -.superfly-button-row { - text-align: right; - margin: 0; +.superfly-button { + /* Map to btn btn-primary styles */ + display: inline-block; + font-weight: 600; + text-align: center; + vertical-align: middle; + cursor: pointer; + background-color: #3182ce; + border: 1px solid #3182ce; + padding: 0 24px; + font-size: 16px; + line-height: 38px; + height: 40px; + border-radius: 6px; + color: #fff; + width: 100%; +} + +.superfly-button:hover { + background-color: #2b6cb0; + border-color: #2b6cb0; } .superfly-reason { - color: red; - margin-bottom: 10px; + color: #721c24; + background-color: #f8d7da; + border: 1px solid #f5c6cb; + padding: 12px 16px; + border-radius: 6px; + margin-bottom: 20px; + font-size: 14px; + text-align: left; + display: block; } -.superfly-subsystem-title { - font-weight: bold; +.superfly-form-label { + display: block; + margin-bottom: 8px; + font-size: 14px; + font-weight: 500; + color: #4a5568; } -.superfly-subsystem-url { - font-style: italic; -} \ No newline at end of file +/* Password Description Box (Change Password Panel) */ +#password-description ul { + padding-left: 20px; + margin: 0; + font-size: 14px; + color: #555; +} +#password-description li { + margin-bottom: 4px; +} +.feedbackPanelERROR { + color: red; +} From 4ab337598c32848bd9d954da5c5e9670ca98205f Mon Sep 17 00:00:00 2001 From: iv Date: Fri, 5 Dec 2025 07:57:28 +0300 Subject: [PATCH 33/91] #111 sso-remote-auth --- .../com/payneteasy/superfly/api/SSOUser.java | 22 +++++++++++++++++++ .../remote/check/RemoteAuthServiceImpl.java | 6 ++++- .../web/wicket/page/sso/BaseSSOPage.java | 2 +- .../web/wicket/page/sso/SSOUtils.java | 7 +++++- .../page/subsystem/EditSubsystemPage.html | 2 +- .../page/subsystem/EditSubsystemPage.java | 10 +++++---- 6 files changed, 41 insertions(+), 8 deletions(-) diff --git a/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/SSOUser.java b/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/SSOUser.java index 7faee5d1..28c2c0bb 100644 --- a/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/SSOUser.java +++ b/superfly-remote-api/src/main/java/com/payneteasy/superfly/api/SSOUser.java @@ -128,6 +128,28 @@ public void setActionsMap(Map actionsMap) { this.actionsMap = actionsMap; } + /** + * Checks if the user has the specified action. + * + * @param actionName action name to check + * @return true if the user has the action + */ + public boolean hasAction(String actionName) { + if (actionsMap == null) { + return false; + } + for (SSOAction[] actions : actionsMap.values()) { + if (actions != null) { + for (SSOAction action : actions) { + if (action != null && actionName.equals(action.getName())) { + return true; + } + } + } + } + return false; + } + /** * Returns preferences. * diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java index 29f6a75f..4d7918c8 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java @@ -35,7 +35,7 @@ public RemoteAuthServiceImpl(SubsystemService subsystemService, RemoteAuthCryptoService remoteAuthCryptoService) { this.subsystemService = subsystemService; this.internalSSOService = internalSSOService; - this.remoteAuthCryptoService = remoteAuthCryptoService; + this.remoteAuthCryptoService = new RemoteAuthCryptoServiceStub(); } @Override @@ -69,6 +69,10 @@ public RemoteAuthSession checkPassword(String subsystemName, String username, St throw new RemoteAuthException("Authentication failed", "BAD_USER_OR_PASSWORD_OR_OTP"); } + if (ssoUser.hasAction("action_temp_password")) { + throw new RemoteAuthException("User should change password", "USER_SHOULD_CHANGE_PASSWORD"); + } + // 4. Generate Session Token and Cache String sessionToken = UUID.randomUUID().toString(); sessionCache.put(sessionToken, new RemoteSession(username, ssoUser.getOtpType())); diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/BaseSSOPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/BaseSSOPage.java index 1ff6da69..0f1789dd 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/BaseSSOPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/BaseSSOPage.java @@ -71,7 +71,7 @@ protected boolean validateCsrfToken() { private HttpSession getHttpSession() { HttpServletRequest request = getHttpServletRequest(); - return request.getSession(false); + return request.getSession(true); } private HttpServletRequest getHttpServletRequest() { diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOUtils.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOUtils.java index 75b1c05a..6741e011 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOUtils.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOUtils.java @@ -116,7 +116,12 @@ public static void onSuccessfulLogin(String username, ssoSession.getId(), loginData.getSubsystemIdentifier()); if (token != null) { // can login: redirecting a user to a subsystem - SSOUtils.redirectToSubsystem(page, loginData, token); + if ("no-target".equals(loginData.getTargetUrl()) || "/no-target".equals(loginData.getTargetUrl())) { + SSOUtils.anonymizeLoginData(page); + SSOUtils.redirect(page, token.getLandingUrl()); + } else { + SSOUtils.redirectToSubsystem(page, loginData, token); + } } else { // can't login: just display an error // actually, this should not happen as we've already diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html index 1d885b01..3be73bdd 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.html @@ -24,7 +24,7 @@
-
key
+ Generate new RSA key pair diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java index 444e9047..903a41cb 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/subsystem/EditSubsystemPage.java @@ -16,6 +16,7 @@ import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.ChoiceRenderer; import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.form.TextArea; import org.apache.wicket.markup.html.form.SubmitLink; import org.apache.wicket.markup.html.link.BookmarkablePageLink; import org.apache.wicket.model.CompoundPropertyModel; @@ -90,16 +91,17 @@ public void onClick(AjaxRequestTarget aTarget) { }); // Public key fields - final Label labelPublicKey = new Label("publicKey", new LoadableDetachableModel() { + final TextArea textAreaPublicKey = new TextArea<>("publicKey", new LoadableDetachableModel<>() { @Override protected String load() { return subsystem.getPublicKey(); } }); - labelPublicKey.setOutputMarkupId(true); + textAreaPublicKey.setOutputMarkupId(true); + textAreaPublicKey.setEnabled(false); form.add(new Label("publicKeyLabel", new ResourceModel("subsystem.edit.publicKey"))); - form.add(labelPublicKey); + form.add(textAreaPublicKey); form.add(new IndicatingAjaxLink("generateNewKeyPair") { private static final long serialVersionUID = 1L; @@ -109,7 +111,7 @@ public void onClick(AjaxRequestTarget aTarget) { subsystem.setPrivateKey(keyPairData.privateKey()); subsystem.setPublicKey(keyPairData.publicKey()); subsystem.setEncryptionAlgorithm(RemoteAuthEncryptionAlgorithm.RSA.name()); - aTarget.add(labelPublicKey); + aTarget.add(textAreaPublicKey); } catch (Exception e) { logger.error("Error while generating key pair for subsystem {}", subsystem.getName(), e); error("Error while generating key pair: " + e.getMessage()); From 27d4e54a21c1c04c529df4135354e8f01700c75a Mon Sep 17 00:00:00 2001 From: iv Date: Fri, 5 Dec 2025 08:00:50 +0300 Subject: [PATCH 34/91] #111 sso-remote-auth --- .../service/impl/remote/check/RemoteAuthServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java index 4d7918c8..3956ad0a 100644 --- a/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java +++ b/superfly-service/src/main/java/com/payneteasy/superfly/service/impl/remote/check/RemoteAuthServiceImpl.java @@ -35,7 +35,7 @@ public RemoteAuthServiceImpl(SubsystemService subsystemService, RemoteAuthCryptoService remoteAuthCryptoService) { this.subsystemService = subsystemService; this.internalSSOService = internalSSOService; - this.remoteAuthCryptoService = new RemoteAuthCryptoServiceStub(); + this.remoteAuthCryptoService = remoteAuthCryptoService; } @Override From 9ed899350d9c8cfd3f4b216258da304ba80153fc Mon Sep 17 00:00:00 2001 From: iv Date: Mon, 8 Dec 2025 17:12:00 +0300 Subject: [PATCH 35/91] #111 ui updates --- .../web/wicket/SuperflyApplication.java | 5 ++-- .../component/otp/GoogleAuthSetupPanel.html | 8 +++--- .../web/wicket/page/sso/BaseSSOPage.html | 4 +-- .../wicket/page/sso/SSOLoginPasswordPage.java | 3 +++ .../web/wicket/page/sso/SSOUtils.java | 27 +++++++++++++++++++ .../wicket/page/user/ChangePasswordPanel.html | 2 +- .../web/wicket/page/user/ListUsersPage.html | 4 +-- .../src/main/webapp/css/sso-login-form.css | 2 +- .../wicket/PageInterceptingRequestMapper.java | 8 +++++- 9 files changed, 49 insertions(+), 14 deletions(-) diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.java index 5068cb16..0a65870d 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/SuperflyApplication.java @@ -42,7 +42,6 @@ import com.payneteasy.superfly.wicket.InterceptionDecisions; import com.payneteasy.superfly.wicket.PageInterceptingRequestMapper; import org.apache.wicket.Page; -import org.apache.wicket.core.request.mapper.CryptoMapper; import org.apache.wicket.request.IRequestMapper; import org.apache.wicket.request.Request; import org.apache.wicket.settings.RequestCycleSettings; @@ -52,8 +51,8 @@ public class SuperflyApplication extends BaseApplication { @Override protected void customInit() { getSecuritySettings().setAuthorizationStrategy(new SpringSecurityAuthorizationStrategy()); - CryptoMapper requestMapper = new CryptoMapper(getRootRequestMapper(), this); - setRootRequestMapper(wrapWithInterceptingMapper(requestMapper)); + // BaseApplication.init() уже создал CryptoMapper, просто оборачиваем его в PageInterceptingRequestMapper + setRootRequestMapper(wrapWithInterceptingMapper(getRootRequestMapper())); mountBookmarkablePageWithPath("/loginbase", LoginPageWithoutHOTP.class); mountBookmarkablePageWithPath("/login", LoginPasswordStepPage.class); diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/component/otp/GoogleAuthSetupPanel.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/component/otp/GoogleAuthSetupPanel.html index 9fbc2484..5c19779a 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/component/otp/GoogleAuthSetupPanel.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/component/otp/GoogleAuthSetupPanel.html @@ -1,17 +1,17 @@
-
+
Your secret key for google authenticator: - +
-
+
-
+
Learn more about Google authenticator diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/BaseSSOPage.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/BaseSSOPage.html index d3c5d56d..4f685e65 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/BaseSSOPage.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/BaseSSOPage.html @@ -20,9 +20,9 @@ - \ No newline at end of file + diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginPasswordPage.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginPasswordPage.java index ec46aa67..d69bc136 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginPasswordPage.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOLoginPasswordPage.java @@ -76,6 +76,9 @@ private void doOnSubmit(LoginBean loginBean, SSOLoginData loginData) { onPasswordChecked(loginBean, loginData); break; case FAILED: + // Clear SSO session and cookie after failed authentication + // to prevent reuse of old sessions + SSOUtils.clearSSOSession(this, sessionService); errorMessageModel.setObject("The username or password you entered is incorrect or user is locked."); errorMessageLabel.setVisible(true); break; diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOUtils.java b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOUtils.java index 6741e011..13c8cc7b 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOUtils.java +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/sso/SSOUtils.java @@ -17,6 +17,7 @@ import org.apache.wicket.request.http.handler.RedirectRequestHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; import jakarta.servlet.http.Cookie; import java.net.URLEncoder; @@ -130,6 +131,32 @@ public static void onSuccessfulLogin(String username, } } + /** + * Clears SSO session and cookie after failed authentication. + * This prevents reuse of old sessions after authentication failure. + * + * @param page the page to get request/response from + * @param sessionService service to delete SSO session + */ + public static void clearSSOSession(SessionAccessorPage page, SessionService sessionService) { + WebRequest request = (WebRequest) page.getRequest(); + String ssoSessionId = getSsoSessionIdFromCookie(request); + + if (StringUtils.hasText(ssoSessionId)) { + logger.debug("Clearing SSO session after failed authentication: {}", ssoSessionId); + sessionService.deleteSSOSession(ssoSessionId); + } + + // Clear cookie by setting maxAge to 0 + Cookie cookie = new Cookie(SSO_SESSION_ID_COOKIE_NAME, ""); + cookie.setMaxAge(0); + cookie.setPath(getApplicationRootPath(page)); + ((WebResponse) RequestCycle.get().getResponse()).addCookie(cookie); + + // Clear username from login data + anonymizeLoginData(page); + } + private static String getApplicationRootPath(SessionAccessorPage page) { String contextPath = ((ServletWebRequest) page.getRequest()).getContextPath(); String filterPath = ((ServletWebRequest) page.getRequest()).getFilterPath(); diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ChangePasswordPanel.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ChangePasswordPanel.html index 2ae56e81..533a8f95 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ChangePasswordPanel.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ChangePasswordPanel.html @@ -12,7 +12,7 @@
- You have to change your password. +

You have to change your password.

diff --git a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.html b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.html index fce11069..66e6e24a 100644 --- a/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.html +++ b/superfly-web/src/main/java/com/payneteasy/superfly/web/wicket/page/user/ListUsersPage.html @@ -15,10 +15,10 @@ - +