44"context"
55"encoding/json"
66"fmt"
7+ "sync"
78"testing"
89
910"github.com/google/uuid"
@@ -12,6 +13,7 @@ import (
1213"github.com/stretchr/testify/require"
1314"golang.org/x/xerrors"
1415
16+ "github.com/coder/coder/coderd/rbac/regosql"
1517"github.com/coder/coder/testutil"
1618)
1719
@@ -63,11 +65,14 @@ func TestFilterError(t *testing.T) {
6365
6466t .Run ("CancelledContext" ,func (t * testing.T ) {
6567t .Parallel ()
66- t .Skipf ("This test is racy as rego eval checks the ctx canceled in a go routine. " +
67- "It is a coin flip if the query finishes before the 'cancel' is checked. " +
68- "So sometimes the 'Authorize' call succeeds even if ctx is canceled." )
6968
70- auth := NewAuthorizer (prometheus .NewRegistry ())
69+ auth := & MockAuthorizer {
70+ AuthorizeFunc :func (ctx context.Context ,subject Subject ,action Action ,object Object )error {
71+ // Authorize func always returns nil, unless the context is cancelled.
72+ return ctx .Err ()
73+ },
74+ }
75+
7176subject := Subject {
7277ID :uuid .NewString (),
7378Roles :RoleNames {
@@ -77,20 +82,44 @@ func TestFilterError(t *testing.T) {
7782Scope :ScopeAll ,
7883}
7984
80- ctx ,cancel := context .WithCancel (context .Background ())
81- defer cancel ()
82- objects := []Objecter {
83- ResourceUser ,
84- ResourceUser ,
85- & objectBomb {
85+ t .Run ("SmallSet" ,func (t * testing.T ) {
86+ t .Parallel ()
87+
88+ ctx ,cancel := context .WithCancel (context .Background ())
89+ defer cancel ()
90+ objects := []Objecter {
91+ ResourceUser ,
92+ ResourceUser ,
93+ & objectBomb {
94+ Objecter :ResourceUser ,
95+ bomb :cancel ,
96+ },
97+ ResourceUser ,
98+ }
99+
100+ _ ,err := Filter (ctx ,auth ,subject ,ActionRead ,objects )
101+ require .ErrorIs (t ,err ,context .Canceled )
102+ })
103+
104+ // Triggers Prepared Authorize
105+ t .Run ("LargeSet" ,func (t * testing.T ) {
106+ t .Parallel ()
107+
108+ ctx ,cancel := context .WithCancel (context .Background ())
109+ defer cancel ()
110+
111+ objects := make ([]Objecter ,100 )
112+ for i := 0 ;i < 100 ;i ++ {
113+ objects [i ]= ResourceUser
114+ }
115+ objects [20 ]= & objectBomb {
86116Objecter :ResourceUser ,
87117bomb :cancel ,
88- },
89- ResourceUser ,
90- }
118+ }
91119
92- _ ,err := Filter (ctx ,auth ,subject ,ActionRead ,objects )
93- require .ErrorIs (t ,err ,context .Canceled )
120+ _ ,err := Filter (ctx ,auth ,subject ,ActionRead ,objects )
121+ require .ErrorIs (t ,err ,context .Canceled )
122+ })
94123})
95124}
96125
@@ -1095,3 +1124,42 @@ func must[T any](value T, err error) T {
10951124}
10961125return value
10971126}
1127+
1128+ type MockAuthorizer struct {
1129+ AuthorizeFunc func (context.Context ,Subject ,Action ,Object )error
1130+ }
1131+
1132+ var _ Authorizer = (* MockAuthorizer )(nil )
1133+
1134+ func (d * MockAuthorizer )Authorize (ctx context.Context ,s Subject ,a Action ,o Object )error {
1135+ return d .AuthorizeFunc (ctx ,s ,a ,o )
1136+ }
1137+
1138+ func (d * MockAuthorizer )Prepare (_ context.Context ,subject Subject ,action Action ,_ string ) (PreparedAuthorized ,error ) {
1139+ return & mockPreparedAuthorizer {
1140+ Original :d ,
1141+ Subject :subject ,
1142+ Action :action ,
1143+ },nil
1144+ }
1145+
1146+ var _ PreparedAuthorized = (* mockPreparedAuthorizer )(nil )
1147+
1148+ // fakePreparedAuthorizer is the prepared version of a FakeAuthorizer. It will
1149+ // return the same error as the original FakeAuthorizer.
1150+ type mockPreparedAuthorizer struct {
1151+ sync.RWMutex
1152+ Original * MockAuthorizer
1153+ Subject Subject
1154+ Action Action
1155+ }
1156+
1157+ func (f * mockPreparedAuthorizer )Authorize (ctx context.Context ,object Object )error {
1158+ return f .Original .Authorize (ctx ,f .Subject ,f .Action ,object )
1159+ }
1160+
1161+ // CompileToSQL returns a compiled version of the authorizer that will work for
1162+ // in memory databases. This fake version will not work against a SQL database.
1163+ func (* mockPreparedAuthorizer )CompileToSQL (_ context.Context ,_ regosql.ConvertConfig ) (string ,error ) {
1164+ return "not a valid sql string" ,nil
1165+ }