generated from coulomb/repo-seed
326 lines
8.9 KiB
Go
326 lines
8.9 KiB
Go
package topaz
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/netkingdom/flex-auth/internal/registry"
|
|
"github.com/netkingdom/flex-auth/pkg/api"
|
|
)
|
|
|
|
// SnapshotToDirectory converts flex-auth's canonical registry snapshot into
|
|
// Topaz directory objects and relations.
|
|
func SnapshotToDirectory(snapshot registry.Snapshot) DirectorySnapshot {
|
|
index := newSnapshotIndex(snapshot)
|
|
objects := map[string]DirectoryObject{}
|
|
relations := map[string]DirectoryRelation{}
|
|
|
|
addObject := func(object DirectoryObject) {
|
|
if object.Type == "" || object.ID == "" {
|
|
return
|
|
}
|
|
objects[objectKey(object.Type, object.ID)] = object
|
|
}
|
|
addRelation := func(relation DirectoryRelation) {
|
|
if relation.ObjectType == "" || relation.ObjectID == "" || relation.Relation == "" ||
|
|
relation.SubjectType == "" || relation.SubjectID == "" {
|
|
return
|
|
}
|
|
relations[relationKey(relation)] = relation
|
|
}
|
|
|
|
for _, tenant := range snapshot.Tenants {
|
|
addObject(DirectoryObject{Type: "tenant", ID: tenant.ID, DisplayName: tenant.Name, Properties: copyStringAnyMap(tenant.Metadata)})
|
|
}
|
|
|
|
for _, subject := range snapshot.Subjects {
|
|
properties := copyStringAnyMap(subject.Metadata)
|
|
addProperty(properties, "principal_type", principalType(subject.Type))
|
|
addProperty(properties, "organization_relation", subject.OrganizationRelation)
|
|
addProperty(properties, "roles", subject.Roles)
|
|
addProperty(properties, "tenant", subject.Tenant)
|
|
addProperties(properties, subject.Claims)
|
|
addObject(DirectoryObject{Type: "user", ID: subject.ID, DisplayName: subject.DisplayName, Properties: properties})
|
|
|
|
identityID := identityObjectID(subject.ID)
|
|
addObject(DirectoryObject{
|
|
Type: "identity",
|
|
ID: identityID,
|
|
Properties: map[string]any{
|
|
"identifier": subject.ID,
|
|
"subject": subject.ID,
|
|
},
|
|
})
|
|
addRelation(DirectoryRelation{
|
|
ObjectType: "identity",
|
|
ObjectID: identityID,
|
|
Relation: "identifier",
|
|
SubjectType: "user",
|
|
SubjectID: subject.ID,
|
|
})
|
|
}
|
|
|
|
for _, group := range snapshot.Groups {
|
|
properties := copyStringAnyMap(group.Metadata)
|
|
addProperty(properties, "tenant", group.Tenant)
|
|
addObject(DirectoryObject{Type: "group", ID: group.ID, DisplayName: group.DisplayName, Properties: properties})
|
|
for _, member := range group.Members {
|
|
addRelation(DirectoryRelation{
|
|
ObjectType: "group",
|
|
ObjectID: group.ID,
|
|
Relation: "member",
|
|
SubjectType: index.subjectType(member),
|
|
SubjectID: index.subjectID(member),
|
|
})
|
|
}
|
|
}
|
|
|
|
for _, team := range snapshot.Teams {
|
|
teamID := teamObjectID(team.ID)
|
|
properties := copyStringAnyMap(team.Metadata)
|
|
addProperty(properties, "kind", "team")
|
|
addProperty(properties, "flex_auth_id", team.ID)
|
|
addProperty(properties, "tenant", team.Tenant)
|
|
addObject(DirectoryObject{Type: "group", ID: teamID, DisplayName: team.DisplayName, Properties: properties})
|
|
for _, member := range team.Members {
|
|
addRelation(DirectoryRelation{
|
|
ObjectType: "group",
|
|
ObjectID: teamID,
|
|
Relation: "member",
|
|
SubjectType: index.subjectType(member),
|
|
SubjectID: index.subjectID(member),
|
|
})
|
|
}
|
|
}
|
|
|
|
for _, manifest := range snapshot.ResourceManifests {
|
|
for _, resource := range manifest.Resources {
|
|
properties := copyStringAnyMap(resource.Attributes)
|
|
addProperty(properties, "system", manifest.System)
|
|
addProperty(properties, "path", resource.Path)
|
|
addProperty(properties, "parent", resource.Parent)
|
|
addProperty(properties, "labels", resource.Labels)
|
|
addProperty(properties, "trust_zone", resource.TrustZone)
|
|
addProperty(properties, "owner", resource.Owner)
|
|
addObject(DirectoryObject{Type: resource.Type, ID: resource.ID, Properties: properties})
|
|
|
|
if resource.Parent != "" {
|
|
addRelation(DirectoryRelation{
|
|
ObjectType: resource.Type,
|
|
ObjectID: resource.ID,
|
|
Relation: "parent",
|
|
SubjectType: index.resourceType(resource.Parent),
|
|
SubjectID: resource.Parent,
|
|
})
|
|
}
|
|
if resource.Owner != "" {
|
|
addRelation(DirectoryRelation{
|
|
ObjectType: resource.Type,
|
|
ObjectID: resource.ID,
|
|
Relation: "owner_team",
|
|
SubjectType: "group",
|
|
SubjectID: teamOrGroupObjectID(resource.Owner),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, relationship := range snapshot.Relationships {
|
|
subjectType := index.subjectType(relationship.Subject)
|
|
subjectRelation := ""
|
|
if subjectType == "group" && relationship.Relation != "member" && relationship.Relation != "owner_team" {
|
|
subjectRelation = "member"
|
|
}
|
|
addRelation(DirectoryRelation{
|
|
ObjectType: index.resourceType(relationship.Object),
|
|
ObjectID: relationship.Object,
|
|
Relation: relationship.Relation,
|
|
SubjectType: subjectType,
|
|
SubjectID: index.subjectID(relationship.Subject),
|
|
SubjectRelation: subjectRelation,
|
|
})
|
|
}
|
|
|
|
return DirectorySnapshot{
|
|
Objects: sortedObjects(objects),
|
|
Relations: sortedRelations(relations),
|
|
}
|
|
}
|
|
|
|
type snapshotIndex struct {
|
|
subjects map[string]api.Subject
|
|
groups map[string]api.Group
|
|
teams map[string]api.Team
|
|
resourceTypes map[string]string
|
|
}
|
|
|
|
func newSnapshotIndex(snapshot registry.Snapshot) snapshotIndex {
|
|
index := snapshotIndex{
|
|
subjects: make(map[string]api.Subject),
|
|
groups: make(map[string]api.Group),
|
|
teams: make(map[string]api.Team),
|
|
resourceTypes: make(map[string]string),
|
|
}
|
|
for _, subject := range snapshot.Subjects {
|
|
index.subjects[subject.ID] = subject
|
|
}
|
|
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 objectKey(objectType, objectID string) string {
|
|
return objectType + "\x00" + objectID
|
|
}
|
|
|
|
func relationKey(relation DirectoryRelation) string {
|
|
return fmt.Sprintf(
|
|
"%s\x00%s\x00%s\x00%s\x00%s\x00%s",
|
|
relation.ObjectType,
|
|
relation.ObjectID,
|
|
relation.Relation,
|
|
relation.SubjectType,
|
|
relation.SubjectID,
|
|
relation.SubjectRelation,
|
|
)
|
|
}
|
|
|
|
func sortedObjects(objects map[string]DirectoryObject) []DirectoryObject {
|
|
keys := make([]string, 0, len(objects))
|
|
for key := range objects {
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
out := make([]DirectoryObject, 0, len(keys))
|
|
for _, key := range keys {
|
|
out = append(out, objects[key])
|
|
}
|
|
return out
|
|
}
|
|
|
|
func sortedRelations(relations map[string]DirectoryRelation) []DirectoryRelation {
|
|
keys := make([]string, 0, len(relations))
|
|
for key := range relations {
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
out := make([]DirectoryRelation, 0, len(keys))
|
|
for _, key := range keys {
|
|
out = append(out, relations[key])
|
|
}
|
|
return out
|
|
}
|
|
|
|
func identityObjectID(subjectID string) string {
|
|
return "identity:" + subjectID
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func principalType(subjectType api.SubjectType) string {
|
|
switch subjectType {
|
|
case api.SubjectTypeService, api.SubjectTypeAutomation, api.SubjectTypeAgent:
|
|
return "service"
|
|
case api.SubjectTypeHuman:
|
|
return "human"
|
|
case "":
|
|
return ""
|
|
default:
|
|
return strings.ToLower(string(subjectType))
|
|
}
|
|
}
|
|
|
|
func copyStringAnyMap(in map[string]any) map[string]any {
|
|
out := make(map[string]any, len(in))
|
|
for key, value := range in {
|
|
out[key] = value
|
|
}
|
|
return out
|
|
}
|
|
|
|
func addProperties(target map[string]any, attrs map[string]any) {
|
|
for key, value := range attrs {
|
|
addProperty(target, key, value)
|
|
}
|
|
}
|
|
|
|
func addProperty(target map[string]any, key string, value any) {
|
|
if target == nil || emptyProperty(value) {
|
|
return
|
|
}
|
|
if _, exists := target[key]; !exists {
|
|
target[key] = value
|
|
}
|
|
}
|
|
|
|
func emptyProperty(value any) bool {
|
|
switch typed := value.(type) {
|
|
case string:
|
|
return typed == ""
|
|
case api.OrganizationRelation:
|
|
return typed == ""
|
|
case []string:
|
|
return len(typed) == 0
|
|
case []api.CanonicalRole:
|
|
return len(typed) == 0
|
|
default:
|
|
return value == nil
|
|
}
|
|
}
|