restructure and process additions and deleteions of relationships, roles and memberships
This commit is contained in:
71
internal/permissions/relationships.go
Normal file
71
internal/permissions/relationships.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
Parent: Relation{
|
||||
Relation: RelateParent,
|
||||
RelatedResource: s.rootResource,
|
||||
},
|
||||
Resource: s.rootResource,
|
||||
},
|
||||
SubjectType: TypeProject,
|
||||
}
|
||||
|
||||
for _, member := range org.Memberships {
|
||||
@@ -32,7 +30,6 @@ func (s *service) buildOrganizationRelationships(org *models.OrganizationDetails
|
||||
}
|
||||
|
||||
relations.Memberships = append(relations.Memberships, ResourceMemberships{
|
||||
Resource: org,
|
||||
Role: role,
|
||||
Member: member.User,
|
||||
})
|
||||
@@ -40,27 +37,10 @@ func (s *service) buildOrganizationRelationships(org *models.OrganizationDetails
|
||||
}
|
||||
|
||||
for _, project := range org.Projects {
|
||||
relations.Relationships = append(relations.Relationships, Relationship{
|
||||
relations.SubjectRelationships = append(relations.SubjectRelationships, Relation{
|
||||
Resource: project,
|
||||
Relation: RelateParent,
|
||||
RelatedResource: org,
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -10,40 +10,29 @@ 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)
|
||||
}
|
||||
|
||||
for resourceID, memberships := range resourceMap {
|
||||
resourceRoleIDs[resourceID] = make(map[string]gidx.PrefixedID)
|
||||
|
||||
wantRoles, wantAssignments := s.mapResourceWants(memberships)
|
||||
|
||||
liveRoles, liveAssignments, err := s.mapResourceDetails(ctx, resourceID)
|
||||
liveRoles, liveAssignments, err := s.mapResourceDetails(ctx, relationships.Resource.PrefixedID())
|
||||
if err != nil {
|
||||
return err
|
||||
rlogger.Errorw("failed to get membership resource details map",
|
||||
"error", err,
|
||||
)
|
||||
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
roleCreations := make(map[string][]string)
|
||||
@@ -56,15 +45,17 @@ func (s *service) syncMemberships(ctx context.Context, memberships []ResourceMem
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
roleMembershipsAdd := make(map[string][]gidx.PrefixedID)
|
||||
roleMembershipsRemove := make(map[string][]gidx.PrefixedID)
|
||||
|
||||
for roleKey, assignments := range wantAssignments {
|
||||
for memberID := range assignments {
|
||||
@@ -74,11 +65,12 @@ func (s *service) syncMemberships(ctx context.Context, memberships []ResourceMem
|
||||
}
|
||||
}
|
||||
|
||||
roleAssignments[roleKey] = append(roleAssignments[roleKey], memberID)
|
||||
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),
|
||||
rlogger.Debugw("processing memberships",
|
||||
"role.create", totalRoleCreate,
|
||||
"role.delete", totalRoleDelete,
|
||||
"role.assign", totalRoleAssign,
|
||||
@@ -114,21 +100,19 @@ 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)
|
||||
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
|
||||
roleIDs[roleKey] = roleID
|
||||
rolesCreated++
|
||||
}
|
||||
|
||||
for _, roleID := range resourceRoleDeletions[resourceID] {
|
||||
for _, roleID := range roleDeletions {
|
||||
if err := s.perms.DeleteRole(ctx, roleID); err != nil {
|
||||
rlogger.Errorw("error deleting role", "role.id", roleID, "error", err)
|
||||
|
||||
@@ -138,8 +122,8 @@ func (s *service) syncMemberships(ctx context.Context, memberships []ResourceMem
|
||||
rolesDeleted++
|
||||
}
|
||||
|
||||
for roleKey, members := range resourceRoleAssignments[resourceID] {
|
||||
roleID, ok := resourceRoleIDs[resourceID][roleKey]
|
||||
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))
|
||||
|
||||
@@ -157,8 +141,8 @@ func (s *service) syncMemberships(ctx context.Context, memberships []ResourceMem
|
||||
}
|
||||
}
|
||||
|
||||
for roleKey, members := range resourceRoleAssignmentRemovals[resourceID] {
|
||||
roleID, ok := resourceRoleIDs[resourceID][roleKey]
|
||||
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))
|
||||
|
||||
@@ -175,17 +159,15 @@ func (s *service) syncMemberships(ctx context.Context, memberships []ResourceMem
|
||||
roleUnassignments++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Debugw("memberships processed",
|
||||
"resources", len(resourceMap),
|
||||
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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
// Relate project to organization.
|
||||
Parent: Relation{
|
||||
Resource: project.Organization,
|
||||
Relation: RelateParent,
|
||||
RelatedResource: project.Organization,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -32,7 +30,6 @@ func (s *service) buildProjectRelationships(project *models.ProjectDetails) (Rel
|
||||
}
|
||||
|
||||
relations.Memberships = append(relations.Memberships, ResourceMemberships{
|
||||
Resource: project,
|
||||
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
|
||||
}
|
||||
|
||||
@@ -16,47 +16,19 @@ type IDPrefixableResource interface {
|
||||
}
|
||||
|
||||
type Relationships struct {
|
||||
Relationships []Relationship
|
||||
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
|
||||
type Relation struct {
|
||||
Relation RelationshipType
|
||||
RelatedResource IDPrefixableResource
|
||||
Resource IDPrefixableResource
|
||||
}
|
||||
|
||||
type ResourceMemberships struct {
|
||||
Resource IDPrefixableResource
|
||||
Role string
|
||||
Member IDPrefixableResource
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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},
|
||||
Memberships: []ResourceMemberships{
|
||||
{
|
||||
Role: role,
|
||||
Member: prefixedID{userID},
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user