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