Skip to content

Commit 9ce1eed

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

Some content is hidden

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

41 files changed

+2529
-724
lines changed

internal/relationships/validation.go

Lines changed: 110 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"
@@ -188,6 +190,11 @@ func ValidateOneRelationship(
188190
return NewCannotWriteToPermissionError(rel)
189191
}
190192

193+
// Validate if the resource, relation or subject is deprecated
194+
if err := checkForDeprecatedRelationsAndObjects(rel, namespaceMap); err != nil {
195+
return err
196+
}
197+
191198
// Validate the subject against the allowed relation(s).
192199
var caveat *core.AllowedCaveat
193200
if rel.OptionalCaveat != nil {
@@ -277,3 +284,106 @@ func hasNonEmptyCaveatContext(relationship tuple.Relationship) bool {
277284
relationship.OptionalCaveat.Context != nil &&
278285
len(relationship.OptionalCaveat.Context.GetFields()) > 0
279286
}
287+
288+
func checkForDeprecatedRelationsAndObjects(rel tuple.Relationship, nsMap map[string]*schema.Definition) error {
289+
// Validate if the resource relation is deprecated
290+
resource := nsMap[rel.Resource.ObjectType]
291+
relDef, ok := resource.GetRelation(rel.Resource.Relation)
292+
if relDef.Deprecation != nil && relDef.Deprecation.DeprecationType != core.DeprecationType_DEPRECATED_TYPE_UNSPECIFIED && ok {
293+
switch relDef.Deprecation.DeprecationType {
294+
case core.DeprecationType_DEPRECATED_TYPE_WARNING:
295+
log.Warn().
296+
Str("namespace", rel.Resource.ObjectType).
297+
Str("relation", rel.Resource.Relation).
298+
Str("comments", relDef.Deprecation.Comments).
299+
Msg("write to deprecated relation")
300+
case core.DeprecationType_DEPRECATED_TYPE_ERROR:
301+
return shared.NewDeprecationError(rel.Resource.ObjectType, rel.Resource.Relation, relDef.Deprecation.Comments)
302+
}
303+
}
304+
305+
// Validate if the resource namespace is deprecated
306+
resourceNS := resource.Namespace()
307+
if resourceNS.Deprecation != nil && resourceNS.Deprecation.DeprecationType != core.DeprecationType_DEPRECATED_TYPE_UNSPECIFIED {
308+
switch resourceNS.Deprecation.DeprecationType {
309+
case core.DeprecationType_DEPRECATED_TYPE_WARNING:
310+
log.Warn().
311+
Str("namespace", rel.Resource.ObjectType).
312+
Str("comments", resourceNS.Deprecation.Comments).
313+
Msg("write to deprecated object")
314+
case core.DeprecationType_DEPRECATED_TYPE_ERROR:
315+
return shared.NewDeprecationError(rel.Resource.ObjectType, "", resourceNS.Deprecation.Comments)
316+
}
317+
}
318+
319+
// Validate if the subject namespace is deprecated
320+
subject := nsMap[rel.Subject.ObjectType]
321+
subjectNS := subject.Namespace()
322+
if subjectNS.Deprecation != nil &&
323+
subjectNS.Deprecation.DeprecationType != core.DeprecationType_DEPRECATED_TYPE_UNSPECIFIED {
324+
switch subjectNS.Deprecation.DeprecationType {
325+
case core.DeprecationType_DEPRECATED_TYPE_WARNING:
326+
log.Warn().
327+
Str("namespace", rel.Subject.ObjectType).
328+
Str("comments", subjectNS.Deprecation.Comments).
329+
Msg("write to deprecated object")
330+
case core.DeprecationType_DEPRECATED_TYPE_ERROR:
331+
return shared.NewDeprecationError(rel.Subject.ObjectType, "", subjectNS.Deprecation.Comments)
332+
}
333+
}
334+
// iterate over the allowed relations in the resource definition
335+
// and see whether the concrete subject we are writing matches an
336+
// entry that has a non-nil deprecation
337+
for _, allowed := range relDef.TypeInformation.AllowedDirectRelations {
338+
// check namespace
339+
if allowed.Namespace != rel.Subject.ObjectType {
340+
continue
341+
}
342+
343+
// Check if both relations/wildcards match
344+
switch w := allowed.RelationOrWildcard.(type) {
345+
case *core.AllowedRelation_Relation:
346+
if w.Relation != rel.Subject.Relation {
347+
continue
348+
}
349+
// If it is a match, check the deprecation
350+
if dep := allowed.RequiredDeprecation; dep != nil &&
351+
dep.DeprecationType != core.DeprecationType_DEPRECATED_TYPE_UNSPECIFIED {
352+
switch dep.DeprecationType {
353+
case core.DeprecationType_DEPRECATED_TYPE_WARNING:
354+
log.Warn().
355+
Str("namespace", rel.Resource.ObjectType).
356+
Str("comments", dep.Comments).
357+
Msg("write to deprecated relation")
358+
case core.DeprecationType_DEPRECATED_TYPE_ERROR:
359+
return shared.NewDeprecationError(
360+
rel.Subject.ObjectType,
361+
"",
362+
dep.Comments,
363+
)
364+
}
365+
}
366+
case *core.AllowedRelation_PublicWildcard_:
367+
if rel.Subject.ObjectID != tuple.PublicWildcard {
368+
continue
369+
}
370+
if dep := allowed.RequiredDeprecation; dep != nil &&
371+
dep.DeprecationType != core.DeprecationType_DEPRECATED_TYPE_UNSPECIFIED {
372+
switch dep.DeprecationType {
373+
case core.DeprecationType_DEPRECATED_TYPE_WARNING:
374+
log.Warn().
375+
Str("namespace", rel.Resource.ObjectType).
376+
Str("comments", dep.Comments).
377+
Msg("write to deprecated wildcard relation")
378+
case core.DeprecationType_DEPRECATED_TYPE_ERROR:
379+
return shared.NewDeprecationError(
380+
rel.Subject.ObjectType,
381+
"*",
382+
dep.Comments,
383+
)
384+
}
385+
}
386+
}
387+
}
388+
return nil
389+
}

internal/relationships/validation_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,49 @@ 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 namespace 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 relation subject type",
326+
`use deprecation
327+
definition user {}
328+
definition testuser {}
329+
330+
definition platform {
331+
relation viewer: user | @deprecated(warn, "comments") testuser
332+
relation auditor: user | @deprecated(error, "test") testuser
333+
}`,
334+
"platform:foo#auditor@testuser:test",
335+
core.RelationTupleUpdate_CREATE,
336+
"the object type testuser is deprecated: test",
337+
},
338+
{
339+
"deprecated relation same subject type with wildcard",
340+
`use deprecation
341+
definition user {}
342+
definition testuser {}
343+
344+
definition platform {
345+
relation viewer: user | @deprecated(warn, "comments") testuser
346+
relation auditor: testuser | @deprecated(error, "no wildcard please") testuser:*
347+
}`,
348+
"platform:foo#auditor@testuser:*",
349+
core.RelationTupleUpdate_CREATE,
350+
"the wildcard allowed type testuser:* is deprecated: no wildcard please",
351+
},
309352
}
310353

311354
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/relationships.go

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

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

138142
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)