diff --git a/.gitignore b/.gitignore index 401d60bc7..96b8ad83a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ node_modules/ .idea/ coverage.txt vendor/ -*-packr.go +pkged.go diff --git a/.schema/config.schema.json b/.schema/config.schema.json index 056e67d52..71be609ae 100644 --- a/.schema/config.schema.json +++ b/.schema/config.schema.json @@ -7,6 +7,11 @@ "namespace": { "type": "object", "properties": { + "$schema": { + "type": "string", + "format": "uri-reference", + "description": "Add this to allow defining the schema, useful for IDE integration" + }, "name": { "type": "string", "title": "The name of the namespace.", diff --git a/.schema/namespace.schema.json b/.schema/namespace.schema.json new file mode 100644 index 000000000..854121e8c --- /dev/null +++ b/.schema/namespace.schema.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "./config.schema.json#/definitions/namespace", + "description": "This schema is just to work around the limitation of some IDEs to support json schema paths." +} diff --git a/.schema/relation_tuple.schema.json b/.schema/relation_tuple.schema.json new file mode 100644 index 000000000..203b4108d --- /dev/null +++ b/.schema/relation_tuple.schema.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "required": ["namespace", "relation", "object", "subject"], + "properties": { + "$schema": { + "type": "string", + "format": "uri-reference", + "description": "Add this to allow defining the schema, useful for IDE integration" + }, + "namespace": { + "type": "string", + "description": "The namespace of the object and relation in this tuple." + }, + "relation": { + "type": "string", + "description": "The relation of the object and subject." + }, + "object": { + "type": "string", + "description": "The object affected by this relation." + }, + "subject": { + "oneOf": [ + { + "type": "string", + "pattern": "^.*:.*#.*$", + "description": "The subject set affected by this relation. Uses the encoding of \":#\"." + }, + { + "type": "string", + "description": "The subject affected by this relation. Use \":#\" to describe a subject set.", + "not": { + "pattern": "^.*:.*#.*$" + } + } + ] + } + } +} diff --git a/Dockerfile b/Dockerfile index 42c4e3bae..d84c3c8c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,45 @@ -# To compile this image manually run: -# -# $ packr; GO111MODULE=on GOOS=linux GOARCH=amd64 go build; docker build -t oryd/keto:latest .; rm keto; packr clean -FROM alpine:3.9 +FROM golang:1.15-alpine AS builder + +RUN apk -U --no-cache add build-base git gcc bash + +WORKDIR /go/src/github.com/ory/keto + +ADD go.mod go.mod +ADD go.sum go.sum + +ENV GO111MODULE on +ENV CGO_ENABLED 1 + +RUN go mod download + +RUN go build -o /usr/bin/pkger github.com/markbates/pkger/cmd/pkger + +ADD . . + +RUN /usr/bin/pkger && go build -tags sqlite -o /usr/bin/keto + +FROM alpine:3.12 + +RUN addgroup -S ory; \ + adduser -S ory -G ory -D -h /home/ory -s /bin/nologin; \ + chown -R ory:ory /home/ory RUN apk add -U --no-cache ca-certificates -FROM scratch +COPY --from=builder /usr/bin/keto /usr/bin/keto + +# By creating the sqlite folder as the ory user, the mounted volume will be owned by ory:ory, which +# is required for read/write of SQLite. +RUN mkdir -p /var/lib/sqlite +RUN chown ory:ory /var/lib/sqlite +VOLUME /var/lib/sqlite + +# Exposing the ory home directory to simplify passing in the configuration. +VOLUME /home/ory -COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ -COPY keto /usr/bin/keto +EXPOSE 4466 4467 -USER 1000 +USER ory ENTRYPOINT ["keto"] diff --git a/Makefile b/Makefile index a04d2a0d7..85fb79905 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ ifneq ("$(shell base64 Makefile) $(shell base64 go.mod) $(shell base64 go.sum)", go build -o .bin/swagger github.com/go-swagger/go-swagger/cmd/swagger go build -o .bin/goimports golang.org/x/tools/cmd/goimports go build -o .bin/ory github.com/ory/cli - go build -o .bin/packr github.com/gobuffalo/packr/packr + go build -o .bin/pkger github.com/markbates/pkger/cmd/pkger go build -o .bin/go-bindata github.com/go-bindata/go-bindata/go-bindata go build -o .bin/buf github.com/bufbuild/buf/cmd/buf go build -o .bin/protoc-gen-go google.golang.org/protobuf/cmd/protoc-gen-go @@ -33,18 +33,18 @@ format: install-stable: deps KETO_LATEST=$$(git describe --abbrev=0 --tags) git checkout $$KETO_LATEST - packr + pkger GO111MODULE=on go install \ -ldflags "-X github.com/ory/keto/cmd.Version=$$KETO_LATEST -X github.com/ory/keto/cmd.Date=`TZ=UTC date -u '+%Y-%m-%dT%H:%M:%SZ'` -X github.com/ory/keto/cmd.Commit=`git rev-parse HEAD`" \ . - packr clean + rm pkged.go git checkout master .PHONY: install install: deps - packr + pkger GO111MODULE=on go install . - packr clean + rm pkged.go # Generates the SDKs .PHONY: sdk @@ -58,13 +58,11 @@ sdk: deps swagger generate client -f ./.schema/api.swagger.json -t internal/httpclient -A Ory_Keto make format -.PHONY: docker -docker: deps - packr - GO111MODULE=on GOOS=linux GOARCH=amd64 go build - docker build -t oryd/keto:latest . - rm keto - packr clean +.PHONY: build +build: deps + pkger + go build -tags sqlite + rm pkged.go # # Generate APIs and client stubs from the definitions diff --git a/README.md b/README.md index f6a342134..0d7801e78 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

ORY Keto - Open Source & Cloud Native Access Control Server

+

ORY Keto - Open Source Cloud Native Access Control Server

Chat | @@ -10,23 +10,21 @@ Support this project!

-# This is the next step for ORY Keto :tada: +# Ory Keto keeps evolving; the next step is a highly distributed authorisation and access control system:tada: -Be part of our journey to build the next-gen Keto based on -[Google's Zanzibar paper](https://research.google/pubs/pub48190/). +Creating fluid access to cloud applicaitons demands an efficient access control and authorisation system. In this part of the Ory Open Source ecosystem we tackle an implementation of +[Google's Zanzibar paper](https://research.google/pubs/pub48190/). If you have used Google Mail, Maps, and Youtube in one session, you have had some experience with Zanzibar. -The following is just a high level view on the paper, trying to grasp all the concepts. +The following is a high level view on the paper, where we interpret the main concepts and explain them for our implementation purposes. ## ACL Language -The ACL is represented by `object#relation@user`, while `user` can be a single user, -or a set of users represented by `object#relation` (e.g. users with editing rights on some object). +The Access Control Language (ACL) is represented by `object#relation@user`, while `user` can be a single user, +or a set of users represented by `object#relation` e.g. users with editing rights on some object. ## Managing content update, important for update ordering -1. > A Zanzibar client (e.g. Google Docs) requests an opaque consistency token called zookie for each content version via a `content change` ACL check. - - The client has to store the token together with the content change. +1. A Zanzibar client e.g. Google Docs requests an opaque consistency token called "zookie" for each content version via a `content change` ACL check. The client has to store the token together with the content change. 2. "The client sends this zookie in subsequent ACL check requests to ensure that the check snapshot is at least as fresh as the timestamp for the content version." @@ -40,9 +38,9 @@ a client content version, or a read snapshot. ### Namespaces Zanzibar clients have to configure their namespace: -1. configure relations +1. Configure relations - optimization via defining relations on relations within the relation definition -2. storage parameters (type of object IDs, sharding) +2. Storage parameters for instance, type of object IDs, or sharding. ## API @@ -68,21 +66,21 @@ response. Watching can be resumed with the response zookie and will not miss any ### Check -1. view check +1. View check The request contains a userset (`object#relation`), authentication token and a zookie corresponding to the object version. -2. content change check +2. Content change check No zookie in the request, the ACL has to be evaluated at the latest snapshot. Returns the new zookie for the object version. ### Expand -Like read but expands all indirect references. +SImilar to "read" and expands all indirect references. # Architecture ## Storage Relation tuples are stored in a database per namespace. Old versions are garbage collected to allow historic evaluation within a certain window. -There is also a global changelog used for the watcher API and optimizations. Changes are commited to both the namespace database and the changelog +There is also a global changelog used for the "Watcher API" and optimizations. Changes are commited to both the namespace database and the changelog in one transaction. Namespace configuration is stored in an extra database with two tables, one for the config and one for a changelog to allow hot reloading. diff --git a/cmd/check/root.go b/cmd/check/root.go index 3a0b61920..e09a32b4e 100644 --- a/cmd/check/root.go +++ b/cmd/check/root.go @@ -35,7 +35,7 @@ func newCheckCmd() *cobra.Command { return err } - _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%v", resp.Allowed) + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%v\n", resp.Allowed) return nil }, } diff --git a/cmd/expand/root.go b/cmd/expand/root.go index f401ea4d8..a9b9e8978 100644 --- a/cmd/expand/root.go +++ b/cmd/expand/root.go @@ -3,6 +3,8 @@ package expand import ( "fmt" + "github.com/ory/x/flagx" + "github.com/ory/x/cmdx" "github.com/spf13/cobra" @@ -48,6 +50,10 @@ func NewExpandCmd() *cobra.Command { } cmdx.PrintJSONAble(cmd, expand.TreeFromProto(resp.Tree)) + switch flagx.MustGetString(cmd, cmdx.FlagFormat) { + case string(cmdx.FormatDefault), "": + _, _ = fmt.Fprintln(cmd.OutOrStdout()) + } return nil }, } diff --git a/cmd/relationtuple/create.go b/cmd/relationtuple/create.go index 20bc002fb..4b40669d4 100644 --- a/cmd/relationtuple/create.go +++ b/cmd/relationtuple/create.go @@ -4,7 +4,9 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "os" + "path/filepath" acl "github.com/ory/keto/api/keto/acl/v1alpha1" @@ -19,48 +21,41 @@ import ( func newCreateCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "create ", - Args: cobra.ExactArgs(1), + Use: "create []", + Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { conn, err := client.GetWriteConn(cmd) if err != nil { return err } - var f io.Reader - if args[0] == "-" { - f = cmd.InOrStdin() - } else { - f, err = os.Open(args[0]) + var tuples []*relationtuple.InternalRelationTuple + var deltas []*acl.RelationTupleDelta + for _, fn := range args { + tuple, err := readTuplesFromArg(cmd, fn) if err != nil { - _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could open %s: %s\n", args[0], err) - return cmdx.FailSilently(cmd) + return err + } + for _, t := range tuple { + tuples = append(tuples, t) + deltas = append(deltas, &acl.RelationTupleDelta{ + Action: acl.RelationTupleDelta_INSERT, + RelationTuple: t.ToProto(), + }) } - } - - var r relationtuple.InternalRelationTuple - err = json.NewDecoder(f).Decode(&r) - if err != nil { - _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not decode: %s\n", err) - return cmdx.FailSilently(cmd) } cl := acl.NewWriteServiceClient(conn) _, err = cl.TransactRelationTuples(cmd.Context(), &acl.TransactRelationTuplesRequest{ - RelationTupleDeltas: []*acl.RelationTupleDelta{ - { - Action: acl.RelationTupleDelta_INSERT, - RelationTuple: r.ToProto(), - }, - }, + RelationTupleDeltas: deltas, }) if err != nil { _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Error doing the request: %s\n", err) return cmdx.FailSilently(cmd) } - cmdx.PrintRow(cmd, &r) + cmdx.PrintTable(cmd, relationtuple.NewRelationCollection(tuples)) return nil }, } @@ -68,3 +63,48 @@ func newCreateCmd() *cobra.Command { return cmd } + +func readTuplesFromArg(cmd *cobra.Command, arg string) ([]*relationtuple.InternalRelationTuple, error) { + var f io.Reader + if arg == "-" { + f = cmd.InOrStdin() + } else { + stats, err := os.Stat(arg) + if err != nil { + _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Error getting stats for %s: %s\n", arg, err) + return nil, cmdx.FailSilently(cmd) + } + + if stats.IsDir() { + fi, err := ioutil.ReadDir(arg) + if err != nil { + return nil, err + } + + var tuples []*relationtuple.InternalRelationTuple + for _, child := range fi { + t, err := readTuplesFromArg(cmd, filepath.Join(arg, child.Name())) + if err != nil { + return nil, err + } + tuples = append(tuples, t...) + } + return tuples, nil + } + + f, err = os.Open(arg) + if err != nil { + _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Error processing arg %s: %s\n", arg, err) + return nil, cmdx.FailSilently(cmd) + } + } + + var r relationtuple.InternalRelationTuple + err := json.NewDecoder(f).Decode(&r) + if err != nil { + _, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not decode: %s\n", err) + return nil, cmdx.FailSilently(cmd) + } + + return []*relationtuple.InternalRelationTuple{&r}, nil +} diff --git a/contrib/cat-videos-example/docker-compose.yml b/contrib/cat-videos-example/docker-compose.yml new file mode 100644 index 000000000..d9c3b167b --- /dev/null +++ b/contrib/cat-videos-example/docker-compose.yml @@ -0,0 +1,27 @@ +version: '3' + +services: + keto: + image: oryd/keto:latest + ports: + - "4466:4466" + - "4467:4467" + command: + serve -c /home/ory/keto.yml + restart: on-failure + volumes: + - type: bind + source: . + target: /home/ory + + keto-init: + image: oryd/keto:latest + environment: + - KETO_GRPC_URL=keto:4467 + volumes: + - type: bind + source: . + target: /home/ory + command: + relation-tuple create /home/ory/relation-tuples + restart: on-failure diff --git a/contrib/cat-videos-example/keto.yml b/contrib/cat-videos-example/keto.yml new file mode 100644 index 000000000..acd2c8131 --- /dev/null +++ b/contrib/cat-videos-example/keto.yml @@ -0,0 +1,18 @@ +version: v1.0.0-alpha + +log: + level: debug + +namespaces: + - id: 0 + name: videos + +dsn: memory + +serve: + rest: + host: 0.0.0.0 + port: 4466 + grpc: + host: 0.0.0.0 + port: 4467 diff --git a/contrib/cat-videos-example/relation-tuples/cats1_owner.json b/contrib/cat-videos-example/relation-tuples/cats1_owner.json new file mode 100644 index 000000000..042d3191a --- /dev/null +++ b/contrib/cat-videos-example/relation-tuples/cats1_owner.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../../.schema/relation_tuple.schema.json", + "namespace": "videos", + "object": "/cats/1.mp4", + "relation": "owner", + "subject": "videos:/cats#owner" +} diff --git a/contrib/cat-videos-example/relation-tuples/cats1_view_owner.json b/contrib/cat-videos-example/relation-tuples/cats1_view_owner.json new file mode 100644 index 000000000..eb8c604d3 --- /dev/null +++ b/contrib/cat-videos-example/relation-tuples/cats1_view_owner.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../../.schema/relation_tuple.schema.json", + "namespace": "videos", + "object": "/cats/1.mp4", + "relation": "view", + "subject": "videos:/cats/1.mp4#owner" +} diff --git a/contrib/cat-videos-example/relation-tuples/cats1_view_public.json b/contrib/cat-videos-example/relation-tuples/cats1_view_public.json new file mode 100644 index 000000000..008ab1ad3 --- /dev/null +++ b/contrib/cat-videos-example/relation-tuples/cats1_view_public.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../../.schema/relation_tuple.schema.json", + "namespace": "videos", + "object": "/cats/1.mp4", + "relation": "view", + "subject": "*" +} diff --git a/contrib/cat-videos-example/relation-tuples/cats2_owner.json b/contrib/cat-videos-example/relation-tuples/cats2_owner.json new file mode 100644 index 000000000..a3ad91ec6 --- /dev/null +++ b/contrib/cat-videos-example/relation-tuples/cats2_owner.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../../.schema/relation_tuple.schema.json", + "namespace": "videos", + "object": "/cats/2.mp4", + "relation": "owner", + "subject": "videos:/cats#owner" +} diff --git a/contrib/cat-videos-example/relation-tuples/cats2_view.json b/contrib/cat-videos-example/relation-tuples/cats2_view.json new file mode 100644 index 000000000..8531f904e --- /dev/null +++ b/contrib/cat-videos-example/relation-tuples/cats2_view.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../../.schema/relation_tuple.schema.json", + "namespace": "videos", + "object": "/cats/2.mp4", + "relation": "view", + "subject": "videos:/cats/2.mp4#owner" +} diff --git a/contrib/cat-videos-example/relation-tuples/cats_owner.json b/contrib/cat-videos-example/relation-tuples/cats_owner.json new file mode 100644 index 000000000..5859a71d5 --- /dev/null +++ b/contrib/cat-videos-example/relation-tuples/cats_owner.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../../.schema/relation_tuple.schema.json", + "namespace": "videos", + "object": "/cats", + "relation": "owner", + "subject": "cat lady" +} diff --git a/contrib/cat-videos-example/relation-tuples/cats_view.json b/contrib/cat-videos-example/relation-tuples/cats_view.json new file mode 100644 index 000000000..d07257e90 --- /dev/null +++ b/contrib/cat-videos-example/relation-tuples/cats_view.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../../.schema/relation_tuple.schema.json", + "namespace": "videos", + "object": "/cats", + "relation": "view", + "subject": "videos:/cats#owner" +} diff --git a/contrib/cat-videos-example/up.sh b/contrib/cat-videos-example/up.sh new file mode 100755 index 000000000..72d54a698 --- /dev/null +++ b/contrib/cat-videos-example/up.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -euo pipefail + +keto serve -c contrib/cat-videos-example/keto.yml & +keto_server_pid=$! + +function teardown() { + kill $keto_server_pid || true +} +trap teardown EXIT + +export KETO_GRPC_URL="127.0.0.1:4467" + +keto relation-tuple create contrib/cat-videos-example/relation-tuples + +echo " + +Created all relation tuples. Now you can use the Keto CLI client like so: + +export KETO_GRPC_URL=\"127.0.0.1:4467\" +keto relation-tuple get videos +keto check \"*\" view videos /cats/1.mp4 +keto expand view videos /cats/2.mp4 +" + +sleep 10d diff --git a/docker-compose.yml b/docker-compose.yml index 0515917f4..c81e29a20 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,8 +5,8 @@ services: image: oryd/keto:latest ports: - "4466:4466" - command: - serve + - "4467:4467" + command: serve environment: - LOG_LEVEL=debug - PORT=4466 diff --git a/docs/docs/examples/cat-videos.md b/docs/docs/examples/cat-videos.md new file mode 100644 index 000000000..667ca6191 --- /dev/null +++ b/docs/docs/examples/cat-videos.md @@ -0,0 +1,148 @@ +--- +id: cat-videos-example +title: Cat Videos Application Example +--- + +This example describes a video sharing service. The individual videos are +organized in directories. Every directory has an owner and every video has the +same owner as it's parent directory. The owner has elevated privileges about the +video files that are not modeled individually in Ory Keto. The only other +privilege modeled in this example is "view access." Every owner has view access +to their objects, and this privilege can be granted to other users as well. The +Ory Keto application interprets the special `*` user ID as any user, including +anonymous users. Note that Ory Keto does not interpret this subject any +differently from other subjects. It also does not know anything about directory +structures or induced ownership. + +## Starting the Example + +First, [install Keto](../install.md). + +Now you can start the example using either `docker-compose` or a bash script. +The bash script requires you to have the `keto` binary in your `$PATH` as +follows: + +```shell +$ sudo mv ./keto /usr/local/bin/ +$ keto help +``` + +Alternatively, using Docker automatically gets the required images: + +```shell +# clone the repository if you don't have it yet +git clone git@github.com:ory/keto.git && cd keto + +docker-compose -f contrib/cat-videos-example/docker-compose.yml up +# or +./contrib/cat-videos-example/up.sh + +# output: all initially created relation tuples + +# NAMESPACE OBJECT RELATION NAME SUBJECT +# videos /cats/1.mp4 owner videos:/cats#owner +# videos /cats/1.mp4 view videos:/cats/1.mp4#owner +# videos /cats/1.mp4 view * +# videos /cats/2.mp4 owner videos:/cats#owner +# videos /cats/2.mp4 view videos:/cats/2.mp4#owner +# videos /cats owner cat lady +# videos /cats view videos:/cats#owner +``` + +## State of the System + +At the current state only one user with the username `cat lady` has added +videos. Both videos are in the `/cats` directory owned by `cat lady`. The file +`/cats/1.mp4` can be viewed by anyone (`*`), while `/cats/2.mp4` has no extra +sharing options, and can therefore only be viewed by its owner, `cat lady`. The +relation tuple definitions are located in the +`contrib/cat-videos-example/relation-tuples` directory. + +## Simulating the Client + +Now you can open a second terminal to run the queries against, just like the +video service client would do. In this example we will use the Keto CLI client. + +:::info + +If you want to run the Keto CLI within **Docker**, set the alias + +```shell +alias keto="docker run -it --network cat-videos-example_default -e KETO_GRPC_URL=\"keto:4467\" oryd/keto:latest" +``` + +in your terminal session. + +::: + +Set the remote endpoint so that the Keto CLI knows where to connect to (not +necessary if using Docker): + +```shell +export KETO_GRPC_URL="127.0.0.1:4467" +``` + +First off, we get a request by an anonymous user that would like to view +`/cats/2.mp4`. The client now has to ask Keto if that operation should be +allowed or denied. + +```shell +# Is "*" allowed to "view" the object "videos":"/cats/2.mp4"? +keto check "*" view videos /cats/2.mp4 +# output: + +# false +``` + +We already discussed that this request should not be allowed, but it is always +good to see this in action. + +Now `cat lady` wants to change some view permissions of `/cats/1.mp4`. For this, +the video service application has to show all users that are currently allowed +to view the video. It uses Keto's [Expand API](/TODO) to get these data: + +```shell +# Who is allowed to "view" the object "videos":"/cats/2.mp4"? +keto expand view videos /cats/1.mp4 +# output: + +# ∪ videos:/cats/1.mp4#view +# ├─ ∪ videos:/cats/1.mp4#owner +# │ ├─ ∪ videos:/cats#owner +# │ │ ├─ ☘ cat lady️ +# ├─ ☘ *️ +``` + +Here we can see the full user set expansion. The first branch + +`videos:/cats/1.mp4#view` + +indicates that every owner of the object is allowed to view + +`videos:/cats/1.mp4#owner` + +In the next step we see that the object's owners are the owners of `/cats` + +`videos:/cats#owner` + +Finally, we see that `cat lady` is the owner of `/cats`. + +Note that there is no direct relation tuple that would grant `cat lady` view +access on `/cats/1.mp4` as this is indirectly defined via the ownership +relation. + +The special user `*` on the other hand was directly granted view access on the +object, as it is a first-level leaf of the expansion tree. The following CLI +command proves that this is the case: + +```shell +# Is "*" allowed to "view" the object "videos":"/cats/1.mp4"? +keto check "*" view videos /cats/1.mp4 +# output: + +# true +``` + + + +Updating the view permissions will be added here at a later stage. diff --git a/docs/package-lock.json b/docs/package-lock.json index 71506aaaf..d8d73633c 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "docusaurus-template", "version": "0.0.0", "dependencies": { "@docusaurus/core": "2.0.0-alpha.415a7973f", diff --git a/docs/sidebar.json b/docs/sidebar.json index 275a02a5c..99e1caec7 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -14,6 +14,9 @@ "engines/acp-ory", "engines/acp-aws" ], + "Examples": [ + "examples/cat-videos-example" + ], "Reference": [ "reference/configuration", "reference/api" diff --git a/go.mod b/go.mod index f25e0baf6..902aaf47f 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/ory/graceful v0.1.1 github.com/ory/herodot v0.9.1 github.com/ory/jsonschema/v3 v3.0.1 - github.com/ory/x v0.0.171 + github.com/ory/x v0.0.175 github.com/pelletier/go-toml v1.8.0 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 github.com/pkg/errors v0.9.1 diff --git a/internal/e2e/grpc_client_test.go b/internal/e2e/grpc_client_test.go index c17af9877..ff63238fd 100644 --- a/internal/e2e/grpc_client_test.go +++ b/internal/e2e/grpc_client_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "strconv" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -56,7 +57,7 @@ func (g *grpcClient) queryTuple(t *testing.T, q *relationtuple.RelationQuery) [] func (g *grpcClient) check(t *testing.T, r *relationtuple.InternalRelationTuple) bool { out := g.c.ExecNoErr(t, "check", r.Subject.String(), r.Relation, r.Namespace, r.Object) - res, err := strconv.ParseBool(out) + res, err := strconv.ParseBool(strings.TrimSpace(out)) require.NoError(t, err) return res } diff --git a/internal/relationtuple/definitions.go b/internal/relationtuple/definitions.go index fa1c6cb4c..e82f8a816 100644 --- a/internal/relationtuple/definitions.go +++ b/internal/relationtuple/definitions.go @@ -382,7 +382,7 @@ func (r *relationCollection) Table() [][]string { for i, rel := range r.internalRelations { data[i] = []string{rel.Namespace, rel.Object, rel.Relation, cmdx.None} if rel.Subject != nil { - data[i][1] = rel.Subject.String() + data[i][3] = rel.Subject.String() } }