247 lines
6.7 KiB
Go
247 lines
6.7 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"go.infratographer.com/x/gidx"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/trace"
|
|
"golang.org/x/exp/slices"
|
|
|
|
"go.equinixmetal.net/infra9-metal-bridge/internal/permissions"
|
|
)
|
|
|
|
// processMemberships determines the changes between what is wanted and what is live and executes on the differences.
|
|
// If skipDeletions is true, no deletes will be executed.
|
|
func (s *service) processMemberships(ctx context.Context, relationships Relationships, skipDeletions bool) (int, int) {
|
|
ctx, span := tracer.Start(ctx, "processMemberships",
|
|
trace.WithAttributes(
|
|
attribute.String("resource.id", relationships.Resource.PrefixedID().String()),
|
|
attribute.Int("resource.memberships", len(relationships.Memberships)),
|
|
),
|
|
)
|
|
|
|
defer span.End()
|
|
|
|
if len(relationships.Memberships) == 0 {
|
|
return 0, 0
|
|
}
|
|
|
|
rlogger := s.logger.With("resource.id", relationships.Resource.PrefixedID())
|
|
|
|
roleIDs := make(map[string]gidx.PrefixedID)
|
|
|
|
var (
|
|
totalRoleCreate, totalRoleDelete int
|
|
totalRoleAssign, totalRoleUnassign int
|
|
)
|
|
|
|
wantRoles, wantAssignments := s.mapResourceWants(relationships.Memberships)
|
|
|
|
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
|
|
}
|
|
|
|
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 {
|
|
if !skipDeletions {
|
|
if _, ok := wantRoles[roleKey]; !ok {
|
|
roleDeletions = append(roleDeletions, role.ID)
|
|
}
|
|
}
|
|
|
|
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 {
|
|
if _, ok := liveAssignments[roleKey]; ok {
|
|
if _, ok := liveAssignments[roleKey][memberID]; ok {
|
|
continue
|
|
}
|
|
}
|
|
|
|
roleMembershipsAdd[roleKey] = append(roleMembershipsAdd[roleKey], memberID)
|
|
totalRoleAssign++
|
|
}
|
|
}
|
|
|
|
if !skipDeletions {
|
|
for roleKey, assignments := range liveAssignments {
|
|
for memberID := range assignments {
|
|
if _, ok := wantAssignments[roleKey]; ok {
|
|
if _, ok := wantAssignments[roleKey][memberID]; ok {
|
|
continue
|
|
}
|
|
}
|
|
|
|
roleMembershipsRemove[roleKey] = append(roleMembershipsRemove[roleKey], memberID)
|
|
totalRoleUnassign++
|
|
}
|
|
}
|
|
}
|
|
|
|
totalRoleCreate += len(roleCreations)
|
|
totalRoleDelete += len(roleDeletions)
|
|
|
|
rlogger.Debugw("processing memberships",
|
|
"role.create", totalRoleCreate,
|
|
"role.delete", totalRoleDelete,
|
|
"role.assign", totalRoleAssign,
|
|
"role.unassign", totalRoleUnassign,
|
|
)
|
|
|
|
var (
|
|
rolesCreated, rolesDeleted int
|
|
roleAssignments, roleUnassignments int
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
roleIDs[roleKey] = roleID
|
|
rolesCreated++
|
|
}
|
|
|
|
for _, roleID := range roleDeletions {
|
|
if err := s.perms.DeleteRole(ctx, roleID); err != nil {
|
|
rlogger.Errorw("error deleting role", "role.id", roleID, "error", err)
|
|
|
|
continue
|
|
}
|
|
|
|
rolesDeleted++
|
|
}
|
|
|
|
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))
|
|
|
|
continue
|
|
}
|
|
|
|
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++
|
|
}
|
|
}
|
|
|
|
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 rolesCreated + rolesDeleted, roleAssignments + roleUnassignments
|
|
}
|
|
|
|
// mapResourceWants processes the provided memberships and returns two maps.
|
|
// A Role Key is computed based on a sorted slice of actions for each role.
|
|
// The first map is of Role Key -> list of actions
|
|
// The second map is of Role Key -> Member ID -> true
|
|
func (s *service) mapResourceWants(memberships []ResourceMemberships) (map[string][]string, map[string]map[gidx.PrefixedID]bool) {
|
|
roleActionsKey := make(map[string]string)
|
|
|
|
for role, actions := range s.roles {
|
|
roleActionsKey[role] = strings.Join(actions, "|")
|
|
}
|
|
|
|
wantRoles := make(map[string][]string)
|
|
wantAssignments := make(map[string]map[gidx.PrefixedID]bool)
|
|
|
|
for _, membership := range memberships {
|
|
roleKey := roleActionsKey[membership.Role]
|
|
|
|
if _, ok := wantRoles[roleKey]; !ok {
|
|
wantRoles[roleKey] = s.roles[membership.Role]
|
|
wantAssignments[roleKey] = make(map[gidx.PrefixedID]bool)
|
|
}
|
|
|
|
wantAssignments[roleKey][membership.Member.PrefixedID()] = true
|
|
}
|
|
|
|
return wantRoles, wantAssignments
|
|
}
|
|
|
|
// mapResourceDetails fetches the provided ResourceID's live state and returns two maps and an error.
|
|
// A Role Key is computed based on a sorted slice of actions for each role.
|
|
// The first map is of Role Key -> Permissions Resource Role
|
|
// The second map is of Role Key -> Member ID -> true
|
|
func (s *service) mapResourceDetails(ctx context.Context, resourceID gidx.PrefixedID) (map[string]permissions.ResourceRole, map[string]map[gidx.PrefixedID]bool, error) {
|
|
roles := make(map[string]permissions.ResourceRole)
|
|
assignments := make(map[string]map[gidx.PrefixedID]bool)
|
|
|
|
liveRoles, err := s.perms.ListResourceRoles(ctx, resourceID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
for _, role := range liveRoles {
|
|
slices.Sort(role.Actions)
|
|
|
|
roleKey := strings.Join(role.Actions, "|")
|
|
roles[roleKey] = role
|
|
|
|
liveAssignments, err := s.perms.ListRoleAssignments(ctx, role.ID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
assignments[roleKey] = make(map[gidx.PrefixedID]bool)
|
|
|
|
for _, assignment := range liveAssignments {
|
|
assignments[roleKey][assignment] = true
|
|
}
|
|
}
|
|
|
|
return roles, assignments, nil
|
|
}
|