From 50b3c6eb1d024653c68a637707fb4b2f9d9bc394 Mon Sep 17 00:00:00 2001 From: yura42 Date: Wed, 26 Nov 2025 23:08:20 +0300 Subject: [PATCH] init:wq --- .gitignore | 38 +++ .idea/.gitignore | 8 + .idea/encodings.xml | 7 + .idea/misc.xml | 14 + .idea/vcs.xml | 6 + pom.xml | 168 ++++++++++++ src/main/java/ru/copperside/AuthServer.java | 11 + .../controller/AuthInfoController.java | 27 ++ .../controller/DebugAuthController.java | 58 ++++ .../copperside/model/authinfo/AuthInfo.java | 25 ++ .../copperside/model/authinfo/SecretData.java | 19 ++ .../copperside/model/dto/ParametersDTO.java | 8 + .../model/dto/RolePermissionDto.java | 14 + .../ru/copperside/model/dto/SecretDTO.java | 10 + .../ru/copperside/model/enums/AuthType.java | 21 ++ .../model/enums/AuthenticationType.java | 16 ++ .../ru/copperside/model/enums/KeyUsage.java | 21 ++ .../ru/copperside/model/enums/SecretType.java | 6 + .../model/permission/Permission.java | 18 ++ .../model/permission/PermissionSettings.java | 21 ++ .../model/session/SessionSettings.java | 20 ++ .../repository/AuthInfoRepository.java | 10 + .../repository/AuthInfoRepositoryJdbc.java | 250 ++++++++++++++++++ .../repository/PermissionsRepository.java | 12 + .../repository/PermissionsRepositoryJdbc.java | 70 +++++ .../repository/PrivateDataRepository.java | 7 + .../repository/SecretRepository.java | 10 + .../repository/SecretRepositoryJdbc.java | 70 +++++ .../repository/SettingsRepository.java | 7 + .../copperside/service/AuthInfoService.java | 24 ++ .../copperside/service/DebugAuthService.java | 45 ++++ .../java/ru/copperside/sql/SqlRegistry.java | 65 +++++ .../copperside/util/PermissionsCompiler.java | 123 +++++++++ .../ru/copperside/util/RepoMappingHelper.java | 82 ++++++ .../util/SessionSettingsHelper.java | 64 +++++ src/main/resources/application.yaml | 9 + .../find_authinfo_init_by_dataid_and_type.sql | 21 ++ ...d_inherited_permissions_by_hierarchyid.sql | 24 ++ ...erited_role_permissions_by_hierarchyid.sql | 22 ++ .../sql/auth/find_privatedata_by_authid.sql | 3 + .../sql/auth/find_rolesettings_by_authid.sql | 11 + .../auth/find_secret_by_dataid_and_type.sql | 13 + 42 files changed, 1478 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 pom.xml create mode 100644 src/main/java/ru/copperside/AuthServer.java create mode 100644 src/main/java/ru/copperside/controller/AuthInfoController.java create mode 100644 src/main/java/ru/copperside/controller/DebugAuthController.java create mode 100644 src/main/java/ru/copperside/model/authinfo/AuthInfo.java create mode 100644 src/main/java/ru/copperside/model/authinfo/SecretData.java create mode 100644 src/main/java/ru/copperside/model/dto/ParametersDTO.java create mode 100644 src/main/java/ru/copperside/model/dto/RolePermissionDto.java create mode 100644 src/main/java/ru/copperside/model/dto/SecretDTO.java create mode 100644 src/main/java/ru/copperside/model/enums/AuthType.java create mode 100644 src/main/java/ru/copperside/model/enums/AuthenticationType.java create mode 100644 src/main/java/ru/copperside/model/enums/KeyUsage.java create mode 100644 src/main/java/ru/copperside/model/enums/SecretType.java create mode 100644 src/main/java/ru/copperside/model/permission/Permission.java create mode 100644 src/main/java/ru/copperside/model/permission/PermissionSettings.java create mode 100644 src/main/java/ru/copperside/model/session/SessionSettings.java create mode 100644 src/main/java/ru/copperside/repository/AuthInfoRepository.java create mode 100644 src/main/java/ru/copperside/repository/AuthInfoRepositoryJdbc.java create mode 100644 src/main/java/ru/copperside/repository/PermissionsRepository.java create mode 100644 src/main/java/ru/copperside/repository/PermissionsRepositoryJdbc.java create mode 100644 src/main/java/ru/copperside/repository/PrivateDataRepository.java create mode 100644 src/main/java/ru/copperside/repository/SecretRepository.java create mode 100644 src/main/java/ru/copperside/repository/SecretRepositoryJdbc.java create mode 100644 src/main/java/ru/copperside/repository/SettingsRepository.java create mode 100644 src/main/java/ru/copperside/service/AuthInfoService.java create mode 100644 src/main/java/ru/copperside/service/DebugAuthService.java create mode 100644 src/main/java/ru/copperside/sql/SqlRegistry.java create mode 100644 src/main/java/ru/copperside/util/PermissionsCompiler.java create mode 100644 src/main/java/ru/copperside/util/RepoMappingHelper.java create mode 100644 src/main/java/ru/copperside/util/SessionSettingsHelper.java create mode 100644 src/main/resources/application.yaml create mode 100644 src/main/resources/sql/auth/find_authinfo_init_by_dataid_and_type.sql create mode 100644 src/main/resources/sql/auth/find_inherited_permissions_by_hierarchyid.sql create mode 100644 src/main/resources/sql/auth/find_inherited_role_permissions_by_hierarchyid.sql create mode 100644 src/main/resources/sql/auth/find_privatedata_by_authid.sql create mode 100644 src/main/resources/sql/auth/find_rolesettings_by_authid.sql create mode 100644 src/main/resources/sql/auth/find_secret_by_dataid_and_type.sql diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..de5c651 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..0f8f25c --- /dev/null +++ b/pom.xml @@ -0,0 +1,168 @@ + + + 4.0.0 + + ru.copperside + AuthServer + 1.0 + jar + AuthServer + + + org.springframework.boot + spring-boot-starter-parent + 3.5.5 + + + + + 25 + 25 + UTF-8 + + 2.8.14 + 23.4.0.24.05 + 23.3.0.23.09 + 3.18.0 + 2.2.39 + + 3.11.0 + 3.2.1 + + + + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + io.swagger.core.v3 + swagger-core-jakarta + ${swagger-core-jakarta.version} + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-jetty + + + + + org.projectlombok + lombok + provided + + + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + + + com.oracle.database.nls + orai18n + ${ojdbc.version} + + + com.oracle.database.jdbc + ojdbc11 + ${ojdbc.version} + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc.version} + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.plugin.version} + + 17 + + -parameters + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven.enforcer.plugin.version} + + + enforce + enforce + + + + + 17 + + + true + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.5.0 + + + + + \ No newline at end of file diff --git a/src/main/java/ru/copperside/AuthServer.java b/src/main/java/ru/copperside/AuthServer.java new file mode 100644 index 0000000..94d227c --- /dev/null +++ b/src/main/java/ru/copperside/AuthServer.java @@ -0,0 +1,11 @@ +package ru.copperside; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AuthServer { + public static void main(String[] args) { + SpringApplication.run(AuthServer.class, args); + } +} \ No newline at end of file diff --git a/src/main/java/ru/copperside/controller/AuthInfoController.java b/src/main/java/ru/copperside/controller/AuthInfoController.java new file mode 100644 index 0000000..16aa082 --- /dev/null +++ b/src/main/java/ru/copperside/controller/AuthInfoController.java @@ -0,0 +1,27 @@ +/* +package ru.copperside.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.copperside.model.authinfo.AuthInfo; +import ru.copperside.service.AuthInfoService; + +@RestController +@RequestMapping("/api/authinfo") +@RequiredArgsConstructor +public class AuthInfoController { + private final AuthInfoService service; + + // Пример: GET /api/authinfo/user123?type=Secret + @GetMapping("/{dataId}") + public ResponseEntity getByDataIdAndType( + @PathVariable String dataId + ) { + return ResponseEntity.ok(service.getByDataIdAndType(dataId)); + } +} +*/ \ No newline at end of file diff --git a/src/main/java/ru/copperside/controller/DebugAuthController.java b/src/main/java/ru/copperside/controller/DebugAuthController.java new file mode 100644 index 0000000..eb05ac6 --- /dev/null +++ b/src/main/java/ru/copperside/controller/DebugAuthController.java @@ -0,0 +1,58 @@ +package ru.copperside.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.copperside.model.authinfo.SecretData; +import ru.copperside.model.dto.RolePermissionDto; +import ru.copperside.model.enums.AuthenticationType; +import ru.copperside.model.permission.Permission; +import ru.copperside.service.DebugAuthService; + +import java.util.List; + +@RestController +@RequestMapping("/debug/auth") +@RequiredArgsConstructor +public class DebugAuthController { + + private final DebugAuthService service; + + /** + * GET /debug/auth/secret/{dataId}?type=Secret + * Если type не указан, используется AuthenticationType.Secret. + */ + @GetMapping("/secret/{dataId}") + public ResponseEntity getSecret( + @PathVariable String dataId, + @RequestParam(required = false) String type + ) { + String resolvedType = (type == null || type.isBlank()) + ? AuthenticationType.Secret.name() + : type; + + SecretData secretData = service.getSecretData(dataId, resolvedType); + return ResponseEntity.ok(secretData); + } + + @GetMapping("/permission/{hierarchyId}") + public ResponseEntity> getRolePermission( + @PathVariable Long hierarchyId + ) { + return ResponseEntity.ok(service.byHierarchy(hierarchyId)); + } + + @GetMapping("/personal_permission/{hierarchyId}") + public ResponseEntity> getPersonalPermission( + @PathVariable Long hierarchyId + ) { + return ResponseEntity.ok(service.PersonalbyHierarchy(hierarchyId)); + } + + @GetMapping("/permissions/{hierarchyId}") + public ResponseEntity> getPermissions( + @PathVariable Long hierarchyId + ) { + return ResponseEntity.ok(service.getPermissions(hierarchyId)); + } +} \ No newline at end of file diff --git a/src/main/java/ru/copperside/model/authinfo/AuthInfo.java b/src/main/java/ru/copperside/model/authinfo/AuthInfo.java new file mode 100644 index 0000000..a2b56a4 --- /dev/null +++ b/src/main/java/ru/copperside/model/authinfo/AuthInfo.java @@ -0,0 +1,25 @@ +package ru.copperside.model.authinfo; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Builder; +import lombok.extern.jackson.Jacksonized; +import ru.copperside.model.permission.Permission; +import ru.copperside.model.session.SessionSettings; + +import java.util.List; + +@Jacksonized +@Builder +public record AuthInfo( + Long authId, + String dataId, + Long hierarchyId, + String displayName, + Boolean isEnabled, + Boolean needActivation, + List permissions, + SessionSettings sessionSettings, + JsonNode sessionData, + JsonNode privateData, + SecretData secretData +) { } \ No newline at end of file diff --git a/src/main/java/ru/copperside/model/authinfo/SecretData.java b/src/main/java/ru/copperside/model/authinfo/SecretData.java new file mode 100644 index 0000000..d41282a --- /dev/null +++ b/src/main/java/ru/copperside/model/authinfo/SecretData.java @@ -0,0 +1,19 @@ +package ru.copperside.model.authinfo; + +import lombok.Builder; +import lombok.extern.jackson.Jacksonized; +import ru.copperside.model.enums.AuthenticationType; +import ru.copperside.model.enums.KeyUsage; +import ru.copperside.model.enums.SecretType; + +import java.util.EnumSet; + +@Jacksonized +@Builder +public record SecretData( + AuthenticationType authType, + Boolean isEnabled, + EnumSet keyUsages, + SecretType secretType, + String secret +) {} diff --git a/src/main/java/ru/copperside/model/dto/ParametersDTO.java b/src/main/java/ru/copperside/model/dto/ParametersDTO.java new file mode 100644 index 0000000..2aab0f9 --- /dev/null +++ b/src/main/java/ru/copperside/model/dto/ParametersDTO.java @@ -0,0 +1,8 @@ +package ru.copperside.model.dto; + +import com.fasterxml.jackson.annotation.JsonAlias; + +public record ParametersDTO( + @JsonAlias({"IsEnabled","isEnabled"}) Boolean isEnabled, + @JsonAlias({"NeedActivation","needActivation"}) Boolean needActivation +) {} \ No newline at end of file diff --git a/src/main/java/ru/copperside/model/dto/RolePermissionDto.java b/src/main/java/ru/copperside/model/dto/RolePermissionDto.java new file mode 100644 index 0000000..24a65c5 --- /dev/null +++ b/src/main/java/ru/copperside/model/dto/RolePermissionDto.java @@ -0,0 +1,14 @@ +package ru.copperside.model.dto; + +public record RolePermissionDto( + Long hierarchyId, + Integer level, + Long roleId, + Long permissionId, + String permissionStrId, + String settingsJson, + Boolean action, + String pDataJson, + String command, + String http +) { } \ No newline at end of file diff --git a/src/main/java/ru/copperside/model/dto/SecretDTO.java b/src/main/java/ru/copperside/model/dto/SecretDTO.java new file mode 100644 index 0000000..8168557 --- /dev/null +++ b/src/main/java/ru/copperside/model/dto/SecretDTO.java @@ -0,0 +1,10 @@ +package ru.copperside.model.dto; + +import com.fasterxml.jackson.annotation.JsonAlias; + +public record SecretDTO( + @JsonAlias({"IsEnable","isEnable"}) Boolean isEnable, + @JsonAlias({"KeyUsage","keyUsage"}) Object keyUsage, + @JsonAlias({"SecretType","secretType"}) String secretType, + @JsonAlias({"Secret","secret"}) String secret +) {} \ No newline at end of file diff --git a/src/main/java/ru/copperside/model/enums/AuthType.java b/src/main/java/ru/copperside/model/enums/AuthType.java new file mode 100644 index 0000000..0be02cb --- /dev/null +++ b/src/main/java/ru/copperside/model/enums/AuthType.java @@ -0,0 +1,21 @@ +package ru.copperside.model.enums; + +public enum AuthType { + Unknown(0), + Simple(1), + TwoStep(2); + + private final int code; + + AuthType(int code) { this.code = code; } + public int code() { return code; } + + public static AuthType fromCode(int code) { + return switch (code) { + case 0 -> Unknown; + case 1 -> Simple; + case 2 -> TwoStep; + default -> throw new IllegalArgumentException("Unknown AuthType code: " + code); + }; + } +} \ No newline at end of file diff --git a/src/main/java/ru/copperside/model/enums/AuthenticationType.java b/src/main/java/ru/copperside/model/enums/AuthenticationType.java new file mode 100644 index 0000000..e92a3bf --- /dev/null +++ b/src/main/java/ru/copperside/model/enums/AuthenticationType.java @@ -0,0 +1,16 @@ +package ru.copperside.model.enums; + +public enum AuthenticationType { + Secret, + Windows; + + /** Возвращает enum по имени, default = Secret. */ + public static AuthenticationType from(String name) { + if (name == null) return Secret; + try { + return AuthenticationType.valueOf(name); + } catch (IllegalArgumentException ex) { + return Secret; + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/copperside/model/enums/KeyUsage.java b/src/main/java/ru/copperside/model/enums/KeyUsage.java new file mode 100644 index 0000000..a0a9a6d --- /dev/null +++ b/src/main/java/ru/copperside/model/enums/KeyUsage.java @@ -0,0 +1,21 @@ +package ru.copperside.model.enums; + +public enum KeyUsage { + None(0), + Password(1), + HMac(2); + + private final int code; + + KeyUsage(int code) { this.code = code; } + public int code() { return code; } + + public static KeyUsage fromCode(int code) { + return switch (code) { + case 0 -> None; + case 1 -> Password; + case 2 -> HMac; + default -> throw new IllegalArgumentException("Unknown KeyUsage code: " + code); + }; + } +} \ No newline at end of file diff --git a/src/main/java/ru/copperside/model/enums/SecretType.java b/src/main/java/ru/copperside/model/enums/SecretType.java new file mode 100644 index 0000000..89e4892 --- /dev/null +++ b/src/main/java/ru/copperside/model/enums/SecretType.java @@ -0,0 +1,6 @@ +package ru.copperside.model.enums; + +public enum SecretType { + EncodedData, + PlainTextData +} \ No newline at end of file diff --git a/src/main/java/ru/copperside/model/permission/Permission.java b/src/main/java/ru/copperside/model/permission/Permission.java new file mode 100644 index 0000000..7897dc8 --- /dev/null +++ b/src/main/java/ru/copperside/model/permission/Permission.java @@ -0,0 +1,18 @@ +package ru.copperside.model.permission; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Builder; +import lombok.extern.jackson.Jacksonized; + +@Jacksonized +@Builder(toBuilder = true) +public record Permission( + Long permissionId, + String permissionStrId, + PermissionSettings settings, + Boolean action, + JsonNode permissionData, + String command, + String http +) {} \ No newline at end of file diff --git a/src/main/java/ru/copperside/model/permission/PermissionSettings.java b/src/main/java/ru/copperside/model/permission/PermissionSettings.java new file mode 100644 index 0000000..ac17331 --- /dev/null +++ b/src/main/java/ru/copperside/model/permission/PermissionSettings.java @@ -0,0 +1,21 @@ +package ru.copperside.model.permission; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Builder; +import lombok.extern.jackson.Jacksonized; + +@Jacksonized +@Builder(toBuilder = true) +public record PermissionSettings( + Boolean needLog, + Boolean requestLog, + Boolean needConfirmationCode +) { + public PermissionSettings normalize() { + return this.toBuilder() + .needLog(Boolean.TRUE.equals(needLog)) + .requestLog(Boolean.TRUE.equals(requestLog)) + .needConfirmationCode(Boolean.TRUE.equals(needConfirmationCode)) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/copperside/model/session/SessionSettings.java b/src/main/java/ru/copperside/model/session/SessionSettings.java new file mode 100644 index 0000000..e6bca14 --- /dev/null +++ b/src/main/java/ru/copperside/model/session/SessionSettings.java @@ -0,0 +1,20 @@ +package ru.copperside.model.session; + +import lombok.Builder; +import lombok.extern.jackson.Jacksonized; +import ru.copperside.model.enums.AuthType; + +import java.time.Duration; +import java.util.List; + +@Jacksonized +@Builder +public record SessionSettings( + Duration ttl, + Boolean autoProlongation, + AuthType authType, + List authStepTypes, + Boolean ignoreConfirmation, + Boolean oneActiveSession, + Boolean inMemory +) {} \ No newline at end of file diff --git a/src/main/java/ru/copperside/repository/AuthInfoRepository.java b/src/main/java/ru/copperside/repository/AuthInfoRepository.java new file mode 100644 index 0000000..ba5b288 --- /dev/null +++ b/src/main/java/ru/copperside/repository/AuthInfoRepository.java @@ -0,0 +1,10 @@ +package ru.copperside.repository; + +import ru.copperside.model.authinfo.AuthInfo; + +import java.util.Optional; + +public interface AuthInfoRepository { + Optional findByDataIdAndType(String dataId, String type); + Optional findByAuthId(Long authId); +} \ No newline at end of file diff --git a/src/main/java/ru/copperside/repository/AuthInfoRepositoryJdbc.java b/src/main/java/ru/copperside/repository/AuthInfoRepositoryJdbc.java new file mode 100644 index 0000000..4ed9480 --- /dev/null +++ b/src/main/java/ru/copperside/repository/AuthInfoRepositoryJdbc.java @@ -0,0 +1,250 @@ +/* +package ru.copperside.repository; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; + + +import ru.copperside.model.authinfo.*; +import ru.copperside.model.enums.AuthType; +import ru.copperside.model.enums.KeyUsage; +import ru.copperside.model.enums.SecretType; +import ru.copperside.model.session.SessionSettings; +import ru.copperside.sql.SqlRegistry; +import ru.copperside.util.SessionSettingsHelper; + +import static ru.copperside.util.RepoMappingHelper.*; + +import java.sql.ResultSet; +import java.time.Duration; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; + +@Repository +@RequiredArgsConstructor +public class AuthInfoRepositoryJdbc implements AuthInfoRepository { + private final NamedParameterJdbcTemplate jdbc; + private final ObjectMapper om; + private final SqlRegistry sqlRegistry; + + private static final String SQL_INIT = "auth/find_authinfo_init_by_dataid_and_type"; + private static final String SQL_PRIV = "auth/find_privatedata_by_authid"; + private static final String SQL_ROLESET = "auth/find_rolesettings_by_authid"; + + @Override + public Optional findByDataIdAndType(String dataId, String type) { + var params = new MapSqlParameterSource() + .addValue("dataId", dataId) + .addValue("type", type); + + RowMapper rm = (rs, rn) -> mapAuthInfo(rs); + + List list = jdbc.query(sqlRegistry.get(SQL_INIT), params, rm); + + if (!list.isEmpty()) { + AuthInfo base = list.get(0); + if (base.authId() != null) { + JsonNode privateData = loadPrivateData(base.authId()); + return Optional.of(base.toBuilder().privateData(privateData).build()); + } + } + + return Optional.empty(); + } + + private JsonNode loadPrivateData(Long authId) { + if (authId == null) return null; + var params = new MapSqlParameterSource().addValue("authId", authId); + ObjectNode pd = om.createObjectNode(); + + jdbc.query(sqlRegistry.get(SQL_PRIV), params, rs -> { + String key = rs.getString("KEY"); + String value = rs.getString("VALUE"); + try { + JsonNode node = om.readTree(value); + pd.set(key, node); + } catch (Exception e) { + pd.put(key, value); + } + }); + return pd; + } + + private AuthInfo mapAuthInfo(ResultSet rs) { + try { + Long authId = getLong(rs, "AD_AUTHID"); // AD_AUTHID -> AuthInfo.authId + String dataId = rs.getString("AD_DATAID"); // AD_DATAID -> AuthInfo.dataId + Long hierarchyId = getLong(rs, "AH_HIERARCHYID"); // AH_HIERARCHYID -> AuthInfo.hierarchyId + String displayName = rs.getString("AH_DISPLAYNAME"); // AH_DISPLAYNAME -> AuthInfo.displayName + String secretType = rs.getString("AD_TYPE"); + + Boolean isEnabled = null; + Boolean needActivation = null; + + JsonNode paramsJson = readJsonFlexible(rs, "AI_PARAMETERS", om); + if (paramsJson != null && !paramsJson.isNull()) { + isEnabled = getBooleanCaseInsensitive(paramsJson, "IsEnabled", "isEnabled"); + needActivation = getBooleanCaseInsensitive(paramsJson, "NeedActivation", "needActivation"); + } + + SecretData secretData = null; + JsonNode secretJson = readJsonFlexible(rs, "AD_DATA", om); + + if (secretJson != null && !secretJson.isNull()) { + Boolean sdIsEnabled = getBooleanCaseInsensitive(secretJson, "IsEnable", "isEnabled"); + String keyUsageStr = getStringCaseInsensitive(secretJson, "KeyUsage", "keyUsage"); + String secretTypeStr = getStringCaseInsensitive(secretJson, "SecretType", "secretType"); + String secretValue = getStringCaseInsensitive(secretJson, "Secret", "secret"); + + EnumSet keyUsages = parseKeyUsageSet(secretJson, keyUsageStr); + SecretType sType = parseSecretType(secretTypeStr); + + secretData = SecretData.builder() + .type(secretType) // тип секрета из AD_TYPE (внешний) + .isEnabled(sdIsEnabled) + .keyUsage(keyUsages) + .secretType(sType) + .secret(secretValue) + .build(); + }else { + secretData = SecretData.builder() + .type(secretType) + .build(); + } + + JsonNode aiSettingsJson = readJsonFlexible(rs, "AI_SETTINGS", om); + SessionSettings sessionSettings = computeSessionSettings(authId, aiSettingsJson); // ⬅️ главный расчёт + + return AuthInfo.builder() + .authId(authId) + .dataId(dataId) + .hierarchyId(hierarchyId) + .displayName(displayName) + .isEnabled(isEnabled) + .needActivation(needActivation) + .permissions(List.of()) + .sessionSettings(sessionSettings) + .sessionData(null) + .privateData(null) + .secretData(secretData) + .build(); + } catch (Exception e) { + throw new IllegalStateException("Failed to map AuthInfo", e); + } + } + + private SessionSettings computeSessionSettings(Long authId, JsonNode aiSettingsJson) { + // userSettings = {}; merge(AI_SETTINGS, false) + SessionSettings userSettings = SessionSettingsHelper.merge( + SessionSettings.builder().build(), + mapSessionSettings(aiSettingsJson), + false + ); + + // Ролевые настройки с ORDER BY LEVEL (накатываем последовательно isRole=true) + var resultRef = new AtomicReference<>(SessionSettings.builder().build()); + var params = new MapSqlParameterSource().addValue("authId", authId); + jdbc.query(sqlRegistry.get(SQL_ROLESET), params, rs -> { + JsonNode roleJson = readJsonFlexible(rs, "SETTINGS", om); + SessionSettings roleSet = mapSessionSettings(roleJson); + resultRef.set(SessionSettingsHelper.merge(resultRef.get(), roleSet, true)); + }); + + // финально поверх — userSettings (isRole=false) + return SessionSettingsHelper.merge(resultRef.get(), userSettings, false); + } + + private SessionSettings mapSessionSettings(JsonNode node) { + if (node == null || node.isNull()) return null; + + Duration ttl = null; + if (node.hasNonNull("ttlSec")) { + ttl = Duration.ofSeconds(node.path("ttlSec").asLong(0)); + } else if (node.hasNonNull("ttl")) { + try { ttl = Duration.parse(node.path("ttl").asText()); } catch (Exception ignore) {} + } + + AuthType authType = null; + if (node.hasNonNull("authType")) { + var at = node.get("authType"); + if (at.isInt()) { + authType = AuthType.fromCode(at.asInt(0)); + } else { + try { authType = AuthType.valueOf(at.asText()); } catch (Exception ignore) {} + } + } + + List steps = new ArrayList<>(); + if (node.hasNonNull("authStepTypes") && node.get("authStepTypes").isArray()) { + for (JsonNode n : node.get("authStepTypes")) { + steps.add(n.asText()); + } + } + + return new SessionSettingsBuilder() + .ttl(ttl) + .autoProlongation(asNullableBool(node, "autoProlongation")) + .authType(authType) + .authStepTypes(steps) + .ignoreConfirmation(asNullableBool(node, "ignoreConfirmation")) + .oneActiveSession(asNullableBool(node, "oneActiveSession")) + .inMemory(asNullableBool(node, "inMemory")) + .build(); + } + + private Boolean asNullableBool(JsonNode node, String field) { + return node.has(field) && !node.get(field).isNull() ? node.get(field).asBoolean() : null; + } + + private static final class SessionSettingsBuilder { + private Duration ttl; + private Boolean autoProlongation; + private AuthType authType; + private List authStepTypes; + private Boolean ignoreConfirmation; + private Boolean oneActiveSession; + private Boolean inMemory; + + SessionSettingsBuilder() {} + SessionSettingsBuilder(SessionSettings base) { + if (base == null) return; + this.ttl = base.ttl(); + this.autoProlongation = base.autoProlongation(); + this.authType = base.authType(); + this.authStepTypes = base.authStepTypes(); + this.ignoreConfirmation = base.ignoreConfirmation(); + this.oneActiveSession = base.oneActiveSession(); + this.inMemory = base.inMemory(); + } + + SessionSettingsBuilder ttl(Duration v) { this.ttl = v; return this; } + SessionSettingsBuilder autoProlongation(Boolean v) { this.autoProlongation = v; return this; } + SessionSettingsBuilder authType(AuthType v) { this.authType = v; return this; } + SessionSettingsBuilder authStepTypes(List v) { this.authStepTypes = v; return this; } + SessionSettingsBuilder ignoreConfirmation(Boolean v) { this.ignoreConfirmation = v; return this; } + SessionSettingsBuilder oneActiveSession(Boolean v) { this.oneActiveSession = v; return this; } + SessionSettingsBuilder inMemory(Boolean v) { this.inMemory = v; return this; } + + SessionSettings build() { + return SessionSettings.builder() + .ttl(ttl) + .autoProlongation(autoProlongation) + .authType(authType) + .authStepTypes(authStepTypes == null ? null : List.copyOf(authStepTypes)) + .ignoreConfirmation(ignoreConfirmation) + .oneActiveSession(oneActiveSession) + .inMemory(inMemory) + .build(); + } + } +} +*/ \ No newline at end of file diff --git a/src/main/java/ru/copperside/repository/PermissionsRepository.java b/src/main/java/ru/copperside/repository/PermissionsRepository.java new file mode 100644 index 0000000..5380a01 --- /dev/null +++ b/src/main/java/ru/copperside/repository/PermissionsRepository.java @@ -0,0 +1,12 @@ +package ru.copperside.repository; + +import ru.copperside.model.dto.RolePermissionDto; +import ru.copperside.model.permission.Permission; + +import java.util.List; + +public interface PermissionsRepository { + List findRoleByHierarchyId(Long hierarchyId); + List findPersonalByHierarchyId(Long hierarchyId); + List findCompiledByHierarchyId(Long hierarchyId); +} diff --git a/src/main/java/ru/copperside/repository/PermissionsRepositoryJdbc.java b/src/main/java/ru/copperside/repository/PermissionsRepositoryJdbc.java new file mode 100644 index 0000000..89bcfdc --- /dev/null +++ b/src/main/java/ru/copperside/repository/PermissionsRepositoryJdbc.java @@ -0,0 +1,70 @@ +package ru.copperside.repository; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; +import ru.copperside.model.dto.RolePermissionDto; +import ru.copperside.sql.SqlRegistry; +import ru.copperside.util.PermissionsCompiler; +import ru.copperside.model.permission.Permission; + +import java.util.List; + +import static ru.copperside.util.RepoMappingHelper.*; + +@Repository +@RequiredArgsConstructor +public class PermissionsRepositoryJdbc implements PermissionsRepository{ + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + private final SqlRegistry sqlRegistry; + private final ObjectMapper objectMapper; + + @Override + public List findRoleByHierarchyId(Long hierarchyId) { + var p = new MapSqlParameterSource().addValue("hierarchyId", hierarchyId); + + return namedParameterJdbcTemplate.query(sqlRegistry.get("auth/find_inherited_role_permissions_by_hierarchyid"), p, (rs, rn) -> { + return new RolePermissionDto( + rs.getLong("HIERARCHYID"), + rs.getInt("LEVEL"), + rs.getLong("ROLEID"), + rs.getLong("PERMISSIONID"), + rs.getString("PERMISSION_STRID"), + readLargeText(rs, "SETTINGS"), + getNullableBoolean(rs, "ACTION"), + readLargeText(rs, "PDATA"), + rs.getString("COMMAND"), + rs.getString("HTTP") + ); + }); + } + + @Override + public List findPersonalByHierarchyId(Long hierarchyId) { + var p = new MapSqlParameterSource().addValue("hierarchyId", hierarchyId); + + return namedParameterJdbcTemplate.query(sqlRegistry.get("auth/find_inherited_permissions_by_hierarchyid"), p, (rs, rn) -> + new RolePermissionDto( + getNullableLong(rs, "HIERARCHYID"), + rs.getInt("LEVEL"), + getNullableLong(rs, "ROLEID"), // здесь всегда null из SQL + rs.getLong("PERMISSIONID"), + rs.getString("PERMISSION_STRID"), + readLargeText(rs, "SETTINGS"), + getNullableBoolean(rs, "ACTION"), + readLargeText(rs, "PDATA"), + rs.getString("COMMAND"), + rs.getString("HTTP") + ) + ); + } + + @Override + public List findCompiledByHierarchyId(Long hierarchyId) { + var rolePerms = findRoleByHierarchyId(hierarchyId); + var personalPerms = findPersonalByHierarchyId(hierarchyId); + return new PermissionsCompiler(objectMapper).compile(rolePerms, personalPerms); + } +} diff --git a/src/main/java/ru/copperside/repository/PrivateDataRepository.java b/src/main/java/ru/copperside/repository/PrivateDataRepository.java new file mode 100644 index 0000000..f345e63 --- /dev/null +++ b/src/main/java/ru/copperside/repository/PrivateDataRepository.java @@ -0,0 +1,7 @@ +package ru.copperside.repository; + +import com.fasterxml.jackson.databind.JsonNode; + +public interface PrivateDataRepository { + JsonNode findByAuthId(Long authId); +} \ No newline at end of file diff --git a/src/main/java/ru/copperside/repository/SecretRepository.java b/src/main/java/ru/copperside/repository/SecretRepository.java new file mode 100644 index 0000000..7ab8ebd --- /dev/null +++ b/src/main/java/ru/copperside/repository/SecretRepository.java @@ -0,0 +1,10 @@ +package ru.copperside.repository; + + +import ru.copperside.model.authinfo.*; + +import java.util.Optional; + +public interface SecretRepository { + Optional findByDataIdAndType(String dataId, String type); +} \ No newline at end of file diff --git a/src/main/java/ru/copperside/repository/SecretRepositoryJdbc.java b/src/main/java/ru/copperside/repository/SecretRepositoryJdbc.java new file mode 100644 index 0000000..13439fa --- /dev/null +++ b/src/main/java/ru/copperside/repository/SecretRepositoryJdbc.java @@ -0,0 +1,70 @@ +package ru.copperside.repository; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; +import ru.copperside.model.enums.AuthenticationType; +import ru.copperside.model.authinfo.SecretData; +import ru.copperside.model.enums.SecretType; +import ru.copperside.model.enums.KeyUsage; +import ru.copperside.model.dto.SecretDTO; +import ru.copperside.repository.SecretRepository; +import ru.copperside.sql.SqlRegistry; + +import java.util.EnumSet; +import java.util.Optional; + +import static ru.copperside.util.RepoMappingHelper.*; + +@Repository +@RequiredArgsConstructor +public class SecretRepositoryJdbc implements SecretRepository { + + private final NamedParameterJdbcTemplate jdbc; + private final ObjectMapper om; + private final SqlRegistry sql; + + private final ObjectReader secretReader(ObjectMapper om) { + return om.readerFor(SecretDTO.class); + } + + @Override + public Optional findByDataIdAndType(String dataId, String type) { + var p = new MapSqlParameterSource() + .addValue("dataId", dataId) + .addValue("type", (type == null || type.isBlank()) + ? AuthenticationType.Secret.name() + : type); + + var list = jdbc.query(sql.get("auth/find_secret_by_dataid_and_type"), p, (rs, rn) -> { + // тип аутентификации из AD_TYPE, по умолчанию Secret + var authType = AuthenticationType.from(rs.getString("AD_TYPE")); + + // читаем AD_DATA -> SecretDTO + SecretDTO dto = readJson(rs, "AD_DATA", secretReader(om)); + + if (dto == null) { + return SecretData.builder() + .authType(authType) + .keyUsages(EnumSet.of(KeyUsage.None)) + .build(); + } + + EnumSet usages = parseKeyUsages(dto.keyUsage()); + SecretType sType = parseSecretType(dto.secretType()); + + return SecretData.builder() + .authType(authType) + .isEnabled(dto.isEnable()) + .keyUsages(usages.isEmpty() ? EnumSet.of(KeyUsage.None) : usages) + .secretType(sType) + .secret(dto.secret()) + .build(); + }); + + return list.stream().findFirst(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/copperside/repository/SettingsRepository.java b/src/main/java/ru/copperside/repository/SettingsRepository.java new file mode 100644 index 0000000..175bdab --- /dev/null +++ b/src/main/java/ru/copperside/repository/SettingsRepository.java @@ -0,0 +1,7 @@ +package ru.copperside.repository; + +import ru.copperside.model.session.SessionSettings; + +public interface SettingsRepository { + SessionSettings loadMerged(Long authId); +} diff --git a/src/main/java/ru/copperside/service/AuthInfoService.java b/src/main/java/ru/copperside/service/AuthInfoService.java new file mode 100644 index 0000000..fc6ae7b --- /dev/null +++ b/src/main/java/ru/copperside/service/AuthInfoService.java @@ -0,0 +1,24 @@ +/*package ru.copperside.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.crossstore.ChangeSetPersister; +import org.springframework.stereotype.Service; +import ru.copperside.model.authinfo.AuthInfo; +import ru.copperside.repository.AuthInfoRepository; + +@Service +@RequiredArgsConstructor +public class AuthInfoService { + private final AuthInfoRepository repo; + + public AuthInfo getByDataIdAndType(String dataId) { + return repo.findByDataIdAndType(dataId, "Secret") + .orElseThrow(() -> new NotFoundException( + "AuthInfo(init) not found by dataId=" + dataId + ", type=Secret")); + } + + public static class NotFoundException extends RuntimeException { + public NotFoundException(String msg) { super(msg); } + } +} +*/ \ No newline at end of file diff --git a/src/main/java/ru/copperside/service/DebugAuthService.java b/src/main/java/ru/copperside/service/DebugAuthService.java new file mode 100644 index 0000000..736bbf3 --- /dev/null +++ b/src/main/java/ru/copperside/service/DebugAuthService.java @@ -0,0 +1,45 @@ +package ru.copperside.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.copperside.model.authinfo.SecretData; +import ru.copperside.model.dto.RolePermissionDto; +import ru.copperside.model.enums.AuthenticationType; +import ru.copperside.model.permission.Permission; +import ru.copperside.repository.PermissionsRepository; +import ru.copperside.repository.SecretRepository; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class DebugAuthService { + + private final SecretRepository secretRepository; + private final PermissionsRepository permissionsRepository; + + public SecretData getSecretData(String dataId, String type) { + // если не указали type — считаем Secret + String resolvedType = (type == null || type.isBlank()) + ? AuthenticationType.Secret.name() + : type; + + return secretRepository.findByDataIdAndType(dataId, resolvedType) + .orElseThrow(() -> + new IllegalArgumentException("Secret not found: dataId=" + dataId + ", type=" + resolvedType)); + } + + public List byHierarchy(Long hierarchyId) { + return permissionsRepository.findRoleByHierarchyId(hierarchyId); + } + + public List PersonalbyHierarchy(Long hierarchyId) { + return permissionsRepository.findPersonalByHierarchyId(hierarchyId); + } + + public List getPermissions(Long hierarchyId) { + return permissionsRepository.findCompiledByHierarchyId(hierarchyId); + } + + +} \ No newline at end of file diff --git a/src/main/java/ru/copperside/sql/SqlRegistry.java b/src/main/java/ru/copperside/sql/SqlRegistry.java new file mode 100644 index 0000000..4057166 --- /dev/null +++ b/src/main/java/ru/copperside/sql/SqlRegistry.java @@ -0,0 +1,65 @@ +package ru.copperside.sql; + +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Component +public class SqlRegistry { + + private final Map sqls = new ConcurrentHashMap<>(); + private final ResourcePatternResolver resolver; + + public SqlRegistry(ResourcePatternResolver resolver) { + this.resolver = resolver; + } + + @PostConstruct + void init() { + try { + // важно: слэш после classpath*: + Resource[] resources = resolver.getResources("classpath*:/sql/**/*.sql"); + int count = 0; + for (Resource res : resources) { + String key = buildKey(res); + String sql = new String(res.getInputStream().readAllBytes(), StandardCharsets.UTF_8).trim(); + sqls.put(key, sql); + count++; + } + log.info("SqlRegistry loaded {} sql files", count); + } catch (IOException e) { + throw new IllegalStateException("Failed to load SQL resources", e); + } + } + + private String buildKey(Resource res) throws IOException { + // поддерживает и jar:...!/BOOT-INF/classes!/sql/... + String url = res.getURL().toString(); + int idx = url.indexOf("/sql/"); + if (idx >= 0) { + String afterSql = url.substring(idx + 5); // после "/sql/" + // убираем всё до возможного "!" для jar-URL (на всякий) + int bang = afterSql.indexOf('!'); + if (bang >= 0) afterSql = afterSql.substring(bang + 1); + return afterSql.replaceFirst("\\.sql$", ""); + } + // fallback — по имени файла + String name = res.getFilename(); + if (name == null) throw new IOException("No filename for resource: " + url); + return name.replaceFirst("\\.sql$", ""); + } + + public String get(String key) { + String sql = sqls.get(key); + if (sql == null) throw new IllegalArgumentException("SQL not found: " + key); + return sql; + } +} \ No newline at end of file diff --git a/src/main/java/ru/copperside/util/PermissionsCompiler.java b/src/main/java/ru/copperside/util/PermissionsCompiler.java new file mode 100644 index 0000000..d51f96b --- /dev/null +++ b/src/main/java/ru/copperside/util/PermissionsCompiler.java @@ -0,0 +1,123 @@ +package ru.copperside.util; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import ru.copperside.model.dto.RolePermissionDto; +import ru.copperside.model.permission.Permission; +import ru.copperside.model.permission.PermissionSettings; + + +import java.util.*; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +public class PermissionsCompiler { + + private final ObjectMapper objectMapper; + + + public List compile(List rolePermissions, + List personalPermissions) { + + Map compiled = new LinkedHashMap<>(); + + int maxLevel = Math.max( + rolePermissions.stream() + .map(rp -> Optional.ofNullable(rp.level()).orElse(0)) + .max(Integer::compareTo) + .orElse(0), + personalPermissions.stream() + .map(p -> Optional.ofNullable(p.level()).orElse(0)) + .max(Integer::compareTo) + .orElse(0) + ); + + for (int level = 1; level <= maxLevel; level++) { + final int lvl = level; + + rolePermissions.stream() + .filter(rp -> Objects.equals(rp.level(), lvl)) + .forEach(rp -> { + PermissionSettings settings = deserializeSettings(rp.settingsJson()); + JsonNode pdata = deserializeJson(rp.pDataJson()); + + if (compiled.containsKey(rp.permissionId())) { + Permission existing = compiled.get(rp.permissionId()); + existing = existing.toBuilder() + .action(true) + .permissionData(pdata) + .build(); + compiled.put(rp.permissionId(), existing); + } else { + compiled.put(rp.permissionId(), + Permission.builder() + .permissionId(rp.permissionId()) + .permissionStrId(rp.permissionStrId()) + .settings(settings) + .action(true) + .permissionData(pdata) + .command(rp.command()) + .http(rp.http()) + .build()); + } + }); + + // ==== персональные (иерархические) ==== + personalPermissions.stream() + .filter(p -> Objects.equals(p.level(), lvl)) + .forEach(p -> { + PermissionSettings settings = deserializeSettings(p.settingsJson()); + JsonNode pdata = deserializeJson(p.pDataJson()); + + if (compiled.containsKey(p.permissionId())) { + Permission existing = compiled.get(p.permissionId()); + existing = existing.toBuilder() + .action(Boolean.TRUE.equals(p.action())) + .permissionData(pdata) + .build(); + compiled.put(p.permissionId(), existing); + } else { + compiled.put(p.permissionId(), + Permission.builder() + .permissionId(p.permissionId()) + .permissionStrId(p.permissionStrId()) + .settings(settings) + .action(Boolean.TRUE.equals(p.action())) + .permissionData(pdata) + .command(p.command()) + .http(p.http()) + .build()); + } + }); + } + + // только активные + return compiled.values().stream() + .filter(Permission::action) + .collect(Collectors.toList()); + } + + /* ===== helpers ===== */ + private PermissionSettings deserializeSettings(String json) { + if (json == null || json.isBlank()) return null; + try { + var s = objectMapper.readValue(json, PermissionSettings.class); + return s == null ? null : s.normalize(); + } + catch (Exception e) { return null; } + } + + private JsonNode deserializeJson(String json) { + try { + if (json == null || json.isBlank()) { + return objectMapper.createObjectNode(); // <-- пустой объект вместо null + } + JsonNode n = objectMapper.readTree(json); + // гарантируем объект + return (n == null || !n.isObject()) ? objectMapper.createObjectNode() : n; + } catch (Exception e) { + return objectMapper.createObjectNode(); // на ошибке тоже пустой объект + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/copperside/util/RepoMappingHelper.java b/src/main/java/ru/copperside/util/RepoMappingHelper.java new file mode 100644 index 0000000..9ca1a73 --- /dev/null +++ b/src/main/java/ru/copperside/util/RepoMappingHelper.java @@ -0,0 +1,82 @@ +package ru.copperside.util; + + +import java.io.Reader; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.EnumSet; +import java.util.List; + +import ru.copperside.model.enums.KeyUsage; +import ru.copperside.model.enums.SecretType; + +public final class RepoMappingHelper { + private RepoMappingHelper() {} + + public static Long getNullableLong(ResultSet rs, String col) throws SQLException { + long v = rs.getLong(col); + return rs.wasNull() ? null : v; + } + + public static Boolean getNullableBoolean(ResultSet rs, String col) { + try { + boolean v = rs.getBoolean(col); + return rs.wasNull() ? null : v; + } catch (Exception e) { + return null; + } + } + + public static T readJson(ResultSet rs, String col, com.fasterxml.jackson.databind.ObjectReader reader) { + try (Reader r = rs.getCharacterStream(col)) { + if (r != null) return reader.readValue(r); + } catch (Exception ignore) {} + try { + String s = rs.getString(col); + return (s == null || s.isBlank()) ? null : reader.readValue(s); + } catch (Exception ignore) {} + return null; + } + + /** Универсальное чтение CLOB / VARCHAR */ + public static String readLargeText(ResultSet rs, String col) { + // 1) CLOB → Reader + try (Reader r = rs.getCharacterStream(col)) { + if (r != null) { + var sw = new java.io.StringWriter(); + r.transferTo(sw); + return sw.toString(); + } + } catch (Exception ignore) {} + // 2) Fallback: обычная строка + try { + return rs.getString(col); + } catch (Exception ignore) {} + return null; + } + + public static EnumSet parseKeyUsages(Object raw) { + EnumSet set = EnumSet.noneOf(KeyUsage.class); + if (raw == null) return EnumSet.of(KeyUsage.None); + if (raw instanceof String s) { + for (String p : s.split("\\s*,\\s*")) addUsage(set, p); + } else if (raw instanceof List list) { + for (Object o : list) addUsage(set, String.valueOf(o)); + } + return set.isEmpty() ? EnumSet.of(KeyUsage.None) : set; + } + + private static void addUsage(EnumSet set, String v) { + switch (v.trim().toLowerCase()) { + case "password" -> set.add(KeyUsage.Password); + case "hmac", "hmacsha256" -> set.add(KeyUsage.HMac); + case "none", "" -> set.add(KeyUsage.None); + default -> {} + } + } + + public static SecretType parseSecretType(String s) { + if (s == null) return null; + try { return SecretType.valueOf(s); } catch (IllegalArgumentException ex) { return null; } + } +} \ No newline at end of file diff --git a/src/main/java/ru/copperside/util/SessionSettingsHelper.java b/src/main/java/ru/copperside/util/SessionSettingsHelper.java new file mode 100644 index 0000000..7b682dc --- /dev/null +++ b/src/main/java/ru/copperside/util/SessionSettingsHelper.java @@ -0,0 +1,64 @@ +package ru.copperside.util; + +import ru.copperside.model.enums.AuthType; +import ru.copperside.model.session.SessionSettings; + +import java.time.Duration; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public final class SessionSettingsHelper { + private SessionSettingsHelper() {} + + public static SessionSettings merge(SessionSettings target, SessionSettings merge, boolean isRole) { + if (target == null && merge == null) return null; + if (target == null) target = SessionSettings.builder().build(); + if (merge == null) return target; + + Duration ttl = target.ttl(); + Boolean auto = target.autoProlongation(); + AuthType at = target.authType(); + List steps = target.authStepTypes(); + Boolean ign = target.ignoreConfirmation(); + Boolean one = target.oneActiveSession(); + Boolean mem = target.inMemory(); + + if (isRole) { + if (merge.ttl() != null) ttl = merge.ttl(); + if (merge.autoProlongation() != null) auto = merge.autoProlongation(); + if (merge.authType() != null) at = (at == null) ? merge.authType() : max(at, merge.authType()); + if (merge.authStepTypes() != null && !merge.authStepTypes().isEmpty()) { + Set s = new LinkedHashSet<>(); + if (steps != null) s.addAll(steps); + s.addAll(merge.authStepTypes()); + steps = List.copyOf(s); + } + if (merge.ignoreConfirmation() != null) { + if (ign == null) ign = merge.ignoreConfirmation(); + else if (!merge.ignoreConfirmation()) ign = false; + } + } else { + if (merge.ttl() != null) ttl = merge.ttl(); + if (merge.autoProlongation() != null) auto = merge.autoProlongation(); + if (merge.authType() != null) at = merge.authType(); + if (merge.authStepTypes() != null) steps = merge.authStepTypes(); + if (merge.ignoreConfirmation() != null) ign = merge.ignoreConfirmation(); + if (merge.oneActiveSession() != null) one = merge.oneActiveSession(); + } + + mem = Boolean.TRUE.equals(mem) || Boolean.TRUE.equals(merge.inMemory()); + + return SessionSettings.builder() + .ttl(ttl).autoProlongation(auto).authType(at) + .authStepTypes(steps) + .ignoreConfirmation(ign).oneActiveSession(one).inMemory(mem) + .build(); + } + + private static AuthType max(AuthType a, AuthType b) { + int ca = a == null ? 0 : a.ordinal(); + int cb = b == null ? 0 : b.ordinal(); + return (ca >= cb) ? a : b; // Unknown < Simple < TwoStep + } +} \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..b369370 --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,9 @@ +server: + port: 8080 + +spring: + datasource: + url: jdbc:oracle:thin:@127.0.0.1:1521/TKBPAYPDB + username: WFMPROP + password: WFMPROP + driver-class-name: oracle.jdbc.OracleDriver diff --git a/src/main/resources/sql/auth/find_authinfo_init_by_dataid_and_type.sql b/src/main/resources/sql/auth/find_authinfo_init_by_dataid_and_type.sql new file mode 100644 index 0000000..d69eee9 --- /dev/null +++ b/src/main/resources/sql/auth/find_authinfo_init_by_dataid_and_type.sql @@ -0,0 +1,21 @@ +SELECT + -- из AUTHDATA + ad.AUTHID AS AD_AUTHID, + ad.TYPE AS AD_TYPE, + ad.DATAID AS AD_DATAID, + ad.DATA AS AD_DATA, + + -- из AUTHHIERARCHY + AUTHIDS + ah.HIERARCHYID AS AH_HIERARCHYID, + ai.AUTHID AS AI_AUTHID, + ai.SETTINGS AS AI_SETTINGS, + ai.PARAMETERS AS AI_PARAMETERS, + ah.DISPLAYNAME AS AH_DISPLAYNAME + +FROM AUTHDATA ad + LEFT JOIN AUTHIDS ai + ON ai.AUTHID = ad.AUTHID + LEFT JOIN AUTHHIERARCHY ah + ON ah.AUTHID = ai.AUTHID +WHERE ad.DATAID = :dataId + AND ad.TYPE = :type diff --git a/src/main/resources/sql/auth/find_inherited_permissions_by_hierarchyid.sql b/src/main/resources/sql/auth/find_inherited_permissions_by_hierarchyid.sql new file mode 100644 index 0000000..271445c --- /dev/null +++ b/src/main/resources/sql/auth/find_inherited_permissions_by_hierarchyid.sql @@ -0,0 +1,24 @@ +SELECT + hp.HIERARCHYID AS HIERARCHYID, + h."LEVEL" AS "LEVEL", + /* у иерархических прав нет роли: оставим NULL как роль */ + NULL AS ROLEID, + + hp.PERMISSIONID AS PERMISSIONID, + p.STRID AS PERMISSION_STRID, + p.SETTINGS AS SETTINGS, + hp.ACTION AS ACTION, + hp.PERMISSIONDATA AS PDATA, + p.COMMAND AS COMMAND, + p.HTTP AS HTTP +FROM HIERARCHYPERMISSION hp + JOIN PERMISSIONS p ON p.PERMISSIONID = hp.PERMISSIONID + JOIN ( + SELECT ah.HIERARCHYID, ah."LEVEL" + FROM AUTHHIERARCHY ah, AUTHHIERARCHY ah2 + WHERE ah.LEFTKEY <= ah2.LEFTKEY + AND ah.RIGHTKEY >= ah2.RIGHTKEY + AND ah2.HIERARCHYID = :hierarchyId + ORDER BY ah."LEVEL" + ) h ON hp.HIERARCHYID = h.HIERARCHYID +ORDER BY h."LEVEL", hp.PERMISSIONID diff --git a/src/main/resources/sql/auth/find_inherited_role_permissions_by_hierarchyid.sql b/src/main/resources/sql/auth/find_inherited_role_permissions_by_hierarchyid.sql new file mode 100644 index 0000000..409cf6f --- /dev/null +++ b/src/main/resources/sql/auth/find_inherited_role_permissions_by_hierarchyid.sql @@ -0,0 +1,22 @@ +SELECT + hr.HIERARCHYID AS HIERARCHYID, + h."LEVEL" AS "LEVEL", + hr.ROLEID AS ROLEID, + rp.PERMISSIONID AS PERMISSIONID, + p.STRID AS PERMISSION_STRID, + p.SETTINGS AS SETTINGS, + rp.PDATA AS PDATA, + p.COMMAND AS COMMAND, + p.HTTP AS HTTP +FROM HIERARCHYROLE hr + JOIN ( + SELECT ah.HIERARCHYID, ah."LEVEL" + FROM AUTHHIERARCHY ah, AUTHHIERARCHY ah2 + WHERE ah.LEFTKEY <= ah2.LEFTKEY + AND ah.RIGHTKEY >= ah2.RIGHTKEY + AND ah2.HIERARCHYID = :hierarchyId + ORDER BY ah."LEVEL" +) h ON hr.HIERARCHYID = h.HIERARCHYID + JOIN ROLEPERMISSIONS rp ON rp.ROLEID = hr.ROLEID + JOIN PERMISSIONS p ON p.PERMISSIONID = rp.PERMISSIONID +ORDER BY h."LEVEL", hr.ROLEID, rp.PERMISSIONID \ No newline at end of file diff --git a/src/main/resources/sql/auth/find_privatedata_by_authid.sql b/src/main/resources/sql/auth/find_privatedata_by_authid.sql new file mode 100644 index 0000000..665cee8 --- /dev/null +++ b/src/main/resources/sql/auth/find_privatedata_by_authid.sql @@ -0,0 +1,3 @@ +SELECT KEY, VALUE +FROM PRIVATEDATA +WHERE AUTHID = :authId \ No newline at end of file diff --git a/src/main/resources/sql/auth/find_rolesettings_by_authid.sql b/src/main/resources/sql/auth/find_rolesettings_by_authid.sql new file mode 100644 index 0000000..0fb87a9 --- /dev/null +++ b/src/main/resources/sql/auth/find_rolesettings_by_authid.sql @@ -0,0 +1,11 @@ +SELECT r.SETTINGS +FROM ( + SELECT ah.HIERARCHYID, ah."LEVEL" + FROM AUTHHIERARCHY ah, AUTHHIERARCHY ah2 + WHERE ah.LEFTKEY <= ah2.LEFTKEY + AND ah.RIGHTKEY >= ah2.RIGHTKEY + AND ah2.AUTHID = :authId + ) h + JOIN HIERARCHYROLE hr ON h.HIERARCHYID = hr.HIERARCHYID + JOIN ROLES r ON r.ROLEID = hr.ROLEID +ORDER BY h."LEVEL" \ No newline at end of file diff --git a/src/main/resources/sql/auth/find_secret_by_dataid_and_type.sql b/src/main/resources/sql/auth/find_secret_by_dataid_and_type.sql new file mode 100644 index 0000000..4188586 --- /dev/null +++ b/src/main/resources/sql/auth/find_secret_by_dataid_and_type.sql @@ -0,0 +1,13 @@ +SELECT + ad.AUTHID AS AD_AUTHID, + ad.DATAID AS AD_DATAID, + ad.TYPE AS AD_TYPE, + ad.DATA AS AD_DATA, + ai.PARAMETERS AS AI_PARAMETERS, + ah.HIERARCHYID AS AH_HIERARCHYID, + ah.DISPLAYNAME AS AH_DISPLAYNAME +FROM AUTHDATA ad + LEFT JOIN AUTHIDS ai ON ai.AUTHID = ad.AUTHID + LEFT JOIN AUTHHIERARCHY ah ON ah.AUTHID = ad.AUTHID +WHERE ad.DATAID = :dataId + AND ad.TYPE = :type \ No newline at end of file