Support atomic organization role sets and owner-managed invitations
verify / verify (push) Successful in 4m17s
verify / verify (push) Successful in 4m17s
Signed-off-by: Cole Speelman <crspeelman@gmail.com>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user