diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d1ef756..fcdc5c5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,64 +1,69 @@ +--- name: "build" on: push: branches: - - "master" + - "master" pull_request: branches: - - "!dependabot/*" - - "*" + - "!dependabot/*" + - "*" jobs: - lint: + lint: name: "Lint" runs-on: "ubuntu-latest" steps: - - uses: "actions/setup-go@v2" - with: - go-version: ^1.16 - - name: "Install linting tools" - run: | + - uses: "actions/setup-go@v2" + with: + go-version: "^1.16" + - name: "Install linting tools" # This is done before checking out, as to not modify go.mod - go install mvdan.cc/gofumpt/gofumports@latest - go install github.com/mgechev/revive@latest - go install golang.org/x/tools/cmd/stringer@latest - - uses: "actions/checkout@v2" - - name: "Go Mod Tidy" - run: "go mod tidy && bash -c '[ $(git status --porcelain | tee /dev/fd/2 | wc -c) -eq 0 ]'" - - name: "Formatting (gofumpt)" - run: | - GOFUMPT_OUTPUT="$(find . -iname '*.go' -type f | grep -v pb.go | grep -v 'pb.*.go' | xargs gofumports -d)" - if [ -n "$GOFUMPT_OUTPUT" ]; then - echo "All the following files are not correctly formatted" - echo "${GOFUMPT_OUTPUT}" - exit 1 - fi - - name: "Linting (revive)" - run: "bash -c '[ $(find . -iname '*.go' -type f | grep -v 'pb.*.go' | xargs revive | tee /dev/fd/2 | wc -c) -eq 0 ]'" + run: "go install mvdan.cc/gofumpt/gofumports@v0.1.1" + - uses: "actions/checkout@v2" + - uses: "bewuethr/yamllint-action@v1.1.1" + with: + config-file: ".yamllint" + - name: "Go Mod Tidy" + run: "go mod tidy && bash -c '[ $(git status --porcelain | tee /dev/fd/2 | wc -c) -eq 0 ]'" + - name: "Formatting (gofumpt)" + run: | + GOFUMPT_OUTPUT="$(find . -iname '*.go' -type f | grep -v pb.go | grep -v 'pb.*.go' | xargs gofumports -d)" + if [ -n "$GOFUMPT_OUTPUT" ]; then + echo "All the following files are not correctly formatted" + echo "${GOFUMPT_OUTPUT}" + exit 1 + fi + - uses: "golangci/golangci-lint-action@v2" + with: + version: "v1.43" + skip-go-installation: true + skip-pkg-cache: true + skip-build-cache: false test: name: "Test" runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v2" - - uses: "actions/setup-go@v2" - with: - go-version: "^1.16" - - uses: "authzed/action-testserver@v3" - - name: "go test" - run: "go test -tags integration ./..." + - uses: "actions/checkout@v2" + - uses: "actions/setup-go@v2" + with: + go-version: "^1.16" + - uses: "authzed/action-testserver@v3" + - name: "go test" + run: "go test -tags integration ./..." protobuf: name: "Generate Protobufs" runs-on: "ubuntu-latest" steps: - - uses: "actions/setup-go@v2" - with: - go-version: "^1.16" - - name: "Install local Go Protobuf plugins" - run: "go install github.com/envoyproxy/protoc-gen-validate@v0.6.1" - - uses: "actions/checkout@v2" - - uses: "bufbuild/buf-setup-action@v0.6.0" - with: - version: "1.0.0-rc6" - - name: "Generate & Diff Protos" - run: "./buf.gen.yaml && git diff && bash -c '[ $(git status --porcelain | tee /dev/fd/2 | wc -c) -eq 0 ]'" + - uses: "actions/setup-go@v2" + with: + go-version: "^1.16" + - name: "Install local Go Protobuf plugins" + run: "go install github.com/envoyproxy/protoc-gen-validate@v0.6.1" + - uses: "actions/checkout@v2" + - uses: "bufbuild/buf-setup-action@v0.6.0" + with: + version: "1.0.0-rc8" + - name: "Generate & Diff Protos" + run: "./buf.gen.yaml && git diff && bash -c '[ $(git status --porcelain | tee /dev/fd/2 | wc -c) -eq 0 ]'" diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..5960fe0 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,26 @@ +--- +run: + timeout: "5m" + skip-dirs: + - "proto" +output: + sort-results: true +linters-settings: + goimports: + local-prefixes: "github.com/authzed/authzed-go" +linters: + enable: + - "deadcode" + - "errcheck" + - "gofumpt" + - "goimports" + - "gosimple" + - "govet" + - "ineffassign" + - "revive" + - "rowserrcheck" + - "staticcheck" + - "structcheck" + - "typecheck" + - "unused" + - "varcheck" diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..9c1e7e3 --- /dev/null +++ b/.yamllint @@ -0,0 +1,10 @@ +# vim: ft=yaml +--- +yaml-files: + - "*.yaml" + - "*.yml" + - ".yamllint" +extends: "default" +rules: + quoted-strings: "enable" + line-length: "disable" diff --git a/README.md b/README.md index 25d9c6a..c6f6144 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,40 @@ # Authzed Go Client [![GoDoc](https://godoc.org/github.com/authzed/authzed-go?status.svg)](https://godoc.org/github.com/authzed/authzed-go) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) +[![Docs](https://img.shields.io/badge/docs-authzed.com-%234B4B6C "Authzed Documentation")](https://docs.authzed.com) [![Build Status](https://github.com/authzed/authzed-go/workflows/build/badge.svg)](https://github.com/authzed/authzed-go/actions) -[![Mailing List](https://img.shields.io/badge/email-google%20groups-4285F4)](https://groups.google.com/g/authzed-oss) [![Discord Server](https://img.shields.io/discord/844600078504951838?color=7289da&logo=discord "Discord Server")](https://discord.gg/jTysUaxXzM) [![Twitter](https://img.shields.io/twitter/follow/authzed?color=%23179CF0&logo=twitter&style=flat-square)](https://twitter.com/authzed) -This repository houses the Go client library for Authzed. +This repository houses the official Go client library for Authzed and SpiceDB. -[Authzed] is a database and service that stores, computes, and validates your application's permissions. +[SpiceDB] is a database system for managing security-critical permissions checking. -Developers create a schema that models their permissions requirements and use a client library, such as this one, to apply the schema to the database, insert data into the database, and query the data to efficiently check permissions in their applications. +SpiceDB acts as a centralized service that stores authorization data. +Once stored, data can be performantly queried to answer questions such as "Does this user have access to this resource?" and "What are all the resources this user has access to?". + +[Authzed] operates the globally available, serverless database platform for SpiceDB. Supported client API versions: -- [v1alpha1](https://docs.authzed.com/reference/api#authzedapiv1alpha1) -- [v0](https://docs.authzed.com/reference/api#authzedapiv0) +- [v1](https://buf.build/authzed/api/docs/main/authzed.api.v1) +- [v1alpha1](https://buf.build/authzed/api/docs/main/authzed.api.v1alpha1) +- [v0](https://buf.build/authzed/api/docs/main/authzed.api.v0) -You can find more info on each API on the [Authzed API reference documentation]. -Additionally, Protobuf API documentation can be found on the [Buf Registry Authzed API repository]. +You can find more info about the API in the [Authzed Documentation API Reference] or the [Authzed API Buf Registry repository]. See [CONTRIBUTING.md] for instructions on how to contribute and perform common tasks like building the project and running tests. +[SpiceDB]: https://github.com/authzed/spicedb [Authzed]: https://authzed.com -[Authzed API Reference documentation]: https://docs.authzed.com/reference/api -[Buf Registry Authzed API repository]: https://buf.build/authzed/api/docs/main +[Authzed Documentation API Reference]: https://docs.authzed.com/reference/api +[Authzed API Buf Registry repository]: https://buf.build/authzed/api [CONTRIBUTING.md]: CONTRIBUTING.md ## Getting Started We highly recommend following the **[Protecting Your First App]** guide to learn the latest best practice to integrate an application with Authzed. -If you're interested in examples for a specific version of the API, they can be found in their respective folders in the [examples directory]. - [Protecting Your First App]: https://docs.authzed.com/guides/first-app -[examples directory]: /examples ## Basic Usage @@ -68,7 +68,7 @@ In order to successfully connect, you will have to provide a [Bearer Token] with ```go import ( - "github.com/authzed/authzed-go/v0" + "github.com/authzed/authzed-go/v1" "github.com/authzed/grpcutil" ) @@ -92,28 +92,33 @@ Because of the verbosity of these types, we recommend writing your own functions ```go import ( - "github.com/authzed/authzed-go/proto/authzed/api/v0" - "github.com/authzed/authzed-go/v0" + "github.com/authzed/authzed-go/proto/authzed/api/v1" + "github.com/authzed/authzed-go/v1" "github.com/authzed/grpcutil" ) ... -emilia := &v0.User{UserOneof: &v0.User_Userset{Userset: &v0.ObjectAndRelation{ - Namespace: "user", +emilia := &pb.SubjectReference{Object: &v1.ObjectReference{ + ObjectType: "blog/user", ObjectId: "emilia", - Relation: "...", -}}} +}} -post1Reader := &v0.ObjectAndRelation{Namespace: "post", ObjectId: "1", Relation: "read"} +firstPost := &pb.ObjectReference{ + ObjectType: "blog/post", + ObjectId: "1", +} -// Is Emilia in the set of users that can read post #1? -resp, err := client.Check(ctx, &v0.CheckRequest{User: emilia, TestUserset: post1Reader}) +resp, err := client.CheckPermission(ctx, &pb.CheckPermissionRequest{ + Resource: firstPost, + Permission: "read", + Subject: emilia, +}) if err != nil { - log.Fatalf("failed to check permission: %s", err) + log.Fatalf("failed to check permission: %s", err) } -if resp.GetMembership() == v0.CheckResponse_MEMBER { +if resp.Permissionship == pb.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION { log.Println("allowed!") } ``` diff --git a/buf.gen.yaml b/buf.gen.yaml index 64c1009..df1c79d 100755 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -1,4 +1,5 @@ #!/usr/bin/env -S buf generate buf.build/authzed/api:262837a8e7451bfd9cac0518972ceaef1d0b9963 --template +--- version: "v1" plugins: - remote: "buf.build/library/plugins/go:v1.27.1-1" diff --git a/examples/v1/example.go b/examples/v1/example.go deleted file mode 100644 index 81ccc4c..0000000 --- a/examples/v1/example.go +++ /dev/null @@ -1,160 +0,0 @@ -package main - -import ( - "context" - "log" - - "github.com/authzed/grpcutil" - - v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" - "github.com/authzed/authzed-go/v1" -) - -const ( - documentNS = "yourtenant/document" - userNS = "yourtenant/user" - - schema = ` - definition yourtenant/user {} - - definition yourtenant/document { - relation viewer: yourtenant/user - relation contributor: yourtenant/user - relation owner: yourtenant/user - - permission read = viewer + contributor + owner - permission write = contributor + owner - permission delete = owner - } - ` -) - -func main() { - // Create an Authzed client. - client, err := authzed.NewClient( - "grpc.authzed.com:443", - grpcutil.WithInsecureBearerToken("t_your_token_here"), - grpcutil.WithSystemCerts(grpcutil.VerifyCA), - ) - if err != nil { - log.Fatalf("unable to initialize client: %s", err) - } - - // Uncomment this block to run against a local SpiceDB. - // client, err = authzed.NewClient( - // "localhost:50051", - // grpcutil.WithInsecureBearerToken("testtesttesttest"), - // grpc.WithInsecure(), - // ) - // if err != nil { - // log.Fatalf("unable to initialize client: %s", err) - // } - - // Write the schema to the permissions system - _, err = client.WriteSchema(context.Background(), &v1.WriteSchemaRequest{ - Schema: schema, - }) - if err != nil { - log.Fatalf("unable to write schema: %s", err) - } - - // Create some objects that will be protected by Authzed. - aDoc := object(documentNS, "doc1") - anOwner := subject(userNS, "theowner") - anEditor := subject(userNS, "userwhocanedit") - aViewer := subject(userNS, "viewonlyuser") - - // Create some relationships that represent roles granted between users and objects. - resp, err := client.WriteRelationships(context.Background(), &v1.WriteRelationshipsRequest{ - Updates: []*v1.RelationshipUpdate{ - createRelationship(relationship(aDoc, "owner", anOwner)), - createRelationship(relationship(aDoc, "contributor", anEditor)), - createRelationship(relationship(aDoc, "viewer", aViewer)), - }, - }) - if err != nil { - log.Fatalf("unable to write tuples: %s", err) - } - - // Save the ZedToken from the Write for future requests in order to enforce - // that responses are at least as fresh as our last write. - // - // We recommend saving this from any call to WriteRelationships and storing it - // alongside the object referenced in the write or check (in this case aDoc)" - // - // For more info see: - // https://github.com/authzed/spicedb/blob/main/docs/zedtokens-and-zookies.md - whenPermsChanged := resp.WrittenAt - - // Run some permission checks on the written data. - aNobody := subject(userNS, "randomnobody") - expected := []struct { - resource *v1.ObjectReference - permission string - subject *v1.SubjectReference - hasAccess bool - }{ - {aDoc, "read", anOwner, true}, - {aDoc, "write", anOwner, true}, - {aDoc, "delete", anOwner, true}, - {aDoc, "read", anEditor, true}, - {aDoc, "write", anEditor, true}, - {aDoc, "delete", anEditor, false}, - {aDoc, "read", aViewer, true}, - {aDoc, "write", aViewer, false}, - {aDoc, "delete", aViewer, false}, - {aDoc, "read", aNobody, false}, - {aDoc, "write", aNobody, false}, - {aDoc, "delete", aNobody, false}, - } - - for _, test := range expected { - testResp, err := client.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ - // Guarantee checks occur on data fresher than the write. - Consistency: &v1.Consistency{ - Requirement: &v1.Consistency_AtLeastAsFresh{ - AtLeastAsFresh: whenPermsChanged, - }, - }, - Resource: test.resource, - Permission: test.permission, - Subject: test.subject, - }) - if err != nil { - log.Fatalf("unable to run check request: %s", err) - } - - hasAccess := testResp.Permissionship == v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION - if hasAccess != test.hasAccess { - log.Fatalf("check returned the wrong result: %#v", test) - } - } -} - -func object(namespace, objectID string) *v1.ObjectReference { - return &v1.ObjectReference{ - ObjectType: namespace, - ObjectId: objectID, - } -} - -func subject(namespace, objectID string) *v1.SubjectReference { - return &v1.SubjectReference{ - Object: object(namespace, objectID), - } -} - -func relationship(resource *v1.ObjectReference, relation string, subject *v1.SubjectReference) *v1.Relationship { - return &v1.Relationship{ - Resource: resource, - Relation: relation, - Subject: subject, - } -} - -func createRelationship(relationship *v1.Relationship) *v1.RelationshipUpdate { - return &v1.RelationshipUpdate{ - Operation: v1.RelationshipUpdate_OPERATION_CREATE, - Relationship: relationship, - } -} diff --git a/proto/authzed/api/v0/acl_service.pb.go b/proto/authzed/api/v0/acl_service.pb.go index 50456bc..69a7768 100644 --- a/proto/authzed/api/v0/acl_service.pb.go +++ b/proto/authzed/api/v0/acl_service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.19.1 +// protoc (unknown) // source: authzed/api/v0/acl_service.proto package v0 diff --git a/proto/authzed/api/v0/core.pb.go b/proto/authzed/api/v0/core.pb.go index cf394cc..df6f9da 100644 --- a/proto/authzed/api/v0/core.pb.go +++ b/proto/authzed/api/v0/core.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.19.1 +// protoc (unknown) // source: authzed/api/v0/core.proto package v0 diff --git a/proto/authzed/api/v0/developer.pb.go b/proto/authzed/api/v0/developer.pb.go index 795fbf9..f63cc2e 100644 --- a/proto/authzed/api/v0/developer.pb.go +++ b/proto/authzed/api/v0/developer.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.19.1 +// protoc (unknown) // source: authzed/api/v0/developer.proto package v0 diff --git a/proto/authzed/api/v0/namespace.pb.go b/proto/authzed/api/v0/namespace.pb.go index 32e1488..e9a37e8 100644 --- a/proto/authzed/api/v0/namespace.pb.go +++ b/proto/authzed/api/v0/namespace.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.19.1 +// protoc (unknown) // source: authzed/api/v0/namespace.proto package v0 diff --git a/proto/authzed/api/v0/namespace_service.pb.go b/proto/authzed/api/v0/namespace_service.pb.go index 5a1da69..02f1536 100644 --- a/proto/authzed/api/v0/namespace_service.pb.go +++ b/proto/authzed/api/v0/namespace_service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.19.1 +// protoc (unknown) // source: authzed/api/v0/namespace_service.proto package v0 diff --git a/proto/authzed/api/v0/watch_service.pb.go b/proto/authzed/api/v0/watch_service.pb.go index be0425b..74c3608 100644 --- a/proto/authzed/api/v0/watch_service.pb.go +++ b/proto/authzed/api/v0/watch_service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.19.1 +// protoc (unknown) // source: authzed/api/v0/watch_service.proto package v0 diff --git a/proto/authzed/api/v1/core.pb.go b/proto/authzed/api/v1/core.pb.go index 988117a..6ff4943 100644 --- a/proto/authzed/api/v1/core.pb.go +++ b/proto/authzed/api/v1/core.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.19.1 +// protoc (unknown) // source: authzed/api/v1/core.proto package v1 diff --git a/proto/authzed/api/v1/openapi.pb.go b/proto/authzed/api/v1/openapi.pb.go index 7affa26..bb25575 100644 --- a/proto/authzed/api/v1/openapi.pb.go +++ b/proto/authzed/api/v1/openapi.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.19.1 +// protoc (unknown) // source: authzed/api/v1/openapi.proto package v1 diff --git a/proto/authzed/api/v1/permission_service.pb.go b/proto/authzed/api/v1/permission_service.pb.go index 501bd6c..5855072 100644 --- a/proto/authzed/api/v1/permission_service.pb.go +++ b/proto/authzed/api/v1/permission_service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.19.1 +// protoc (unknown) // source: authzed/api/v1/permission_service.proto package v1 diff --git a/proto/authzed/api/v1/schema_service.pb.go b/proto/authzed/api/v1/schema_service.pb.go index 38ccfc2..d8db406 100644 --- a/proto/authzed/api/v1/schema_service.pb.go +++ b/proto/authzed/api/v1/schema_service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.19.1 +// protoc (unknown) // source: authzed/api/v1/schema_service.proto package v1 diff --git a/proto/authzed/api/v1/watch_service.pb.go b/proto/authzed/api/v1/watch_service.pb.go index 66c26e0..d303072 100644 --- a/proto/authzed/api/v1/watch_service.pb.go +++ b/proto/authzed/api/v1/watch_service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.19.1 +// protoc (unknown) // source: authzed/api/v1/watch_service.proto package v1 diff --git a/proto/authzed/api/v1alpha1/schema.pb.go b/proto/authzed/api/v1alpha1/schema.pb.go index fe7b873..7471672 100644 --- a/proto/authzed/api/v1alpha1/schema.pb.go +++ b/proto/authzed/api/v1alpha1/schema.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.19.1 +// protoc (unknown) // source: authzed/api/v1alpha1/schema.proto package v1alpha1 diff --git a/v1/client.go b/v1/client.go index 48c18f8..740e1d5 100644 --- a/v1/client.go +++ b/v1/client.go @@ -1,9 +1,10 @@ package authzed import ( - v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" "github.com/jzelinskie/stringz" "google.golang.org/grpc" + + v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" ) // Client represents an open connection to Authzed. diff --git a/v1/client_test.go b/v1/client_test.go index 7fd9711..1c6303d 100644 --- a/v1/client_test.go +++ b/v1/client_test.go @@ -1,15 +1,14 @@ package authzed_test import ( - "fmt" "log" - "testing" - authzed "github.com/authzed/authzed-go/v1" "github.com/authzed/grpcutil" + + authzed "github.com/authzed/authzed-go/v1" ) -func ExampleNewClient(_ *testing.T) { +func ExampleNewClient() { client, err := authzed.NewClient( "grpc.authzed.com:443", grpcutil.WithBearerToken("tc_my_token_deadbeefdeadbeefdeadbeef"), @@ -18,5 +17,5 @@ func ExampleNewClient(_ *testing.T) { if err != nil { log.Fatalf("failed to connect to authzed: %s", err) } - fmt.Println(client) + log.Println(client) } diff --git a/v1alpha1/client_test.go b/v1alpha1/client_test.go index 4577ccf..a81fefe 100644 --- a/v1alpha1/client_test.go +++ b/v1alpha1/client_test.go @@ -1,16 +1,14 @@ package authzed_test import ( - "fmt" "log" - "testing" "github.com/authzed/grpcutil" authzed "github.com/authzed/authzed-go/v1alpha1" ) -func ExampleNewClient(_ *testing.T) { +func ExampleNewClient() { client, err := authzed.NewClient( "grpc.authzed.com:443", grpcutil.WithBearerToken("tc_my_token_deadbeefdeadbeefdeadbeef"), @@ -19,5 +17,5 @@ func ExampleNewClient(_ *testing.T) { if err != nil { log.Fatalf("failed to connect to authzed: %s", err) } - fmt.Println(client) + log.Println(client) }