diff --git a/cmd/expand/root.go b/cmd/expand/root.go index 3fae19e8c..63f0a0971 100644 --- a/cmd/expand/root.go +++ b/cmd/expand/root.go @@ -45,9 +45,9 @@ func NewExpandCmd() *cobra.Command { return cmdx.FailSilently(cmd) } - var tree *ketoapi.ExpandTree + var tree *ketoapi.Tree[*ketoapi.RelationTuple] if resp.Tree != nil { - tree = (&ketoapi.ExpandTree{}).FromProto(resp.Tree) + tree = ketoapi.TreeFromProto[*ketoapi.RelationTuple](resp.Tree) } cmdx.PrintJSONAble(cmd, tree) diff --git a/contrib/docs-code-samples/expand-api-display-access/01-expand-beach/expected_output.json b/contrib/docs-code-samples/expand-api-display-access/01-expand-beach/expected_output.json index 020cf9130..8c61c2eaf 100644 --- a/contrib/docs-code-samples/expand-api-display-access/01-expand-beach/expected_output.json +++ b/contrib/docs-code-samples/expand-api-display-access/01-expand-beach/expected_output.json @@ -1,46 +1,76 @@ { - "type": "union", "children": [ { - "type": "union", "children": [ { - "type": "leaf", - "subject_set": { - "namespace": "directories", - "object": "/photos", - "relation": "owner" - } - }, - { - "type": "leaf", - "subject_id": "laura" + "tuple": { + "namespace": "", + "object": "", + "relation": "", + "subject_id": "maureen" + }, + "type": "leaf" } ], - "subject_set": { - "namespace": "directories", - "object": "/photos", - "relation": "access" - } + "tuple": { + "namespace": "", + "object": "", + "relation": "", + "subject_set": { + "namespace": "files", + "object": "/photos/beach.jpg", + "relation": "owner" + } + }, + "type": "union" }, { - "type": "union", "children": [ { - "type": "leaf", - "subject_id": "maureen" + "tuple": { + "namespace": "", + "object": "", + "relation": "", + "subject_id": "laura" + }, + "type": "leaf" + }, + { + "tuple": { + "namespace": "", + "object": "", + "relation": "", + "subject_set": { + "namespace": "directories", + "object": "/photos", + "relation": "owner" + } + }, + "type": "leaf" } ], - "subject_set": { - "namespace": "files", - "object": "/photos/beach.jpg", - "relation": "owner" - } + "tuple": { + "namespace": "", + "object": "", + "relation": "", + "subject_set": { + "namespace": "directories", + "object": "/photos", + "relation": "access" + } + }, + "type": "union" } ], - "subject_set": { - "namespace": "files", - "object": "/photos/beach.jpg", - "relation": "access" - } + "tuple": { + "namespace": "", + "object": "", + "relation": "", + "subject_set": { + "namespace": "files", + "object": "/photos/beach.jpg", + "relation": "access" + } + }, + "type": "union" } diff --git a/contrib/docs-code-samples/expand-api-display-access/01-expand-beach/index.js b/contrib/docs-code-samples/expand-api-display-access/01-expand-beach/index.js index e07066ad1..1d4cd61c8 100644 --- a/contrib/docs-code-samples/expand-api-display-access/01-expand-beach/index.js +++ b/contrib/docs-code-samples/expand-api-display-access/01-expand-beach/index.js @@ -35,16 +35,23 @@ const subjectJSON = (subject) => { // helper to get a nice result const prettyTree = (tree) => { - const [nodeType, subject, children] = [ + const [nodeType, tuple, children] = [ tree.getNodeType(), - subjectJSON(tree.getSubject()), + { + tuple: { + namespace: "", + object: "", + relation: "", + ...subjectJSON(tree.getSubject()), + }, + }, tree.getChildrenList(), ] switch (nodeType) { case expand.NodeType.NODE_TYPE_LEAF: - return { type: "leaf", ...subject } + return { type: "leaf", ...tuple } case expand.NodeType.NODE_TYPE_UNION: - return { type: "union", children: children.map(prettyTree), ...subject } + return { type: "union", children: children.map(prettyTree), ...tuple } } } diff --git a/contrib/docs-code-samples/expand-api-display-access/01-expand-beach/main.go b/contrib/docs-code-samples/expand-api-display-access/01-expand-beach/main.go index 8655dea82..43a038bbe 100644 --- a/contrib/docs-code-samples/expand-api-display-access/01-expand-beach/main.go +++ b/contrib/docs-code-samples/expand-api-display-access/01-expand-beach/main.go @@ -28,7 +28,7 @@ func main() { panic(err) } - tree := (&ketoapi.ExpandTree{}).FromProto(res.Tree) + tree := ketoapi.TreeFromProto[*ketoapi.RelationTuple](res.Tree) enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") diff --git a/contrib/docs-code-samples/package-lock.json b/contrib/docs-code-samples/package-lock.json index cc0de7b41..9b23bfb01 100644 --- a/contrib/docs-code-samples/package-lock.json +++ b/contrib/docs-code-samples/package-lock.json @@ -4,6 +4,7 @@ "requires": true, "packages": { "": { + "name": "docs-code-samples", "dependencies": { "@grpc/grpc-js": "^1.2.6", "@ory/keto-grpc-client": "file:../../proto" @@ -190,10 +191,11 @@ } }, "node_modules/google-p12-pem": { - "version": "3.0.3", - "license": "MIT", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", + "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", "dependencies": { - "node-forge": "^0.10.0" + "node-forge": "^1.3.1" }, "bin": { "gp12-pem": "build/src/bin/gp12-pem.js" @@ -290,10 +292,11 @@ } }, "node_modules/node-forge": { - "version": "0.10.0", - "license": "(BSD-3-Clause OR GPL-2.0)", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "engines": { - "node": ">= 6.0.0" + "node": ">= 6.13.0" } }, "node_modules/safe-buffer": { @@ -425,9 +428,11 @@ } }, "google-p12-pem": { - "version": "3.0.3", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", + "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", "requires": { - "node-forge": "^0.10.0" + "node-forge": "^1.3.1" } }, "gtoken": { @@ -487,7 +492,9 @@ } }, "node-forge": { - "version": "0.10.0" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" }, "safe-buffer": { "version": "5.1.2" diff --git a/internal/e2e/cases_test.go b/internal/e2e/cases_test.go index 0f2349dee..2befa69ef 100644 --- a/internal/e2e/cases_test.go +++ b/internal/e2e/cases_test.go @@ -57,36 +57,40 @@ func runCases(c client, m *namespaceTestManager) func(*testing.T) { rel := "expand" subjects := []string{"s1", "s2"} - expectedTree := &ketoapi.ExpandTree{ - Type: ketoapi.ExpandNodeUnion, - SubjectSet: &ketoapi.SubjectSet{ - Namespace: n.Name, - Object: obj, - Relation: rel, + expectedTree := &ketoapi.Tree[*ketoapi.RelationTuple]{ + Type: ketoapi.TreeNodeUnion, + Tuple: &ketoapi.RelationTuple{ + SubjectSet: &ketoapi.SubjectSet{ + Namespace: n.Name, + Object: obj, + Relation: rel, + }, }, - Children: make([]*ketoapi.ExpandTree, len(subjects)), + Children: make([]*ketoapi.Tree[*ketoapi.RelationTuple], len(subjects)), } for i, subjectID := range subjects { + subjectID := subjectID c.createTuple(t, &ketoapi.RelationTuple{ Namespace: n.Name, Object: obj, Relation: rel, SubjectID: &subjectID, }) - expectedTree.Children[i] = &ketoapi.ExpandTree{ - Type: ketoapi.ExpandNodeLeaf, - SubjectID: &subjectID, + expectedTree.Children[i] = &ketoapi.Tree[*ketoapi.RelationTuple]{ + Type: ketoapi.TreeNodeLeaf, + Tuple: &ketoapi.RelationTuple{ + SubjectID: &subjectID, + }, } } - actualTree := c.expand(t, expectedTree.SubjectSet, 100) + actualTree := c.expand(t, expectedTree.Tuple.SubjectSet, 100) assert.Equal(t, expectedTree.Type, actualTree.Type) - assert.Equal(t, expectedTree.SubjectSet, actualTree.SubjectSet) - assert.Equal(t, expectedTree.SubjectID, actualTree.SubjectID) + assert.Equalf(t, expectedTree.Tuple, actualTree.Tuple, + "want:\t%s\ngot:\t%s", expectedTree.Tuple, actualTree.Tuple) assert.Equal(t, len(expectedTree.Children), len(actualTree.Children), "expected: %+v; actual: %+v", expectedTree.Children, actualTree.Children) - expand.AssertExternalTreesAreEqual(t, expectedTree, actualTree) }) diff --git a/internal/e2e/cli_client_test.go b/internal/e2e/cli_client_test.go index 8d5570df3..ea9bdbb6c 100644 --- a/internal/e2e/cli_client_test.go +++ b/internal/e2e/cli_client_test.go @@ -99,9 +99,9 @@ func (g *cliClient) check(t require.TestingT, r *ketoapi.RelationTuple) bool { return res.Allowed } -func (g *cliClient) expand(t require.TestingT, r *ketoapi.SubjectSet, depth int) *ketoapi.ExpandTree { +func (g *cliClient) expand(t require.TestingT, r *ketoapi.SubjectSet, depth int) *ketoapi.Tree[*ketoapi.RelationTuple] { out := g.c.ExecNoErr(t, "expand", r.Relation, r.Namespace, r.Object, "--"+cliexpand.FlagMaxDepth, fmt.Sprintf("%d", depth), "--"+cmdx.FlagFormat, string(cmdx.FormatJSON)) - res := ketoapi.ExpandTree{} + res := ketoapi.Tree[*ketoapi.RelationTuple]{} require.NoError(t, json.Unmarshal([]byte(out), &res)) return &res } diff --git a/internal/e2e/full_suit_test.go b/internal/e2e/full_suit_test.go index a1a99b61f..c795ef440 100644 --- a/internal/e2e/full_suit_test.go +++ b/internal/e2e/full_suit_test.go @@ -35,7 +35,7 @@ type ( queryTuple(t require.TestingT, q *ketoapi.RelationQuery, opts ...x.PaginationOptionSetter) *ketoapi.GetResponse queryTupleErr(t require.TestingT, expected herodot.DefaultError, q *ketoapi.RelationQuery, opts ...x.PaginationOptionSetter) check(t require.TestingT, r *ketoapi.RelationTuple) bool - expand(t require.TestingT, r *ketoapi.SubjectSet, depth int) *ketoapi.ExpandTree + expand(t require.TestingT, r *ketoapi.SubjectSet, depth int) *ketoapi.Tree[*ketoapi.RelationTuple] waitUntilLive(t require.TestingT) } ) diff --git a/internal/e2e/grpc_client_test.go b/internal/e2e/grpc_client_test.go index 92d3c66f6..75e47687c 100644 --- a/internal/e2e/grpc_client_test.go +++ b/internal/e2e/grpc_client_test.go @@ -129,7 +129,7 @@ func (g *grpcClient) check(t require.TestingT, r *ketoapi.RelationTuple) bool { return resp.Allowed } -func (g *grpcClient) expand(t require.TestingT, r *ketoapi.SubjectSet, depth int) *ketoapi.ExpandTree { +func (g *grpcClient) expand(t require.TestingT, r *ketoapi.SubjectSet, depth int) *ketoapi.Tree[*ketoapi.RelationTuple] { c := rts.NewExpandServiceClient(g.readConn(t)) resp, err := c.Expand(g.ctx, &rts.ExpandRequest{ @@ -138,7 +138,7 @@ func (g *grpcClient) expand(t require.TestingT, r *ketoapi.SubjectSet, depth int }) require.NoError(t, err) - return (&ketoapi.ExpandTree{}).FromProto(resp.Tree) + return ketoapi.TreeFromProto[*ketoapi.RelationTuple](resp.Tree) } func (g *grpcClient) waitUntilLive(t require.TestingT) { diff --git a/internal/e2e/rest_client_test.go b/internal/e2e/rest_client_test.go index 4c9ad415b..e0f93c171 100644 --- a/internal/e2e/rest_client_test.go +++ b/internal/e2e/rest_client_test.go @@ -140,14 +140,14 @@ func (rc *restClient) check(t require.TestingT, r *ketoapi.RelationTuple) bool { return false } -func (rc *restClient) expand(t require.TestingT, r *ketoapi.SubjectSet, depth int) *ketoapi.ExpandTree { +func (rc *restClient) expand(t require.TestingT, r *ketoapi.SubjectSet, depth int) *ketoapi.Tree[*ketoapi.RelationTuple] { query := r.ToURLQuery() query.Set("max-depth", fmt.Sprintf("%d", depth)) body, code := rc.makeRequest(t, http.MethodGet, fmt.Sprintf("%s?%s", expand.RouteBase, query.Encode()), "", false) require.Equal(t, http.StatusOK, code, body) - tree := &ketoapi.ExpandTree{} + tree := &ketoapi.Tree[*ketoapi.RelationTuple]{} require.NoError(t, json.Unmarshal([]byte(body), tree)) return tree diff --git a/internal/expand/engine.go b/internal/expand/engine.go index e82e6c2c4..408c47d44 100644 --- a/internal/expand/engine.go +++ b/internal/expand/engine.go @@ -38,14 +38,14 @@ func (e *Engine) BuildTree(ctx context.Context, subject relationtuple.Subject, r restDepth = globalMaxDepth } - if us, isUserSet := subject.(*relationtuple.SubjectSet); isUserSet { - ctx, wasAlreadyVisited := graph.CheckAndAddVisited(ctx, subject.UniqueID()) + if subSet, isSubjectSet := subject.(*relationtuple.SubjectSet); isSubjectSet { + ctx, wasAlreadyVisited := graph.CheckAndAddVisited(ctx, subject) if wasAlreadyVisited { return nil, nil } subTree := &relationtuple.Tree{ - Type: ketoapi.ExpandNodeUnion, + Type: ketoapi.TreeNodeUnion, Subject: subject, } @@ -59,9 +59,9 @@ func (e *Engine) BuildTree(ctx context.Context, subject relationtuple.Subject, r rels, nextPage, err = e.d.RelationTupleManager().GetRelationTuples( ctx, &relationtuple.RelationQuery{ - Relation: &us.Relation, - Object: &us.Object, - Namespace: &us.Namespace, + Relation: &subSet.Relation, + Object: &subSet.Object, + Namespace: &subSet.Namespace, }, x.WithToken(nextPage), ) @@ -72,7 +72,7 @@ func (e *Engine) BuildTree(ctx context.Context, subject relationtuple.Subject, r } if restDepth <= 1 { - subTree.Type = ketoapi.ExpandNodeLeaf + subTree.Type = ketoapi.TreeNodeLeaf return subTree, nil } @@ -84,7 +84,7 @@ func (e *Engine) BuildTree(ctx context.Context, subject relationtuple.Subject, r } if child == nil { child = &relationtuple.Tree{ - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: r.Subject, } } @@ -98,7 +98,7 @@ func (e *Engine) BuildTree(ctx context.Context, subject relationtuple.Subject, r // is SubjectID return &relationtuple.Tree{ - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: subject, }, nil } diff --git a/internal/expand/engine_test.go b/internal/expand/engine_test.go index 8918098b1..2ec2d860d 100644 --- a/internal/expand/engine_test.go +++ b/internal/expand/engine_test.go @@ -54,7 +54,7 @@ func TestEngine(t *testing.T) { tree, err := e.BuildTree(context.Background(), user, 100) require.NoError(t, err) assert.Equal(t, &relationtuple.Tree{ - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: user, }, tree) }) @@ -86,15 +86,15 @@ func TestEngine(t *testing.T) { tree, err := e.BuildTree(context.Background(), bouldererUserSet, 100) require.NoError(t, err) expand.AssertInternalTreesAreEqual(t, &relationtuple.Tree{ - Type: ketoapi.ExpandNodeUnion, + Type: ketoapi.TreeNodeUnion, Subject: bouldererUserSet, Children: []*relationtuple.Tree{ { - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: paul, }, { - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: tommy, }, }, @@ -103,50 +103,50 @@ func TestEngine(t *testing.T) { t.Run("case=expands two levels", func(t *testing.T) { expectedTree := &relationtuple.Tree{ - Type: ketoapi.ExpandNodeUnion, + Type: ketoapi.TreeNodeUnion, Subject: &relationtuple.SubjectSet{ Object: uuid.Must(uuid.NewV4()), Relation: "transitive member", }, Children: []*relationtuple.Tree{ { - Type: ketoapi.ExpandNodeUnion, + Type: ketoapi.TreeNodeUnion, Subject: &relationtuple.SubjectSet{ Object: uuid.Must(uuid.NewV4()), Relation: "member", }, Children: []*relationtuple.Tree{ { - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: &relationtuple.SubjectID{ID: uuid.Must(uuid.NewV4())}, }, { - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: &relationtuple.SubjectID{ID: uuid.Must(uuid.NewV4())}, }, { - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: &relationtuple.SubjectID{ID: uuid.Must(uuid.NewV4())}, }, }, }, { - Type: ketoapi.ExpandNodeUnion, + Type: ketoapi.TreeNodeUnion, Subject: &relationtuple.SubjectSet{ Object: uuid.Must(uuid.NewV4()), Relation: "member", }, Children: []*relationtuple.Tree{ { - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: &relationtuple.SubjectID{ID: uuid.Must(uuid.NewV4())}, }, { - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: &relationtuple.SubjectID{ID: uuid.Must(uuid.NewV4())}, }, { - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: &relationtuple.SubjectID{ID: uuid.Must(uuid.NewV4())}, }, }, @@ -196,28 +196,28 @@ func TestEngine(t *testing.T) { } expectedTree := &relationtuple.Tree{ - Type: ketoapi.ExpandNodeUnion, + Type: ketoapi.TreeNodeUnion, Subject: &relationtuple.SubjectSet{ Object: ids[0], Relation: "child", }, Children: []*relationtuple.Tree{ { - Type: ketoapi.ExpandNodeUnion, + Type: ketoapi.TreeNodeUnion, Subject: &relationtuple.SubjectSet{ Object: ids[1], Relation: "child", }, Children: []*relationtuple.Tree{ { - Type: ketoapi.ExpandNodeUnion, + Type: ketoapi.TreeNodeUnion, Subject: &relationtuple.SubjectSet{ Object: ids[2], Relation: "child", }, Children: []*relationtuple.Tree{ { - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: &relationtuple.SubjectSet{ Object: ids[3], Relation: "child", @@ -242,7 +242,7 @@ func TestEngine(t *testing.T) { root := uuid.Must(uuid.NewV4()) users := x.UUIDs(4) expectedTree := &relationtuple.Tree{ - Type: ketoapi.ExpandNodeUnion, + Type: ketoapi.TreeNodeUnion, Subject: &relationtuple.SubjectSet{Object: root, Relation: "access"}, } @@ -253,7 +253,7 @@ func TestEngine(t *testing.T) { Subject: &relationtuple.SubjectID{ID: user}, })) expectedTree.Children = append(expectedTree.Children, &relationtuple.Tree{ - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: &relationtuple.SubjectID{ID: user}, }) } @@ -272,14 +272,14 @@ func TestEngine(t *testing.T) { reg, e := newTestEngine(t, []*namespace.Namespace{{}}) expectedTree := &relationtuple.Tree{ - Type: ketoapi.ExpandNodeUnion, + Type: ketoapi.TreeNodeUnion, Subject: &relationtuple.SubjectSet{ Object: uuid.Must(uuid.NewV4()), Relation: "rel", }, Children: []*relationtuple.Tree{ { - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: &relationtuple.SubjectSet{ Object: uuid.Must(uuid.NewV4()), Relation: "sr", @@ -319,19 +319,19 @@ func TestEngine(t *testing.T) { reg, e := newTestEngine(t, []*namespace.Namespace{{Name: namesp}}) expectedTree := &relationtuple.Tree{ - Type: ketoapi.ExpandNodeUnion, + Type: ketoapi.TreeNodeUnion, Subject: sendlingerTorSS, Children: []*relationtuple.Tree{ { - Type: ketoapi.ExpandNodeUnion, + Type: ketoapi.TreeNodeUnion, Subject: odeonsplatzSS, Children: []*relationtuple.Tree{ { - Type: ketoapi.ExpandNodeUnion, + Type: ketoapi.TreeNodeUnion, Subject: centralStationSS, Children: []*relationtuple.Tree{ { - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: sendlingerTorSS, Children: nil, }, diff --git a/internal/expand/handler.go b/internal/expand/handler.go index 0ccd26ed1..d16240b03 100644 --- a/internal/expand/handler.go +++ b/internal/expand/handler.go @@ -61,23 +61,23 @@ type getExpandRequest struct { // swagger:route GET /relation-tuples/expand read getExpand // -// Expand a Relation Tuple +// # Expand a Relation Tuple // // Use this endpoint to expand a relation tuple. // -// Consumes: -// - application/x-www-form-urlencoded +// Consumes: +// - application/x-www-form-urlencoded // -// Produces: -// - application/json +// Produces: +// - application/json // -// Schemes: http, https +// Schemes: http, https // -// Responses: -// 200: expandTree -// 400: genericError -// 404: genericError -// 500: genericError +// Responses: +// 200: expandTree +// 400: genericError +// 404: genericError +// 500: genericError func (h *handler) getExpand(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { maxDepth, err := x.GetMaxDepthFromQuery(r.URL.Query()) if err != nil { diff --git a/internal/expand/handler_test.go b/internal/expand/handler_test.go index 1273b908d..01a464a60 100644 --- a/internal/expand/handler_test.go +++ b/internal/expand/handler_test.go @@ -67,17 +67,23 @@ func TestRESTHandler(t *testing.T) { Object: "root", Relation: "parent of", } - expectedTree := &ketoapi.ExpandTree{ - Type: ketoapi.ExpandNodeUnion, - SubjectSet: rootSub, - Children: []*ketoapi.ExpandTree{ + expectedTree := &ketoapi.Tree[*ketoapi.RelationTuple]{ + Type: ketoapi.TreeNodeUnion, + Tuple: &ketoapi.RelationTuple{ + SubjectSet: rootSub, + }, + Children: []*ketoapi.Tree[*ketoapi.RelationTuple]{ { - Type: ketoapi.ExpandNodeLeaf, - SubjectID: x.Ptr("child0"), + Type: ketoapi.TreeNodeLeaf, + Tuple: &ketoapi.RelationTuple{ + SubjectID: x.Ptr("child0"), + }, }, { - Type: ketoapi.ExpandNodeLeaf, - SubjectID: x.Ptr("child1"), + Type: ketoapi.TreeNodeLeaf, + Tuple: &ketoapi.RelationTuple{ + SubjectID: x.Ptr("child1"), + }, }, }, } @@ -87,13 +93,13 @@ func TestRESTHandler(t *testing.T) { Namespace: nspace.Name, Object: rootSub.Object, Relation: rootSub.Relation, - SubjectID: expectedTree.Children[0].SubjectID, + SubjectID: expectedTree.Children[0].Tuple.SubjectID, }, &ketoapi.RelationTuple{ Namespace: nspace.Name, Object: rootSub.Object, Relation: rootSub.Relation, - SubjectID: expectedTree.Children[1].SubjectID, + SubjectID: expectedTree.Children[1].Tuple.SubjectID, }, ) @@ -104,7 +110,7 @@ func TestRESTHandler(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) - actualTree := ketoapi.ExpandTree{} + actualTree := ketoapi.Tree[*ketoapi.RelationTuple]{} body, err := io.ReadAll(resp.Body) require.NoError(t, err) t.Logf("body: %s", string(body)) diff --git a/internal/expand/testhelper.go b/internal/expand/testhelper.go index 951737f9d..845566bcc 100644 --- a/internal/expand/testhelper.go +++ b/internal/expand/testhelper.go @@ -10,13 +10,14 @@ import ( "github.com/ory/keto/ketoapi" ) -func AssertExternalTreesAreEqual(t *testing.T, expected, actual *ketoapi.ExpandTree) { +func AssertExternalTreesAreEqual(t *testing.T, expected, actual *ketoapi.Tree[*ketoapi.RelationTuple]) { t.Helper() assert.Truef(t, treesAreEqual(t, expected, actual), "expected:\n%+v\n\nactual:\n%+v", expected, actual) } -func treesAreEqual(t *testing.T, expected, actual *ketoapi.ExpandTree) bool { +// TODO(hperl): Refactor to generic tree equality helper. +func treesAreEqual(t *testing.T, expected, actual *ketoapi.Tree[*ketoapi.RelationTuple]) bool { if expected == nil || actual == nil { return expected == actual } @@ -25,8 +26,8 @@ func treesAreEqual(t *testing.T, expected, actual *ketoapi.ExpandTree) bool { t.Logf("expected type %q, actual type %q", expected.Type, actual.Type) return false } - if !assert.ObjectsAreEqual(expected.SubjectID, actual.SubjectID) || !assert.ObjectsAreEqual(expected.SubjectSet, actual.SubjectSet) { - t.Logf("expected subject: %+v %+v, actual subject: %+v %+v", expected.SubjectID, expected.SubjectSet, actual.SubjectID, actual.SubjectSet) + if !assert.ObjectsAreEqual(expected.Tuple.SubjectID, actual.Tuple.SubjectID) || !assert.ObjectsAreEqual(expected.Tuple.SubjectSet, actual.Tuple.SubjectSet) { + t.Logf("expected subject: %+v %+v, actual subject: %+v %+v", expected.Tuple.SubjectID, expected.Tuple.SubjectSet, actual.Tuple.SubjectID, actual.Tuple.SubjectSet) return false } if len(expected.Children) != len(actual.Children) { diff --git a/internal/relationtuple/definitions.go b/internal/relationtuple/definitions.go index fc62e689f..789285160 100644 --- a/internal/relationtuple/definitions.go +++ b/internal/relationtuple/definitions.go @@ -2,15 +2,14 @@ package relationtuple import ( "context" - "testing" - - "github.com/ory/keto/ketoapi" + "fmt" + "sync" "github.com/gofrs/uuid" - rts "github.com/ory/keto/proto/ory/keto/relation_tuples/v1alpha2" - "github.com/ory/keto/internal/x" + "github.com/ory/keto/ketoapi" + rts "github.com/ory/keto/proto/ory/keto/relation_tuples/v1alpha2" ) type ( @@ -42,6 +41,7 @@ type ( Subject interface { Equals(Subject) bool UniqueID() uuid.UUID + String() string } RelationTuple struct { Namespace string `json:"namespace"` @@ -55,10 +55,12 @@ type ( Object uuid.UUID `json:"object"` Relation string `json:"relation"` } + + // TODO(hperl): Also use a ketoapi.Tree here. Tree struct { - Type ketoapi.ExpandNodeType `json:"type"` - Subject Subject `json:"subject"` - Children []*Tree `json:"children,omitempty"` + Type ketoapi.TreeNodeType `json:"type"` + Subject Subject `json:"subject"` + Children []*Tree `json:"children,omitempty"` } ) @@ -74,9 +76,8 @@ func (s *SubjectID) Equals(other Subject) bool { return uv.ID == s.ID } -func (s *SubjectID) UniqueID() uuid.UUID { - return s.ID -} +func (s *SubjectID) UniqueID() uuid.UUID { return s.ID } +func (s *SubjectID) String() string { return s.ID.String() } func (s *SubjectSet) Equals(other Subject) bool { uv, ok := other.(*SubjectSet) @@ -90,6 +91,10 @@ func (s *SubjectSet) UniqueID() uuid.UUID { return uuid.NewV5(s.Object, s.Namespace+"-"+s.Relation) } +func (s *SubjectSet) String() string { + return fmt.Sprintf("%s:%s#%s", s.Namespace, s.Object, s.Relation) +} + func (t *RelationTuple) ToQuery() *RelationQuery { return &RelationQuery{ Namespace: &t.Namespace, @@ -99,10 +104,28 @@ func (t *RelationTuple) ToQuery() *RelationQuery { } } +func (t *RelationTuple) String() string { + if t == nil { + return "" + } + return fmt.Sprintf("%s:%s#%s@%s", t.Namespace, t.Object, t.Relation, t.Subject) +} + +func (t *RelationTuple) FromProto(proto *rts.RelationTuple) *RelationTuple { + // TODO(hperl) + return t +} +func (t *RelationTuple) ToProto() *rts.RelationTuple { + // TODO(hperl) + return &rts.RelationTuple{} +} + type ManagerWrapper struct { Reg ManagerProvider PageOpts []x.PaginationOptionSetter RequestedPages []string + // lock is necessary so that GetRelationTuples() is safe for concurrency. + requestedPagesLock sync.Mutex } var ( @@ -110,7 +133,7 @@ var ( _ ManagerProvider = (*ManagerWrapper)(nil) ) -func NewManagerWrapper(_ *testing.T, reg ManagerProvider, options ...x.PaginationOptionSetter) *ManagerWrapper { +func NewManagerWrapper(_ any, reg ManagerProvider, options ...x.PaginationOptionSetter) *ManagerWrapper { return &ManagerWrapper{ Reg: reg, PageOpts: options, @@ -119,6 +142,8 @@ func NewManagerWrapper(_ *testing.T, reg ManagerProvider, options ...x.Paginatio func (t *ManagerWrapper) GetRelationTuples(ctx context.Context, query *RelationQuery, options ...x.PaginationOptionSetter) ([]*RelationTuple, string, error) { opts := x.GetPaginationOptions(options...) + t.requestedPagesLock.Lock() + defer t.requestedPagesLock.Unlock() t.RequestedPages = append(t.RequestedPages, opts.Token) return t.Reg.RelationTupleManager().GetRelationTuples(ctx, query, append(t.PageOpts, options...)...) } diff --git a/internal/relationtuple/read_server.go b/internal/relationtuple/read_server.go index 4160b0c54..d7e88b12a 100644 --- a/internal/relationtuple/read_server.go +++ b/internal/relationtuple/read_server.go @@ -68,8 +68,8 @@ func (h *handler) ListRelationTuples(ctx context.Context, req *rts.ListRelationT switch { case req.RelationQuery != nil: q.FromDataProvider(&queryWrapper{req.RelationQuery}) - case req.Query != nil: - q.FromDataProvider(&deprecatedQueryWrapper{req.Query}) + case req.Query != nil: // nolint + q.FromDataProvider(&deprecatedQueryWrapper{req.Query}) // nolint default: return nil, herodot.ErrBadRequest.WithError("you must provide a query") } @@ -103,22 +103,22 @@ func (h *handler) ListRelationTuples(ctx context.Context, req *rts.ListRelationT // swagger:route GET /relation-tuples read getRelationTuples // -// Query relation tuples +// # Query relation tuples // // Get all relation tuples that match the query. Only the namespace field is required. // -// Consumes: -// - application/x-www-form-urlencoded +// Consumes: +// - application/x-www-form-urlencoded // -// Produces: -// - application/json +// Produces: +// - application/json // -// Schemes: http, https +// Schemes: http, https // -// Responses: -// 200: getRelationTuplesResponse -// 404: genericError -// 500: genericError +// Responses: +// 200: getRelationTuplesResponse +// 404: genericError +// 500: genericError func (h *handler) getRelations(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { ctx := r.Context() diff --git a/internal/relationtuple/read_server_test.go b/internal/relationtuple/read_server_test.go index 8108a40a2..ce51e6ba0 100644 --- a/internal/relationtuple/read_server_test.go +++ b/internal/relationtuple/read_server_test.go @@ -216,17 +216,17 @@ func TestReadHandlers(t *testing.T) { } withDeprecatedQuery := func(req *rts.ListRelationTuplesRequest, query *ketoapi.RelationQuery) { pq := query.ToProto() - req.Query = &rts.ListRelationTuplesRequest_Query{ + req.Query = &rts.ListRelationTuplesRequest_Query{ // nolint Subject: pq.Subject, } if pq.Namespace != nil { - req.Query.Namespace = *pq.Namespace + req.Query.Namespace = *pq.Namespace // nolint } if pq.Object != nil { - req.Query.Object = *pq.Object + req.Query.Object = *pq.Object // nolint } if pq.Relation != nil { - req.Query.Relation = *pq.Relation + req.Query.Relation = *pq.Relation // nolint } } apiTuplesFromProto := func(t *testing.T, pts ...*rts.RelationTuple) []*ketoapi.RelationTuple { @@ -238,11 +238,11 @@ func TestReadHandlers(t *testing.T) { } return actual } - soc, err := net.Listen("tcp", ":0") + soc, err := net.Listen("tcp", ":0") // nolint require.NoError(t, err) srv := grpc.NewServer() h.RegisterReadGRPC(srv) - go srv.Serve(soc) + go srv.Serve(soc) // nolint t.Cleanup(srv.Stop) con, err := grpc.Dial(soc.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) diff --git a/internal/relationtuple/transact_server.go b/internal/relationtuple/transact_server.go index f45d91a83..c823824f8 100644 --- a/internal/relationtuple/transact_server.go +++ b/internal/relationtuple/transact_server.go @@ -67,8 +67,8 @@ func (h *handler) DeleteRelationTuples(ctx context.Context, req *rts.DeleteRelat switch { case req.RelationQuery != nil: q.FromDataProvider(&queryWrapper{req.RelationQuery}) - case req.Query != nil: - q.FromDataProvider(&deprecatedQueryWrapper{(*rts.ListRelationTuplesRequest_Query)(req.Query)}) + case req.Query != nil: // nolint + q.FromDataProvider(&deprecatedQueryWrapper{(*rts.ListRelationTuplesRequest_Query)(req.Query)}) // nolint default: return nil, errors.WithStack(herodot.ErrBadRequest.WithReason("invalid request")) } @@ -86,22 +86,22 @@ func (h *handler) DeleteRelationTuples(ctx context.Context, req *rts.DeleteRelat // swagger:route PUT /admin/relation-tuples write createRelationTuple // -// Create a Relation Tuple +// # Create a Relation Tuple // // Use this endpoint to create a relation tuple. // -// Consumes: -// - application/json +// Consumes: +// - application/json // -// Produces: -// - application/json +// Produces: +// - application/json // -// Schemes: http, https +// Schemes: http, https // -// Responses: -// 201: relationQuery -// 400: genericError -// 500: genericError +// Responses: +// 201: relationQuery +// 400: genericError +// 500: genericError func (h *handler) createRelation(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { ctx := r.Context() @@ -133,22 +133,22 @@ func (h *handler) createRelation(w http.ResponseWriter, r *http.Request, _ httpr // swagger:route DELETE /admin/relation-tuples write deleteRelationTuples // -// Delete Relation Tuples +// # Delete Relation Tuples // // Use this endpoint to delete relation tuples // -// Consumes: -// - application/x-www-form-urlencoded +// Consumes: +// - application/x-www-form-urlencoded // -// Produces: -// - application/json +// Produces: +// - application/json // -// Schemes: http, https +// Schemes: http, https // -// Responses: -// 204: emptyResponse -// 400: genericError -// 500: genericError +// Responses: +// 204: emptyResponse +// 400: genericError +// 500: genericError func (h *handler) deleteRelations(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { ctx := r.Context() @@ -191,23 +191,23 @@ func internalTuplesWithAction(deltas []*ketoapi.PatchDelta, action ketoapi.Patch // swagger:route PATCH /admin/relation-tuples write patchRelationTuples // -// Patch Multiple Relation Tuples +// # Patch Multiple Relation Tuples // // Use this endpoint to patch one or more relation tuples. // -// Consumes: -// - application/json +// Consumes: +// - application/json // -// Produces: -// - application/json +// Produces: +// - application/json // -// Schemes: http, https +// Schemes: http, https // -// Responses: -// 204: emptyResponse -// 400: genericError -// 404: genericError -// 500: genericError +// Responses: +// 204: emptyResponse +// 400: genericError +// 404: genericError +// 500: genericError func (h *handler) patchRelationTuples(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { ctx := r.Context() diff --git a/internal/relationtuple/uuid_mapping.go b/internal/relationtuple/uuid_mapping.go index cc1a9a82f..8eb8659d1 100644 --- a/internal/relationtuple/uuid_mapping.go +++ b/internal/relationtuple/uuid_mapping.go @@ -304,14 +304,15 @@ func (m *Mapper) FromSubjectSet(ctx context.Context, set *ketoapi.SubjectSet) (* }, nil } -func (m *Mapper) ToTree(ctx context.Context, tree *Tree) (res *ketoapi.ExpandTree, err error) { +func (m *Mapper) ToTree(ctx context.Context, tree *Tree) (res *ketoapi.Tree[*ketoapi.RelationTuple], err error) { onSuccess := newSuccess(&err) defer onSuccess.apply() var s []string var u []uuid.UUID - res = &ketoapi.ExpandTree{ - Type: tree.Type, + res = &ketoapi.Tree[*ketoapi.RelationTuple]{ + Type: tree.Type, + Tuple: &ketoapi.RelationTuple{}, } nm, err := m.D.Config(ctx).NamespaceManager() @@ -327,7 +328,7 @@ func (m *Mapper) ToTree(ctx context.Context, tree *Tree) (res *ketoapi.ExpandTre return nil, err } onSuccess.do(func() { - res.SubjectSet = &ketoapi.SubjectSet{ + res.Tuple.SubjectSet = &ketoapi.SubjectSet{ Namespace: n.Name, Object: s[0], Relation: sub.Relation, @@ -336,7 +337,7 @@ func (m *Mapper) ToTree(ctx context.Context, tree *Tree) (res *ketoapi.ExpandTre case *SubjectID: u = append(u, sub.ID) onSuccess.do(func() { - res.SubjectID = x.Ptr(s[0]) + res.Tuple.SubjectID = x.Ptr(s[0]) }) } for _, c := range tree.Children { diff --git a/internal/relationtuple/uuid_mapping_test.go b/internal/relationtuple/uuid_mapping_test.go index c21805366..7e8b678fa 100644 --- a/internal/relationtuple/uuid_mapping_test.go +++ b/internal/relationtuple/uuid_mapping_test.go @@ -210,14 +210,14 @@ func TestMapper(t *testing.T) { { name: "basic tree", tree: &relationtuple.Tree{ - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: &relationtuple.SubjectID{ID: uuids[0]}, }, }, { name: "basic tree with children", tree: &relationtuple.Tree{ - Type: ketoapi.ExpandNodeUnion, + Type: ketoapi.TreeNodeUnion, Subject: &relationtuple.SubjectSet{ Namespace: nspace.Name, Object: uuids[0], @@ -225,11 +225,11 @@ func TestMapper(t *testing.T) { }, Children: []*relationtuple.Tree{ { - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: &relationtuple.SubjectID{ID: uuids[1]}, }, { - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: &relationtuple.SubjectID{ID: uuids[2]}, }, }, @@ -238,7 +238,7 @@ func TestMapper(t *testing.T) { { name: "deeply nested tree", tree: &relationtuple.Tree{ - Type: ketoapi.ExpandNodeUnion, + Type: ketoapi.TreeNodeUnion, Subject: &relationtuple.SubjectSet{ Namespace: nspace.Name, Object: uuids[0], @@ -246,7 +246,7 @@ func TestMapper(t *testing.T) { }, Children: []*relationtuple.Tree{ { - Type: ketoapi.ExpandNodeUnion, + Type: ketoapi.TreeNodeUnion, Subject: &relationtuple.SubjectSet{ Namespace: nspace.Name, Object: uuids[1], @@ -254,7 +254,7 @@ func TestMapper(t *testing.T) { }, Children: []*relationtuple.Tree{ { - Type: ketoapi.ExpandNodeLeaf, + Type: ketoapi.TreeNodeLeaf, Subject: &relationtuple.SubjectID{ID: uuids[2]}, }, }, @@ -271,19 +271,19 @@ func TestMapper(t *testing.T) { return } - var checkTree func(*ketoapi.ExpandTree, *relationtuple.Tree) - checkTree = func(mapped *ketoapi.ExpandTree, original *relationtuple.Tree) { + var checkTree func(*ketoapi.Tree[*ketoapi.RelationTuple], *relationtuple.Tree) + checkTree = func(mapped *ketoapi.Tree[*ketoapi.RelationTuple], original *relationtuple.Tree) { switch s := original.Subject.(type) { case *relationtuple.SubjectID: - require.NotNil(t, mapped.SubjectID) - assert.Nil(t, mapped.SubjectSet) - assert.Equal(t, strs[slices.Index(uuids, s.ID)], *mapped.SubjectID) + require.NotNil(t, mapped.Tuple.SubjectID) + assert.Nil(t, mapped.Tuple.SubjectSet) + assert.Equal(t, strs[slices.Index(uuids, s.ID)], *mapped.Tuple.SubjectID) case *relationtuple.SubjectSet: - require.NotNil(t, mapped.SubjectSet) - assert.Nil(t, mapped.SubjectID) - assert.Equal(t, nspace.Name, mapped.SubjectSet.Namespace) - assert.Equal(t, strs[slices.Index(uuids, s.Object)], mapped.SubjectSet.Object) - assert.Equal(t, s.Relation, mapped.SubjectSet.Relation) + require.NotNil(t, mapped.Tuple.SubjectSet) + assert.Nil(t, mapped.Tuple.SubjectID) + assert.Equal(t, nspace.Name, mapped.Tuple.SubjectSet.Namespace) + assert.Equal(t, strs[slices.Index(uuids, s.Object)], mapped.Tuple.SubjectSet.Object) + assert.Equal(t, s.Relation, mapped.Tuple.SubjectSet.Relation) default: t.Fatalf("expected subject to be set: %+v", mapped) } diff --git a/ketoapi/enc_proto.go b/ketoapi/enc_proto.go index 275593e61..e57f7983b 100644 --- a/ketoapi/enc_proto.go +++ b/ketoapi/enc_proto.go @@ -57,6 +57,26 @@ func (r *RelationTuple) ToProto() *rts.RelationTuple { return res } +func (r *RelationTuple) FromProto(proto *rts.RelationTuple) *RelationTuple { + r = &RelationTuple{ + Namespace: proto.Namespace, + Object: proto.Object, + Relation: proto.Relation, + } + switch subject := proto.Subject.Ref.(type) { + case *rts.Subject_Id: + r.SubjectID = x.Ptr(subject.Id) + case *rts.Subject_Set: + r.SubjectSet = &SubjectSet{ + Namespace: subject.Set.Namespace, + Object: subject.Set.Object, + Relation: subject.Set.Relation, + } + } + + return r +} + func (q *RelationQuery) FromDataProvider(d queryData) *RelationQuery { q.Namespace = d.GetNamespace() q.Object = d.GetObject() @@ -93,40 +113,73 @@ func (q *RelationQuery) ToProto() *rts.RelationQuery { return res } -func (t *ExpandTree) ToProto() *rts.SubjectTree { +func (t *Tree[NodeT]) ToProto() *rts.SubjectTree { res := &rts.SubjectTree{ NodeType: t.Type.ToProto(), Children: make([]*rts.SubjectTree, len(t.Children)), } - if t.SubjectID != nil { - res.Subject = rts.NewSubjectID(*t.SubjectID) - } else { - res.Subject = rts.NewSubjectSet(t.SubjectSet.Namespace, t.SubjectSet.Object, t.SubjectSet.Relation) - } + res.Tuple = t.Tuple.ToProto() + // nolint - fill deprecated field + res.Subject = res.Tuple.Subject for i := range t.Children { res.Children[i] = t.Children[i].ToProto() } return res } -func (t *ExpandTree) FromProto(pt *rts.SubjectTree) *ExpandTree { - t.Type = ExpandNodeType("").FromProto(pt.NodeType) +func TreeFromProto[T tuple[T]](pt *rts.SubjectTree) *Tree[T] { + t := new(Tree[T]) + t.Type = TreeNodeType("").FromProto(pt.NodeType) - switch sub := pt.Subject.Ref.(type) { - case *rts.Subject_Id: - t.SubjectID = x.Ptr(sub.Id) - case *rts.Subject_Set: - t.SubjectSet = &SubjectSet{ - Namespace: sub.Set.Namespace, - Object: sub.Set.Object, - Relation: sub.Set.Relation, + var tuple T + if pt.Tuple == nil { + // legacy case: fetch from deprecated fields + // nolint + switch sub := pt.Subject.Ref.(type) { + case *rts.Subject_Id: + pt.Tuple.Subject = rts.NewSubjectID(sub.Id) + case *rts.Subject_Set: + pt.Tuple.Subject = rts.NewSubjectSet( + sub.Set.Namespace, + sub.Set.Object, + sub.Set.Relation, + ) } } + t.Tuple = tuple.FromProto(pt.Tuple) - t.Children = make([]*ExpandTree, len(pt.Children)) + t.Children = make([]*Tree[T], len(pt.Children)) for i := range pt.Children { - t.Children[i] = (&ExpandTree{}).FromProto(pt.Children[i]) + t.Children[i] = TreeFromProto[T](pt.Children[i]) } return t } + +func (t TreeNodeType) ToProto() rts.NodeType { + switch t { + case TreeNodeLeaf: + return rts.NodeType_NODE_TYPE_LEAF + case TreeNodeUnion: + return rts.NodeType_NODE_TYPE_UNION + case TreeNodeExclusion: + return rts.NodeType_NODE_TYPE_EXCLUSION + case TreeNodeIntersection: + return rts.NodeType_NODE_TYPE_INTERSECTION + } + return rts.NodeType_NODE_TYPE_UNSPECIFIED +} + +func (TreeNodeType) FromProto(pt rts.NodeType) TreeNodeType { + switch pt { + case rts.NodeType_NODE_TYPE_LEAF: + return TreeNodeLeaf + case rts.NodeType_NODE_TYPE_UNION: + return TreeNodeUnion + case rts.NodeType_NODE_TYPE_EXCLUSION: + return TreeNodeExclusion + case rts.NodeType_NODE_TYPE_INTERSECTION: + return TreeNodeIntersection + } + return TreeNodeUnspecified +} diff --git a/ketoapi/enc_string.go b/ketoapi/enc_string.go index 79d2c7762..3f9104b53 100644 --- a/ketoapi/enc_string.go +++ b/ketoapi/enc_string.go @@ -11,6 +11,9 @@ import ( var ErrMalformedInput = herodot.ErrBadRequest.WithError("malformed string input") func (r *RelationTuple) String() string { + if r == nil { + return "" + } sb := strings.Builder{} sb.WriteString(r.Namespace) sb.WriteRune(':') @@ -91,27 +94,59 @@ func (s *SubjectSet) FromString(str string) (*SubjectSet, error) { }, nil } -func (t *ExpandTree) String() string { +func (t TreeNodeType) String() string { + return string(t) +} + +func (t *Tree[NodeT]) Label() string { if t == nil { return "" } - sub := "" - switch { - case t.SubjectID != nil: - sub = *t.SubjectID - case t.SubjectSet != nil: - sub = t.SubjectSet.String() + return t.Tuple.String() +} + +func (t *Tree[NodeT]) String() string { + if t == nil { + return "" } - if t.Type == ExpandNodeLeaf { - return fmt.Sprintf("☘ %s️", sub) + nodeLabel := t.Label() + + if t.Type == TreeNodeLeaf { + return fmt.Sprintf("∋ %s️", nodeLabel) } children := make([]string, len(t.Children)) for i, c := range t.Children { - children[i] = strings.Join(strings.Split(c.String(), "\n"), "\n│ ") + var indent string + if i == len(t.Children)-1 { + indent = " " + } else { + indent = "│ " + } + children[i] = strings.Join(strings.Split(c.String(), "\n"), "\n"+indent) } - return fmt.Sprintf("∪ %s\n├─ %s", sub, strings.Join(children, "\n├─ ")) + setOperation := "" + switch t.Type { + case TreeNodeIntersection: + setOperation = "and" + case TreeNodeUnion: + setOperation = "or" + case TreeNodeExclusion: + setOperation = `\` + case TreeNodeNot: + setOperation = `not` + case TreeNodeTupleToSubjectSet: + setOperation = "┐ tuple to userset" + case TreeNodeComputedSubjectSet: + setOperation = "┐ computed userset" + } + + boxSymbol := "├" + if len(children) == 1 { + boxSymbol = "└" + } + return fmt.Sprintf("%s %s\n%s──%s", setOperation, nodeLabel, boxSymbol, strings.Join(children, "\n└──")) } diff --git a/ketoapi/public_api_definitions.go b/ketoapi/public_api_definitions.go index f6f4d5021..76c9f6e91 100644 --- a/ketoapi/public_api_definitions.go +++ b/ketoapi/public_api_definitions.go @@ -1,12 +1,14 @@ package ketoapi import ( + "encoding/json" "errors" + "fmt" + + rts "github.com/ory/keto/proto/ory/keto/relation_tuples/v1alpha2" "github.com/ory/herodot" "github.com/sirupsen/logrus" - - rts "github.com/ory/keto/proto/ory/keto/relation_tuples/v1alpha2" ) var ( @@ -128,74 +130,72 @@ func (r *RelationTuple) ToLoggerFields() logrus.Fields { } // swagger:enum ExpandNodeType -type ExpandNodeType string +type ExpandNodeType TreeNodeType + +// swagger:enum TreeNodeType +type TreeNodeType string const ( - ExpandNodeUnion ExpandNodeType = "union" - ExpandNodeExclusion ExpandNodeType = "exclusion" - ExpandNodeIntersection ExpandNodeType = "intersection" - ExpandNodeLeaf ExpandNodeType = "leaf" - ExpandNodeUnspecified ExpandNodeType = "unspecified" + TreeNodeUnion TreeNodeType = "union" + TreeNodeExclusion TreeNodeType = "exclusion" + TreeNodeIntersection TreeNodeType = "intersection" + TreeNodeLeaf TreeNodeType = "leaf" + TreeNodeTupleToSubjectSet TreeNodeType = "tuple_to_subject_set" + TreeNodeComputedSubjectSet TreeNodeType = "computed_subject_set" + TreeNodeNot TreeNodeType = "not" + TreeNodeUnspecified TreeNodeType = "unspecified" ) -// swagger:model expandTree -type ExpandTree struct { +func (t *TreeNodeType) UnmarshalJSON(v []byte) error { + var s string + if err := json.Unmarshal(v, &s); err != nil { + return err + } + switch nt := TreeNodeType(s); nt { + case TreeNodeUnion, TreeNodeExclusion, TreeNodeIntersection, TreeNodeLeaf, TreeNodeTupleToSubjectSet, TreeNodeComputedSubjectSet, TreeNodeNot, TreeNodeUnspecified: + *t = nt + default: + return ErrUnknownNodeType + } + return nil +} + +type tuple[T any] interface { + fmt.Stringer + ToProto() *rts.RelationTuple + FromProto(*rts.RelationTuple) T +} + +// Tree is a generic tree of either internal relation tuples (with UUIDs for +// objects, etc.) or API relation tuples (with strings for objects, etc.). +type Tree[T tuple[T]] struct { + // Propagate all struct changes to `swaggerOnlyExpandTree` as well. // The type of the node. // // required: true - Type ExpandNodeType `json:"type"` + Type TreeNodeType `json:"type"` + // The children of the node, possibly none. - Children []*ExpandTree `json:"children,omitempty"` - // The subject set the node represents. Either this field, or SubjectID are set. - SubjectSet *SubjectSet `json:"subject_set,omitempty"` - // The subject ID the node represents. Either this field, or SubjectSet are set. - SubjectID *string `json:"subject_id,omitempty"` -} + Children []*Tree[T] `json:"children,omitempty"` -func (t ExpandNodeType) String() string { - return string(t) + // The relation tuple the node represents. + Tuple T `json:"tuple"` } -func (t *ExpandNodeType) UnmarshalJSON(v []byte) error { - switch string(v) { - case `"union"`: - *t = ExpandNodeUnion - case `"exclusion"`: - *t = ExpandNodeExclusion - case `"intersection"`: - *t = ExpandNodeIntersection - case `"leaf"`: - *t = ExpandNodeLeaf - default: - return ErrUnknownNodeType - } - return nil -} +// IMPORTANT: We need a manual instantiation of the generic Tree[T] for the +// OpenAPI spec, since go-swagger does not understand generics :(. +// This can be fixed by using grpc-gateway. -func (t ExpandNodeType) ToProto() rts.NodeType { - switch t { - case ExpandNodeLeaf: - return rts.NodeType_NODE_TYPE_LEAF - case ExpandNodeUnion: - return rts.NodeType_NODE_TYPE_UNION - case ExpandNodeExclusion: - return rts.NodeType_NODE_TYPE_EXCLUSION - case ExpandNodeIntersection: - return rts.NodeType_NODE_TYPE_INTERSECTION - } - return rts.NodeType_NODE_TYPE_UNSPECIFIED -} +// swagger:model expandTree +type swaggerOnlyExpandTree struct { // nolint + // The type of the node. + // + // required: true + Type TreeNodeType `json:"type"` -func (ExpandNodeType) FromProto(pt rts.NodeType) ExpandNodeType { - switch pt { - case rts.NodeType_NODE_TYPE_LEAF: - return ExpandNodeLeaf - case rts.NodeType_NODE_TYPE_UNION: - return ExpandNodeUnion - case rts.NodeType_NODE_TYPE_EXCLUSION: - return ExpandNodeExclusion - case rts.NodeType_NODE_TYPE_INTERSECTION: - return ExpandNodeIntersection - } - return ExpandNodeUnspecified + // The children of the node, possibly none. + Children []*swaggerOnlyExpandTree `json:"children,omitempty"` + + // The relation tuple the node represents. + Tuple *RelationTuple `json:"tuple"` } diff --git a/proto/ory/keto/relation_tuples/v1alpha2/expand_service.pb.go b/proto/ory/keto/relation_tuples/v1alpha2/expand_service.pb.go index 370d38b0a..17a6a10f9 100644 --- a/proto/ory/keto/relation_tuples/v1alpha2/expand_service.pb.go +++ b/proto/ory/keto/relation_tuples/v1alpha2/expand_service.pb.go @@ -229,7 +229,12 @@ type SubjectTree struct { // The type of the node. NodeType NodeType `protobuf:"varint,1,opt,name=node_type,json=nodeType,proto3,enum=ory.keto.relation_tuples.v1alpha2.NodeType" json:"node_type,omitempty"` // The subject this node represents. + // Deprecated: More information is now available in the tuple field. + // + // Deprecated: Do not use. Subject *Subject `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` + // The relation tuple this node represents. + Tuple *RelationTuple `protobuf:"bytes,4,opt,name=tuple,proto3" json:"tuple,omitempty"` // The children of this node. // // This is never set if `node_type` == `NODE_TYPE_LEAF`. @@ -275,6 +280,7 @@ func (x *SubjectTree) GetNodeType() NodeType { return NodeType_NODE_TYPE_UNSPECIFIED } +// Deprecated: Do not use. func (x *SubjectTree) GetSubject() *Subject { if x != nil { return x.Subject @@ -282,6 +288,13 @@ func (x *SubjectTree) GetSubject() *Subject { return nil } +func (x *SubjectTree) GetTuple() *RelationTuple { + if x != nil { + return x.Tuple + } + return nil +} + func (x *SubjectTree) GetChildren() []*SubjectTree { if x != nil { return x.Children @@ -315,51 +328,56 @@ var file_ory_keto_relation_tuples_v1alpha2_expand_service_proto_rawDesc = []byte 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6f, 0x72, 0x79, 0x2e, 0x6b, 0x65, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x32, 0x2e, 0x53, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x54, 0x72, 0x65, 0x65, 0x52, 0x04, 0x74, 0x72, 0x65, 0x65, 0x22, 0xe9, 0x01, + 0x65, 0x63, 0x74, 0x54, 0x72, 0x65, 0x65, 0x52, 0x04, 0x74, 0x72, 0x65, 0x65, 0x22, 0xb5, 0x02, 0x0a, 0x0b, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x72, 0x65, 0x65, 0x12, 0x48, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x6f, 0x72, 0x79, 0x2e, 0x6b, 0x65, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x6e, - 0x6f, 0x64, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x44, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x6f, 0x64, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x48, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6f, 0x72, 0x79, 0x2e, 0x6b, 0x65, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x32, 0x2e, 0x53, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x4a, 0x0a, - 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x2e, 0x2e, 0x6f, 0x72, 0x79, 0x2e, 0x6b, 0x65, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x32, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x72, 0x65, 0x65, 0x52, - 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x2a, 0x83, 0x01, 0x0a, 0x08, 0x4e, 0x6f, - 0x64, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, - 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, - 0x4e, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x45, 0x58, 0x43, 0x4c, 0x55, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x12, - 0x1a, 0x0a, 0x16, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x54, - 0x45, 0x52, 0x53, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x4e, - 0x4f, 0x44, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4c, 0x45, 0x41, 0x46, 0x10, 0x04, 0x32, - 0x7e, 0x0a, 0x0d, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x6d, 0x0a, 0x06, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x12, 0x30, 0x2e, 0x6f, 0x72, 0x79, - 0x2e, 0x6b, 0x65, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, - 0x75, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x32, 0x2e, 0x45, - 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x6f, - 0x72, 0x79, 0x2e, 0x6b, 0x65, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x32, - 0x2e, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, - 0xc3, 0x01, 0x0a, 0x24, 0x73, 0x68, 0x2e, 0x6f, 0x72, 0x79, 0x2e, 0x6b, 0x65, 0x74, 0x6f, 0x2e, - 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x32, 0x42, 0x12, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3f, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x72, 0x79, 0x2f, 0x6b, - 0x65, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6f, 0x72, 0x79, 0x2f, 0x6b, 0x65, - 0x74, 0x6f, 0x2f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x75, 0x70, 0x6c, - 0x65, 0x73, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x32, 0x3b, 0x72, 0x74, 0x73, 0xaa, - 0x02, 0x20, 0x4f, 0x72, 0x79, 0x2e, 0x4b, 0x65, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x32, 0xca, 0x02, 0x20, 0x4f, 0x72, 0x79, 0x5c, 0x4b, 0x65, 0x74, 0x6f, 0x5c, 0x52, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x5c, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6a, 0x65, 0x63, 0x74, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x12, 0x46, 0x0a, 0x05, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x30, 0x2e, 0x6f, 0x72, 0x79, 0x2e, 0x6b, 0x65, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x32, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x75, 0x70, + 0x6c, 0x65, 0x52, 0x05, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x12, 0x4a, 0x0a, 0x08, 0x63, 0x68, 0x69, + 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6f, 0x72, + 0x79, 0x2e, 0x6b, 0x65, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x32, 0x2e, + 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x72, 0x65, 0x65, 0x52, 0x08, 0x63, 0x68, 0x69, + 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x2a, 0x83, 0x01, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x13, 0x0a, + 0x0f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x49, 0x4f, 0x4e, + 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x45, 0x58, 0x43, 0x4c, 0x55, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x4e, + 0x4f, 0x44, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x53, 0x45, + 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x4e, 0x4f, 0x44, 0x45, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4c, 0x45, 0x41, 0x46, 0x10, 0x04, 0x32, 0x7e, 0x0a, 0x0d, 0x45, + 0x78, 0x70, 0x61, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6d, 0x0a, 0x06, + 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x12, 0x30, 0x2e, 0x6f, 0x72, 0x79, 0x2e, 0x6b, 0x65, 0x74, + 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x75, 0x70, 0x6c, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x32, 0x2e, 0x45, 0x78, 0x70, 0x61, 0x6e, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x6f, 0x72, 0x79, 0x2e, 0x6b, + 0x65, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x75, 0x70, + 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x32, 0x2e, 0x45, 0x78, 0x70, + 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xc3, 0x01, 0x0a, 0x24, + 0x73, 0x68, 0x2e, 0x6f, 0x72, 0x79, 0x2e, 0x6b, 0x65, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x32, 0x42, 0x12, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3f, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x72, 0x79, 0x2f, 0x6b, 0x65, 0x74, 0x6f, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6f, 0x72, 0x79, 0x2f, 0x6b, 0x65, 0x74, 0x6f, 0x2f, 0x72, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x32, 0x3b, 0x72, 0x74, 0x73, 0xaa, 0x02, 0x20, 0x4f, 0x72, + 0x79, 0x2e, 0x4b, 0x65, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, + 0x75, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x32, 0xca, 0x02, + 0x20, 0x4f, 0x72, 0x79, 0x5c, 0x4b, 0x65, 0x74, 0x6f, 0x5c, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x5c, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -382,20 +400,22 @@ var file_ory_keto_relation_tuples_v1alpha2_expand_service_proto_goTypes = []inte (*ExpandResponse)(nil), // 2: ory.keto.relation_tuples.v1alpha2.ExpandResponse (*SubjectTree)(nil), // 3: ory.keto.relation_tuples.v1alpha2.SubjectTree (*Subject)(nil), // 4: ory.keto.relation_tuples.v1alpha2.Subject + (*RelationTuple)(nil), // 5: ory.keto.relation_tuples.v1alpha2.RelationTuple } var file_ory_keto_relation_tuples_v1alpha2_expand_service_proto_depIdxs = []int32{ 4, // 0: ory.keto.relation_tuples.v1alpha2.ExpandRequest.subject:type_name -> ory.keto.relation_tuples.v1alpha2.Subject 3, // 1: ory.keto.relation_tuples.v1alpha2.ExpandResponse.tree:type_name -> ory.keto.relation_tuples.v1alpha2.SubjectTree 0, // 2: ory.keto.relation_tuples.v1alpha2.SubjectTree.node_type:type_name -> ory.keto.relation_tuples.v1alpha2.NodeType 4, // 3: ory.keto.relation_tuples.v1alpha2.SubjectTree.subject:type_name -> ory.keto.relation_tuples.v1alpha2.Subject - 3, // 4: ory.keto.relation_tuples.v1alpha2.SubjectTree.children:type_name -> ory.keto.relation_tuples.v1alpha2.SubjectTree - 1, // 5: ory.keto.relation_tuples.v1alpha2.ExpandService.Expand:input_type -> ory.keto.relation_tuples.v1alpha2.ExpandRequest - 2, // 6: ory.keto.relation_tuples.v1alpha2.ExpandService.Expand:output_type -> ory.keto.relation_tuples.v1alpha2.ExpandResponse - 6, // [6:7] is the sub-list for method output_type - 5, // [5:6] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name + 5, // 4: ory.keto.relation_tuples.v1alpha2.SubjectTree.tuple:type_name -> ory.keto.relation_tuples.v1alpha2.RelationTuple + 3, // 5: ory.keto.relation_tuples.v1alpha2.SubjectTree.children:type_name -> ory.keto.relation_tuples.v1alpha2.SubjectTree + 1, // 6: ory.keto.relation_tuples.v1alpha2.ExpandService.Expand:input_type -> ory.keto.relation_tuples.v1alpha2.ExpandRequest + 2, // 7: ory.keto.relation_tuples.v1alpha2.ExpandService.Expand:output_type -> ory.keto.relation_tuples.v1alpha2.ExpandResponse + 7, // [7:8] is the sub-list for method output_type + 6, // [6:7] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_ory_keto_relation_tuples_v1alpha2_expand_service_proto_init() } diff --git a/proto/ory/keto/relation_tuples/v1alpha2/expand_service.proto b/proto/ory/keto/relation_tuples/v1alpha2/expand_service.proto index d5380f5a8..e0c776bf5 100644 --- a/proto/ory/keto/relation_tuples/v1alpha2/expand_service.proto +++ b/proto/ory/keto/relation_tuples/v1alpha2/expand_service.proto @@ -77,8 +77,14 @@ enum NodeType { message SubjectTree { // The type of the node. NodeType node_type = 1; + // The subject this node represents. - Subject subject = 2; + // Deprecated: More information is now available in the tuple field. + Subject subject = 2 [deprecated = true]; + + // The relation tuple this node represents. + RelationTuple tuple = 4; + // The children of this node. // // This is never set if `node_type` == `NODE_TYPE_LEAF`. diff --git a/proto/ory/keto/relation_tuples/v1alpha2/expand_service_pb.d.ts b/proto/ory/keto/relation_tuples/v1alpha2/expand_service_pb.d.ts index bd4b2b3e8..bbc463a9d 100644 --- a/proto/ory/keto/relation_tuples/v1alpha2/expand_service_pb.d.ts +++ b/proto/ory/keto/relation_tuples/v1alpha2/expand_service_pb.d.ts @@ -67,6 +67,11 @@ export class SubjectTree extends jspb.Message { clearSubject(): void; getSubject(): ory_keto_relation_tuples_v1alpha2_relation_tuples_pb.Subject | undefined; setSubject(value?: ory_keto_relation_tuples_v1alpha2_relation_tuples_pb.Subject): SubjectTree; + + hasTuple(): boolean; + clearTuple(): void; + getTuple(): ory_keto_relation_tuples_v1alpha2_relation_tuples_pb.RelationTuple | undefined; + setTuple(value?: ory_keto_relation_tuples_v1alpha2_relation_tuples_pb.RelationTuple): SubjectTree; clearChildrenList(): void; getChildrenList(): Array; setChildrenList(value: Array): SubjectTree; @@ -86,6 +91,7 @@ export namespace SubjectTree { export type AsObject = { nodeType: NodeType, subject?: ory_keto_relation_tuples_v1alpha2_relation_tuples_pb.Subject.AsObject, + tuple?: ory_keto_relation_tuples_v1alpha2_relation_tuples_pb.RelationTuple.AsObject, childrenList: Array, } } diff --git a/proto/ory/keto/relation_tuples/v1alpha2/expand_service_pb.js b/proto/ory/keto/relation_tuples/v1alpha2/expand_service_pb.js index a2f8a316f..0f87759fb 100644 --- a/proto/ory/keto/relation_tuples/v1alpha2/expand_service_pb.js +++ b/proto/ory/keto/relation_tuples/v1alpha2/expand_service_pb.js @@ -493,6 +493,7 @@ proto.ory.keto.relation_tuples.v1alpha2.SubjectTree.toObject = function(includeI var f, obj = { nodeType: jspb.Message.getFieldWithDefault(msg, 1, 0), subject: (f = msg.getSubject()) && ory_keto_relation_tuples_v1alpha2_relation_tuples_pb.Subject.toObject(includeInstance, f), + tuple: (f = msg.getTuple()) && ory_keto_relation_tuples_v1alpha2_relation_tuples_pb.RelationTuple.toObject(includeInstance, f), childrenList: jspb.Message.toObjectList(msg.getChildrenList(), proto.ory.keto.relation_tuples.v1alpha2.SubjectTree.toObject, includeInstance) }; @@ -540,6 +541,11 @@ proto.ory.keto.relation_tuples.v1alpha2.SubjectTree.deserializeBinaryFromReader reader.readMessage(value,ory_keto_relation_tuples_v1alpha2_relation_tuples_pb.Subject.deserializeBinaryFromReader); msg.setSubject(value); break; + case 4: + var value = new ory_keto_relation_tuples_v1alpha2_relation_tuples_pb.RelationTuple; + reader.readMessage(value,ory_keto_relation_tuples_v1alpha2_relation_tuples_pb.RelationTuple.deserializeBinaryFromReader); + msg.setTuple(value); + break; case 3: var value = new proto.ory.keto.relation_tuples.v1alpha2.SubjectTree; reader.readMessage(value,proto.ory.keto.relation_tuples.v1alpha2.SubjectTree.deserializeBinaryFromReader); @@ -589,6 +595,14 @@ proto.ory.keto.relation_tuples.v1alpha2.SubjectTree.serializeBinaryToWriter = fu ory_keto_relation_tuples_v1alpha2_relation_tuples_pb.Subject.serializeBinaryToWriter ); } + f = message.getTuple(); + if (f != null) { + writer.writeMessage( + 4, + f, + ory_keto_relation_tuples_v1alpha2_relation_tuples_pb.RelationTuple.serializeBinaryToWriter + ); + } f = message.getChildrenList(); if (f.length > 0) { writer.writeRepeatedMessage( @@ -655,6 +669,43 @@ proto.ory.keto.relation_tuples.v1alpha2.SubjectTree.prototype.hasSubject = funct }; +/** + * optional RelationTuple tuple = 4; + * @return {?proto.ory.keto.relation_tuples.v1alpha2.RelationTuple} + */ +proto.ory.keto.relation_tuples.v1alpha2.SubjectTree.prototype.getTuple = function() { + return /** @type{?proto.ory.keto.relation_tuples.v1alpha2.RelationTuple} */ ( + jspb.Message.getWrapperField(this, ory_keto_relation_tuples_v1alpha2_relation_tuples_pb.RelationTuple, 4)); +}; + + +/** + * @param {?proto.ory.keto.relation_tuples.v1alpha2.RelationTuple|undefined} value + * @return {!proto.ory.keto.relation_tuples.v1alpha2.SubjectTree} returns this +*/ +proto.ory.keto.relation_tuples.v1alpha2.SubjectTree.prototype.setTuple = function(value) { + return jspb.Message.setWrapperField(this, 4, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.ory.keto.relation_tuples.v1alpha2.SubjectTree} returns this + */ +proto.ory.keto.relation_tuples.v1alpha2.SubjectTree.prototype.clearTuple = function() { + return this.setTuple(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.ory.keto.relation_tuples.v1alpha2.SubjectTree.prototype.hasTuple = function() { + return jspb.Message.getField(this, 4) != null; +}; + + /** * repeated SubjectTree children = 3; * @return {!Array}