add Session Settings

This commit is contained in:
root
2025-11-27 14:24:10 +03:00
parent 50b3c6eb1d
commit 47f64dc4b8
12 changed files with 199 additions and 340 deletions

View File

@@ -18,8 +18,8 @@
</parent> </parent>
<properties> <properties>
<maven.compiler.source>25</maven.compiler.source> <maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target> <maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- основные версии --> <!-- основные версии -->
<springdoc.version>2.8.14</springdoc.version> <springdoc.version>2.8.14</springdoc.version>

View File

@@ -1,27 +0,0 @@
/*
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<AuthInfo> getByDataIdAndType(
@PathVariable String dataId
) {
return ResponseEntity.ok(service.getByDataIdAndType(dataId));
}
}
*/

View File

@@ -7,6 +7,7 @@ import ru.copperside.model.authinfo.SecretData;
import ru.copperside.model.dto.RolePermissionDto; import ru.copperside.model.dto.RolePermissionDto;
import ru.copperside.model.enums.AuthenticationType; import ru.copperside.model.enums.AuthenticationType;
import ru.copperside.model.permission.Permission; import ru.copperside.model.permission.Permission;
import ru.copperside.model.session.SessionSettings;
import ru.copperside.service.DebugAuthService; import ru.copperside.service.DebugAuthService;
import java.util.List; import java.util.List;
@@ -55,4 +56,9 @@ public class DebugAuthController {
) { ) {
return ResponseEntity.ok(service.getPermissions(hierarchyId)); return ResponseEntity.ok(service.getPermissions(hierarchyId));
} }
@GetMapping("/merged")
public ResponseEntity<SessionSettings> merged(@RequestParam Long authId) {
return ResponseEntity.ok(service.getMerged(authId));
}
} }

View File

@@ -3,18 +3,38 @@ package ru.copperside.model.session;
import lombok.Builder; import lombok.Builder;
import lombok.extern.jackson.Jacksonized; import lombok.extern.jackson.Jacksonized;
import ru.copperside.model.enums.AuthType; import ru.copperside.model.enums.AuthType;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import ru.copperside.util.DurationFlexibleDeserializer;
import java.time.Duration; import java.time.Duration;
import java.util.List; import java.util.List;
@Jacksonized @Jacksonized
@Builder @Builder(toBuilder = true)
public record SessionSettings( public record SessionSettings(
Duration ttl, @JsonAlias({"Ttl", "TTL", "ttl"})
@JsonDeserialize(
using = DurationFlexibleDeserializer.class
)
java.time.Duration ttl,
@JsonAlias({"AutoProlongation", "autoProlongation"})
Boolean autoProlongation, Boolean autoProlongation,
@JsonAlias({"AuthType", "authType"})
AuthType authType, AuthType authType,
List<String> authStepTypes,
@JsonAlias({"AuthStepTypes", "authStepTypes"})
java.util.List<String> authStepTypes,
@JsonAlias({"IgnoreConfirmation", "ignoreConfirmation"})
Boolean ignoreConfirmation, Boolean ignoreConfirmation,
@JsonAlias({"OneActiveSession", "oneActiveSession"})
Boolean oneActiveSession, Boolean oneActiveSession,
@JsonAlias({"InMemory", "inMemory"})
Boolean inMemory Boolean inMemory
) {} ) {}

View File

@@ -1,250 +0,0 @@
/*
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<AuthInfo> findByDataIdAndType(String dataId, String type) {
var params = new MapSqlParameterSource()
.addValue("dataId", dataId)
.addValue("type", type);
RowMapper<AuthInfo> rm = (rs, rn) -> mapAuthInfo(rs);
List<AuthInfo> 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<KeyUsage> 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<String> 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<String> 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<String> 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();
}
}
}
*/

View File

@@ -0,0 +1,49 @@
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.session.SessionSettings;
import ru.copperside.sql.SqlRegistry;
import java.util.List;
import java.util.Objects;
import static ru.copperside.util.RepoMappingHelper.readJson;
import static ru.copperside.util.SessionSettingsHelper.merge;
@Repository
@RequiredArgsConstructor
public class SettingsRepositoryJdbc implements SettingsRepository {
private final NamedParameterJdbcTemplate jdbc;
private final ObjectMapper om;
private final SqlRegistry sql;
private ObjectReader settingsReader() {
return om.readerFor(SessionSettings.class);
}
@Override
public SessionSettings loadMerged(Long authId) {
var p = new MapSqlParameterSource().addValue("authId", authId);
// user
SessionSettings user = jdbc.query(sql.get("auth/find_settings_by_authid"), p, rs ->
rs.next() ? readJson(rs, "AI_SETTINGS", settingsReader()) : null);
// roles (по уровням, порядке дерева)
List<SessionSettings> roles = jdbc.query(sql.get("auth/find_rolesettings_by_authid"), p,
(rs, rn) -> readJson(rs, "SETTINGS", settingsReader()));
// merge: роли последовательно (isRole=true), затем поверх пользовательские (isRole=false)
SessionSettings acc = roles.stream()
.filter(Objects::nonNull)
.reduce(SessionSettings.builder().build(), (a, r) -> merge(a, r, true), (a, b) -> b);
return merge(acc, user, false);
}
}

View File

@@ -6,8 +6,10 @@ import ru.copperside.model.authinfo.SecretData;
import ru.copperside.model.dto.RolePermissionDto; import ru.copperside.model.dto.RolePermissionDto;
import ru.copperside.model.enums.AuthenticationType; import ru.copperside.model.enums.AuthenticationType;
import ru.copperside.model.permission.Permission; import ru.copperside.model.permission.Permission;
import ru.copperside.model.session.SessionSettings;
import ru.copperside.repository.PermissionsRepository; import ru.copperside.repository.PermissionsRepository;
import ru.copperside.repository.SecretRepository; import ru.copperside.repository.SecretRepository;
import ru.copperside.repository.SettingsRepository;
import java.util.List; import java.util.List;
@@ -17,6 +19,7 @@ public class DebugAuthService {
private final SecretRepository secretRepository; private final SecretRepository secretRepository;
private final PermissionsRepository permissionsRepository; private final PermissionsRepository permissionsRepository;
private final SettingsRepository settingsRepository;
public SecretData getSecretData(String dataId, String type) { public SecretData getSecretData(String dataId, String type) {
// если не указали type — считаем Secret // если не указали type — считаем Secret
@@ -40,6 +43,8 @@ public class DebugAuthService {
public List<Permission> getPermissions(Long hierarchyId) { public List<Permission> getPermissions(Long hierarchyId) {
return permissionsRepository.findCompiledByHierarchyId(hierarchyId); return permissionsRepository.findCompiledByHierarchyId(hierarchyId);
} }
public SessionSettings getMerged(Long authId) {
return settingsRepository.loadMerged(authId);
}
} }

View File

@@ -0,0 +1,54 @@
package ru.copperside.util;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.time.Duration;
public class DurationFlexibleDeserializer extends JsonDeserializer<Duration> {
@Override
public Duration deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode node = p.getCodec().readTree(p);
if (node == null || node.isNull()) return null;
// число → секунды
if (node.isNumber()) {
return Duration.ofSeconds(node.longValue());
}
String s = node.asText(null);
if (s == null || s.isBlank()) return null;
// Формат "hh:mm:ss" как в C# TimeSpan ("00:10:00")
if (s.matches("\\d{1,2}:\\d{2}:\\d{2}")) {
String[] parts = s.split(":");
long h = Long.parseLong(parts[0]);
long m = Long.parseLong(parts[1]);
long sec = Long.parseLong(parts[2]);
return Duration.ofHours(h).plusMinutes(m).plusSeconds(sec);
}
// ISO-8601 (P1D, PT10M, ...)
if (s.startsWith("P") || s.startsWith("p")) {
try { return Duration.parse(s); } catch (Exception ignore) {}
}
// короткие формы: "1h", "30m", "15s", "600" (сек)
try {
String v = s.trim().toLowerCase();
if (v.endsWith("h")) return Duration.ofHours(Long.parseLong(v.substring(0, v.length()-1)));
if (v.endsWith("m")) return Duration.ofMinutes(Long.parseLong(v.substring(0, v.length()-1)));
if (v.endsWith("s")) return Duration.ofSeconds(Long.parseLong(v.substring(0, v.length()-1)));
return Duration.ofSeconds(Long.parseLong(v));
} catch (Exception ignore) {}
// последняя попытка — ISO
try { return Duration.parse(s); } catch (Exception ignore) {}
return null;
}
}

View File

@@ -1,64 +1,63 @@
package ru.copperside.util; package ru.copperside.util;
import ru.copperside.model.enums.AuthType;
import ru.copperside.model.session.SessionSettings; import ru.copperside.model.session.SessionSettings;
import java.time.Duration;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
public final class SessionSettingsHelper { public final class SessionSettingsHelper {
private SessionSettingsHelper() {} private SessionSettingsHelper() {}
public static SessionSettings merge(SessionSettings target, SessionSettings merge, boolean isRole) { /** Merge session settings. isRole=true — логика «ролевого» слияния из C#. */
if (target == null && merge == null) return null; public static SessionSettings merge(SessionSettings base, SessionSettings merge, boolean isRole) {
if (target == null) target = SessionSettings.builder().build(); if (base == null) base = SessionSettings.builder().build();
if (merge == null) return target; if (merge == null) return base;
Duration ttl = target.ttl(); var b = base.toBuilder();
Boolean auto = target.autoProlongation();
AuthType at = target.authType();
List<String> steps = target.authStepTypes();
Boolean ign = target.ignoreConfirmation();
Boolean one = target.oneActiveSession();
Boolean mem = target.inMemory();
if (isRole) { // TTL, AutoProlongation
if (merge.ttl() != null) ttl = merge.ttl(); if (merge.ttl() != null) b.ttl(merge.ttl());
if (merge.autoProlongation() != null) auto = merge.autoProlongation(); if (merge.autoProlongation() != null) b.autoProlongation(merge.autoProlongation());
if (merge.authType() != null) at = (at == null) ? merge.authType() : max(at, merge.authType());
if (merge.authStepTypes() != null && !merge.authStepTypes().isEmpty()) { // AuthType: для ролей — берём максимальный (по ordinal), иначе — прямое присваивание
Set<String> s = new LinkedHashSet<>(); if (merge.authType() != null) {
if (steps != null) s.addAll(steps); if (isRole) {
s.addAll(merge.authStepTypes()); var current = b.build().authType();
steps = List.copyOf(s); if (current == null || current.ordinal() < merge.authType().ordinal()) {
b.authType(merge.authType());
}
} else {
b.authType(merge.authType());
} }
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()); // AuthStepTypes: роли — объединение множеств; пользователь — overwrite
if (merge.authStepTypes() != null) {
if (isRole && base.authStepTypes() != null) {
var set = new LinkedHashSet<>(base.authStepTypes());
set.addAll(merge.authStepTypes());
b.authStepTypes(set.stream().toList());
} else {
b.authStepTypes(merge.authStepTypes());
}
}
return SessionSettings.builder() // IgnoreConfirmation: роли — если хоть одна роль false → false; пользователь — overwrite
.ttl(ttl).autoProlongation(auto).authType(at) if (merge.ignoreConfirmation() != null) {
.authStepTypes(steps) if (isRole) {
.ignoreConfirmation(ign).oneActiveSession(one).inMemory(mem) var cur = base.ignoreConfirmation();
.build(); if (cur == null) b.ignoreConfirmation(merge.ignoreConfirmation());
} else if (!merge.ignoreConfirmation()) b.ignoreConfirmation(false);
} else {
b.ignoreConfirmation(merge.ignoreConfirmation());
}
}
private static AuthType max(AuthType a, AuthType b) { // OneActiveSession — простое присваивание (если задано)
int ca = a == null ? 0 : a.ordinal(); if (merge.oneActiveSession() != null) b.oneActiveSession(merge.oneActiveSession());
int cb = b == null ? 0 : b.ordinal();
return (ca >= cb) ? a : b; // Unknown < Simple < TwoStep // InMemory: ИЛИ
b.inMemory(Boolean.TRUE.equals(base.inMemory()) || Boolean.TRUE.equals(merge.inMemory()));
return b.build();
} }
} }

View File

@@ -3,7 +3,7 @@ server:
spring: spring:
datasource: datasource:
url: jdbc:oracle:thin:@127.0.0.1:1521/TKBPAYPDB url: jdbc:oracle:thin:@cdm-oradb-02.ad.transcapital.com:1523/wfm
username: WFMPROP username: AUTH
password: WFMPROP password: .,z;pws-2shds
driver-class-name: oracle.jdbc.OracleDriver driver-class-name: oracle.jdbc.OracleDriver

View File

@@ -1,11 +1,11 @@
SELECT r.SETTINGS SELECT r.SETTINGS
FROM ( FROM (
SELECT ah.HIERARCHYID, ah."LEVEL" SELECT ah.HIERARCHYID, ah."LEVEL"
FROM AUTHHIERARCHY ah, AUTHHIERARCHY ah2 FROM AUTHHIERARCHY ah, AUTHHIERARCHY ah2
WHERE ah.LEFTKEY <= ah2.LEFTKEY WHERE ah.LEFTKEY <= ah2.LEFTKEY
AND ah.RIGHTKEY >= ah2.RIGHTKEY AND ah.RIGHTKEY >= ah2.RIGHTKEY
AND ah2.AUTHID = :authId AND ah2.AUTHID = :authId
) h ) h
JOIN HIERARCHYROLE hr ON h.HIERARCHYID = hr.HIERARCHYID JOIN HIERARCHYROLE hr ON hr.HIERARCHYID = h.HIERARCHYID
JOIN ROLES r ON r.ROLEID = hr.ROLEID JOIN ROLES r ON r.ROLEID = hr.ROLEID
ORDER BY h."LEVEL" ORDER BY h."LEVEL"

View File

@@ -0,0 +1,3 @@
SELECT ai.SETTINGS AS AI_SETTINGS
FROM AUTHIDS ai
WHERE ai.AUTHID = :authId