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" "go.uber.org/zap" ) type fakeMembershipChecker struct { isMember bool err error } func (f *fakeMembershipChecker) IsMember(_ context.Context, _, _ bson.ObjectID) (bool, error) { return f.isMember, f.err } func TestScopedAuthorizer_Authorize(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("membership checker error propagates for org-owned resource and is not masked as denied", func(t *testing.T) { boom := merrors.Define("checker down") a := &ScopedAuthorizer{Logger: zap.NewNop(), Membership: &fakeMembershipChecker{err: boom}, Enforcer: &fakeEnforcer{}, } err := a.Authorize(context.Background(), 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("checker error must not be reported as access denied") } }) t.Run("non-member owning account-owned resource is allowed", func(t *testing.T) { a := &ScopedAuthorizer{Logger: zap.NewNop(), Membership: &fakeMembershipChecker{isMember: false}, Enforcer: &fakeEnforcer{}, } if err := a.Authorize(context.Background(), base(model.OwnerKindAccount, caller)); err != nil { t.Fatalf("expected allow for personal resource owner regardless of membership, got %v", err) } }) t.Run("non-member is denied for org-owned resource", func(t *testing.T) { a := &ScopedAuthorizer{Logger: zap.NewNop(), Membership: &fakeMembershipChecker{isMember: false}, Enforcer: &fakeEnforcer{allow: true}, } err := a.Authorize(context.Background(), base(model.OwnerKindOrganization, org)) if !errors.Is(err, merrors.ErrAccessDenied) { t.Fatalf("expected access denied for non-member on org resource, got %v", err) } }) t.Run("member owning account-owned resource is allowed", func(t *testing.T) { a := &ScopedAuthorizer{Logger: zap.NewNop(), Membership: &fakeMembershipChecker{isMember: true}, Enforcer: &fakeEnforcer{}, } if err := a.Authorize(context.Background(), base(model.OwnerKindAccount, caller)); err != nil { t.Fatalf("expected allow, got %v", err) } }) t.Run("member accessing another account's resource is denied", func(t *testing.T) { a := &ScopedAuthorizer{Logger: zap.NewNop(), Membership: &fakeMembershipChecker{isMember: true}, Enforcer: &fakeEnforcer{}, } err := a.Authorize(context.Background(), base(model.OwnerKindAccount, other)) if !errors.Is(err, merrors.ErrAccessDenied) { t.Fatalf("expected access denied, got %v", err) } }) t.Run("member with org-owned resource allowed when enforcer approves", func(t *testing.T) { a := &ScopedAuthorizer{Logger: zap.NewNop(), Membership: &fakeMembershipChecker{isMember: true}, Enforcer: &fakeEnforcer{allow: true}, } if err := a.Authorize(context.Background(), base(model.OwnerKindOrganization, org)); err != nil { t.Fatalf("expected allow, got %v", err) } }) t.Run("member with org-owned resource denied when enforcer refuses", func(t *testing.T) { a := &ScopedAuthorizer{Logger: zap.NewNop(), Membership: &fakeMembershipChecker{isMember: true}, Enforcer: &fakeEnforcer{allow: false}, } err := a.Authorize(context.Background(), base(model.OwnerKindOrganization, org)) if !errors.Is(err, merrors.ErrAccessDenied) { t.Fatalf("expected access denied, got %v", err) } }) t.Run("enforcer error propagates and is not masked as denied", func(t *testing.T) { boom := merrors.Define("policy backend down") a := &ScopedAuthorizer{Logger: zap.NewNop(), Membership: &fakeMembershipChecker{isMember: true}, Enforcer: &fakeEnforcer{err: boom}, } err := a.Authorize(context.Background(), 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("enforcer error must not be reported as access denied") } }) t.Run("unknown owner kind is denied", func(t *testing.T) { a := &ScopedAuthorizer{Logger: zap.NewNop(), Membership: &fakeMembershipChecker{isMember: true}, Enforcer: &fakeEnforcer{allow: true}, } err := a.Authorize(context.Background(), base(model.OwnerKind("garbage"), caller)) if !errors.Is(err, merrors.ErrAccessDenied) { t.Fatalf("expected access denied for unknown owner kind, got %v", err) } }) }