Skip to content

Commit 3a5f4d4

Browse files
support for rel and namespace deprecation
Signed-off-by: Kartikay <[email protected]>
1 parent 145df27 commit 3a5f4d4

Some content is hidden

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

44 files changed

+2675
-726
lines changed

internal/relationships/validation.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package relationships
33
import (
44
"context"
55

6+
log "github.com/authzed/spicedb/internal/logging"
67
"github.com/authzed/spicedb/internal/namespace"
8+
"github.com/authzed/spicedb/internal/services/shared"
79
"github.com/authzed/spicedb/pkg/caveats"
810
caveattypes "github.com/authzed/spicedb/pkg/caveats/types"
911
"github.com/authzed/spicedb/pkg/datastore"
@@ -34,6 +36,19 @@ func ValidateRelationshipUpdates(
3436
return err
3537
}
3638

39+
ts := schema.NewTypeSystem(schema.ResolverForDatastoreReader(reader))
40+
41+
// check if the relation is deprecated for create or touch operations
42+
var relsToCheck []tuple.Relationship
43+
for _, update := range updates {
44+
if update.Operation == tuple.UpdateOperationTouch || update.Operation == tuple.UpdateOperationCreate {
45+
relsToCheck = append(relsToCheck, update.Relationship)
46+
}
47+
}
48+
if err := CheckDeprecationsOnRelationships(ctx, relsToCheck, ts); err != nil {
49+
return err
50+
}
51+
3752
// Validate each updates's types.
3853
for _, update := range updates {
3954
option := ValidateRelationshipForCreateOrTouch
@@ -71,6 +86,13 @@ func ValidateRelationshipsForCreateOrTouch(
7186
return err
7287
}
7388

89+
ts := schema.NewTypeSystem(schema.ResolverForDatastoreReader(reader))
90+
91+
// Validate if the resource, relation or subject is deprecated
92+
if err := CheckDeprecationsOnRelationships(ctx, rels, ts); err != nil {
93+
return err
94+
}
95+
7496
// Validate each relationship's types.
7597
for _, rel := range rels {
7698
if err := ValidateOneRelationship(
@@ -277,3 +299,104 @@ func hasNonEmptyCaveatContext(relationship tuple.Relationship) bool {
277299
relationship.OptionalCaveat.Context != nil &&
278300
len(relationship.OptionalCaveat.Context.GetFields()) > 0
279301
}
302+
303+
func CheckDeprecationsOnRelationships(
304+
ctx context.Context,
305+
relationships []tuple.Relationship,
306+
ts *schema.TypeSystem,
307+
) error {
308+
for _, rel := range relationships {
309+
if err := checkForDeprecatedRelationsAndObjects(ctx, rel, ts); err != nil {
310+
return err
311+
}
312+
}
313+
return nil
314+
}
315+
316+
func checkForDeprecatedRelationsAndObjects(ctx context.Context, rel tuple.Relationship, ts *schema.TypeSystem) error {
317+
// Validate if the resource relation is deprecated
318+
relDep, ok, err := ts.GetDeprecationForRelation(ctx, rel.Resource.ObjectType, rel.Resource.Relation)
319+
if err != nil {
320+
return err
321+
}
322+
if ok {
323+
switch relDep.DeprecationType {
324+
case core.DeprecationType_DEPRECATED_TYPE_WARNING:
325+
log.Warn().
326+
Str("namespace", rel.Resource.ObjectType).
327+
Str("relation", rel.Resource.Relation).
328+
Str("comments", relDep.Comments).
329+
Msg("write to deprecated relation")
330+
case core.DeprecationType_DEPRECATED_TYPE_ERROR:
331+
return shared.NewDeprecationError(rel.Resource.ObjectType, rel.Resource.Relation, relDep.Comments)
332+
}
333+
}
334+
335+
// Validate if the resource namespace is deprecated
336+
resDep, ok, err := ts.GetDeprecationForNamespace(ctx, rel.Resource.ObjectType)
337+
if err != nil {
338+
return err
339+
}
340+
if ok {
341+
switch resDep.DeprecationType {
342+
case core.DeprecationType_DEPRECATED_TYPE_WARNING:
343+
log.Warn().
344+
Str("namespace", rel.Resource.ObjectType).
345+
Str("comments", resDep.Comments).
346+
Msg("write to deprecated object")
347+
case core.DeprecationType_DEPRECATED_TYPE_ERROR:
348+
return shared.NewDeprecationError(rel.Resource.ObjectType, "", resDep.Comments)
349+
}
350+
}
351+
352+
// Validate if the subject namespace is deprecated
353+
subDep, ok, err := ts.GetDeprecationForNamespace(ctx, rel.Subject.ObjectType)
354+
if err != nil {
355+
return err
356+
}
357+
if ok {
358+
switch subDep.DeprecationType {
359+
case core.DeprecationType_DEPRECATED_TYPE_WARNING:
360+
log.Warn().
361+
Str("namespace", rel.Subject.ObjectType).
362+
Str("comments", subDep.Comments).
363+
Msg("write to deprecated object")
364+
case core.DeprecationType_DEPRECATED_TYPE_ERROR:
365+
return shared.NewDeprecationError(rel.Subject.ObjectType, "", subDep.Comments)
366+
}
367+
}
368+
369+
// check deprecation for allowed relation types
370+
dep, ok, err := ts.GetDeprecationForAllowedRelation(
371+
ctx,
372+
rel.Resource.ObjectType,
373+
rel.Resource.Relation,
374+
rel.Subject.ObjectType,
375+
rel.Subject.Relation,
376+
rel.Subject.ObjectID == tuple.PublicWildcard,
377+
)
378+
if err != nil {
379+
return err
380+
}
381+
382+
errMsg := ""
383+
if rel.Subject.ObjectID == tuple.PublicWildcard {
384+
errMsg = tuple.PublicWildcard
385+
}
386+
387+
if ok {
388+
switch dep.DeprecationType {
389+
case core.DeprecationType_DEPRECATED_TYPE_WARNING:
390+
log.Warn().
391+
Str("namespace", rel.Resource.ObjectType).
392+
Str("comments", dep.Comments).
393+
Msg("write to deprecated relation")
394+
case core.DeprecationType_DEPRECATED_TYPE_ERROR:
395+
return shared.NewDeprecationError(
396+
rel.Subject.ObjectType,
397+
errMsg,
398+
dep.Comments)
399+
}
400+
}
401+
return nil
402+
}

internal/relationships/validation_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,63 @@ 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+
"the 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+
"the object 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+
"the object 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+
"the wildcard allowed type testuser:* is deprecated: no wildcard please",
365+
},
309366
}
310367

311368
for _, tc := range tcs {

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: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,54 @@ 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+
v1.ErrorReason_ERROR_REASON_SCHEMA_TYPE_ERROR,
61+
map[string]string{},
62+
),
63+
)
64+
}
65+
66+
func NewDeprecationError(namespace, relation, comments string) DeprecationError {
67+
switch {
68+
case relation == "*":
69+
// Wildcard deprecation
70+
if comments != "" {
71+
return DeprecationError{
72+
error: fmt.Errorf("the wildcard allowed type %s:* is deprecated: %s", namespace, comments),
73+
}
74+
}
75+
return DeprecationError{
76+
error: fmt.Errorf("the wildcard allowed type %s:* has been marked as deprecated", namespace),
77+
}
78+
79+
case relation == "" && comments != "":
80+
return DeprecationError{
81+
error: fmt.Errorf("the object type %s is deprecated: %s", namespace, comments),
82+
}
83+
case relation == "":
84+
return DeprecationError{
85+
error: fmt.Errorf("the object type %s has been marked as deprecated", namespace),
86+
}
87+
case comments != "":
88+
return DeprecationError{
89+
error: fmt.Errorf("the relation %s#%s is deprecated: %s", namespace, relation, comments),
90+
}
91+
default:
92+
return DeprecationError{
93+
error: fmt.Errorf("the relation %s#%s has been marked as deprecated", namespace, relation),
94+
}
95+
}
96+
}
97+
5098
// SchemaWriteDataValidationError occurs when a schema cannot be applied due to leaving data unreferenced.
5199
type SchemaWriteDataValidationError struct {
52100
error

internal/services/v1/experimental.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ type bulkLoadAdapter struct {
148148
err error
149149
}
150150

151-
func (a *bulkLoadAdapter) Next(_ context.Context) (*tuple.Relationship, error) {
151+
func (a *bulkLoadAdapter) Next(ctx context.Context) (*tuple.Relationship, error) {
152152
for a.err == nil && a.numSent == len(a.currentBatch) {
153153
// Load a new batch
154154
batch, err := a.stream.Recv()

internal/services/v1/permissions.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,7 @@ type loadBulkAdapter struct {
808808
err error
809809
}
810810

811-
func (a *loadBulkAdapter) Next(_ context.Context) (*tuple.Relationship, error) {
811+
func (a *loadBulkAdapter) Next(ctx context.Context) (*tuple.Relationship, error) {
812812
for a.err == nil && a.numSent == len(a.currentBatch) {
813813
// Load a new batch
814814
batch, err := a.stream.Recv()

internal/services/v1/relationships.go

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ type PermissionsServerConfig struct {
104104
// ExpiringRelationshipsEnabled defines whether or not expiring relationships are enabled.
105105
ExpiringRelationshipsEnabled bool
106106

107+
// DeprecatedRelationshipsEnabled defines whether or not deprecated relationships are enabled.
108+
DeprecatedRelationshipsAndObjectsEnabled bool
109+
107110
// CaveatTypeSet is the set of caveat types to use for caveats. If not specified,
108111
// the default type set is used.
109112
CaveatTypeSet *caveattypes.TypeSet
@@ -118,22 +121,23 @@ func NewPermissionsServer(
118121
config PermissionsServerConfig,
119122
) v1.PermissionsServiceServer {
120123
configWithDefaults := PermissionsServerConfig{
121-
MaxPreconditionsCount: defaultIfZero(config.MaxPreconditionsCount, 1000),
122-
MaxUpdatesPerWrite: defaultIfZero(config.MaxUpdatesPerWrite, 1000),
123-
MaximumAPIDepth: defaultIfZero(config.MaximumAPIDepth, 50),
124-
StreamingAPITimeout: defaultIfZero(config.StreamingAPITimeout, 30*time.Second),
125-
MaxCaveatContextSize: defaultIfZero(config.MaxCaveatContextSize, 4096),
126-
MaxRelationshipContextSize: defaultIfZero(config.MaxRelationshipContextSize, 25_000),
127-
MaxDatastoreReadPageSize: defaultIfZero(config.MaxDatastoreReadPageSize, 1_000),
128-
MaxReadRelationshipsLimit: defaultIfZero(config.MaxReadRelationshipsLimit, 1_000),
129-
MaxDeleteRelationshipsLimit: defaultIfZero(config.MaxDeleteRelationshipsLimit, 1_000),
130-
MaxLookupResourcesLimit: defaultIfZero(config.MaxLookupResourcesLimit, 1_000),
131-
MaxBulkExportRelationshipsLimit: defaultIfZero(config.MaxBulkExportRelationshipsLimit, 100_000),
132-
DispatchChunkSize: defaultIfZero(config.DispatchChunkSize, 100),
133-
MaxCheckBulkConcurrency: defaultIfZero(config.MaxCheckBulkConcurrency, 50),
134-
CaveatTypeSet: caveattypes.TypeSetOrDefault(config.CaveatTypeSet),
135-
ExpiringRelationshipsEnabled: config.ExpiringRelationshipsEnabled,
136-
PerformanceInsightMetricsEnabled: config.PerformanceInsightMetricsEnabled,
124+
MaxPreconditionsCount: defaultIfZero(config.MaxPreconditionsCount, 1000),
125+
MaxUpdatesPerWrite: defaultIfZero(config.MaxUpdatesPerWrite, 1000),
126+
MaximumAPIDepth: defaultIfZero(config.MaximumAPIDepth, 50),
127+
StreamingAPITimeout: defaultIfZero(config.StreamingAPITimeout, 30*time.Second),
128+
MaxCaveatContextSize: defaultIfZero(config.MaxCaveatContextSize, 4096),
129+
MaxRelationshipContextSize: defaultIfZero(config.MaxRelationshipContextSize, 25_000),
130+
MaxDatastoreReadPageSize: defaultIfZero(config.MaxDatastoreReadPageSize, 1_000),
131+
MaxReadRelationshipsLimit: defaultIfZero(config.MaxReadRelationshipsLimit, 1_000),
132+
MaxDeleteRelationshipsLimit: defaultIfZero(config.MaxDeleteRelationshipsLimit, 1_000),
133+
MaxLookupResourcesLimit: defaultIfZero(config.MaxLookupResourcesLimit, 1_000),
134+
MaxBulkExportRelationshipsLimit: defaultIfZero(config.MaxBulkExportRelationshipsLimit, 100_000),
135+
DispatchChunkSize: defaultIfZero(config.DispatchChunkSize, 100),
136+
MaxCheckBulkConcurrency: defaultIfZero(config.MaxCheckBulkConcurrency, 50),
137+
CaveatTypeSet: caveattypes.TypeSetOrDefault(config.CaveatTypeSet),
138+
ExpiringRelationshipsEnabled: config.ExpiringRelationshipsEnabled,
139+
DeprecatedRelationshipsAndObjectsEnabled: config.DeprecatedRelationshipsAndObjectsEnabled,
140+
PerformanceInsightMetricsEnabled: config.PerformanceInsightMetricsEnabled,
137141
}
138142

139143
return &permissionServer{
@@ -324,6 +328,7 @@ func (ps *permissionServer) WriteRelationships(ctx context.Context, req *v1.Writ
324328
updateRelationshipSet := mapz.NewSet[string]()
325329
for _, update := range req.Updates {
326330
// TODO(jschorr): Change to struct-based keys.
331+
327332
tupleStr := tuple.V1StringRelationshipWithoutCaveatOrExpiration(update.Relationship)
328333
if !updateRelationshipSet.Add(tupleStr) {
329334
return nil, ps.rewriteError(

internal/services/v1/schema.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ type SchemaServerConfig struct {
4343

4444
// PerformanceInsightMetricsEnabled indicates whether performance insight metrics are enabled.
4545
PerformanceInsightMetricsEnabled bool
46+
47+
// DeprecatedRelsEnabled indicates whether deprecated relations are enabled.
48+
FeatureDeprecationEnabled bool
4649
}
4750

4851
// NewSchemaServer creates a SchemaServiceServer instance.
@@ -61,19 +64,21 @@ func NewSchemaServer(config SchemaServerConfig) v1.SchemaServiceServer {
6164
perfinsights.StreamServerInterceptor(config.PerformanceInsightMetricsEnabled),
6265
),
6366
},
64-
additiveOnly: config.AdditiveOnly,
65-
expiringRelsEnabled: config.ExpiringRelsEnabled,
66-
caveatTypeSet: cts,
67+
additiveOnly: config.AdditiveOnly,
68+
expiringRelsEnabled: config.ExpiringRelsEnabled,
69+
featureDeprecationEnabled: config.FeatureDeprecationEnabled,
70+
caveatTypeSet: cts,
6771
}
6872
}
6973

7074
type schemaServer struct {
7175
v1.UnimplementedSchemaServiceServer
7276
shared.WithServiceSpecificInterceptors
7377

74-
caveatTypeSet *caveattypes.TypeSet
75-
additiveOnly bool
76-
expiringRelsEnabled bool
78+
caveatTypeSet *caveattypes.TypeSet
79+
additiveOnly bool
80+
expiringRelsEnabled bool
81+
featureDeprecationEnabled bool
7782
}
7883

7984
func (ss *schemaServer) rewriteError(ctx context.Context, err error) error {
@@ -147,6 +152,9 @@ func (ss *schemaServer) WriteSchema(ctx context.Context, in *v1.WriteSchemaReque
147152
if !ss.expiringRelsEnabled {
148153
opts = append(opts, compiler.DisallowExpirationFlag())
149154
}
155+
if !ss.featureDeprecationEnabled {
156+
opts = append(opts, compiler.DisallowDeprecationFlag())
157+
}
150158

151159
opts = append(opts, compiler.CaveatTypeSet(ss.caveatTypeSet))
152160

0 commit comments

Comments
 (0)