From b84f50f115120bdadc84e2eb6276a9975bc0bb3c Mon Sep 17 00:00:00 2001 From: Chris Moran Date: Fri, 29 Dec 2023 09:11:13 -0700 Subject: [PATCH] feat: compare this with ctx.subject (#1204) --- contrib/rewrites-example/package-lock.json | 1 + go.mod | 2 +- internal/check/rewrites.go | 51 ++++++++++++++++++++++ internal/check/rewrites_test.go | 32 +++++++++++--- internal/driver/daemon.go | 8 ++-- internal/namespace/ast/ast_definitions.go | 5 +++ internal/schema/itemtype_string.go | 36 ++++++++------- internal/schema/lexer.go | 2 + internal/schema/parser.go | 7 ++- 9 files changed, 112 insertions(+), 32 deletions(-) diff --git a/contrib/rewrites-example/package-lock.json b/contrib/rewrites-example/package-lock.json index 7fe9354fe..f72408455 100644 --- a/contrib/rewrites-example/package-lock.json +++ b/contrib/rewrites-example/package-lock.json @@ -13,6 +13,7 @@ "extraneous": true }, "../namespace-type-lib": { + "name": "@ory/keto-namespace-types", "version": "0.9.0-alpha.0", "dev": true, "devDependencies": { diff --git a/go.mod b/go.mod index 5032633c1..4a13fc12b 100644 --- a/go.mod +++ b/go.mod @@ -182,7 +182,7 @@ require ( go.opentelemetry.io/otel/metric v1.21.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.15.0 // indirect + golang.org/x/crypto v0.18.0 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.18.0 // indirect golang.org/x/sys v0.14.0 // indirect diff --git a/internal/check/rewrites.go b/internal/check/rewrites.go index 9b2008af5..0837a69b1 100644 --- a/internal/check/rewrites.go +++ b/internal/check/rewrites.go @@ -122,6 +122,11 @@ func (e *Engine) checkSubjectSetRewrite( Tuple: *tuple, Type: ketoapi.TreeNodeNot, }, e.checkInverted(ctx, tuple, c, restDepth))) + case *ast.SubjectEqualsObject: + checks = append(checks, checkgroup.WithEdge(checkgroup.Edge{ + Tuple: *tuple, + Type: ketoapi.TreeNodeLeaf, + }, e.checkSubjectEqualsObject(ctx, tuple, restDepth))) default: return checkNotImplemented @@ -175,6 +180,11 @@ func (e *Engine) checkInverted( Tuple: *tuple, Type: ketoapi.TreeNodeNot, }, e.checkInverted(ctx, tuple, c, restDepth)) + case *ast.SubjectEqualsObject: + check = checkgroup.WithEdge(checkgroup.Edge{ + Tuple: *tuple, + Type: ketoapi.TreeNodeLeaf, + }, e.checkSubjectEqualsObject(ctx, tuple, restDepth)) default: return checkNotImplemented @@ -199,6 +209,47 @@ func (e *Engine) checkInverted( } } +// checkSubjectEqualsObject rewrites the relation tuple to use the subject-set relation +// instead of the relation from the tuple. +// +// A relation tuple n:obj#original_rel@user is rewritten to +// n:obj#subject-set@user, where the 'subject-set' relation is taken from the +// subjectSet.Relation. +func (e *Engine) checkSubjectEqualsObject( + _ context.Context, + r *relationTuple, + restDepth int, +) checkgroup.CheckFunc { + if restDepth < 0 { + e.d.Logger().Debug("reached max-depth, therefore this query will not be further expanded") + return checkgroup.UnknownMemberFunc + } + + e.d.Logger(). + WithField("request", r.String()). + Trace("check subject equals object") + + var objAsSubj relationtuple.Subject + switch r.Subject.(type) { + case *relationtuple.SubjectSet: + objAsSubj = &relationtuple.SubjectSet{ + Namespace: r.Namespace, + Object: r.Object, + } + case *relationtuple.SubjectID: + objAsSubj = &relationtuple.SubjectID{ + ID: r.Object, + } + default: + return checkgroup.UnknownMemberFunc + } + if r.Subject.Equals(objAsSubj) { + return checkgroup.IsMemberFunc + } + + return checkgroup.NotMemberFunc +} + // checkComputedSubjectSet rewrites the relation tuple to use the subject-set relation // instead of the relation from the tuple. // diff --git a/internal/check/rewrites_test.go b/internal/check/rewrites_test.go index ba629338c..a0e6a69ad 100644 --- a/internal/check/rewrites_test.go +++ b/internal/check/rewrites_test.go @@ -61,20 +61,29 @@ var namespaces = []*namespace.Namespace{ {Name: "read", SubjectSetRewrite: &ast.SubjectSetRewrite{ Children: ast.Children{ + &ast.SubjectEqualsObject{}, &ast.ComputedSubjectSet{Relation: "viewer"}, &ast.ComputedSubjectSet{Relation: "owner"}}}}, {Name: "update", SubjectSetRewrite: &ast.SubjectSetRewrite{ Children: ast.Children{ + &ast.SubjectEqualsObject{}, &ast.ComputedSubjectSet{Relation: "owner"}}}}, {Name: "delete", SubjectSetRewrite: &ast.SubjectSetRewrite{ - Operation: ast.OperatorAnd, + Operation: ast.OperatorOr, Children: ast.Children{ - &ast.ComputedSubjectSet{Relation: "owner"}, - &ast.TupleToSubjectSet{ - Relation: "level", - ComputedSubjectSetRelation: "member"}}}}, + &ast.SubjectSetRewrite{ + Operation: ast.OperatorAnd, + Children: ast.Children{ + &ast.ComputedSubjectSet{Relation: "owner"}, + &ast.TupleToSubjectSet{ + Relation: "level", + ComputedSubjectSetRelation: "member"}, + }, + }, + &ast.SubjectEqualsObject{}, + }}}, }}, {Name: "acl", Relations: []ast.Relation{ @@ -192,9 +201,18 @@ func TestUsersetRewrites(t *testing.T) { query: "resource:topsecret#delete@mark", expected: checkgroup.ResultIsMember, // mark is both editor and has correct level expectedPaths: []path{ - {"*", "resource:topsecret#delete@mark", "level:superadmin#member@mark"}, - {"*", "resource:topsecret#delete@mark", "resource:topsecret#owner@mark", "group:editors#member@mark"}, + {"*", "*", "resource:topsecret#delete@mark", "level:superadmin#member@mark"}, + {"*", "*", "resource:topsecret#delete@mark", "resource:topsecret#owner@mark", "group:editors#member@mark"}, }, + }, { + query: "resource:topsecret#delete@topsecret", + expected: checkgroup.ResultIsMember, // topsecret may delete topsecret + }, { + query: "resource:topsecret#update@topsecret", + expected: checkgroup.ResultIsMember, // topsecret may update topsecret + }, { + query: "resource:topsecret#read@topsecret", + expected: checkgroup.ResultIsMember, // topsecret may read topsecret }, { query: "resource:topsecret#update@mike", expected: checkgroup.ResultIsMember, // mike owns the resource diff --git a/internal/driver/daemon.go b/internal/driver/daemon.go index 6d176d9e7..733793df0 100644 --- a/internal/driver/daemon.go +++ b/internal/driver/daemon.go @@ -18,7 +18,7 @@ import ( "github.com/ory/x/otelx/semconv" - grpcRecovery "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery" + grpcRecovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -32,7 +32,7 @@ import ( "github.com/ory/x/logrusx" - grpcLogrus "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" + grpcLogrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus" "github.com/julienschmidt/httprouter" "github.com/ory/herodot" "github.com/ory/x/reqlog" @@ -457,7 +457,7 @@ func (r *RegistryDefault) unaryInterceptors(ctx context.Context) []grpc.UnarySer is = append(is, r.defaultUnaryInterceptors...) is = append(is, herodot.UnaryErrorUnwrapInterceptor, - grpcLogrus.UnaryServerInterceptor(InterceptorLogger(r.l.Logrus())), + grpcLogrus.UnaryServerInterceptor(r.l.Entry), r.pmm.UnaryServerInterceptor, ) if r.sqaService != nil { @@ -476,7 +476,7 @@ func (r *RegistryDefault) streamInterceptors(ctx context.Context) []grpc.StreamS is = append(is, r.defaultStreamInterceptors...) is = append(is, herodot.StreamErrorUnwrapInterceptor, - grpcLogrus.StreamServerInterceptor(InterceptorLogger(r.l.Logrus())), + grpcLogrus.StreamServerInterceptor(r.l.Entry), r.pmm.StreamServerInterceptor, ) if r.sqaService != nil { diff --git a/internal/namespace/ast/ast_definitions.go b/internal/namespace/ast/ast_definitions.go index cb7200ffb..61ec9af1d 100644 --- a/internal/namespace/ast/ast_definitions.go +++ b/internal/namespace/ast/ast_definitions.go @@ -31,6 +31,8 @@ type ( AsRewrite() *SubjectSetRewrite } + SubjectEqualsObject struct{} + ComputedSubjectSet struct { Relation string `json:"relation"` } @@ -69,3 +71,6 @@ func (t *TupleToSubjectSet) AsRewrite() *SubjectSetRewrite { func (i *InvertResult) AsRewrite() *SubjectSetRewrite { return &SubjectSetRewrite{Children: []Child{i}} } +func (e *SubjectEqualsObject) AsRewrite() *SubjectSetRewrite { + return &SubjectSetRewrite{Children: []Child{e}} +} diff --git a/internal/schema/itemtype_string.go b/internal/schema/itemtype_string.go index c38fa384f..c3ad6fb10 100644 --- a/internal/schema/itemtype_string.go +++ b/internal/schema/itemtype_string.go @@ -1,6 +1,3 @@ -// Copyright © 2023 Ory Corp -// SPDX-License-Identifier: Apache-2.0 - // Code generated by "stringer -type=itemType -trimprefix item -linecomment"; DO NOT EDIT. package schema @@ -24,25 +21,26 @@ func _() { _ = x[itemOperatorOr-10] _ = x[itemOperatorNot-11] _ = x[itemOperatorAssign-12] - _ = x[itemOperatorArrow-13] - _ = x[itemOperatorDot-14] - _ = x[itemOperatorColon-15] - _ = x[itemOperatorComma-16] - _ = x[itemSemicolon-17] - _ = x[itemTypeUnion-18] - _ = x[itemParenLeft-19] - _ = x[itemParenRight-20] - _ = x[itemBraceLeft-21] - _ = x[itemBraceRight-22] - _ = x[itemBracketLeft-23] - _ = x[itemBracketRight-24] - _ = x[itemAngledLeft-25] - _ = x[itemAngledRight-26] + _ = x[itemOperatorEquals-13] + _ = x[itemOperatorArrow-14] + _ = x[itemOperatorDot-15] + _ = x[itemOperatorColon-16] + _ = x[itemOperatorComma-17] + _ = x[itemSemicolon-18] + _ = x[itemTypeUnion-19] + _ = x[itemParenLeft-20] + _ = x[itemParenRight-21] + _ = x[itemBraceLeft-22] + _ = x[itemBraceRight-23] + _ = x[itemBracketLeft-24] + _ = x[itemBracketRight-25] + _ = x[itemAngledLeft-26] + _ = x[itemAngledRight-27] } -const _itemType_name = "ErrorEOFIdentifierCommentStringLiteralKeywordClassKeywordImplementsKeywordThisKeywordCtx\"&&\"\"||\"\"!\"\"=\"\"=>\"\".\"\":\"\",\"\";\"\"|\"\"(\"\")\"\"{\"\"}\"\"[\"\"]\"\"<\"\">\"" +const _itemType_name = "ErrorEOFIdentifierCommentStringLiteralKeywordClassKeywordImplementsKeywordThisKeywordCtx\"&&\"\"||\"\"!\"\"=\"\"==\"\"=>\"\".\"\":\"\",\"\";\"\"|\"\"(\"\")\"\"{\"\"}\"\"[\"\"]\"\"<\"\">\"" -var _itemType_index = [...]uint8{0, 5, 8, 18, 25, 38, 50, 67, 78, 88, 92, 96, 99, 102, 106, 109, 112, 115, 118, 121, 124, 127, 130, 133, 136, 139, 142, 145} +var _itemType_index = [...]uint8{0, 5, 8, 18, 25, 38, 50, 67, 78, 88, 92, 96, 99, 102, 106, 110, 113, 116, 119, 122, 125, 128, 131, 134, 137, 140, 143, 146, 149} func (i itemType) String() string { if i < 0 || i >= itemType(len(_itemType_index)-1) { diff --git a/internal/schema/lexer.go b/internal/schema/lexer.go index 7a365d9a1..1153925e3 100644 --- a/internal/schema/lexer.go +++ b/internal/schema/lexer.go @@ -61,6 +61,7 @@ const ( itemOperatorOr // "||" itemOperatorNot // "!" itemOperatorAssign // "=" + itemOperatorEquals // "==" itemOperatorArrow // "=>" itemOperatorDot // "." itemOperatorColon // ":" @@ -236,6 +237,7 @@ var oneRuneTokens = map[rune]itemType{ } var multiRuneTokens = map[string]itemType{ + "==": itemOperatorEquals, "=>": itemOperatorArrow, "||": itemOperatorOr, "&&": itemOperatorAnd, diff --git a/internal/schema/parser.go b/internal/schema/parser.go index 36e8dcd04..813e34368 100644 --- a/internal/schema/parser.go +++ b/internal/schema/parser.go @@ -423,7 +423,12 @@ func (p *parser) matchPropertyAccess(propertyName any) bool { func (p *parser) parsePermissionExpression() (child ast.Child) { var name, verb item - if !p.match("this", ".", &verb) { + switch { + case !p.match("this"): + return + case p.matchIf(is(itemOperatorEquals), "==", "ctx", ".", "subject"): + return &ast.SubjectEqualsObject{} + case !p.match(".", &verb): return } if !p.matchPropertyAccess(&name) {