From 2f9f0675f92cf72958737462b4b23191edfdf7a5 Mon Sep 17 00:00:00 2001 From: Mike Mason Date: Mon, 17 Jul 2023 15:36:52 +0000 Subject: [PATCH] restructure and process additions and deleteions of relationships, roles and memberships --- internal/permissions/relationships.go | 71 ++++++++ internal/service/options.go | 2 +- internal/service/organizations.go | 51 ++---- internal/service/process_memberships.go | 190 ++++++++++---------- internal/service/process_relationships.go | 200 ++++++++++++++++++++-- internal/service/projects.go | 28 +-- internal/service/relationships.go | 48 ++---- internal/service/service.go | 37 +++- internal/service/users.go | 32 ++-- 9 files changed, 441 insertions(+), 218 deletions(-) create mode 100644 internal/permissions/relationships.go diff --git a/internal/permissions/relationships.go b/internal/permissions/relationships.go new file mode 100644 index 0000000..5381c9b --- /dev/null +++ b/internal/permissions/relationships.go @@ -0,0 +1,71 @@ +package permissions + +import ( + "context" + "fmt" + "net/http" + "net/url" + + "go.infratographer.com/x/gidx" +) + +type resourceRelationship struct { + ResourceID string `json:"resource_id"` + Relation string `json:"relation"` + SubjectID string `json:"subject_id"` +} + +type ResourceRelationship struct { + ResourceID gidx.PrefixedID + Relation string + SubjectID gidx.PrefixedID +} + +func (c *Client) ListResourceRelationships(ctx context.Context, resourceID gidx.PrefixedID, relatedResourceType string) ([]ResourceRelationship, error) { + query := url.Values{ + "resourceType": []string{relatedResourceType}, + } + + url := url.URL{ + Path: fmt.Sprintf("/api/v1/resources/%s/relationships", resourceID.String()), + RawQuery: query.Encode(), + } + + var response struct { + Data []resourceRelationship `json:"data"` + } + + if _, err := c.DoRequest(ctx, http.MethodGet, url.String(), nil, &response); err != nil { + return nil, err + } + + data := make([]ResourceRelationship, len(response.Data)) + for i, entry := range response.Data { + var ( + resID, subID gidx.PrefixedID + err error + ) + + if entry.ResourceID != "" { + resID, err = gidx.Parse(entry.ResourceID) + if err != nil { + return nil, err + } + } + + if entry.SubjectID != "" { + subID, err = gidx.Parse(entry.SubjectID) + if err != nil { + return nil, err + } + } + + data[i] = ResourceRelationship{ + ResourceID: resID, + Relation: entry.Relation, + SubjectID: subID, + } + } + + return data, nil +} diff --git a/internal/service/options.go b/internal/service/options.go index 0e78a65..f1f7e6f 100644 --- a/internal/service/options.go +++ b/internal/service/options.go @@ -39,7 +39,7 @@ func WithPermissionsClient(client *permissions.Client) Option { } // WithPrefixMap sets the id prefix map relating id prefixes to type names. -func WithPrefixMap(idMap map[string]string) Option { +func WithPrefixMap(idMap map[string]ObjectType) Option { return func(s *service) error { s.idPrefixMap = idMap diff --git a/internal/service/organizations.go b/internal/service/organizations.go index 42fa141..01ad064 100644 --- a/internal/service/organizations.go +++ b/internal/service/organizations.go @@ -13,14 +13,12 @@ const organizationEvent = "metalorganization" func (s *service) buildOrganizationRelationships(org *models.OrganizationDetails) (Relationships, error) { relations := Relationships{ - Relationships: []Relationship{ - // Related org to the root tenant. - { - Resource: org, - Relation: RelateParent, - RelatedResource: s.rootResource, - }, + Resource: org, + Parent: Relation{ + Relation: RelateParent, + Resource: s.rootResource, }, + SubjectType: TypeProject, } for _, member := range org.Memberships { @@ -32,35 +30,17 @@ func (s *service) buildOrganizationRelationships(org *models.OrganizationDetails } relations.Memberships = append(relations.Memberships, ResourceMemberships{ - Resource: org, - Role: role, - Member: member.User, + Role: role, + Member: member.User, }) } } for _, project := range org.Projects { - relations.Relationships = append(relations.Relationships, Relationship{ - Resource: project, - Relation: RelateParent, - RelatedResource: org, + relations.SubjectRelationships = append(relations.SubjectRelationships, Relation{ + Resource: project, + Relation: RelateParent, }) - - for _, member := range project.Memberships { - for _, role := range member.Roles { - if _, ok := s.roles[role]; !ok { - s.logger.Warnf("unrecognized project role '%s' for %s on %s", role, member.User.PrefixedID(), project.PrefixedID()) - - continue - } - - relations.Memberships = append(relations.Memberships, ResourceMemberships{ - Resource: project, - Role: role, - Member: member.User, - }) - } - } } return relations, nil @@ -91,10 +71,15 @@ func (s *service) TouchOrganization(ctx context.Context, id gidx.PrefixedID) err return err } - s.processRelationships(ctx, organizationEvent, relationships.Relationships) - s.syncMemberships(ctx, relationships.Memberships) + relationshipChanges := s.processRelationships(ctx, organizationEvent, relationships) + rolesChanged, assignmentsChanged := s.syncMemberships(ctx, relationships, false) - s.logger.Infow("organization sync complete", "relationships", len(relationships.Relationships), "memberships", len(relationships.Memberships)) + s.logger.Infow("organization sync complete", + "resource.id", org.PrefixedID(), + "relationships.changed", relationshipChanges, + "membership.roles_changed", rolesChanged, + "membership.assignments_changed", assignmentsChanged, + ) return nil } diff --git a/internal/service/process_memberships.go b/internal/service/process_memberships.go index 2a3c002..543480b 100644 --- a/internal/service/process_memberships.go +++ b/internal/service/process_memberships.go @@ -10,75 +10,67 @@ import ( "go.equinixmetal.net/infra9-metal-bridge/internal/permissions" ) -func (s *service) syncMemberships(ctx context.Context, memberships []ResourceMemberships) error { - if len(memberships) == 0 { - return nil +func (s *service) syncMemberships(ctx context.Context, relationships Relationships, skipDeletions bool) (int, int) { + if len(relationships.Memberships) == 0 { + return 0, 0 } - resourceMap := make(map[gidx.PrefixedID][]ResourceMemberships) + rlogger := s.logger.With("resource.id", relationships.Resource.PrefixedID()) - resourceRoleIDs := make(map[gidx.PrefixedID]map[string]gidx.PrefixedID) - - resourceRoleCreations := make(map[gidx.PrefixedID]map[string][]string) - resourceRoleDeletions := make(map[gidx.PrefixedID][]gidx.PrefixedID) - - resourceRoleAssignments := make(map[gidx.PrefixedID]map[string][]gidx.PrefixedID) - resourceRoleAssignmentRemovals := make(map[gidx.PrefixedID]map[string][]gidx.PrefixedID) + roleIDs := make(map[string]gidx.PrefixedID) var ( totalRoleCreate, totalRoleDelete int totalRoleAssign, totalRoleUnassign int ) - for _, membership := range memberships { - resourceID := membership.Resource.PrefixedID() + wantRoles, wantAssignments := s.mapResourceWants(relationships.Memberships) - resourceMap[resourceID] = append(resourceMap[resourceID], membership) + liveRoles, liveAssignments, err := s.mapResourceDetails(ctx, relationships.Resource.PrefixedID()) + if err != nil { + rlogger.Errorw("failed to get membership resource details map", + "error", err, + ) + + return 0, 0 } - for resourceID, memberships := range resourceMap { - resourceRoleIDs[resourceID] = make(map[string]gidx.PrefixedID) + roleCreations := make(map[string][]string) + roleDeletions := make([]gidx.PrefixedID, 0) - wantRoles, wantAssignments := s.mapResourceWants(memberships) - - liveRoles, liveAssignments, err := s.mapResourceDetails(ctx, resourceID) - if err != nil { - return err + for roleKey, actions := range wantRoles { + if _, ok := liveRoles[roleKey]; !ok { + roleCreations[roleKey] = actions } + } - roleCreations := make(map[string][]string) - roleDeletions := make([]gidx.PrefixedID, 0) - - for roleKey, actions := range wantRoles { - if _, ok := liveRoles[roleKey]; !ok { - roleCreations[roleKey] = actions - } - } - - for roleKey, role := range liveRoles { + for roleKey, role := range liveRoles { + if !skipDeletions { if _, ok := wantRoles[roleKey]; !ok { roleDeletions = append(roleDeletions, role.ID) } - - resourceRoleIDs[resourceID][roleKey] = role.ID } - roleAssignments := make(map[string][]gidx.PrefixedID) - roleAssignmentRemovals := make(map[string][]gidx.PrefixedID) + roleIDs[roleKey] = role.ID + } - for roleKey, assignments := range wantAssignments { - for memberID := range assignments { - if _, ok := liveAssignments[roleKey]; ok { - if _, ok := liveAssignments[roleKey][memberID]; ok { - continue - } + roleMembershipsAdd := make(map[string][]gidx.PrefixedID) + roleMembershipsRemove := make(map[string][]gidx.PrefixedID) + + for roleKey, assignments := range wantAssignments { + for memberID := range assignments { + if _, ok := liveAssignments[roleKey]; ok { + if _, ok := liveAssignments[roleKey][memberID]; ok { + continue } - - roleAssignments[roleKey] = append(roleAssignments[roleKey], memberID) - totalRoleAssign++ } - } + roleMembershipsAdd[roleKey] = append(roleMembershipsAdd[roleKey], memberID) + totalRoleAssign++ + } + } + + if !skipDeletions { for roleKey, assignments := range liveAssignments { for memberID := range assignments { if _, ok := wantAssignments[roleKey]; ok { @@ -87,22 +79,16 @@ func (s *service) syncMemberships(ctx context.Context, memberships []ResourceMem } } - roleAssignmentRemovals[roleKey] = append(roleAssignmentRemovals[roleKey], memberID) + roleMembershipsRemove[roleKey] = append(roleMembershipsRemove[roleKey], memberID) totalRoleUnassign++ } } - - resourceRoleCreations[resourceID] = roleCreations - resourceRoleDeletions[resourceID] = roleDeletions - resourceRoleAssignments[resourceID] = roleAssignments - resourceRoleAssignmentRemovals[resourceID] = roleAssignmentRemovals - - totalRoleCreate += len(roleCreations) - totalRoleDelete += len(roleDeletions) } - s.logger.Debugw("processing memberships", - "resources", len(resourceMap), + totalRoleCreate += len(roleCreations) + totalRoleDelete += len(roleDeletions) + + rlogger.Debugw("processing memberships", "role.create", totalRoleCreate, "role.delete", totalRoleDelete, "role.assign", totalRoleAssign, @@ -114,78 +100,74 @@ func (s *service) syncMemberships(ctx context.Context, memberships []ResourceMem roleAssignments, roleUnassignments int ) - for resourceID := range resourceMap { - rlogger := s.logger.With("resource.id", resourceID.String()) - for roleKey, actions := range resourceRoleCreations[resourceID] { - roleID, err := s.perms.CreateRole(ctx, resourceID, actions) - if err != nil { - rlogger.Errorw("error creating role", "actions", actions, "error", err) + for roleKey, actions := range roleCreations { + roleID, err := s.perms.CreateRole(ctx, relationships.Resource.PrefixedID(), actions) + if err != nil { + rlogger.Errorw("error creating role", "actions", actions, "error", err) - continue - } - - resourceRoleIDs[resourceID][roleKey] = roleID - rolesCreated++ + continue } - for _, roleID := range resourceRoleDeletions[resourceID] { - if err := s.perms.DeleteRole(ctx, roleID); err != nil { - rlogger.Errorw("error deleting role", "role.id", roleID, "error", err) + roleIDs[roleKey] = roleID + rolesCreated++ + } - continue - } + for _, roleID := range roleDeletions { + if err := s.perms.DeleteRole(ctx, roleID); err != nil { + rlogger.Errorw("error deleting role", "role.id", roleID, "error", err) - rolesDeleted++ + continue } - for roleKey, members := range resourceRoleAssignments[resourceID] { - roleID, ok := resourceRoleIDs[resourceID][roleKey] - if !ok { - rlogger.Errorw("role id not found for role actions key", "role_actions_key", roleKey, "members", len(members)) + rolesDeleted++ + } - continue - } + for roleKey, members := range roleMembershipsAdd { + roleID, ok := roleIDs[roleKey] + if !ok { + rlogger.Errorw("role id not found for role actions key", "role_actions_key", roleKey, "members", len(members)) - for _, memberID := range members { - if err := s.perms.AssignRole(ctx, roleID, memberID); err != nil { - rlogger.Errorw("error assigning member to role", "role.id", roleID, "member.id", memberID, "error", err) - - continue - } - - roleAssignments++ - } + continue } - for roleKey, members := range resourceRoleAssignmentRemovals[resourceID] { - roleID, ok := resourceRoleIDs[resourceID][roleKey] - if !ok { - rlogger.Errorw("role id not found for role actions key", "role_actions_key", roleKey, "members", len(members)) + for _, memberID := range members { + if err := s.perms.AssignRole(ctx, roleID, memberID); err != nil { + rlogger.Errorw("error assigning member to role", "role.id", roleID, "member.id", memberID, "error", err) continue } - for _, memberID := range members { - if err := s.perms.UnassignRole(ctx, roleID, memberID); err != nil { - rlogger.Errorw("error removing member from role", "role.id", roleID, "member.id", memberID, "error", err) - - continue - } - - roleUnassignments++ - } + roleAssignments++ } } - s.logger.Debugw("memberships processed", - "resources", len(resourceMap), + for roleKey, members := range roleMembershipsRemove { + roleID, ok := roleIDs[roleKey] + if !ok { + rlogger.Errorw("role id not found for role actions key", "role_actions_key", roleKey, "members", len(members)) + + continue + } + + for _, memberID := range members { + if err := s.perms.UnassignRole(ctx, roleID, memberID); err != nil { + rlogger.Errorw("error removing member from role", "role.id", roleID, "member.id", memberID, "error", err) + + continue + } + + roleUnassignments++ + } + } + + rlogger.Debugw("memberships processed", "role.create", rolesCreated, "role.delete", rolesDeleted, "role.assign", roleAssignments, "role.unassign", roleUnassignments, ) - return nil + return rolesCreated + rolesDeleted, roleAssignments + roleUnassignments } func (s *service) mapResourceWants(memberships []ResourceMemberships) (map[string][]string, map[string]map[gidx.PrefixedID]bool) { diff --git a/internal/service/process_relationships.go b/internal/service/process_relationships.go index 3da1655..a46dfef 100644 --- a/internal/service/process_relationships.go +++ b/internal/service/process_relationships.go @@ -5,27 +5,207 @@ import ( "go.infratographer.com/x/events" "go.infratographer.com/x/gidx" + + "go.equinixmetal.net/infra9-metal-bridge/internal/permissions" ) -func (s *service) processRelationships(ctx context.Context, subjectType string, relationships []Relationship) { - var err error +type relationshipStats struct { + parentCreated bool + parentsDeleted int + subjectRelationshipsCreated int + subjectRelationshipsDeleted int +} - for _, rel := range relationships { - err = s.publisher.PublishChange(ctx, subjectType, events.ChangeMessage{ - SubjectID: rel.Resource.PrefixedID(), +func (s *service) processRelationships(ctx context.Context, eventType string, relationships Relationships) int { + rlogger := s.logger.With("resource.id", relationships.Resource.PrefixedID()) + + wantParentRelationship, wantSubjectRelationships := s.mapRelationWants(relationships) + + liveParentRelationships, liveSubjectRelationships, err := s.getRelationshipMap(ctx, relationships.Resource, relationships.SubjectType) + if err != nil { + rlogger.Errorw("failed to get relationship map", + "relationships.subject_type", relationships.SubjectType, + "error", err, + ) + + return 0 + } + + var ( + createParentRelationship *Relation + deleteParentRelationships []gidx.PrefixedID + + foundParent bool + + createSubjectRelationships []Relation + deleteSubjectRelationships []Relation + ) + + if wantParentRelationship != nil { + for subjID := range liveParentRelationships { + if subjID == wantParentRelationship.Resource.PrefixedID() { + foundParent = true + + continue + } + + deleteParentRelationships = append(deleteParentRelationships, subjID) + } + + if !foundParent { + createParentRelationship = wantParentRelationship + } + } else { + for subjID := range liveParentRelationships { + deleteParentRelationships = append(deleteParentRelationships, subjID) + } + } + + for resID, relation := range wantSubjectRelationships { + if _, ok := liveSubjectRelationships[resID]; ok { + continue + } + + createSubjectRelationships = append(createSubjectRelationships, Relation{ + Resource: prefixedID{resID}, + Relation: relation, + }) + } + + for resID, relation := range liveSubjectRelationships { + if _, ok := wantSubjectRelationships[resID]; ok { + continue + } + + deleteSubjectRelationships = append(deleteSubjectRelationships, Relation{ + Resource: prefixedID{resID}, + Relation: relation, + }) + } + + var processEvents []events.ChangeMessage + + rlogger.Debugw("processing relationships", + "parent.create", createParentRelationship != nil, + "parent.delete", len(deleteParentRelationships), + "subject.create", len(createSubjectRelationships), + "subject.delete", len(deleteSubjectRelationships), + ) + + if createParentRelationship != nil { + processEvents = append(processEvents, events.ChangeMessage{ + SubjectID: relationships.Resource.PrefixedID(), EventType: string(events.CreateChangeType), AdditionalSubjectIDs: []gidx.PrefixedID{ - rel.RelatedResource.PrefixedID(), + createParentRelationship.Resource.PrefixedID(), }, }) + } + + for _, relatedResourceID := range deleteParentRelationships { + processEvents = append(processEvents, events.ChangeMessage{ + SubjectID: relationships.Resource.PrefixedID(), + EventType: string(events.DeleteChangeType), + AdditionalSubjectIDs: []gidx.PrefixedID{ + relatedResourceID, + }, + }) + } + + for _, relation := range createSubjectRelationships { + processEvents = append(processEvents, events.ChangeMessage{ + SubjectID: relation.Resource.PrefixedID(), + EventType: string(events.CreateChangeType), + AdditionalSubjectIDs: []gidx.PrefixedID{ + relationships.Resource.PrefixedID(), + }, + }) + } + + for _, relation := range deleteSubjectRelationships { + processEvents = append(processEvents, events.ChangeMessage{ + SubjectID: relation.Resource.PrefixedID(), + EventType: string(events.DeleteChangeType), + AdditionalSubjectIDs: []gidx.PrefixedID{ + relationships.Resource.PrefixedID(), + }, + }) + } + + for _, event := range processEvents { + err = s.publisher.PublishChange(ctx, eventType, event) if err != nil { - s.logger.Errorw("error publishing change", - "subject_type", subjectType, - "resource.id", rel.Resource.PrefixedID(), - "related_resource.id", rel.RelatedResource.PrefixedID(), + rlogger.Errorw("error publishing change", + "subject_type", eventType, + "subject.id", event.SubjectID, + "event.type", event.EventType, + "additional_subject_ids", event.AdditionalSubjectIDs, "error", err, ) } } + + rlogger.Debugw("relationships processed", + "parent.create", createParentRelationship != nil, + "parent.delete", len(deleteParentRelationships), + "subject.create", len(createSubjectRelationships), + "subject.delete", len(deleteSubjectRelationships), + ) + + changes := len(deleteParentRelationships) + len(createSubjectRelationships) + len(deleteSubjectRelationships) + + if createParentRelationship != nil { + changes++ + } + + return changes +} + +func (s *service) mapRelationWants(relationships Relationships) (*Relation, map[gidx.PrefixedID]RelationshipType) { + var wantParent *Relation + + wantSubject := make(map[gidx.PrefixedID]RelationshipType) + + if relationships.Parent.Resource != nil { + wantParent = &relationships.Parent + } + + for _, relationship := range relationships.SubjectRelationships { + wantSubject[relationship.Resource.PrefixedID()] = relationship.Relation + } + + return wantParent, wantSubject +} + +func (s *service) getRelationshipMap(ctx context.Context, resource IDPrefixableResource, relatedObjectType ObjectType) (map[gidx.PrefixedID]RelationshipType, map[gidx.PrefixedID]RelationshipType, error) { + liveResource, err := s.perms.ListResourceRelationships(ctx, resource.PrefixedID(), "") + if err != nil { + return nil, nil, err + } + + var liveSubject []permissions.ResourceRelationship + + if relatedObjectType != "" { + liveSubject, err = s.perms.ListResourceRelationships(ctx, resource.PrefixedID(), relatedObjectType.Prefix()) + if err != nil { + return nil, nil, err + } + } + + parents := make(map[gidx.PrefixedID]RelationshipType, len(liveResource)) + for _, relationship := range liveResource { + if relationship.Relation != string(RelateParent) { + continue + } + + parents[relationship.SubjectID] = RelationshipType(relationship.Relation) + } + + subject := make(map[gidx.PrefixedID]RelationshipType, len(liveSubject)) + for _, relationship := range liveSubject { + subject[relationship.ResourceID] = RelationshipType(relationship.Relation) + } + + return parents, subject, nil } diff --git a/internal/service/projects.go b/internal/service/projects.go index 496add4..7ad68f5 100644 --- a/internal/service/projects.go +++ b/internal/service/projects.go @@ -13,13 +13,11 @@ const projectEvent = "metalproject" func (s *service) buildProjectRelationships(project *models.ProjectDetails) (Relationships, error) { relations := Relationships{ - Relationships: []Relationship{ - // Relate project to organization. - { - Resource: project, - Relation: RelateParent, - RelatedResource: project.Organization, - }, + Resource: project, + // Relate project to organization. + Parent: Relation{ + Resource: project.Organization, + Relation: RelateParent, }, } @@ -32,9 +30,8 @@ func (s *service) buildProjectRelationships(project *models.ProjectDetails) (Rel } relations.Memberships = append(relations.Memberships, ResourceMemberships{ - Resource: project, - Role: role, - Member: member.User, + Role: role, + Member: member.User, }) } } @@ -67,10 +64,15 @@ func (s *service) TouchProject(ctx context.Context, id gidx.PrefixedID) error { return err } - s.processRelationships(ctx, projectEvent, relationships.Relationships) - s.syncMemberships(ctx, relationships.Memberships) + relationshipChanges := s.processRelationships(ctx, projectEvent, relationships) + rolesChanged, assignmentsChanged := s.syncMemberships(ctx, relationships, false) - s.logger.Infow("project sync complete", "relationships", len(relationships.Relationships), "memberships", len(relationships.Memberships)) + s.logger.Infow("project sync complete", + "resource.id", project.PrefixedID(), + "relationships.changed", relationshipChanges, + "membership.roles_changed", rolesChanged, + "membership.assignments_changed", assignmentsChanged, + ) return nil } diff --git a/internal/service/relationships.go b/internal/service/relationships.go index 199dc96..252daf8 100644 --- a/internal/service/relationships.go +++ b/internal/service/relationships.go @@ -16,47 +16,19 @@ type IDPrefixableResource interface { } type Relationships struct { - Relationships []Relationship - Memberships []ResourceMemberships + Resource IDPrefixableResource + Parent Relation + SubjectType ObjectType + SubjectRelationships []Relation + Memberships []ResourceMemberships } -func (r Relationships) DeDupe() Relationships { - rels := make(map[string]bool) - mems := make(map[string]bool) - - var results Relationships - - for _, rel := range r.Relationships { - key := rel.Resource.PrefixedID().String() + "/" + string(rel.Relation) + "/" + rel.RelatedResource.PrefixedID().String() - - if _, ok := rels[key]; !ok { - rels[key] = true - - results.Relationships = append(results.Relationships, rel) - } - } - - for _, member := range r.Memberships { - key := member.Resource.PrefixedID().String() + "/" + member.Role + "/" + member.Member.PrefixedID().String() - - if _, ok := mems[key]; !ok { - mems[key] = true - - results.Memberships = append(results.Memberships, member) - } - } - - return results -} - -type Relationship struct { - Resource IDPrefixableResource - Relation RelationshipType - RelatedResource IDPrefixableResource +type Relation struct { + Relation RelationshipType + Resource IDPrefixableResource } type ResourceMemberships struct { - Resource IDPrefixableResource - Role string - Member IDPrefixableResource + Role string + Member IDPrefixableResource } diff --git a/internal/service/service.go b/internal/service/service.go index b5fccef..aa4b2ed 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -13,20 +13,39 @@ import ( const ( // TypeOrganization defines the organization type. - TypeOrganization = "organization" + TypeOrganization ObjectType = "organization" // TypeProject defines the project type. - TypeProject = "project" + TypeProject ObjectType = "project" // TypeUser defines the user type. - TypeUser = "user" + TypeUser ObjectType = "user" ) // DefaultPrefixMap is the default id prefix to type relationship. -var DefaultPrefixMap = map[string]string{ - "metlorg": TypeOrganization, - "metlprj": TypeProject, - "metlusr": TypeUser, +var DefaultPrefixMap = map[string]ObjectType{ + TypeOrganization.Prefix(): TypeOrganization, + TypeProject.Prefix(): TypeProject, + TypeUser.Prefix(): TypeUser, +} + +type ObjectType string + +func (t ObjectType) Prefix() string { + switch t { + case TypeOrganization: + return "metlorg" + case TypeProject: + return "metlprj" + case TypeUser: + return "metlusr" + default: + return "" + } +} + +func (t ObjectType) String() string { + return string(t) } // Service defines a bridge service methods @@ -63,7 +82,7 @@ type service struct { publisher *events.Publisher metal *metal.Client perms *permissions.Client - idPrefixMap map[string]string + idPrefixMap map[string]ObjectType rootResource prefixedID roles map[string][]string @@ -82,7 +101,7 @@ func New(publisher *events.Publisher, metal *metal.Client, perms *permissions.Cl publisher: publisher, metal: metal, perms: perms, - idPrefixMap: make(map[string]string), + idPrefixMap: make(map[string]ObjectType), } for _, opt := range options { diff --git a/internal/service/users.go b/internal/service/users.go index d712c5e..71834c0 100644 --- a/internal/service/users.go +++ b/internal/service/users.go @@ -28,13 +28,16 @@ func (s *service) IsAssignableResource(id gidx.PrefixedID) bool { } func (s *service) AssignUser(ctx context.Context, userID gidx.PrefixedID, resourceIDs ...gidx.PrefixedID) error { - var memberships []ResourceMemberships + var totalResources, rolesChanged, assignmentsChanged int + + mlogger := s.logger.With("member.id", userID.String()) + + memberID := prefixedID{userID} for _, resourceID := range resourceIDs { role, err := s.getUserResourceRole(ctx, userID, resourceID) if err != nil { - s.logger.Warnw("failed to determine role for user resource", "error", err) - + mlogger.Warnw("failed to determine role for user resource", "error", err) continue } @@ -42,16 +45,25 @@ func (s *service) AssignUser(ctx context.Context, userID gidx.PrefixedID, resour continue } - memberships = append(memberships, ResourceMemberships{ + roles, assignments := s.syncMemberships(ctx, Relationships{ Resource: prefixedID{resourceID}, - Role: role, - Member: prefixedID{userID}, - }) + Memberships: []ResourceMemberships{ + { + Role: role, + Member: memberID, + }, + }, + }, true) + + totalResources++ + rolesChanged += roles + assignmentsChanged += assignments } - s.syncMemberships(ctx, memberships) - - s.logger.Infow("assignment sync complete", "memberships", len(memberships)) + mlogger.Infow("assignment sync complete", + "membership.roles_changed", rolesChanged, + "membership.assignments_changed", assignmentsChanged, + ) return nil }