handle unassignment of role
This commit is contained in:
@@ -45,6 +45,29 @@ func (c *Client) AssignRole(ctx context.Context, roleID gidx.PrefixedID, memberI
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) UnassignRole(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) error {
|
||||||
|
path := fmt.Sprintf("/api/v1/roles/%s/assignments", roleID.String())
|
||||||
|
|
||||||
|
body, err := encodeJSON(RoleAssign{
|
||||||
|
SubjectID: memberID.String(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var response RoleAssignResponse
|
||||||
|
|
||||||
|
if _, err = c.DoRequest(ctx, http.MethodDelete, path, body, &response); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !response.Success {
|
||||||
|
return ErrUnassignmentFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) ListRoleAssignments(ctx context.Context, roleID gidx.PrefixedID) ([]gidx.PrefixedID, error) {
|
func (c *Client) ListRoleAssignments(ctx context.Context, roleID gidx.PrefixedID) ([]gidx.PrefixedID, error) {
|
||||||
path := fmt.Sprintf("/api/v1/roles/%s/assignments", roleID.String())
|
path := fmt.Sprintf("/api/v1/roles/%s/assignments", roleID.String())
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,6 @@ import "errors"
|
|||||||
var (
|
var (
|
||||||
ErrRoleNotFound = errors.New("role not found")
|
ErrRoleNotFound = errors.New("role not found")
|
||||||
ErrAssignmentFailed = errors.New("assignment failed")
|
ErrAssignmentFailed = errors.New("assignment failed")
|
||||||
|
ErrUnassignmentFailed = errors.New("unassignment failed")
|
||||||
|
ErrUnexpectedRoleDeleteFailed = errors.New("unknown role delete error")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ type ResourceRoleCreateResponse struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResourceRoleDeleteResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
type ResourceRoles []ResourceRole
|
type ResourceRoles []ResourceRole
|
||||||
|
|
||||||
type ResourceRole struct {
|
type ResourceRole struct {
|
||||||
@@ -48,6 +52,22 @@ func (c *Client) CreateRole(ctx context.Context, resourceID gidx.PrefixedID, act
|
|||||||
return roleID, nil
|
return roleID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeleteRole(ctx context.Context, roleID gidx.PrefixedID) error {
|
||||||
|
path := fmt.Sprintf("/api/v1/roles/%s", roleID.String())
|
||||||
|
|
||||||
|
var response ResourceRoleDeleteResponse
|
||||||
|
|
||||||
|
if _, err := c.DoRequest(ctx, http.MethodDelete, path, nil, &response); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !response.Success {
|
||||||
|
return ErrUnexpectedRoleDeleteFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) ListResourceRoles(ctx context.Context, resourceID gidx.PrefixedID) (ResourceRoles, error) {
|
func (c *Client) ListResourceRoles(ctx context.Context, resourceID gidx.PrefixedID) (ResourceRoles, error) {
|
||||||
path := fmt.Sprintf("/api/v1/resources/%s/roles", resourceID.String())
|
path := fmt.Sprintf("/api/v1/resources/%s/roles", resourceID.String())
|
||||||
|
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ func (s *Subscriber) handleDeleteEvent(ctx context.Context, msg *message.Message
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s.svc.IsUser(changeMsg.SubjectID) {
|
if s.svc.IsUser(changeMsg.SubjectID) {
|
||||||
if err := s.svc.RemoveUser(ctx, changeMsg.SubjectID, changeMsg.AdditionalSubjectIDs...); err != nil {
|
if err := s.svc.UnassignUser(ctx, changeMsg.SubjectID, changeMsg.AdditionalSubjectIDs...); err != nil {
|
||||||
// TODO: only return errors on retryable errors
|
// TODO: only return errors on retryable errors
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ func (s *service) TouchOrganization(ctx context.Context, id gidx.PrefixedID) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.processRelationships(ctx, "metal-relation", relationships.Relationships)
|
s.processRelationships(ctx, "metal-relation", relationships.Relationships)
|
||||||
s.processMemberships(ctx, relationships.Memberships)
|
s.syncMemberships(ctx, relationships.Memberships)
|
||||||
|
|
||||||
s.logger.Infow("organization sync complete", "relationships", len(relationships.Relationships), "memberships", len(relationships.Memberships))
|
s.logger.Infow("organization sync complete", "relationships", len(relationships.Relationships), "memberships", len(relationships.Memberships))
|
||||||
|
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"go.infratographer.com/x/events"
|
|
||||||
"go.infratographer.com/x/gidx"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *service) processRelationships(ctx context.Context, subjectType string, relationships []Relationship) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
for _, rel := range relationships {
|
|
||||||
err = s.publisher.PublishChange(ctx, subjectType, events.ChangeMessage{
|
|
||||||
SubjectID: rel.Resource.PrefixedID(),
|
|
||||||
EventType: string(events.CreateChangeType),
|
|
||||||
AdditionalSubjectIDs: []gidx.PrefixedID{
|
|
||||||
rel.RelatedResource.PrefixedID(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Errorw("error publishing change",
|
|
||||||
"subject_type", subjectType,
|
|
||||||
"resource.id", rel.Resource.PrefixedID(),
|
|
||||||
"related_resource.id", rel.RelatedResource.PrefixedID(),
|
|
||||||
"error", err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *service) processMemberships(ctx context.Context, memberships []ResourceMemberships) {
|
|
||||||
resourceRoleID := make(map[gidx.PrefixedID]map[string]gidx.PrefixedID)
|
|
||||||
resourceRoleMembers := make(map[gidx.PrefixedID]map[string]map[gidx.PrefixedID]bool)
|
|
||||||
roleActions := make(map[string][]string)
|
|
||||||
|
|
||||||
for _, membership := range memberships {
|
|
||||||
resourceID := membership.Resource.PrefixedID()
|
|
||||||
role := membership.Role
|
|
||||||
memberID := membership.Member.PrefixedID()
|
|
||||||
|
|
||||||
if _, ok := resourceRoleMembers[resourceID]; !ok {
|
|
||||||
resourceRoleID[resourceID] = make(map[string]gidx.PrefixedID)
|
|
||||||
resourceRoleMembers[resourceID] = make(map[string]map[gidx.PrefixedID]bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := resourceRoleMembers[resourceID][role]; !ok {
|
|
||||||
resourceRoleMembers[resourceID][role] = make(map[gidx.PrefixedID]bool)
|
|
||||||
roleActions[role] = s.roles[role]
|
|
||||||
}
|
|
||||||
|
|
||||||
resourceRoleID[resourceID][role] = gidx.NullPrefixedID
|
|
||||||
resourceRoleMembers[resourceID][role][memberID] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
resourceRoleAssignments := make(map[gidx.PrefixedID]map[gidx.PrefixedID]map[gidx.PrefixedID]bool)
|
|
||||||
|
|
||||||
for resourceID, roles := range resourceRoleID {
|
|
||||||
resourceRoleAssignments[resourceID] = make(map[gidx.PrefixedID]map[gidx.PrefixedID]bool)
|
|
||||||
|
|
||||||
for role := range roles {
|
|
||||||
actions := roleActions[role]
|
|
||||||
|
|
||||||
resourceRole, err := s.perms.FindResourceRoleByActions(ctx, resourceID, actions)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Warnw("failed to find role by actions for resource", "resource.id", resourceID, "role", role, "actions", actions, "error", err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
resourceRoleID[resourceID][role] = resourceRole.ID
|
|
||||||
resourceRoleAssignments[resourceID][resourceRole.ID] = make(map[gidx.PrefixedID]bool)
|
|
||||||
|
|
||||||
assignments, err := s.perms.ListRoleAssignments(ctx, resourceRole.ID)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Warnw("failed to get role assignments for resource", "resource.id", resourceID, "role", role, "error", err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, assignment := range assignments {
|
|
||||||
resourceRoleAssignments[resourceID][resourceRole.ID][assignment] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for resourceID, roles := range resourceRoleMembers {
|
|
||||||
for role, members := range roles {
|
|
||||||
roleID := resourceRoleID[resourceID][role]
|
|
||||||
actions := roleActions[role]
|
|
||||||
|
|
||||||
logger := s.logger.With("resource.id", resourceID, "role.name", role, "actions", actions)
|
|
||||||
|
|
||||||
var createdRole bool
|
|
||||||
|
|
||||||
if roleID == gidx.NullPrefixedID {
|
|
||||||
logger.Infow("creating role for resource")
|
|
||||||
|
|
||||||
resourceRoleID, err := s.perms.CreateRole(ctx, resourceID, actions)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorw("failed to create role for resource", "error", err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
createdRole = true
|
|
||||||
roleID = resourceRoleID
|
|
||||||
}
|
|
||||||
|
|
||||||
logger = logger.With("role.id", roleID)
|
|
||||||
|
|
||||||
assignments := make(map[gidx.PrefixedID]bool)
|
|
||||||
|
|
||||||
if !createdRole {
|
|
||||||
assignments = resourceRoleAssignments[resourceID][roleID]
|
|
||||||
}
|
|
||||||
|
|
||||||
for memberID := range members {
|
|
||||||
mlogger := logger.With("member.id", memberID)
|
|
||||||
|
|
||||||
if _, ok := assignments[memberID]; ok {
|
|
||||||
mlogger.Infow("skipping already assigned member")
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.perms.AssignRole(ctx, roleID, memberID); err != nil {
|
|
||||||
mlogger.Errorw("failed to assign member to role", "error", err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
mlogger.Infow("role assigned to member")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
245
internal/service/process_memberships.go
Normal file
245
internal/service/process_memberships.go
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
31
internal/service/process_relationships.go
Normal file
31
internal/service/process_relationships.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"go.infratographer.com/x/events"
|
||||||
|
"go.infratographer.com/x/gidx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *service) processRelationships(ctx context.Context, subjectType string, relationships []Relationship) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for _, rel := range relationships {
|
||||||
|
err = s.publisher.PublishChange(ctx, subjectType, events.ChangeMessage{
|
||||||
|
SubjectID: rel.Resource.PrefixedID(),
|
||||||
|
EventType: string(events.CreateChangeType),
|
||||||
|
AdditionalSubjectIDs: []gidx.PrefixedID{
|
||||||
|
rel.RelatedResource.PrefixedID(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Errorw("error publishing change",
|
||||||
|
"subject_type", subjectType,
|
||||||
|
"resource.id", rel.Resource.PrefixedID(),
|
||||||
|
"related_resource.id", rel.RelatedResource.PrefixedID(),
|
||||||
|
"error", err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ func (s *service) TouchProject(ctx context.Context, id gidx.PrefixedID) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.processRelationships(ctx, "metal-relation", relationships.Relationships)
|
s.processRelationships(ctx, "metal-relation", relationships.Relationships)
|
||||||
s.processMemberships(ctx, relationships.Memberships)
|
s.syncMemberships(ctx, relationships.Memberships)
|
||||||
|
|
||||||
s.logger.Infow("project sync complete", "relationships", len(relationships.Relationships), "memberships", len(relationships.Memberships))
|
s.logger.Infow("project sync complete", "relationships", len(relationships.Relationships), "memberships", len(relationships.Memberships))
|
||||||
|
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ type Service interface {
|
|||||||
IsUser(id gidx.PrefixedID) bool
|
IsUser(id gidx.PrefixedID) bool
|
||||||
// AssignUser assigns a user to the given resource.
|
// AssignUser assigns a user to the given resource.
|
||||||
AssignUser(ctx context.Context, userID gidx.PrefixedID, resourceIDs ...gidx.PrefixedID) error
|
AssignUser(ctx context.Context, userID gidx.PrefixedID, resourceIDs ...gidx.PrefixedID) error
|
||||||
// RemoveUser removes the users from the given resource.
|
// UnassignUser removes the users from the given resource.
|
||||||
RemoveUser(ctx context.Context, userID gidx.PrefixedID, resourceIDs ...gidx.PrefixedID) error
|
UnassignUser(ctx context.Context, userID gidx.PrefixedID, resourceIDs ...gidx.PrefixedID) error
|
||||||
|
|
||||||
// IsAssignableResource checks if the provided resource ID may have assigned users.
|
// IsAssignableResource checks if the provided resource ID may have assigned users.
|
||||||
IsAssignableResource(id gidx.PrefixedID) bool
|
IsAssignableResource(id gidx.PrefixedID) bool
|
||||||
|
|||||||
@@ -31,6 +31,83 @@ func (s *service) AssignUser(ctx context.Context, userID gidx.PrefixedID, resour
|
|||||||
var memberships []ResourceMemberships
|
var memberships []ResourceMemberships
|
||||||
|
|
||||||
for _, resourceID := range resourceIDs {
|
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)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if role == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
memberships = append(memberships, ResourceMemberships{
|
||||||
|
Resource: prefixedID{resourceID},
|
||||||
|
Role: role,
|
||||||
|
Member: prefixedID{userID},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
s.syncMemberships(ctx, memberships)
|
||||||
|
|
||||||
|
s.logger.Infow("assignment sync complete", "memberships", len(memberships))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) UnassignUser(ctx context.Context, userID gidx.PrefixedID, resourceIDs ...gidx.PrefixedID) error {
|
||||||
|
for _, resourceID := range resourceIDs {
|
||||||
|
rlogger := s.logger.With("user.id", userID, "resource.id", resourceID)
|
||||||
|
|
||||||
|
role, err := s.getUserResourceRole(ctx, userID, resourceID)
|
||||||
|
if err != nil {
|
||||||
|
rlogger.Warnw("failed to determine role for user resource", "error", err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if role == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
actions := s.roles[role]
|
||||||
|
|
||||||
|
rlogger = rlogger.With("role.name", role, "role.actions", actions)
|
||||||
|
|
||||||
|
resourceRole, err := s.perms.FindResourceRoleByActions(ctx, resourceID, actions)
|
||||||
|
if err != nil {
|
||||||
|
rlogger.Warnw("failed to find role by actions for resource", "error", err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rlogger = rlogger.With("role.id", resourceRole.ID)
|
||||||
|
|
||||||
|
assigned, err := s.perms.RoleHasAssignment(ctx, resourceRole.ID, userID)
|
||||||
|
if err != nil {
|
||||||
|
rlogger.Warnw("failed to check role assignment", "error", err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !assigned {
|
||||||
|
rlogger.Warnw("unable to unassign member which is not assigned")
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = s.perms.UnassignRole(ctx, resourceRole.ID, userID); err != nil {
|
||||||
|
rlogger.Errorw("failed to unassign member from role", "error", err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) getUserResourceRole(ctx context.Context, userID, resourceID gidx.PrefixedID) (string, error) {
|
||||||
var (
|
var (
|
||||||
role string
|
role string
|
||||||
err error
|
err error
|
||||||
@@ -46,27 +123,8 @@ func (s *service) AssignUser(ctx context.Context, userID gidx.PrefixedID, resour
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if role == "" {
|
return role, nil
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
memberships = append(memberships, ResourceMemberships{
|
|
||||||
Resource: prefixedID{resourceID},
|
|
||||||
Role: role,
|
|
||||||
Member: prefixedID{userID},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
s.processMemberships(ctx, memberships)
|
|
||||||
|
|
||||||
s.logger.Infow("assignment sync complete", "memberships", len(memberships))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *service) RemoveUser(ctx context.Context, userID gidx.PrefixedID, resourceIDs ...gidx.PrefixedID) error {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user