import type { Person } from "../person.entity"; import type { AccessDefinition } from "./access-definition"; import type { AccessItem } from "./access-item.entity"; type Reason = | "incomplete" | "expired" | "inactive_person" | "inactive_target" | "unjustified" | "uncovered" | "pending_configuration" | "pending_removal" | "inactive_catalog"; type Coverage = { needId: string; covered: boolean; missingPermissionIds: string[] }; class AccessProjection { private readonly entries: ReadonlyMap>; constructor( private readonly items: readonly Pick[], private readonly people: readonly Pick[], private readonly today: string ) { this.entries = new Map(items.map((item) => [item.id.toPrimitive(), item])); } data = (id: string) => this.entries.get(id)?.values.data; isOpen = (id: string) => { const value = this.data(id); return value && "endDate" in value && value.endDate === null; }; configured = (id: string) => { const value = this.data(id); return ( this.isOpen(id) && value && "configuration" in value && value.configuration === "configured" ); }; isActive = (id: string) => { const value = this.data(id); return ( value && (!("active" in value) || value.active) && (value.kind !== "account" || value.expiresOn === null || value.expiresOn >= this.today) ); }; personActive = (id: string) => this.people.some((person) => person.id.toPrimitive() === id && person.active); private accountCovered(relation: Extract) { return this.personActive(relation.personId) && this.isActive(relation.accountId); } private membershipAuthorized(id: string | null) { if (id === null) { return true; } const membership = this.data(id); return ( membership?.kind === "membership" && this.isOpen(id) && this.isActive(membership.groupId) ); } private permissionNeeded(needId: string, permissionId: string) { const need = this.data(needId); return need?.kind === "need" && need.permissionIds.includes(permissionId); } authorizedLink = (item: Pick) => { const value = item.values.data; if (value.kind !== "coverage" || value.endDate !== null || !this.isOpen(value.needId)) { return false; } const relation = this.data(value.accountPersonId); const grant = this.data(value.grantId); if (relation?.kind !== "account_person" || grant?.kind !== "grant") { return false; } if (!(this.isOpen(value.accountPersonId) && this.isOpen(value.grantId))) { return false; } return ( this.accountCovered(relation) && this.isActive(grant.resourceId) && this.isActive(grant.permissionId) && this.permissionNeeded(value.needId, grant.permissionId) && this.membershipAuthorized(value.membershipId) ); }; validLink = (item: Pick) => { const value = item.values.data; if (value.kind !== "coverage" || !this.authorizedLink(item)) { return false; } return ( this.configured(value.accountPersonId) && this.configured(value.grantId) && (value.membershipId === null || this.configured(value.membershipId)) ); }; needCoverage(item: Pick): Coverage[] { const value = item.values.data; if (value.kind !== "need") { return []; } const links = this.items.filter( (link) => link.values.data.kind === "coverage" && link.values.data.needId === item.id.toPrimitive() && this.validLink(link) ); const coveredPermissions = links.flatMap((link) => { if (link.values.data.kind !== "coverage") { return []; } const grant = this.data(link.values.data.grantId); return grant?.kind === "grant" ? [grant.permissionId] : []; }); const missingPermissionIds = value.permissionIds.filter( (id) => !coveredPermissions.includes(id) ); return [ { needId: item.id.toPrimitive(), covered: value.endDate === null && missingPermissionIds.length === 0, missingPermissionIds, }, ]; } private accountReview( item: Pick, value: Extract ): Reason[] { const reasons: Reason[] = []; if (value.active && value.expiresOn !== null && value.expiresOn < this.today) { reasons.push("expired"); } if ( value.usage !== "technical" && !this.items.some( (relation) => relation.values.data.kind === "account_person" && relation.values.data.accountId === item.id.toPrimitive() && relation.values.data.endDate === null ) ) { reasons.push("incomplete"); } return reasons; } private personRelationReview( value: Extract ): Reason[] { if (value.endDate !== null) { return []; } const reasons: Reason[] = []; if (!this.personActive(value.personId)) { reasons.push("inactive_person"); } if (!this.isActive(value.accountId)) { reasons.push("inactive_target"); } return reasons; } private grantReview( item: Pick, value: Extract ): Reason[] { if (value.endDate !== null) { return []; } const reasons: Reason[] = []; if ( !( this.isActive(value.resourceId) && this.isActive(value.permissionId) && this.isActive(value.accountId ?? value.groupId ?? "") ) ) { reasons.push("inactive_target"); } if ( !this.items.some( (link) => link.values.data.kind === "coverage" && link.values.data.grantId === item.id.toPrimitive() && this.authorizedLink(link) ) ) { reasons.push("unjustified"); } return reasons; } private needReview( item: Pick, value: Extract, coverage: readonly Coverage[] ): Reason[] { if (value.endDate !== null) { return []; } const reasons: Reason[] = []; if (!this.personActive(value.personId)) { reasons.push("inactive_person"); } if (!coverage.find((result) => result.needId === item.id.toPrimitive())?.covered) { reasons.push("uncovered"); } return reasons; } private specificReview( item: Pick, coverage: readonly Coverage[] ): Reason[] { const value = item.values.data; switch (value.kind) { case "account": return this.accountReview(item, value); case "account_person": return this.personRelationReview(value); case "grant": return this.grantReview(item, value); case "need": return this.needReview(item, value, coverage); case "membership": return value.endDate === null && !(this.isActive(value.accountId) && this.isActive(value.groupId)) ? ["inactive_target"] : []; default: return []; } } private configurationReview(value: AccessDefinition): Reason[] { if (!("configuration" in value)) { return []; } if (value.configuration === "pending" && value.endDate === null) { return ["pending_configuration"]; } return value.configuration === "removal_pending" ? ["pending_removal"] : []; } private catalogReview(value: AccessDefinition): Reason[] { const inactive = Object.entries(value).some( ([key, id]) => ["systemId", "environmentId", "resourceTypeId"].includes(key) && typeof id === "string" && !this.isActive(id) ); return inactive ? ["inactive_catalog"] : []; } project() { const coverage = this.items.flatMap((item) => this.needCoverage(item)); const reviews = this.items.flatMap((item) => [ ...this.specificReview(item, coverage), ...this.configurationReview(item.values.data), ...this.catalogReview(item.values.data), ].map((reason) => ({ itemId: item.id.toPrimitive(), reason })) ); return { coverage, reviews }; } } export function projectAccess( items: readonly Pick[], people: readonly Pick[], today: string ) { return new AccessProjection(items, people, today).project(); }