access-engine/internal/adapters/relationship/translate.go
tegwick f758381cea
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 6s
Build and Publish Container Image / build-and-push (push) Successful in 1m23s
FLEX-WP-0036: Go module is github.com/netkingdom/access-engine; T02 consumer inventory
- module path renamed (no external importers); golangci local-prefixes
- ACCESS_ENGINE_RUN_TOPAZ_INTEGRATION replaces FLEX_AUTH_RUN_TOPAZ_INTEGRATION
- README title and identity paragraph reflect the landed renames
- docs/runtime-rename-inventory.md: per-consumer references by kind and order
- chart name, image package and /opt paths deliberately left for T05

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 735967@bnt-lap001
Assistant-Session: 5971967f-77f8-4ced-9e02-c6ef67040ffc
2026-10-10 15:29:00 +02:00

197 lines
4.9 KiB
Go

package relationship
import (
"fmt"
"sort"
"strings"
"github.com/netkingdom/access-engine/internal/registry"
"github.com/netkingdom/access-engine/pkg/api"
)
// SnapshotToTuples maps flex-auth registry data to relationship tuples.
func SnapshotToTuples(snapshot registry.Snapshot) []Tuple {
index := newSnapshotIndex(snapshot)
tuples := map[string]Tuple{}
addTuple := func(tuple Tuple) {
if tuple.ObjectType == "" || tuple.ObjectID == "" || tuple.Relation == "" ||
tuple.SubjectType == "" || tuple.SubjectID == "" {
return
}
tuples[tupleKey(tuple)] = tuple
}
for _, group := range snapshot.Groups {
for _, member := range group.Members {
addTuple(Tuple{
ObjectType: "group",
ObjectID: group.ID,
Relation: "member",
SubjectType: index.subjectType(member),
SubjectID: index.subjectID(member),
})
}
}
for _, team := range snapshot.Teams {
for _, member := range team.Members {
addTuple(Tuple{
ObjectType: "group",
ObjectID: teamObjectID(team.ID),
Relation: "member",
SubjectType: index.subjectType(member),
SubjectID: index.subjectID(member),
})
}
}
for _, manifest := range snapshot.ResourceManifests {
for _, resource := range manifest.Resources {
if resource.Parent != "" {
addTuple(Tuple{
ObjectType: resource.Type,
ObjectID: resource.ID,
Relation: "parent",
SubjectType: index.resourceType(resource.Parent),
SubjectID: resource.Parent,
Metadata: map[string]any{
"system": manifest.System,
},
})
}
if resource.Owner != "" {
addTuple(Tuple{
ObjectType: resource.Type,
ObjectID: resource.ID,
Relation: "owner_team",
SubjectType: "group",
SubjectID: teamOrGroupObjectID(resource.Owner),
Metadata: map[string]any{
"system": manifest.System,
},
})
}
}
}
for _, relationship := range snapshot.Relationships {
subjectType := index.subjectType(relationship.Subject)
subjectRelation := ""
if subjectType == "group" && relationship.Relation != "member" && relationship.Relation != "owner_team" {
subjectRelation = "member"
}
addTuple(Tuple{
ObjectType: index.resourceType(relationship.Object),
ObjectID: relationship.Object,
Relation: relationship.Relation,
SubjectType: subjectType,
SubjectID: index.subjectID(relationship.Subject),
SubjectRelation: subjectRelation,
Conditions: append([]api.Condition(nil), relationship.Conditions...),
Caring: relationship.Caring,
Provenance: copyMap(relationship.Provenance),
Metadata: copyMap(relationship.Metadata),
})
}
return sortedTuples(tuples)
}
type snapshotIndex struct {
groups map[string]api.Group
teams map[string]api.Team
resourceTypes map[string]string
}
func newSnapshotIndex(snapshot registry.Snapshot) snapshotIndex {
index := snapshotIndex{
groups: make(map[string]api.Group),
teams: make(map[string]api.Team),
resourceTypes: make(map[string]string),
}
for _, group := range snapshot.Groups {
index.groups[group.ID] = group
}
for _, team := range snapshot.Teams {
index.teams[team.ID] = team
index.teams[teamObjectID(team.ID)] = team
}
for _, manifest := range snapshot.ResourceManifests {
for _, resource := range manifest.Resources {
index.resourceTypes[resource.ID] = resource.Type
}
}
return index
}
func (i snapshotIndex) subjectType(id string) string {
if _, ok := i.groups[id]; ok {
return "group"
}
if _, ok := i.teams[id]; ok {
return "group"
}
if strings.HasPrefix(id, "group:") || strings.HasPrefix(id, "team:") || strings.HasPrefix(id, "reader:") {
return "group"
}
if resourceType := i.resourceType(id); resourceType != "" {
return resourceType
}
return "user"
}
func (i snapshotIndex) subjectID(id string) string {
if _, ok := i.teams[id]; ok {
return teamObjectID(id)
}
return id
}
func (i snapshotIndex) resourceType(id string) string {
if resourceType := i.resourceTypes[id]; resourceType != "" {
return resourceType
}
if inferred := inferTypeFromID(id); inferred != "" {
return inferred
}
return "resource"
}
func tupleKey(tuple Tuple) string {
return fmt.Sprintf(
"%s\x00%s\x00%s\x00%s\x00%s\x00%s",
tuple.ObjectType,
tuple.ObjectID,
tuple.Relation,
tuple.SubjectType,
tuple.SubjectID,
tuple.SubjectRelation,
)
}
func sortedTuples(tuples map[string]Tuple) []Tuple {
keys := make([]string, 0, len(tuples))
for key := range tuples {
keys = append(keys, key)
}
sort.Strings(keys)
out := make([]Tuple, 0, len(keys))
for _, key := range keys {
out = append(out, tuples[key])
}
return out
}
func teamObjectID(id string) string {
if strings.HasPrefix(id, "team:") {
return id
}
return "team:" + id
}
func teamOrGroupObjectID(id string) string {
if strings.HasPrefix(id, "team:") || strings.HasPrefix(id, "group:") || strings.HasPrefix(id, "reader:") {
return id
}
return teamObjectID(id)
}