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,87 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type roleSetRepositoryStub struct {
|
||||
repositoryStub
|
||||
calls int
|
||||
expected []string
|
||||
roles []Binding
|
||||
audit AuditEvent
|
||||
}
|
||||
|
||||
func (r *roleSetRepositoryStub) ReplaceOrganizationUserRoles(_ context.Context, expected []string, bindings []Binding, _ string, audit AuditEvent) error {
|
||||
r.calls++
|
||||
r.expected, r.roles, r.audit = expected, bindings, audit
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestRoleSetServiceBoundsAndCanonicalCopies(t *testing.T) {
|
||||
policy := Policy{Roles: map[string]string{"owner": "Owner", "buyer": "Buyer", "billing": "Billing"}, Permissions: map[string]string{"purchase": "Purchase"}, Grants: map[string][]string{"owner": {"purchase"}, "buyer": {"purchase"}, "billing": {}}}
|
||||
r := &roleSetRepositoryStub{}
|
||||
service, err := New(r, policy, Options{OwnerRole: "owner"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
input := OrganizationUserRolesChange{OrganizationID: "organization-123", UserID: "member-12345678", ActorUserID: "owner-12345678", Roles: []string{"buyer", "billing"}, ExpectedBindingIDs: []string{"binding-second", "binding-first"}, RequestID: "request-roles"}
|
||||
bindings, err := service.ReplaceOrganizationUserRoles(t.Context(), input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r.calls != 1 || len(bindings) != 2 || bindings[0].Role != "billing" || bindings[1].Role != "buyer" || bindings[0].ID == bindings[1].ID || !slices.Equal(r.expected, []string{"binding-first", "binding-second"}) {
|
||||
t.Fatalf("bindings=%v expected=%v calls=%d", bindings, r.expected, r.calls)
|
||||
}
|
||||
if !slices.Equal(input.Roles, []string{"buyer", "billing"}) || !slices.Equal(input.ExpectedBindingIDs, []string{"binding-second", "binding-first"}) {
|
||||
t.Fatal("caller input was sorted in place")
|
||||
}
|
||||
if r.audit.RequestID != input.RequestID || r.audit.ActorUserID != input.ActorUserID || r.audit.ResourceID != input.UserID {
|
||||
t.Fatalf("audit=%+v", r.audit)
|
||||
}
|
||||
for _, roles := range [][]string{nil, {"buyer", "buyer"}, {"missing"}, make([]string, 17)} {
|
||||
invalid := input
|
||||
invalid.Roles = roles
|
||||
if _, err = service.ReplaceOrganizationUserRoles(t.Context(), invalid); err == nil {
|
||||
t.Fatalf("invalid roles=%v", roles)
|
||||
}
|
||||
}
|
||||
for _, expected := range [][]string{{"bad"}, {"binding-first", "binding-first"}, make([]string, 17)} {
|
||||
invalid := input
|
||||
invalid.ExpectedBindingIDs = expected
|
||||
if _, err = service.ReplaceOrganizationUserRoles(t.Context(), invalid); err == nil {
|
||||
t.Fatalf("invalid IDs=%v", expected)
|
||||
}
|
||||
}
|
||||
if r.calls != 1 {
|
||||
t.Fatal("invalid input reached repository")
|
||||
}
|
||||
legacy, err := New(&repositoryStub{}, policy, Options{OwnerRole: "owner"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = legacy.ReplaceOrganizationUserRoles(t.Context(), input); !errors.Is(err, ErrRoleSetUnsupported) {
|
||||
t.Fatalf("fallback=%v", err)
|
||||
}
|
||||
broken, err := New(r, policy, Options{OwnerRole: "owner", Random: strings.NewReader("")})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = broken.ReplaceOrganizationUserRoles(t.Context(), input); err == nil || r.calls != 1 {
|
||||
t.Fatal("random failure reached storage")
|
||||
}
|
||||
withoutOwner, err := New(r, policy, Options{Now: func() time.Time { return time.Unix(2000, 0) }})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = withoutOwner.ReplaceOrganizationUserRoles(t.Context(), input); err == nil || r.calls != 1 {
|
||||
t.Fatal("role set without owner boundary accepted")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user