verify / verify (push) Successful in 4m17s
Signed-off-by: Cole Speelman <crspeelman@gmail.com>
66 lines
2.7 KiB
Go
66 lines
2.7 KiB
Go
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package access
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sort"
|
|
)
|
|
|
|
var ErrRoleSetUnsupported = errors.New("access: atomic role sets are unsupported")
|
|
|
|
// RoleSetRepository commits every replacement and the audit atomically. There
|
|
// is no sequence of individual Grant/Revoke calls as a fallback.
|
|
type RoleSetRepository interface {
|
|
ReplaceOrganizationUserRoles(context.Context, []string, []Binding, string, AuditEvent) error
|
|
}
|
|
|
|
type OrganizationUserRolesChange struct {
|
|
OrganizationID, UserID, ActorUserID, RequestID string
|
|
Roles, ExpectedBindingIDs []string
|
|
}
|
|
|
|
// ReplaceOrganizationUserRoles replaces the direct organization-wide role set
|
|
// for one active member. Team and narrower grants are unaffected. This bulk
|
|
// operation requires a current direct owner inside the write transaction;
|
|
// applications still authorize their customer/merchant and allowed-role boundary.
|
|
func (service *Service) ReplaceOrganizationUserRoles(ctx context.Context, input OrganizationUserRolesChange) ([]Binding, error) {
|
|
repository, ok := service.repository.(RoleSetRepository)
|
|
if !ok {
|
|
return nil, ErrRoleSetUnsupported
|
|
}
|
|
if service.ownerRole == "" || !idPattern.MatchString(input.OrganizationID) || !idPattern.MatchString(input.UserID) || !idPattern.MatchString(input.ActorUserID) || !text(input.RequestID, 128, true) || len(input.Roles) < 1 || len(input.Roles) > 16 {
|
|
return nil, errors.New("access: invalid organization role set")
|
|
}
|
|
roles := append([]string(nil), input.Roles...)
|
|
sort.Strings(roles)
|
|
for i, role := range roles {
|
|
if _, exists := service.policy.Roles[role]; !exists || i > 0 && roles[i-1] == role {
|
|
return nil, errors.New("access: unknown or duplicate role")
|
|
}
|
|
}
|
|
expected, err := canonicalBindingIDs(input.ExpectedBindingIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
now := service.now().UTC()
|
|
bindings := make([]Binding, 0, len(roles))
|
|
for _, role := range roles {
|
|
id, err := randomID(service.random)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bindings = append(bindings, Binding{ID: id, SubjectKind: User, SubjectID: input.UserID, Role: role, Scope: Scope{OrganizationID: input.OrganizationID}, GrantedBy: input.ActorUserID, GrantedAt: now})
|
|
}
|
|
id, err := randomID(service.random)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
audit := AuditEvent{ID: id, OrganizationID: input.OrganizationID, ActorUserID: input.ActorUserID, Action: "access.role.replace", ResourceType: "user", ResourceID: input.UserID, RequestID: input.RequestID, Summary: "Direct organization roles replaced", CreatedAt: now}
|
|
if err := repository.ReplaceOrganizationUserRoles(ctx, expected, bindings, service.ownerRole, audit); err != nil {
|
|
return nil, err
|
|
}
|
|
return bindings, nil
|
|
}
|