github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/services/integrationtesting/dispatch_test.go (about) 1 //go:build ci && docker && !skipintegrationtests 2 // +build ci,docker,!skipintegrationtests 3 4 package integrationtesting_test 5 6 import ( 7 "context" 8 "slices" 9 "testing" 10 "time" 11 12 v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" 13 "github.com/stretchr/testify/require" 14 15 "github.com/authzed/spicedb/internal/datastore/spanner" 16 "github.com/authzed/spicedb/internal/testserver" 17 testdatastore "github.com/authzed/spicedb/internal/testserver/datastore" 18 "github.com/authzed/spicedb/internal/testserver/datastore/config" 19 dsconfig "github.com/authzed/spicedb/pkg/cmd/datastore" 20 "github.com/authzed/spicedb/pkg/datastore" 21 "github.com/authzed/spicedb/pkg/tuple" 22 ) 23 24 type testCase struct { 25 name string 26 schema string 27 runOp func(t *testing.T, client v1.PermissionsServiceClient) 28 } 29 30 func TestDispatchIntegration(t *testing.T) { 31 blacklist := []string{ 32 spanner.Engine, // spanner emulator doesn't support parallel transactions 33 } 34 35 testCases := []testCase{ 36 { 37 "basic dispatched permissions checks", 38 `definition user {} 39 40 definition resource { 41 relation parent: resource 42 relation viewer: user 43 permission view = viewer + parent->view 44 }`, 45 func(t *testing.T, client v1.PermissionsServiceClient) { 46 resp, err := client.WriteRelationships(context.Background(), &v1.WriteRelationshipsRequest{ 47 Updates: []*v1.RelationshipUpdate{ 48 { 49 Operation: v1.RelationshipUpdate_OPERATION_CREATE, 50 Relationship: tuple.MustToRelationship(tuple.MustParse("resource:foo#viewer@user:tom")), 51 }, 52 { 53 Operation: v1.RelationshipUpdate_OPERATION_CREATE, 54 Relationship: tuple.MustToRelationship(tuple.MustParse("resource:foo#parent@resource:bar")), 55 }, 56 { 57 Operation: v1.RelationshipUpdate_OPERATION_CREATE, 58 Relationship: tuple.MustToRelationship(tuple.MustParse("resource:bar#viewer@user:jill")), 59 }, 60 }, 61 }) 62 require.NoError(t, err) 63 64 cresp, err := client.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ 65 Consistency: &v1.Consistency{ 66 Requirement: &v1.Consistency_AtLeastAsFresh{ 67 AtLeastAsFresh: resp.WrittenAt, 68 }, 69 }, 70 Resource: &v1.ObjectReference{ 71 ObjectType: "resource", 72 ObjectId: "foo", 73 }, 74 Permission: "view", 75 Subject: &v1.SubjectReference{ 76 Object: &v1.ObjectReference{ 77 ObjectType: "user", 78 ObjectId: "tom", 79 }, 80 }, 81 }) 82 require.NoError(t, err) 83 require.Equal(t, v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION, cresp.Permissionship) 84 85 cresp2, err := client.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ 86 Consistency: &v1.Consistency{ 87 Requirement: &v1.Consistency_AtLeastAsFresh{ 88 AtLeastAsFresh: resp.WrittenAt, 89 }, 90 }, 91 Resource: &v1.ObjectReference{ 92 ObjectType: "resource", 93 ObjectId: "foo", 94 }, 95 Permission: "view", 96 Subject: &v1.SubjectReference{ 97 Object: &v1.ObjectReference{ 98 ObjectType: "user", 99 ObjectId: "jill", 100 }, 101 }, 102 }) 103 require.NoError(t, err) 104 require.Equal(t, v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION, cresp2.Permissionship) 105 }, 106 }, 107 { 108 "unknown parent relation test", 109 `definition user {} 110 111 definition someothertype {} 112 113 definition resource { 114 relation parent: someothertype 115 relation viewer: user 116 permission view = viewer + parent->unknown 117 }`, 118 func(t *testing.T, client v1.PermissionsServiceClient) { 119 resp, err := client.WriteRelationships(context.Background(), &v1.WriteRelationshipsRequest{ 120 Updates: []*v1.RelationshipUpdate{ 121 { 122 Operation: v1.RelationshipUpdate_OPERATION_CREATE, 123 Relationship: tuple.MustToRelationship(tuple.MustParse("resource:foo#parent@someothertype:bar")), 124 }, 125 }, 126 }) 127 require.NoError(t, err) 128 129 cresp, err := client.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ 130 Consistency: &v1.Consistency{ 131 Requirement: &v1.Consistency_AtLeastAsFresh{ 132 AtLeastAsFresh: resp.WrittenAt, 133 }, 134 }, 135 Resource: &v1.ObjectReference{ 136 ObjectType: "resource", 137 ObjectId: "foo", 138 }, 139 Permission: "view", 140 Subject: &v1.SubjectReference{ 141 Object: &v1.ObjectReference{ 142 ObjectType: "user", 143 ObjectId: "tom", 144 }, 145 }, 146 }) 147 require.NoError(t, err) 148 require.Equal(t, v1.CheckPermissionResponse_PERMISSIONSHIP_NO_PERMISSION, cresp.Permissionship) 149 }, 150 }, 151 { 152 "unknown relation test", 153 `definition user {} 154 155 definition resource { 156 relation viewer: user 157 permission view = viewer 158 }`, 159 func(t *testing.T, client v1.PermissionsServiceClient) { 160 resp, err := client.WriteRelationships(context.Background(), &v1.WriteRelationshipsRequest{ 161 Updates: []*v1.RelationshipUpdate{ 162 { 163 Operation: v1.RelationshipUpdate_OPERATION_CREATE, 164 Relationship: tuple.MustToRelationship(tuple.MustParse("resource:foo#viewer@user:someuser")), 165 }, 166 }, 167 }) 168 require.NoError(t, err) 169 170 _, cerr := client.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ 171 Consistency: &v1.Consistency{ 172 Requirement: &v1.Consistency_AtLeastAsFresh{ 173 AtLeastAsFresh: resp.WrittenAt, 174 }, 175 }, 176 Resource: &v1.ObjectReference{ 177 ObjectType: "resource", 178 ObjectId: "foo", 179 }, 180 Permission: "unknown", 181 Subject: &v1.SubjectReference{ 182 Object: &v1.ObjectReference{ 183 ObjectType: "user", 184 ObjectId: "tom", 185 }, 186 }, 187 }) 188 require.Error(t, cerr) 189 }, 190 }, 191 { 192 "delete preconditions test", 193 `definition user {} 194 195 definition resource { 196 relation viewer: user 197 permission view = viewer 198 }`, 199 func(t *testing.T, client v1.PermissionsServiceClient) { 200 // Ensure the delete fails on the precondition. 201 _, derr := client.DeleteRelationships(context.Background(), &v1.DeleteRelationshipsRequest{ 202 OptionalPreconditions: []*v1.Precondition{ 203 { 204 Operation: v1.Precondition_OPERATION_MUST_MATCH, 205 Filter: &v1.RelationshipFilter{ 206 ResourceType: "resource", 207 OptionalResourceId: "someresource", 208 OptionalRelation: "viewer", 209 OptionalSubjectFilter: &v1.SubjectFilter{ 210 SubjectType: "user", 211 OptionalSubjectId: "sarah", 212 }, 213 }, 214 }, 215 }, 216 RelationshipFilter: &v1.RelationshipFilter{ 217 ResourceType: "resource", 218 }, 219 }) 220 require.Error(t, derr) 221 require.Contains(t, derr.Error(), "unable to satisfy write precondition") 222 223 // Write a relationship, but not the one we want. 224 _, werr := client.WriteRelationships(context.Background(), &v1.WriteRelationshipsRequest{ 225 Updates: []*v1.RelationshipUpdate{ 226 { 227 Operation: v1.RelationshipUpdate_OPERATION_CREATE, 228 Relationship: tuple.MustToRelationship(tuple.MustParse("resource:someresource#viewer@user:someuser")), 229 }, 230 }, 231 }) 232 require.NoError(t, werr) 233 234 // Ensure the delete still fails on the precondition. 235 _, derr = client.DeleteRelationships(context.Background(), &v1.DeleteRelationshipsRequest{ 236 OptionalPreconditions: []*v1.Precondition{ 237 { 238 Operation: v1.Precondition_OPERATION_MUST_MATCH, 239 Filter: &v1.RelationshipFilter{ 240 ResourceType: "resource", 241 OptionalResourceId: "someresource", 242 OptionalRelation: "viewer", 243 OptionalSubjectFilter: &v1.SubjectFilter{ 244 SubjectType: "user", 245 OptionalSubjectId: "sarah", 246 }, 247 }, 248 }, 249 }, 250 RelationshipFilter: &v1.RelationshipFilter{ 251 ResourceType: "resource", 252 }, 253 }) 254 require.Error(t, derr) 255 require.Contains(t, derr.Error(), "unable to satisfy write precondition") 256 257 // Write the relationship needed. 258 _, werr = client.WriteRelationships(context.Background(), &v1.WriteRelationshipsRequest{ 259 Updates: []*v1.RelationshipUpdate{ 260 { 261 Operation: v1.RelationshipUpdate_OPERATION_CREATE, 262 Relationship: tuple.MustToRelationship(tuple.MustParse("resource:someresource#viewer@user:sarah")), 263 }, 264 }, 265 }) 266 require.NoError(t, werr) 267 268 // Ensure a delete with an inverse precondition now fails. 269 _, derr = client.DeleteRelationships(context.Background(), &v1.DeleteRelationshipsRequest{ 270 OptionalPreconditions: []*v1.Precondition{ 271 { 272 Operation: v1.Precondition_OPERATION_MUST_NOT_MATCH, 273 Filter: &v1.RelationshipFilter{ 274 ResourceType: "resource", 275 OptionalResourceId: "someresource", 276 OptionalRelation: "viewer", 277 OptionalSubjectFilter: &v1.SubjectFilter{ 278 SubjectType: "user", 279 OptionalSubjectId: "sarah", 280 }, 281 }, 282 }, 283 }, 284 RelationshipFilter: &v1.RelationshipFilter{ 285 ResourceType: "resource", 286 }, 287 }) 288 require.Error(t, derr) 289 require.Contains(t, derr.Error(), "unable to satisfy write precondition") 290 291 // Ensure the delete with MUST_MATCH now works. 292 resp, derr := client.DeleteRelationships(context.Background(), &v1.DeleteRelationshipsRequest{ 293 OptionalPreconditions: []*v1.Precondition{ 294 { 295 Operation: v1.Precondition_OPERATION_MUST_MATCH, 296 Filter: &v1.RelationshipFilter{ 297 ResourceType: "resource", 298 OptionalResourceId: "someresource", 299 OptionalRelation: "viewer", 300 OptionalSubjectFilter: &v1.SubjectFilter{ 301 SubjectType: "user", 302 OptionalSubjectId: "sarah", 303 }, 304 }, 305 }, 306 }, 307 RelationshipFilter: &v1.RelationshipFilter{ 308 ResourceType: "resource", 309 }, 310 }) 311 require.NoError(t, derr) 312 require.NotNil(t, resp.DeletedAt) 313 }, 314 }, 315 } 316 317 for _, engine := range datastore.Engines { 318 if slices.Contains(blacklist, engine) { 319 continue 320 } 321 b := testdatastore.RunDatastoreEngine(t, engine) 322 t.Run(engine, func(t *testing.T) { 323 for _, tc := range testCases { 324 t.Run(tc.name, func(t *testing.T) { 325 ds := b.NewDatastore(t, config.DatastoreConfigInitFunc(t, 326 dsconfig.WithWatchBufferLength(0), 327 dsconfig.WithGCWindow(time.Duration(90_000_000_000_000)), 328 dsconfig.WithRevisionQuantization(10), 329 dsconfig.WithMaxRetries(50), 330 dsconfig.WithRequestHedgingEnabled(false))) 331 332 conns, cleanup := testserver.TestClusterWithDispatch(t, 1, ds) 333 t.Cleanup(cleanup) 334 335 schemaClient := v1.NewSchemaServiceClient(conns[0]) 336 _, err := schemaClient.WriteSchema(context.Background(), &v1.WriteSchemaRequest{ 337 Schema: tc.schema, 338 }) 339 require.NoError(t, err) 340 341 client := v1.NewPermissionsServiceClient(conns[0]) 342 tc.runOp(t, client) 343 }) 344 } 345 }) 346 } 347 }