diff --git a/ordinis-auth/src/main/java/cloud/nstart/terravault/ordinis/auth/OrgContext.java b/ordinis-auth/src/main/java/cloud/nstart/terravault/ordinis/auth/OrgContext.java new file mode 100644 index 0000000..a609a0e --- /dev/null +++ b/ordinis-auth/src/main/java/cloud/nstart/terravault/ordinis/auth/OrgContext.java @@ -0,0 +1,113 @@ +package cloud.nstart.terravault.ordinis.auth; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +/** + * Phase 3 cheap-prep skeleton — resolver «текущая организация юзера». + * + *

Сейчас всегда возвращает empty — Ordinis single-tenant, никакая + * логика не использует org-scoping. Bean существует чтобы при старте Phase 3: + *

+ * + *

Когда Phase 3 стартует, имплементация поменяется на чтение JWT claim + * {@code organization} (altum auth-service кладёт туда array of org UUIDs). + * Параллельно появится: + *

+ * + *

Не путать со {@link ScopeContext}: scope = data sensitivity tier + * (PUBLIC/INTERNAL/RESTRICTED), org = data tenant. Они ортогональны: + * Customer-X RESTRICTED data ≠ Customer-Y RESTRICTED data. + */ +@Component +public class OrgContext { + + private static final Logger log = LoggerFactory.getLogger(OrgContext.class); + private static final String ORG_CLAIM = "organization"; + + /** + * Все организации текущего юзера. Пустой Set если: + *

+ * + *

Реализация ниже уже умеет читать claim — это сделано чтобы при + * включении Phase 3 не пришлось трогать сам метод, только downstream + * consumers. Сейчас downstream'ов нет → выход пустой → ничего не делает. + */ + public Set currentOrgs() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (!(auth instanceof JwtAuthenticationToken jwtAuth)) return Set.of(); + return readOrgClaim(jwtAuth.getToken()); + } + + /** + * Single «active» org для current-request scope. Когда юзер в одной org — + * она. Когда в нескольких — нужен явный select (header / query param), + * пока не реализовано → возвращает первую (детерминистично, sorted), либо + * empty если нет ни одной. + * + *

В Phase 3 этот метод будет принимать requested org из request и + * валидировать что юзер in её состав. Сейчас просто placeholder. + */ + public Optional activeOrg() { + Set orgs = currentOrgs(); + if (orgs.isEmpty()) return Optional.empty(); + return orgs.stream().sorted().findFirst(); + } + + /** + * Парсит {@code organization} claim из JWT. Поддерживает оба формата + * которые встречаются у altum auth-service: + *

+ * Null/missing claim → empty set. + */ + private static Set readOrgClaim(Jwt token) { + Object raw = token.getClaim(ORG_CLAIM); + if (raw == null) return Set.of(); + Set orgs = new HashSet<>(); + if (raw instanceof Collection col) { + for (Object o : col) { + if (o == null) continue; + String s = o.toString().trim(); + if (!s.isEmpty()) orgs.add(s); + } + } else { + String s = raw.toString().trim(); + if (!s.isEmpty()) orgs.add(s); + } + if (log.isDebugEnabled() && !orgs.isEmpty()) { + log.debug("OrgContext: JWT organization claim resolved → {}", orgs); + } + return Set.copyOf(orgs); + } +} diff --git a/ordinis-migrations/src/main/resources/db/changelog/changes/0028-phase3-prep-org-id.xml b/ordinis-migrations/src/main/resources/db/changelog/changes/0028-phase3-prep-org-id.xml new file mode 100644 index 0000000..30c70c3 --- /dev/null +++ b/ordinis-migrations/src/main/resources/db/changelog/changes/0028-phase3-prep-org-id.xml @@ -0,0 +1,63 @@ + + + + + + + Phase 3 prep: nullable org_id на dictionary_definitions + + + + + + + + + Phase 3 prep: nullable org_id на dictionary_records + + + + + + + + + Phase 3 prep: nullable org_id на audit_log + + + + + + + diff --git a/ordinis-migrations/src/main/resources/db/changelog/master.xml b/ordinis-migrations/src/main/resources/db/changelog/master.xml index e9bfddf..ba6ef33 100644 --- a/ordinis-migrations/src/main/resources/db/changelog/master.xml +++ b/ordinis-migrations/src/main/resources/db/changelog/master.xml @@ -37,5 +37,6 @@ +