Files
bridge/internal/service/process_memberships.go
2023-07-13 23:38:02 +00:00

246 lines
6.6 KiB
Go

package service
import (
"context"
"strings"
"go.infratographer.com/x/gidx"
"golang.org/x/exp/slices"
"go.equinixmetal.net/infra9-metal-bridge/internal/permissions"
)
func (s *service) syncMemberships(ctx context.Context, memberships []ResourceMemberships) error {
if len(memberships) == 0 {
return nil
}
resourceMap := make(map[gidx.PrefixedID][]ResourceMemberships)
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)
var (
totalRoleCreate, totalRoleDelete int
totalRoleAssign, totalRoleUnassign int
)
for _, membership := range memberships {
resourceID := membership.Resource.PrefixedID()
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)
if err != nil {
return err
}
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 _, 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)
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++
}
}
for roleKey, assignments := range liveAssignments {
for memberID := range assignments {
if _, ok := wantAssignments[roleKey]; ok {
if _, ok := wantAssignments[roleKey][memberID]; ok {
continue
}
}
roleAssignmentRemovals[roleKey] = append(roleAssignmentRemovals[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),
"role.create", totalRoleCreate,
"role.delete", totalRoleDelete,
"role.assign", totalRoleAssign,
"role.unassign", totalRoleUnassign,
)
var (
rolesCreated, rolesDeleted int
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)
continue
}
resourceRoleIDs[resourceID][roleKey] = roleID
rolesCreated++
}
for _, roleID := range resourceRoleDeletions[resourceID] {
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 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))
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 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))
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++
}
}
}
s.logger.Debugw("memberships processed",
"resources", len(resourceMap),
"role.create", rolesCreated,
"role.delete", rolesDeleted,
"role.assign", roleAssignments,
"role.unassign", roleUnassignments,
)
return nil
}
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 {
slices.Sort(actions)
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
}
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
}