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.
|
// 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 {
|
return func(s *service) error {
|
||||||
s.idPrefixMap = idMap
|
s.idPrefixMap = idMap
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,12 @@ const organizationEvent = "metalorganization"
|
|||||||
|
|
||||||
func (s *service) buildOrganizationRelationships(org *models.OrganizationDetails) (Relationships, error) {
|
func (s *service) buildOrganizationRelationships(org *models.OrganizationDetails) (Relationships, error) {
|
||||||
relations := Relationships{
|
relations := Relationships{
|
||||||
Relationships: []Relationship{
|
|
||||||
// Related org to the root tenant.
|
|
||||||
{
|
|
||||||
Resource: org,
|
Resource: org,
|
||||||
|
Parent: Relation{
|
||||||
Relation: RelateParent,
|
Relation: RelateParent,
|
||||||
RelatedResource: s.rootResource,
|
Resource: s.rootResource,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
SubjectType: TypeProject,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, member := range org.Memberships {
|
for _, member := range org.Memberships {
|
||||||
@@ -32,7 +30,6 @@ func (s *service) buildOrganizationRelationships(org *models.OrganizationDetails
|
|||||||
}
|
}
|
||||||
|
|
||||||
relations.Memberships = append(relations.Memberships, ResourceMemberships{
|
relations.Memberships = append(relations.Memberships, ResourceMemberships{
|
||||||
Resource: org,
|
|
||||||
Role: role,
|
Role: role,
|
||||||
Member: member.User,
|
Member: member.User,
|
||||||
})
|
})
|
||||||
@@ -40,27 +37,10 @@ func (s *service) buildOrganizationRelationships(org *models.OrganizationDetails
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, project := range org.Projects {
|
for _, project := range org.Projects {
|
||||||
relations.Relationships = append(relations.Relationships, Relationship{
|
relations.SubjectRelationships = append(relations.SubjectRelationships, Relation{
|
||||||
Resource: project,
|
Resource: project,
|
||||||
Relation: RelateParent,
|
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
|
return relations, nil
|
||||||
@@ -91,10 +71,15 @@ func (s *service) TouchOrganization(ctx context.Context, id gidx.PrefixedID) err
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.processRelationships(ctx, organizationEvent, relationships.Relationships)
|
relationshipChanges := s.processRelationships(ctx, organizationEvent, relationships)
|
||||||
s.syncMemberships(ctx, relationships.Memberships)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,40 +10,29 @@ import (
|
|||||||
"go.equinixmetal.net/infra9-metal-bridge/internal/permissions"
|
"go.equinixmetal.net/infra9-metal-bridge/internal/permissions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *service) syncMemberships(ctx context.Context, memberships []ResourceMemberships) error {
|
func (s *service) syncMemberships(ctx context.Context, relationships Relationships, skipDeletions bool) (int, int) {
|
||||||
if len(memberships) == 0 {
|
if len(relationships.Memberships) == 0 {
|
||||||
return nil
|
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)
|
roleIDs := make(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)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
totalRoleCreate, totalRoleDelete int
|
totalRoleCreate, totalRoleDelete int
|
||||||
totalRoleAssign, totalRoleUnassign int
|
totalRoleAssign, totalRoleUnassign int
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, membership := range memberships {
|
wantRoles, wantAssignments := s.mapResourceWants(relationships.Memberships)
|
||||||
resourceID := membership.Resource.PrefixedID()
|
|
||||||
|
|
||||||
resourceMap[resourceID] = append(resourceMap[resourceID], membership)
|
liveRoles, liveAssignments, err := s.mapResourceDetails(ctx, relationships.Resource.PrefixedID())
|
||||||
}
|
|
||||||
|
|
||||||
for resourceID, memberships := range resourceMap {
|
|
||||||
resourceRoleIDs[resourceID] = make(map[string]gidx.PrefixedID)
|
|
||||||
|
|
||||||
wantRoles, wantAssignments := s.mapResourceWants(memberships)
|
|
||||||
|
|
||||||
liveRoles, liveAssignments, err := s.mapResourceDetails(ctx, resourceID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
rlogger.Errorw("failed to get membership resource details map",
|
||||||
|
"error", err,
|
||||||
|
)
|
||||||
|
|
||||||
|
return 0, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
roleCreations := make(map[string][]string)
|
roleCreations := make(map[string][]string)
|
||||||
@@ -56,15 +45,17 @@ func (s *service) syncMemberships(ctx context.Context, memberships []ResourceMem
|
|||||||
}
|
}
|
||||||
|
|
||||||
for roleKey, role := range liveRoles {
|
for roleKey, role := range liveRoles {
|
||||||
|
if !skipDeletions {
|
||||||
if _, ok := wantRoles[roleKey]; !ok {
|
if _, ok := wantRoles[roleKey]; !ok {
|
||||||
roleDeletions = append(roleDeletions, role.ID)
|
roleDeletions = append(roleDeletions, role.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceRoleIDs[resourceID][roleKey] = role.ID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
roleAssignments := make(map[string][]gidx.PrefixedID)
|
roleIDs[roleKey] = role.ID
|
||||||
roleAssignmentRemovals := make(map[string][]gidx.PrefixedID)
|
}
|
||||||
|
|
||||||
|
roleMembershipsAdd := make(map[string][]gidx.PrefixedID)
|
||||||
|
roleMembershipsRemove := make(map[string][]gidx.PrefixedID)
|
||||||
|
|
||||||
for roleKey, assignments := range wantAssignments {
|
for roleKey, assignments := range wantAssignments {
|
||||||
for memberID := range assignments {
|
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++
|
totalRoleAssign++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !skipDeletions {
|
||||||
for roleKey, assignments := range liveAssignments {
|
for roleKey, assignments := range liveAssignments {
|
||||||
for memberID := range assignments {
|
for memberID := range assignments {
|
||||||
if _, ok := wantAssignments[roleKey]; ok {
|
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++
|
totalRoleUnassign++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
resourceRoleCreations[resourceID] = roleCreations
|
|
||||||
resourceRoleDeletions[resourceID] = roleDeletions
|
|
||||||
resourceRoleAssignments[resourceID] = roleAssignments
|
|
||||||
resourceRoleAssignmentRemovals[resourceID] = roleAssignmentRemovals
|
|
||||||
|
|
||||||
totalRoleCreate += len(roleCreations)
|
totalRoleCreate += len(roleCreations)
|
||||||
totalRoleDelete += len(roleDeletions)
|
totalRoleDelete += len(roleDeletions)
|
||||||
}
|
|
||||||
|
|
||||||
s.logger.Debugw("processing memberships",
|
rlogger.Debugw("processing memberships",
|
||||||
"resources", len(resourceMap),
|
|
||||||
"role.create", totalRoleCreate,
|
"role.create", totalRoleCreate,
|
||||||
"role.delete", totalRoleDelete,
|
"role.delete", totalRoleDelete,
|
||||||
"role.assign", totalRoleAssign,
|
"role.assign", totalRoleAssign,
|
||||||
@@ -114,21 +100,19 @@ func (s *service) syncMemberships(ctx context.Context, memberships []ResourceMem
|
|||||||
roleAssignments, roleUnassignments int
|
roleAssignments, roleUnassignments int
|
||||||
)
|
)
|
||||||
|
|
||||||
for resourceID := range resourceMap {
|
for roleKey, actions := range roleCreations {
|
||||||
rlogger := s.logger.With("resource.id", resourceID.String())
|
roleID, err := s.perms.CreateRole(ctx, relationships.Resource.PrefixedID(), actions)
|
||||||
for roleKey, actions := range resourceRoleCreations[resourceID] {
|
|
||||||
roleID, err := s.perms.CreateRole(ctx, resourceID, actions)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rlogger.Errorw("error creating role", "actions", actions, "error", err)
|
rlogger.Errorw("error creating role", "actions", actions, "error", err)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceRoleIDs[resourceID][roleKey] = roleID
|
roleIDs[roleKey] = roleID
|
||||||
rolesCreated++
|
rolesCreated++
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, roleID := range resourceRoleDeletions[resourceID] {
|
for _, roleID := range roleDeletions {
|
||||||
if err := s.perms.DeleteRole(ctx, roleID); err != nil {
|
if err := s.perms.DeleteRole(ctx, roleID); err != nil {
|
||||||
rlogger.Errorw("error deleting role", "role.id", roleID, "error", err)
|
rlogger.Errorw("error deleting role", "role.id", roleID, "error", err)
|
||||||
|
|
||||||
@@ -138,8 +122,8 @@ func (s *service) syncMemberships(ctx context.Context, memberships []ResourceMem
|
|||||||
rolesDeleted++
|
rolesDeleted++
|
||||||
}
|
}
|
||||||
|
|
||||||
for roleKey, members := range resourceRoleAssignments[resourceID] {
|
for roleKey, members := range roleMembershipsAdd {
|
||||||
roleID, ok := resourceRoleIDs[resourceID][roleKey]
|
roleID, ok := roleIDs[roleKey]
|
||||||
if !ok {
|
if !ok {
|
||||||
rlogger.Errorw("role id not found for role actions key", "role_actions_key", roleKey, "members", len(members))
|
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] {
|
for roleKey, members := range roleMembershipsRemove {
|
||||||
roleID, ok := resourceRoleIDs[resourceID][roleKey]
|
roleID, ok := roleIDs[roleKey]
|
||||||
if !ok {
|
if !ok {
|
||||||
rlogger.Errorw("role id not found for role actions key", "role_actions_key", roleKey, "members", len(members))
|
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++
|
roleUnassignments++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
s.logger.Debugw("memberships processed",
|
rlogger.Debugw("memberships processed",
|
||||||
"resources", len(resourceMap),
|
|
||||||
"role.create", rolesCreated,
|
"role.create", rolesCreated,
|
||||||
"role.delete", rolesDeleted,
|
"role.delete", rolesDeleted,
|
||||||
"role.assign", roleAssignments,
|
"role.assign", roleAssignments,
|
||||||
"role.unassign", roleUnassignments,
|
"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) {
|
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/events"
|
||||||
"go.infratographer.com/x/gidx"
|
"go.infratographer.com/x/gidx"
|
||||||
|
|
||||||
|
"go.equinixmetal.net/infra9-metal-bridge/internal/permissions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *service) processRelationships(ctx context.Context, subjectType string, relationships []Relationship) {
|
type relationshipStats struct {
|
||||||
var err error
|
parentCreated bool
|
||||||
|
parentsDeleted int
|
||||||
|
subjectRelationshipsCreated int
|
||||||
|
subjectRelationshipsDeleted int
|
||||||
|
}
|
||||||
|
|
||||||
for _, rel := range relationships {
|
func (s *service) processRelationships(ctx context.Context, eventType string, relationships Relationships) int {
|
||||||
err = s.publisher.PublishChange(ctx, subjectType, events.ChangeMessage{
|
rlogger := s.logger.With("resource.id", relationships.Resource.PrefixedID())
|
||||||
SubjectID: rel.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),
|
EventType: string(events.CreateChangeType),
|
||||||
AdditionalSubjectIDs: []gidx.PrefixedID{
|
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 {
|
if err != nil {
|
||||||
s.logger.Errorw("error publishing change",
|
rlogger.Errorw("error publishing change",
|
||||||
"subject_type", subjectType,
|
"subject_type", eventType,
|
||||||
"resource.id", rel.Resource.PrefixedID(),
|
"subject.id", event.SubjectID,
|
||||||
"related_resource.id", rel.RelatedResource.PrefixedID(),
|
"event.type", event.EventType,
|
||||||
|
"additional_subject_ids", event.AdditionalSubjectIDs,
|
||||||
"error", err,
|
"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) {
|
func (s *service) buildProjectRelationships(project *models.ProjectDetails) (Relationships, error) {
|
||||||
relations := Relationships{
|
relations := Relationships{
|
||||||
Relationships: []Relationship{
|
|
||||||
// Relate project to organization.
|
|
||||||
{
|
|
||||||
Resource: project,
|
Resource: project,
|
||||||
|
// Relate project to organization.
|
||||||
|
Parent: Relation{
|
||||||
|
Resource: project.Organization,
|
||||||
Relation: RelateParent,
|
Relation: RelateParent,
|
||||||
RelatedResource: project.Organization,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +30,6 @@ func (s *service) buildProjectRelationships(project *models.ProjectDetails) (Rel
|
|||||||
}
|
}
|
||||||
|
|
||||||
relations.Memberships = append(relations.Memberships, ResourceMemberships{
|
relations.Memberships = append(relations.Memberships, ResourceMemberships{
|
||||||
Resource: project,
|
|
||||||
Role: role,
|
Role: role,
|
||||||
Member: member.User,
|
Member: member.User,
|
||||||
})
|
})
|
||||||
@@ -67,10 +64,15 @@ func (s *service) TouchProject(ctx context.Context, id gidx.PrefixedID) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.processRelationships(ctx, projectEvent, relationships.Relationships)
|
relationshipChanges := s.processRelationships(ctx, projectEvent, relationships)
|
||||||
s.syncMemberships(ctx, relationships.Memberships)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,47 +16,19 @@ type IDPrefixableResource interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Relationships struct {
|
type Relationships struct {
|
||||||
Relationships []Relationship
|
Resource IDPrefixableResource
|
||||||
|
Parent Relation
|
||||||
|
SubjectType ObjectType
|
||||||
|
SubjectRelationships []Relation
|
||||||
Memberships []ResourceMemberships
|
Memberships []ResourceMemberships
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Relationships) DeDupe() Relationships {
|
type Relation struct {
|
||||||
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
|
Relation RelationshipType
|
||||||
RelatedResource IDPrefixableResource
|
Resource IDPrefixableResource
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourceMemberships struct {
|
type ResourceMemberships struct {
|
||||||
Resource IDPrefixableResource
|
|
||||||
Role string
|
Role string
|
||||||
Member IDPrefixableResource
|
Member IDPrefixableResource
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,20 +13,39 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// TypeOrganization defines the organization type.
|
// TypeOrganization defines the organization type.
|
||||||
TypeOrganization = "organization"
|
TypeOrganization ObjectType = "organization"
|
||||||
|
|
||||||
// TypeProject defines the project type.
|
// TypeProject defines the project type.
|
||||||
TypeProject = "project"
|
TypeProject ObjectType = "project"
|
||||||
|
|
||||||
// TypeUser defines the user type.
|
// TypeUser defines the user type.
|
||||||
TypeUser = "user"
|
TypeUser ObjectType = "user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultPrefixMap is the default id prefix to type relationship.
|
// DefaultPrefixMap is the default id prefix to type relationship.
|
||||||
var DefaultPrefixMap = map[string]string{
|
var DefaultPrefixMap = map[string]ObjectType{
|
||||||
"metlorg": TypeOrganization,
|
TypeOrganization.Prefix(): TypeOrganization,
|
||||||
"metlprj": TypeProject,
|
TypeProject.Prefix(): TypeProject,
|
||||||
"metlusr": TypeUser,
|
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
|
// Service defines a bridge service methods
|
||||||
@@ -63,7 +82,7 @@ type service struct {
|
|||||||
publisher *events.Publisher
|
publisher *events.Publisher
|
||||||
metal *metal.Client
|
metal *metal.Client
|
||||||
perms *permissions.Client
|
perms *permissions.Client
|
||||||
idPrefixMap map[string]string
|
idPrefixMap map[string]ObjectType
|
||||||
|
|
||||||
rootResource prefixedID
|
rootResource prefixedID
|
||||||
roles map[string][]string
|
roles map[string][]string
|
||||||
@@ -82,7 +101,7 @@ func New(publisher *events.Publisher, metal *metal.Client, perms *permissions.Cl
|
|||||||
publisher: publisher,
|
publisher: publisher,
|
||||||
metal: metal,
|
metal: metal,
|
||||||
perms: perms,
|
perms: perms,
|
||||||
idPrefixMap: make(map[string]string),
|
idPrefixMap: make(map[string]ObjectType),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range options {
|
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 {
|
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 {
|
for _, resourceID := range resourceIDs {
|
||||||
role, err := s.getUserResourceRole(ctx, userID, resourceID)
|
role, err := s.getUserResourceRole(ctx, userID, resourceID)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,16 +45,25 @@ func (s *service) AssignUser(ctx context.Context, userID gidx.PrefixedID, resour
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
memberships = append(memberships, ResourceMemberships{
|
roles, assignments := s.syncMemberships(ctx, Relationships{
|
||||||
Resource: prefixedID{resourceID},
|
Resource: prefixedID{resourceID},
|
||||||
|
Memberships: []ResourceMemberships{
|
||||||
|
{
|
||||||
Role: role,
|
Role: role,
|
||||||
Member: prefixedID{userID},
|
Member: memberID,
|
||||||
})
|
},
|
||||||
|
},
|
||||||
|
}, true)
|
||||||
|
|
||||||
|
totalResources++
|
||||||
|
rolesChanged += roles
|
||||||
|
assignmentsChanged += assignments
|
||||||
}
|
}
|
||||||
|
|
||||||
s.syncMemberships(ctx, memberships)
|
mlogger.Infow("assignment sync complete",
|
||||||
|
"membership.roles_changed", rolesChanged,
|
||||||
s.logger.Infow("assignment sync complete", "memberships", len(memberships))
|
"membership.assignments_changed", assignmentsChanged,
|
||||||
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user