Skip to content

Commit 1b3efa7

Browse files
support for rel and namespace deprecation
Signed-off-by: Kartikay <[email protected]>
1 parent fc6d208 commit 1b3efa7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3384
-730
lines changed

internal/relationships/validation.go

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55

66
"github.com/authzed/spicedb/internal/namespace"
7+
"github.com/authzed/spicedb/internal/services/shared"
78
"github.com/authzed/spicedb/pkg/caveats"
89
caveattypes "github.com/authzed/spicedb/pkg/caveats/types"
910
"github.com/authzed/spicedb/pkg/datastore"
@@ -28,8 +29,9 @@ func ValidateRelationshipUpdates(
2829
return item.Relationship
2930
})
3031

32+
ts := schema.NewTypeSystem(schema.ResolverForDatastoreReader(reader))
3133
// Load namespaces and caveats.
32-
referencedNamespaceMap, referencedCaveatMap, err := loadNamespacesAndCaveats(ctx, rels, reader)
34+
referencedNamespaceMap, referencedCaveatMap, err := loadNamespacesAndCaveats(ctx, rels, reader, ts)
3335
if err != nil {
3436
return err
3537
}
@@ -52,6 +54,17 @@ func ValidateRelationshipUpdates(
5254
}
5355
}
5456

57+
// check if the relation is deprecated for create or touch operations
58+
relsToCheck := make([]tuple.Relationship, 0, len(updates))
59+
for _, update := range updates {
60+
if update.Operation == tuple.UpdateOperationTouch || update.Operation == tuple.UpdateOperationCreate {
61+
relsToCheck = append(relsToCheck, update.Relationship)
62+
}
63+
}
64+
if err := CheckDeprecationsOnRelationships(ctx, relsToCheck, ts); err != nil {
65+
return err
66+
}
67+
5568
return nil
5669
}
5770

@@ -65,8 +78,10 @@ func ValidateRelationshipsForCreateOrTouch(
6578
caveatTypeSet *caveattypes.TypeSet,
6679
rels ...tuple.Relationship,
6780
) error {
81+
ts := schema.NewTypeSystem(schema.ResolverForDatastoreReader(reader))
82+
6883
// Load namespaces and caveats.
69-
referencedNamespaceMap, referencedCaveatMap, err := loadNamespacesAndCaveats(ctx, rels, reader)
84+
referencedNamespaceMap, referencedCaveatMap, err := loadNamespacesAndCaveats(ctx, rels, reader, ts)
7085
if err != nil {
7186
return err
7287
}
@@ -84,10 +99,15 @@ func ValidateRelationshipsForCreateOrTouch(
8499
}
85100
}
86101

102+
// Validate if the resource, relation or subject is deprecated
103+
if err := CheckDeprecationsOnRelationships(ctx, rels, ts); err != nil {
104+
return err
105+
}
106+
87107
return nil
88108
}
89109

90-
func loadNamespacesAndCaveats(ctx context.Context, rels []tuple.Relationship, reader datastore.Reader) (map[string]*schema.Definition, map[string]*core.CaveatDefinition, error) {
110+
func loadNamespacesAndCaveats(ctx context.Context, rels []tuple.Relationship, reader datastore.Reader, ts *schema.TypeSystem) (map[string]*schema.Definition, map[string]*core.CaveatDefinition, error) {
91111
referencedNamespaceNames := mapz.NewSet[string]()
92112
referencedCaveatNamesWithContext := mapz.NewSet[string]()
93113
for _, rel := range rels {
@@ -106,7 +126,6 @@ func loadNamespacesAndCaveats(ctx context.Context, rels []tuple.Relationship, re
106126
if err != nil {
107127
return nil, nil, err
108128
}
109-
ts := schema.NewTypeSystem(schema.ResolverForDatastoreReader(reader))
110129

111130
referencedNamespaceMap = make(map[string]*schema.Definition, len(foundNamespaces))
112131
for _, nsDef := range foundNamespaces {
@@ -277,3 +296,24 @@ func hasNonEmptyCaveatContext(relationship tuple.Relationship) bool {
277296
relationship.OptionalCaveat.Context != nil &&
278297
len(relationship.OptionalCaveat.Context.GetFields()) > 0
279298
}
299+
300+
// CheckDeprecationsOnRelationships checks the provided relationships for any deprecations, returning an error if applicable
301+
func CheckDeprecationsOnRelationships(
302+
ctx context.Context,
303+
relationships []tuple.Relationship,
304+
ts *schema.TypeSystem,
305+
) error {
306+
for _, rel := range relationships {
307+
if err := checkForDeprecatedRelationsAndObjects(ctx, rel, ts); err != nil {
308+
return err
309+
}
310+
}
311+
return nil
312+
}
313+
314+
func checkForDeprecatedRelationsAndObjects(ctx context.Context, rel tuple.Relationship, ts *schema.TypeSystem) error {
315+
if err := ts.CheckRelationshipDeprecation(ctx, rel); err != nil {
316+
return shared.NewDeprecationError(err)
317+
}
318+
return nil
319+
}

internal/relationships/validation_test.go

Lines changed: 209 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,214 @@ func TestValidateRelationshipOperations(t *testing.T) {
306306
core.RelationTupleUpdate_CREATE,
307307
"subjects of type `user with somecaveat and expiration` are not allowed on relation `resource#viewer`",
308308
},
309+
{
310+
"deprecation relation test",
311+
`use deprecation
312+
definition testuser {}
313+
definition user {}
314+
315+
definition document {
316+
@deprecated(error,"deprecated, migrate away")
317+
relation editor: testuser
318+
relation viewer: user
319+
}`,
320+
"document:foo#editor@testuser:tom",
321+
core.RelationTupleUpdate_CREATE,
322+
"relation document#editor is deprecated: deprecated, migrate away",
323+
},
324+
{
325+
"deprecated namespace test",
326+
`use deprecation
327+
@deprecated(error, "deprecated, migrate away")
328+
definition testuser {}
329+
definition user {}
330+
331+
definition document {
332+
relation editor: testuser
333+
}`,
334+
"document:foo#editor@testuser:tom",
335+
core.RelationTupleUpdate_CREATE,
336+
"resource_type testuser is deprecated: deprecated, migrate away",
337+
},
338+
{
339+
"deprecated relation subject type",
340+
`use deprecation
341+
definition user {}
342+
definition testuser {}
343+
344+
definition platform {
345+
relation viewer: user | @deprecated(warn, "comments") testuser
346+
relation auditor: user | @deprecated(error, "test") testuser
347+
}`,
348+
"platform:foo#auditor@testuser:test",
349+
core.RelationTupleUpdate_CREATE,
350+
"resource_type testuser is deprecated: test",
351+
},
352+
{
353+
"deprecated relation same subject type with wildcard",
354+
`use deprecation
355+
definition user {}
356+
definition testuser {}
357+
358+
definition platform {
359+
relation viewer: user | @deprecated(warn, "comments") testuser
360+
relation auditor: testuser | @deprecated(error, "no wildcard please") testuser:*
361+
}`,
362+
"platform:foo#auditor@testuser:*",
363+
core.RelationTupleUpdate_CREATE,
364+
"wildcard allowed type testuser:* is deprecated: no wildcard please",
365+
},
366+
{
367+
"deprecated relation same subject type with write on non-wildcard relation",
368+
`use deprecation
369+
definition user {}
370+
definition testuser {}
371+
372+
definition platform {
373+
relation viewer: user | @deprecated(warn, "comments") testuser
374+
relation auditor: testuser | @deprecated(error, "no wildcard please") testuser:*
375+
}`,
376+
"platform:foo#auditor@testuser:test1",
377+
core.RelationTupleUpdate_CREATE,
378+
"",
379+
},
380+
{
381+
"deprecated subject without expiration when expiration required",
382+
`use expiration
383+
use deprecation
384+
385+
@deprecated(error, "do not use testuser")
386+
definition testuser {}
387+
388+
definition document {
389+
relation viewer: testuser with expiration
390+
}`,
391+
"document:foo#viewer@testuser:tom",
392+
core.RelationTupleUpdate_CREATE,
393+
"subjects of type `testuser` are not allowed on relation `document#viewer`; did you mean `testuser with expiration`?",
394+
},
395+
{
396+
"deprecated subject with expiration",
397+
`use expiration
398+
use deprecation
399+
400+
@deprecated(error, "do not use testuser")
401+
definition testuser {}
402+
403+
definition document {
404+
relation viewer: testuser with expiration
405+
}`,
406+
"document:foo#viewer@testuser:tom[expiration:2021-01-01T00:00:00Z]",
407+
core.RelationTupleUpdate_CREATE,
408+
"resource_type testuser is deprecated: do not use testuser",
409+
},
410+
{
411+
"deprecated subject with wrong caveat",
412+
`use deprecation
413+
414+
caveat somecaveat(somecondition int) {
415+
somecondition == 42
416+
}
417+
418+
caveat anothercaveat(somecondition int) {
419+
somecondition == 42
420+
}
421+
422+
@deprecated(error, "do not use testuser")
423+
definition testuser {}
424+
425+
definition document {
426+
relation viewer: testuser with somecaveat
427+
}`,
428+
"document:foo#viewer@testuser:tom[anothercaveat]",
429+
core.RelationTupleUpdate_CREATE,
430+
"subjects of type `testuser with anothercaveat` are not allowed on relation `document#viewer`",
431+
},
432+
{
433+
"deprecated subject with correct caveat",
434+
`use deprecation
435+
436+
caveat somecaveat(somecondition int) {
437+
somecondition == 42
438+
}
439+
440+
@deprecated(error, "do not use testuser")
441+
definition testuser {}
442+
443+
definition document {
444+
relation viewer: testuser with somecaveat
445+
}`,
446+
"document:foo#viewer@testuser:tom[somecaveat]",
447+
core.RelationTupleUpdate_CREATE,
448+
"resource_type testuser is deprecated: do not use testuser",
449+
},
450+
{
451+
"deprecated subject without caveat when required",
452+
`use deprecation
453+
454+
caveat somecaveat(somecondition int) {
455+
somecondition == 42
456+
}
457+
458+
@deprecated(error, "do not use testuser")
459+
definition testuser {}
460+
461+
definition document {
462+
relation viewer: testuser with somecaveat
463+
}`,
464+
"document:foo#viewer@testuser:tom",
465+
core.RelationTupleUpdate_CREATE,
466+
"subjects of type `testuser` are not allowed on relation `document#viewer` without one of the following caveats: somecaveat",
467+
},
468+
{
469+
"deprecated user with caveat and expiration",
470+
`use expiration
471+
use deprecation
472+
473+
caveat somecaveat(somecondition int) {
474+
somecondition == 42
475+
}
476+
477+
definition user {}
478+
479+
definition document {
480+
relation viewer: user | @deprecated(error, "don't use this") user with somecaveat and expiration
481+
}`,
482+
"document:foo#viewer@user:tom[somecaveat][expiration:2021-01-01T00:00:00Z]",
483+
core.RelationTupleUpdate_CREATE,
484+
"resource_type user with caveat `somecaveat` and expiration is deprecated: don't use this",
485+
},
486+
{
487+
"deprecated user with caveat only",
488+
`use deprecation
489+
490+
caveat somecaveat(somecondition int) {
491+
somecondition == 42
492+
}
493+
494+
definition user {}
495+
496+
definition document {
497+
relation viewer: user | @deprecated(error, "caveated access deprecated") user with somecaveat
498+
}`,
499+
"document:foo#viewer@user:tom[somecaveat]",
500+
core.RelationTupleUpdate_CREATE,
501+
"resource_type user with caveat `somecaveat` is deprecated: caveated access deprecated",
502+
},
503+
{
504+
"deprecated user with expiration only",
505+
`use expiration
506+
use deprecation
507+
508+
definition user {}
509+
510+
definition document {
511+
relation viewer: user | @deprecated(error, "expiring access deprecated") user with expiration
512+
}`,
513+
"document:foo#viewer@user:tom[expiration:2021-01-01T00:00:00Z]",
514+
core.RelationTupleUpdate_CREATE,
515+
"resource_type user with expiration is deprecated: expiring access deprecated",
516+
},
309517
}
310518

311519
for _, tc := range tcs {
@@ -325,7 +533,7 @@ func TestValidateRelationshipOperations(t *testing.T) {
325533
op = tuple.Delete
326534
}
327535

328-
// Validate update.
536+
// Validate update for delete.
329537
err = ValidateRelationshipUpdates(t.Context(), reader, caveattypes.Default.TypeSet, []tuple.RelationshipUpdate{
330538
op(tuple.MustParse(tc.relationship)),
331539
})

internal/services/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func RegisterGrpcServices(
7373
CaveatTypeSet: permSysConfig.CaveatTypeSet,
7474
AdditiveOnly: schemaServiceOption == V1SchemaServiceAdditiveOnly,
7575
ExpiringRelsEnabled: permSysConfig.ExpiringRelationshipsEnabled,
76+
FeatureDeprecationEnabled: permSysConfig.DeprecatedRelationshipsAndObjectsEnabled,
7677
PerformanceInsightMetricsEnabled: permSysConfig.PerformanceInsightMetricsEnabled,
7778
}
7879
v1.RegisterSchemaServiceServer(srv, v1svc.NewSchemaServer(schemaConfig))

internal/services/shared/errors.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,30 @@ func NewSchemaWriteDataValidationError(message string, args ...any) SchemaWriteD
4747
}
4848
}
4949

50+
// DeprecationError is an error returned when a schema object or relation is deprecated.
51+
type DeprecationError struct {
52+
error
53+
}
54+
55+
func (err DeprecationError) GRPCStatus() *status.Status {
56+
return spiceerrors.WithCodeAndDetails(
57+
err,
58+
codes.Aborted,
59+
spiceerrors.ForReason(
60+
// TODO: replace with a deprecation type error reason
61+
v1.ErrorReason_ERROR_REASON_SCHEMA_TYPE_ERROR,
62+
map[string]string{},
63+
),
64+
)
65+
}
66+
67+
// NewDeprecationError wraps an error to indicate an attempted write to a deprecated object type, subject or allowed relation type.
68+
func NewDeprecationError(err error) DeprecationError {
69+
return DeprecationError{
70+
error: err,
71+
}
72+
}
73+
5074
// SchemaWriteDataValidationError occurs when a schema cannot be applied due to leaving data unreferenced.
5175
type SchemaWriteDataValidationError struct {
5276
error

0 commit comments

Comments
 (0)