Skip to content

Commit f20b462

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

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

+2606
-726
lines changed

internal/relationships/validation.go

Lines changed: 102 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"
@@ -42,6 +44,7 @@ func ValidateRelationshipUpdates(
4244
}
4345

4446
if err := ValidateOneRelationship(
47+
ctx,
4548
referencedNamespaceMap,
4649
referencedCaveatMap,
4750
caveatTypeSet,
@@ -74,6 +77,7 @@ func ValidateRelationshipsForCreateOrTouch(
7477
// Validate each relationship's types.
7578
for _, rel := range rels {
7679
if err := ValidateOneRelationship(
80+
ctx,
7781
referencedNamespaceMap,
7882
referencedCaveatMap,
7983
caveatTypeSet,
@@ -146,6 +150,7 @@ const (
146150

147151
// ValidateOneRelationship validates a single relationship for CREATE/TOUCH or DELETE.
148152
func ValidateOneRelationship(
153+
ctx context.Context,
149154
namespaceMap map[string]*schema.Definition,
150155
caveatMap map[string]*core.CaveatDefinition,
151156
caveatTypeSet *caveattypes.TypeSet,
@@ -188,6 +193,11 @@ func ValidateOneRelationship(
188193
return NewCannotWriteToPermissionError(rel)
189194
}
190195

196+
// Validate if the resource, relation or subject is deprecated
197+
if err := checkForDeprecatedRelationsAndObjects(ctx, rel, namespaceMap); err != nil {
198+
return err
199+
}
200+
191201
// Validate the subject against the allowed relation(s).
192202
var caveat *core.AllowedCaveat
193203
if rel.OptionalCaveat != nil {
@@ -277,3 +287,95 @@ func hasNonEmptyCaveatContext(relationship tuple.Relationship) bool {
277287
relationship.OptionalCaveat.Context != nil &&
278288
len(relationship.OptionalCaveat.Context.GetFields()) > 0
279289
}
290+
291+
func checkForDeprecatedRelationsAndObjects(ctx context.Context, rel tuple.Relationship, nsMap map[string]*schema.Definition) error {
292+
// Validate if the resource relation is deprecated
293+
resource := nsMap[rel.Resource.ObjectType]
294+
resourceTS := resource.TypeSystem()
295+
relDep, ok, err := resourceTS.GetDeprecationForRelation(ctx, rel.Resource.ObjectType, rel.Resource.Relation)
296+
if err != nil {
297+
return err
298+
}
299+
if ok {
300+
switch relDep.DeprecationType {
301+
case core.DeprecationType_DEPRECATED_TYPE_WARNING:
302+
log.Warn().
303+
Str("namespace", rel.Resource.ObjectType).
304+
Str("relation", rel.Resource.Relation).
305+
Str("comments", relDep.Comments).
306+
Msg("write to deprecated relation")
307+
case core.DeprecationType_DEPRECATED_TYPE_ERROR:
308+
return shared.NewDeprecationError(rel.Resource.ObjectType, rel.Resource.Relation, relDep.Comments)
309+
}
310+
}
311+
312+
// Validate if the resource namespace is deprecated
313+
resDep, ok, err := resourceTS.GetDeprecationForNamespace(ctx, rel.Resource.ObjectType)
314+
if err != nil {
315+
return err
316+
}
317+
if ok {
318+
switch resDep.DeprecationType {
319+
case core.DeprecationType_DEPRECATED_TYPE_WARNING:
320+
log.Warn().
321+
Str("namespace", rel.Resource.ObjectType).
322+
Str("comments", resDep.Comments).
323+
Msg("write to deprecated object")
324+
case core.DeprecationType_DEPRECATED_TYPE_ERROR:
325+
return shared.NewDeprecationError(rel.Resource.ObjectType, "", resDep.Comments)
326+
}
327+
}
328+
329+
// Validate if the subject namespace is deprecated
330+
subject := nsMap[rel.Subject.ObjectType]
331+
subjectTS := subject.TypeSystem()
332+
subDep, ok, err := subjectTS.GetDeprecationForNamespace(ctx, rel.Subject.ObjectType)
333+
if err != nil {
334+
return err
335+
}
336+
if ok {
337+
switch subDep.DeprecationType {
338+
case core.DeprecationType_DEPRECATED_TYPE_WARNING:
339+
log.Warn().
340+
Str("namespace", rel.Subject.ObjectType).
341+
Str("comments", subDep.Comments).
342+
Msg("write to deprecated object")
343+
case core.DeprecationType_DEPRECATED_TYPE_ERROR:
344+
return shared.NewDeprecationError(rel.Subject.ObjectType, "", subDep.Comments)
345+
}
346+
}
347+
348+
// check deprecation for allowed relation types
349+
dep, ok, err := resourceTS.GetDeprecationForAllowedRelation(
350+
ctx,
351+
rel.Resource.ObjectType,
352+
rel.Resource.Relation,
353+
rel.Subject.ObjectType,
354+
rel.Subject.Relation,
355+
rel.Subject.ObjectID == tuple.PublicWildcard,
356+
)
357+
if err != nil {
358+
return err
359+
}
360+
361+
errMsg := ""
362+
if rel.Subject.ObjectID == tuple.PublicWildcard {
363+
errMsg = tuple.PublicWildcard
364+
}
365+
366+
if ok {
367+
switch dep.DeprecationType {
368+
case core.DeprecationType_DEPRECATED_TYPE_WARNING:
369+
log.Warn().
370+
Str("namespace", rel.Resource.ObjectType).
371+
Str("comments", dep.Comments).
372+
Msg("write to deprecated relation")
373+
case core.DeprecationType_DEPRECATED_TYPE_ERROR:
374+
return shared.NewDeprecationError(
375+
rel.Subject.ObjectType,
376+
errMsg,
377+
dep.Comments)
378+
}
379+
}
380+
return nil
381+
}

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/experimental.go

Lines changed: 2 additions & 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()
@@ -200,6 +200,7 @@ func (a *bulkLoadAdapter) Next(_ context.Context) (*tuple.Relationship, error) {
200200
a.current.OptionalIntegrity = nil
201201

202202
if err := relationships.ValidateOneRelationship(
203+
ctx,
203204
a.referencedNamespaceMap,
204205
a.referencedCaveatMap,
205206
a.caveatTypeSet,

internal/services/v1/permissions.go

Lines changed: 2 additions & 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()
@@ -860,6 +860,7 @@ func (a *loadBulkAdapter) Next(_ context.Context) (*tuple.Relationship, error) {
860860
a.current.OptionalIntegrity = nil
861861

862862
if err := relationships.ValidateOneRelationship(
863+
ctx,
863864
a.referencedNamespaceMap,
864865
a.referencedCaveatMap,
865866
a.caveatTypeSet,

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)