package auth import ( "context" "errors" "testing" "git.sendico.io/tech/sxchnge/bff/pkg/merrors" "git.sendico.io/tech/sxchnge/bff/pkg/model" "git.sendico.io/tech/sxchnge/bff/pkg/mservice" "go.mongodb.org/mongo-driver/v2/bson" ) // fakeEnforcer records the last call and returns canned results, so the scoped // access rules can be exercised without a real policy backend. type fakeEnforcer struct { allow bool err error called bool gotPolicyRef bson.ObjectID gotAccount bson.ObjectID gotOrg bson.ObjectID gotObject bson.ObjectID gotAction model.Action } func (f *fakeEnforcer) Enforce(_ context.Context, permissionRef, accountRef, orgRef, objectRef bson.ObjectID, action model.Action) (bool, error) { f.called = true f.gotPolicyRef, f.gotAccount, f.gotOrg, f.gotObject, f.gotAction = permissionRef, accountRef, orgRef, objectRef, action return f.allow, f.err } func (f *fakeEnforcer) EnforceBatch(context.Context, []model.PermissionBoundStorable, bson.ObjectID, model.Action) (map[bson.ObjectID]bool, error) { return map[bson.ObjectID]bool{}, nil } func (f *fakeEnforcer) GetRoles(context.Context, bson.ObjectID, bson.ObjectID) ([]model.Role, error) { return nil, nil } func (f *fakeEnforcer) GetPermissions(context.Context, bson.ObjectID, bson.ObjectID) ([]model.Role, []model.Permission, error) { return nil, nil, nil } func TestEnforceOwnershipAccess(t *testing.T) { caller := bson.NewObjectID() other := bson.NewObjectID() org := bson.NewObjectID() object := bson.NewObjectID() policy := bson.NewObjectID() base := func(kind model.OwnerKind, owner bson.ObjectID) ScopedAccessParams { return ScopedAccessParams{ Service: mservice.Recipients, PolicyRef: policy, CallerRef: caller, OrgRef: org, ObjectRef: object, OwnerKind: kind, OwnerRef: owner, Action: model.ActionRead, } } t.Run("personal owner is allowed without policy", func(t *testing.T) { enf := &fakeEnforcer{} if err := enforceOwnershipAccess(context.Background(), enf, base(model.OwnerKindAccount, caller)); err != nil { t.Fatalf("expected allow, got %v", err) } if enf.called { t.Fatal("personal access must not consult the enforcer") } }) t.Run("personal non-owner is denied without policy", func(t *testing.T) { enf := &fakeEnforcer{} err := enforceOwnershipAccess(context.Background(), enf, base(model.OwnerKindAccount, other)) if !errors.Is(err, merrors.ErrAccessDenied) { t.Fatalf("expected access denied, got %v", err) } if enf.called { t.Fatal("personal access must not consult the enforcer") } }) t.Run("org owner consults policy and passes object ref", func(t *testing.T) { enf := &fakeEnforcer{allow: true} if err := enforceOwnershipAccess(context.Background(), enf, base(model.OwnerKindOrganization, org)); err != nil { t.Fatalf("expected allow, got %v", err) } if !enf.called { t.Fatal("org access must consult the enforcer") } if enf.gotObject != object || enf.gotOrg != org || enf.gotAccount != caller || enf.gotPolicyRef != policy { t.Fatalf("enforcer received unexpected args: %+v", enf) } }) t.Run("org owner denied when policy refuses", func(t *testing.T) { enf := &fakeEnforcer{allow: false} err := enforceOwnershipAccess(context.Background(), enf, base(model.OwnerKindOrganization, org)) if !errors.Is(err, merrors.ErrAccessDenied) { t.Fatalf("expected access denied, got %v", err) } }) t.Run("org policy error is propagated, not masked as denial", func(t *testing.T) { boom := merrors.Define("backend down") enf := &fakeEnforcer{err: boom} err := enforceOwnershipAccess(context.Background(), enf, base(model.OwnerKindOrganization, org)) if !errors.Is(err, boom) { t.Fatalf("expected underlying error, got %v", err) } if errors.Is(err, merrors.ErrAccessDenied) { t.Fatal("internal error must not be reported as access denied") } }) t.Run("unknown owner kind is denied by default", func(t *testing.T) { enf := &fakeEnforcer{allow: true} err := enforceOwnershipAccess(context.Background(), enf, base(model.OwnerKind("garbage"), caller)) if !errors.Is(err, merrors.ErrAccessDenied) { t.Fatalf("expected access denied, got %v", err) } if enf.called { t.Fatal("unknown kind must not consult the enforcer") } }) }