Skip to content

Latest commit

 

History

History
1007 lines (838 loc) · 28.8 KB

protecting-a-blog.mdx

File metadata and controls

1007 lines (838 loc) · 28.8 KB

import { Callout, Tabs } from 'nextra/components'

Protecting a Blog Application

This guide walks through the steps required to deeply integrate an application with Authzed or SpiceDB. Not all software requires this level of integration, but it is preferable for greenfield applications or applications that are central in an architecture with multiple services.

Instead of introducing an unfamiliar example app and altering various locations in its code, this guide is written such that each step is a standalone snippet of code that demonstrates an integration point and finding where those points exist in your codebase is an exercise left to the reader.

Prerequisites

One of:

Installation

The first step to integrating any software is ensuring you have an API client.

Each tool is installed with its ecosystem's package management tools:

<Tabs items={['zed', 'Node', 'Go', 'Python', 'Ruby', 'Java']}> <Tabs.Tab>

brew install authzed/tap/zed
zed context set blog grpc.authzed.com:443 t_your_token_here_1234567deadbeef

</Tabs.Tab> <Tabs.Tab>

# JavaScript and TypeScript
npm i @authzed/authzed-node

</Tabs.Tab> <Tabs.Tab>

mkdir first_app && cd first_app
go mod init first_app
go get github.com/authzed/authzed-go
go get github.com/authzed/grpcutil
go mod tidy

</Tabs.Tab> <Tabs.Tab>

pip install authzed

</Tabs.Tab> <Tabs.Tab>

gem install authzed

</Tabs.Tab> <Tabs.Tab>

// build.gradle
dependencies {
  implementation "com.authzed.api:authzed:0.6.0"
  implementation 'io.grpc:grpc-protobuf:1.54.1'
  implementation 'io.grpc:grpc-stub:1.54.1'
}

</Tabs.Tab>

Defining and Applying a Schema

Regardless of whether or not you have a preexisting schema written, integrating a new application will typically require you add new definitions to the Schema.

As a quick recap, Schemas define the objects, their relations, and their checkable permissions that will be available to be used with the Permission System.

We'll be using the following blog example throughout this guide:

definition user {}
definition post {
  relation reader: user
  relation writer: user
  permission read = reader + writer
  permission write = writer
}

This example defines two types of objects that will be used in the permissions system: user and post. Each post can have two kinds of relations to users: reader and writer. Each post can have two permissions checked: read and write. The read permission unions together both readers and writers, so that any writer is implicitly granted read, as well. Feel free to start with design to modify and test your own experiments in the playground.

With a schema designed, we can now move on using our client to to apply that schema to the Permission System.

Similar to applying schema changes for relational databases, all changes to a schema must be backwards compatible.

In production environments where relations change, you will likely want to write data migrations and apply those changes using a schema migration toolchain.

<Tabs items={['zed', 'Node', 'Go', 'Python', 'Ruby', 'Java']}> <Tabs.Tab>

zed schema write <(cat << EOF
definition user {}
definition post {
    relation reader: user
    relation writer: user
    permission read = reader + writer
    permission write = writer
}
EOF)

</Tabs.Tab> <Tabs.Tab>

import { v1 } from '@authzed/authzed-node';

const { promises: client } = v1.NewClient(
  't_your_token_here_1234567deadbeef',
);

const schema = `
definition user {}
definition post {
	relation reader: user
	relation writer: user
	permission read = reader + writer
	permission write = writer
}`;

const schemaRequest = v1.WriteSchemaRequest.create({
  schema: schema,
});

const schemaResponse = await client.writeSchema(schemaRequest)
console.log(schemaResponse)

</Tabs.Tab> <Tabs.Tab>

package main

import (
	"context"
	"log"

	pb "github.com/authzed/authzed-go/proto/authzed/api/v1"
	"github.com/authzed/authzed-go/v1"
	"github.com/authzed/grpcutil"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

const schema = `definition user {}
definition post {
	relation reader: user
	relation writer: user
	permission read = reader + writer
	permission write = writer
}`

const spicedbEndpoint = "grpc.authzed.com:443"

func main() {
	systemCerts, err := grpcutil.WithSystemCerts(grpcutil.VerifyCA)
	if err != nil {
		log.Fatalf("unable to load system CA certificates: %s", err)
	}

	client, err := authzed.NewClient(
		spicedbEndpoint,
		grpcutil.WithBearerToken("t_your_token_here_1234567deadbeef"),
		systemCerts,
	)
	if err != nil {
		log.Fatalf("unable to initialize client: %s", err)
	}

	request := &pb.WriteSchemaRequest{Schema: schema}
	_, err = client.WriteSchema(context.Background(), request)
	if err != nil {
		log.Fatalf("failed to write schema: %s", err)
	}
}

</Tabs.Tab> <Tabs.Tab>

from authzed.api.v1 import Client, WriteSchemaRequest
from grpcutil import bearer_token_credentials

SCHEMA = """definition user {}
definition post {
	relation reader: user
	relation writer: user
	permission read = reader + writer
	permission write = writer
}"""

ENDPOINT = "grpc.authzed.com:443"
CREDENTIALS = bearer_token_credentials("t_your_token_here_1234567deadbeef")

client = Client(ENDPOINT, CREDENTIALS)
resp = client.WriteSchema(WriteSchemaRequest(schema=SCHEMA))

</Tabs.Tab> <Tabs.Tab>

require 'authzed'

schema = <<~SCHEMA
definition user {}
definition post {
	relation reader: user
	relation writer: user
	permission read = reader + writer
	permission write = writer
}
SCHEMA

endpoint = 'grpc.authzed.com:443'
credentials = Authzed::GrpcUtil::BearerToken.new(token: 't_your_token_here_1234567deadbeef')

client = Authzed::Api::V1::Client.new(
  target: endpoint,
  interceptors: [credentials],
)

resp = client.schema_service.write_schema(
  Authzed::Api::V1::WriteSchemaRequest.new(schema: schema)
)

</Tabs.Tab> <Tabs.Tab>

import com.authzed.api.v1.SchemaServiceGrpc;
import com.authzed.api.v1.SchemaServiceOuterClass.*;
import com.authzed.grpcutil.BearerToken;
import io.grpc.*;
public class App {
  public static void main(String[] args) {
    ManagedChannel channel = ManagedChannelBuilder
            .forTarget("grpc.authzed.com:443")
            .useTransportSecurity()
            .build();
    BearerToken bearerToken = new BearerToken("t_your_token_here_1234567deadbeef");
    SchemaServiceGrpc.SchemaServiceBlockingStub schemaService = SchemaServiceGrpc.newBlockingStub(channel)
            .withCallCredentials(bearerToken);
    String schema = """
            definition user {}
            definition post {
                relation reader: user
                relation writer: user
                permission read = reader + writer
                permission write = writer
            }
            """;
    WriteSchemaRequest request = WriteSchemaRequest
            .newBuilder()
            .setSchema(schema)
            .build();
    WriteSchemaResponse response;
    try {
      response = schemaService.writeSchema(request);
    } catch (Exception e) {
      // Uh oh!
    }
  }
}

</Tabs.Tab>

Storing Relationships

After a permission system has its schema applied, it is ready to have its relationships be created, touched, or deleted. Relationships are live instances of relations between objects. Because the relationships stored in the system can change at runtime, this is a powerful primitive for dynamically granting or revoking access to the resources you've modeled. When applications modify or create rows in their database, they will also typically create or update relationships.

Writing relationships returns a [ZedToken] which is critical to ensuring performance and [consistency].

In the following example, we'll be creating two relationships: one making Emilia a writer of the first post and another making Beatrice a reader of the first post. You can also touch and delete relationships, but those are not as immediately useful for an empty permission system.

<Tabs items={['zed', 'Node', 'Go', 'Python', 'Ruby', 'Java']}> <Tabs.Tab>

zed relationship create post:1 writer user:emilia
zed relationship create post:1 reader user:beatrice

</Tabs.Tab> <Tabs.Tab>

import { v1 } from '@authzed/authzed-node';

const { promises: client } = v1.NewClient(
  't_your_token_here_1234567deadbeef',
  'grpc.authzed.com:50051',
);

const resource = v1.ObjectReference.create({
  objectType: 'post',
  objectId: '1',
});

const emilia = v1.ObjectReference.create({
  objectType: 'user',
  objectId: 'emilia',
});

const beatrice = v1.ObjectReference.create({
  objectType: 'user',
  objectId: 'beatrice',
});

const writeRequest = v1.WriteRelationshipsRequest.create({
  updates: [
    // Emilia is a Writer on Post 1
    v1.RelationshipUpdate.create({
      relationship: v1.Relationship.create({
        resource: resource,
        relation: 'writer',
        subject: v1.SubjectReference.create({ object: emilia }),
      }),
      operation: v1.RelationshipUpdate_Operation.CREATE,
    }),
    //  Beatrice is a Reader on Post 1
    v1.RelationshipUpdate.create({
      relationship: v1.Relationship.create({
        resource: resource,
        relation: 'reader',
        subject: v1.SubjectReference.create({ object: beatrice }),
      }),
      operation: v1.RelationshipUpdate_Operation.CREATE,
    }),
  ],
});

const response = await client.writeRelationships(writeRequest)

console.log(response)

</Tabs.Tab> <Tabs.Tab>

package main

import (
	"context"
	"fmt"
	"log"

	pb "github.com/authzed/authzed-go/proto/authzed/api/v1"
	"github.com/authzed/authzed-go/v1"
	"github.com/authzed/grpcutil"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

const (
	spicedbEndpoint = "grpc.authzed.com:443"
	token = "t_your_token_here_1234567deadbeef"
)

func main() {
	systemCerts, err := grpcutil.WithSystemCerts(grpcutil.VerifyCA)
	if err != nil {
		log.Fatalf("unable to load system CA certificates: %s", err)
	}

	client, err := authzed.NewClient(
		spicedbEndpoint,
		grpcutil.WithBearerToken(token),
		systemCerts,
		// These options are if you're self-hosting and don't want TLS:
		// grpcutil.WithInsecureBearerToken(token),
		// grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	if err != nil {
		log.Fatalf("unable to initialize client: %s", err)
	}

	request := &pb.WriteRelationshipsRequest{Updates: []*pb.RelationshipUpdate{
		{ // Emilia is a Writer on Post 1
			Operation: pb.RelationshipUpdate_OPERATION_CREATE,
			Relationship: &pb.Relationship{
				Resource: &pb.ObjectReference{
					ObjectType: "post",
					ObjectId: "1",
				},
				Relation: "writer",
				Subject: &pb.SubjectReference{
					Object: &pb.ObjectReference{
						ObjectType: "user",
						ObjectId: "emilia",
					},
				},
			},
		},
		{ // Beatrice is a Reader on Post 1
			Operation: pb.RelationshipUpdate_OPERATION_CREATE,
			Relationship: &pb.Relationship{
				Resource: &pb.ObjectReference{
					ObjectType: "post",
					ObjectId: "1",
				},
				Relation: "reader",
				Subject: &pb.SubjectReference{
					Object: &pb.ObjectReference{
						ObjectType: "user",
						ObjectId: "beatrice",
					},
				},
			},
		},
	}}

	 resp, err := client.WriteRelationships(context.Background(), request)
	 if err != nil {
		log.Fatalf("failed to write relations: %s", err)
	}
	fmt.Println(resp.WrittenAt.Token)
}

</Tabs.Tab> <Tabs.Tab>

from authzed.api.v1 import (
    Client,
    ObjectReference,
    Relationship,
    RelationshipUpdate,
    SubjectReference,
    WriteRelationshipsRequest,
)
from grpcutil import bearer_token_credentials

client = Client(
    "grpc.authzed.com:443",
    bearer_token_credentials("t_your_token_here_1234567deadbeef"),
)

resp = client.WriteRelationships(
    WriteRelationshipsRequest(
        updates=[
            # Emilia is a Writer on Post 1
            RelationshipUpdate(
                operation=RelationshipUpdate.Operation.OPERATION_CREATE,
                relationship=Relationship(
                    resource=ObjectReference(object_type="post", object_id="1"),
                    relation="writer",
                    subject=SubjectReference(
                        object=ObjectReference(
                            object_type="user",
                            object_id="emilia",
                        )
                    ),
                ),
            ),
            # Beatrice is a Reader on Post 1
            RelationshipUpdate(
                operation=RelationshipUpdate.Operation.OPERATION_CREATE,
                relationship=Relationship(
                    resource=ObjectReference(object_type="post", object_id="1"),
                    relation="reader",
                    subject=SubjectReference(
                        object=ObjectReference(
                            object_type="user",
                            object_id="beatrice",
                        )
                    ),
                ),
            ),
        ]
    )
)

print(resp.written_at.token)

</Tabs.Tab> <Tabs.Tab>

require 'authzed'

client = Authzed::Api::V1::Client.new(
  target: 'grpc.authzed.com:443',
  interceptors: [Authzed::GrpcUtil::BearerToken.new(token: 't_your_token_here_1234567deadbeef')],
)

resp = client.permissions_service.write_relationships(
  Authzed::Api::V1::WriteRelationshipsRequest.new(
    updates: [
      # Emilia is a Writer on Post 1
      Authzed::Api::V1::RelationshipUpdate.new(
        operation: Authzed::Api::V1::RelationshipUpdate::Operation::OPERATION_CREATE,
        relationship: Authzed::Api::V1::Relationship.new(
          resource: Authzed::Api::V1::ObjectReference.new(object_type: 'post', object_id: '1'),
          relation: 'writer',
          subject: Authzed::Api::V1::SubjectReference.new(
            object: Authzed::Api::V1::ObjectReference.new(object_type: 'user', object_id: 'emilia'),
          ),
        ),
      ),
      # Beatrice is a Reader on Post 1
      Authzed::Api::V1::RelationshipUpdate.new(
        operation: Authzed::Api::V1::RelationshipUpdate::Operation::OPERATION_CREATE,
        relationship: Authzed::Api::V1::Relationship.new(
          resource: Authzed::Api::V1::ObjectReference.new(object_type: 'post', object_id: '1'),
          relation: 'reader',
          subject: Authzed::Api::V1::SubjectReference.new(
            object: Authzed::Api::V1::ObjectReference.new(object_type: 'user', object_id: 'beatrice'),
          ),
        ),
      ),
    ]
  )
)

puts resp.written_at.token

</Tabs.Tab> <Tabs.Tab>

import com.authzed.api.v1.PermissionService;
import com.authzed.api.v1.PermissionsServiceGrpc;
import com.authzed.grpcutil.BearerToken;
import com.authzed.api.v1.Core.*;
import io.grpc.*;

public class App {
  public static void main(String[] args) {
    ManagedChannel channel = ManagedChannelBuilder
            .forTarget("grpc.authzed.com:443")
            .useTransportSecurity()
            .build();

    BearerToken bearerToken = new BearerToken("t_your_token_here_1234567deadbeef");
    PermissionsServiceGrpc.PermissionsServiceBlockingStub permissionsService = PermissionsServiceGrpc.newBlockingStub(channel)
            .withCallCredentials(bearerToken);

    PermissionService.WriteRelationshipsRequest request = PermissionService.WriteRelationshipsRequest.newBuilder()
            .addUpdates(
                    RelationshipUpdate.newBuilder()
                            .setOperation(RelationshipUpdate.Operation.OPERATION_CREATE)
                            .setRelationship(
                                    Relationship.newBuilder()
                                            .setResource(
                                                    ObjectReference.newBuilder()
                                                            .setObjectType("post")
                                                            .setObjectId("1")
                                                            .build())
                                            .setRelation("writer")
                                            .setSubject(
                                                    SubjectReference.newBuilder()
                                                            .setObject(
                                                                    ObjectReference.newBuilder()
                                                                            .setObjectType("user")
                                                                            .setObjectId("emilia")
                                                                            .build())
                                                            .build())
                                            .build())
                            .build())
            .build();

    PermissionService.WriteRelationshipsResponse response;
    try {
      response = permissionsService.writeRelationships(request);
      String zedToken = response.getWrittenAt().getToken();
    } catch (Exception e) {
      // Uh oh!
    }
  }
}

</Tabs.Tab>

Checking Permissions

Permissions Systems that have stored relationships are capable of performing permission checks. Checks do not only test for the existence of direct relationships, but will also compute and traverse transitive relationships. For example, in our example schema, writers have both write and read, so there's no need to create a read relationship for a subject that is already a writer.

In addition to checking _permissions_, it is also possible to perform checks on _relations_ to determine membership.

However, this goes against best practice: permissions can be redefined in backwards compatible ways by changing schema, so it's ideal to rely on permissions as the contract between SpiceDB and applications querying SpiceDB.

In order to get read-after-write consistency, you must provide a [ZedToken] from the WriteRelationships response or request [full consistency].

The following examples demonstrate the transitive property of checks:

<Tabs items={['zed', 'Node', 'Go', 'Python', 'Ruby', 'Java']}> <Tabs.Tab>

zed permission check post:1 read  user:emilia   --revision "zedtokenfromwriterel" # true
zed permission check post:1 write user:emilia   --revision "zedtokenfromwriterel" # true
zed permission check post:1 read  user:beatrice --revision "zedtokenfromwriterel" # true
zed permission check post:1 write user:beatrice --revision "zedtokenfromwriterel" # false

</Tabs.Tab> <Tabs.Tab>

import { v1 } from '@authzed/authzed-node';

const { promises: client } = v1.NewClient(
  't_your_token_here_1234567deadbeef',
  'grpc.authzed.com:50051',
  // NOTE: Remove if SpiceDB is behind TLS
  v1.ClientSecurity.INSECURE_PLAINTEXT_CREDENTIALS
);

const resource = v1.ObjectReference.create({
  objectType: 'post',
  objectId: '1',
});

const emilia = v1.ObjectReference.create({
  objectType: 'user',
  objectId: 'emilia',
});

const beatrice = v1.ObjectReference.create({
  objectType: 'user',
  objectId: 'beatrice',
});

const emiliaCanRead = await client.checkPermission(v1.CheckPermissionRequest.create({
  resource,
  permission: 'read',
  subject: v1.SubjectReference.create({
    object: emilia,
  }),
}));
console.log(emiliaCanRead.permissionship === v1.CheckPermissionResponse_Permissionship.HAS_PERMISSION);

const emiliaCanWrite = await client.checkPermission(v1.CheckPermissionRequest.create({
  resource,
  permission: 'write',
  subject: v1.SubjectReference.create({
    object: emilia,
  }),
}));
console.log(emiliaCanWrite.permissionship === v1.CheckPermissionResponse_Permissionship.HAS_PERMISSION);

const beatriceCanRead = await client.checkPermission(v1.CheckPermissionRequest.create({
  resource,
  permission: 'read',
  subject: v1.SubjectReference.create({
    object: beatrice,
  }),
}));
console.log(beatriceCanRead.permissionship === v1.CheckPermissionResponse_Permissionship.HAS_PERMISSION);

const beatriceCanWrite = await client.checkPermission(v1.CheckPermissionRequest.create({
  resource,
  permission: 'write',
  subject: v1.SubjectReference.create({
    object: beatrice,
  }),
}));
console.log(beatriceCanWrite.permissionship === v1.CheckPermissionResponse_Permissionship.HAS_PERMISSION);

</Tabs.Tab> <Tabs.Tab>

package main

import (
	"context"
 	"log"

 	pb "github.com/authzed/authzed-go/proto/authzed/api/v1"
 	"github.com/authzed/authzed-go/v1"
 	"github.com/authzed/grpcutil"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials/insecure"
)

const spicedbEndpoint = "grpc.authzed.com:443"

func main() {
	systemCerts, err := grpcutil.WithSystemCerts(grpcutil.VerifyCA)
	if err != nil {
		log.Fatalf("unable to load system CA certificates: %s", err)
	}

	client, err := authzed.NewClient(
		spicedbEndpoint,
		grpcutil.WithBearerToken("t_your_token_here_1234567deadbeef"),
		systemCerts,
		// These options are if you're self-hosting and don't want TLS:
		// grpcutil.WithInsecureBearerToken("t_your_token_here_1234567deadbeef"),
		// grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	if err != nil {
		log.Fatalf("unable to initialize client: %s", err)
	}

	ctx := context.Background()

	emilia := &pb.SubjectReference{Object: &pb.ObjectReference{
		ObjectType: "user",
		ObjectId:  "emilia",
	}}

	beatrice := &pb.SubjectReference{Object: &pb.ObjectReference{
		ObjectType: "user",
		ObjectId:  "beatrice",
	}}

	firstPost := &pb.ObjectReference{
		ObjectType: "post",
		ObjectId: "1",
	}

	resp, err := client.CheckPermission(ctx, &pb.CheckPermissionRequest{
		Resource: firstPost,
		Permission: "read",
		Subject: emilia,
	})
	if err != nil {
		log.Fatalf("failed to check permission: %s", err)
	}
	// resp.Permissionship == pb.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION

	resp, err = client.CheckPermission(ctx, &pb.CheckPermissionRequest{
		Resource: firstPost,
		Permission: "write",
		Subject: emilia,
	})
	if err != nil {
		log.Fatalf("failed to check permission: %s", err)
	}
	// resp.Permissionship == pb.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION

	resp, err = client.CheckPermission(ctx, &pb.CheckPermissionRequest{
		Resource: firstPost,
		Permission: "read",
		Subject: beatrice,
	})
	if err != nil {
		log.Fatalf("failed to check permission: %s", err)
	}
	// resp.Permissionship == pb.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION

	resp, err = client.CheckPermission(ctx, &pb.CheckPermissionRequest{
		Resource: firstPost,
		Permission: "write",
		Subject: beatrice,
	})
	if err != nil {
		log.Fatalf("failed to check permission: %s", err)
	}
	// resp.Permissionship == pb.CheckPermissionResponse_PERMISSIONSHIP_NO_PERMISSION
}

</Tabs.Tab> <Tabs.Tab>

from authzed.api.v1 import (
    CheckPermissionRequest,
    CheckPermissionResponse,
    Client,
    ObjectReference,
    SubjectReference,
)
from grpcutil import insecure_bearer_token_credentials

client = Client(
    "grpc.authzed.com:50051",
    insecure_bearer_token_credentials("t_your_token_here_1234567deadbeef"),
)

emilia = SubjectReference(
    object=ObjectReference(
        object_type="user",
        object_id="emilia",
    )
)
beatrice = SubjectReference(
    object=ObjectReference(
        object_type="user",
        object_id="beatrice",
    )
)

post_one = ObjectReference(object_type="post", object_id="1")

resp = client.CheckPermission(
    CheckPermissionRequest(
        resource=post_one,
        permission="read",
        subject=emilia,
    )
)
assert resp.permissionship == CheckPermissionResponse.PERMISSIONSHIP_HAS_PERMISSION

resp = client.CheckPermission(
    CheckPermissionRequest(
        resource=post_one,
        permission="write",
        subject=emilia,
    )
)
assert resp.permissionship == CheckPermissionResponse.PERMISSIONSHIP_HAS_PERMISSION

resp = client.CheckPermission(
    CheckPermissionRequest(
        resource=post_one,
        permission="read",
        subject=beatrice,
    )
)
assert resp.permissionship == CheckPermissionResponse.PERMISSIONSHIP_HAS_PERMISSION

resp = client.CheckPermission(
    CheckPermissionRequest(
        resource=post_one,
        permission="write",
        subject=beatrice,
    )
)
assert resp.permissionship == CheckPermissionResponse.PERMISSIONSHIP_NO_PERMISSION

</Tabs.Tab> <Tabs.Tab>

require 'authzed'

emilia = Authzed::Api::V1::SubjectReference.new(object: Authzed::Api::V1::ObjectReference.new(
  object_type: 'user',
  object_id: 'emilia',
))
beatrice = Authzed::Api::V1::SubjectReference.new(object: Authzed::Api::V1::ObjectReference.new(
  object_type: 'user',
  object_id: 'beatrice',
))
first_post = Authzed::Api::V1::ObjectReference.new(object_type: 'post', object_id: '1')

client = Authzed::Api::V1::Client.new(
    target: 'grpc.authzed.com:50051',
    interceptors: [Authzed::GrpcUtil::BearerToken.new(token: 't_your_token_here_1234567deadbeef')],
    credentials: :this_channel_is_insecure,
)

resp = client.permissions_service.check_permission(Authzed::Api::V1::CheckPermissionRequest.new(
  resource: first_post,
  permission: 'read',
  subject: emilia,
))
raise unless Authzed::Api::V1::CheckPermissionResponse::Permissionship.resolve(resp.permissionship) ==
  Authzed::Api::V1::CheckPermissionResponse::Permissionship::PERMISSIONSHIP_HAS_PERMISSION

resp = client.permissions_service.check_permission(Authzed::Api::V1::CheckPermissionRequest.new(
  resource: first_post,
  permission: 'write',
  subject: emilia,
))
raise unless Authzed::Api::V1::CheckPermissionResponse::Permissionship.resolve(resp.permissionship) ==
  Authzed::Api::V1::CheckPermissionResponse::Permissionship::PERMISSIONSHIP_HAS_PERMISSION

resp = client.permissions_service.check_permission(Authzed::Api::V1::CheckPermissionRequest.new(
  resource: first_post,
  permission: 'read',
  subject: beatrice,
))
raise unless Authzed::Api::V1::CheckPermissionResponse::Permissionship.resolve(resp.permissionship) ==
  Authzed::Api::V1::CheckPermissionResponse::Permissionship::PERMISSIONSHIP_HAS_PERMISSION

resp = client.permissions_service.check_permission(Authzed::Api::V1::CheckPermissionRequest.new(
  resource: first_post,
  permission: 'write',
  subject: beatrice,
))
raise unless Authzed::Api::V1::CheckPermissionResponse::Permissionship.resolve(resp.permissionship) ==
  Authzed::Api::V1::CheckPermissionResponse::Permissionship::PERMISSIONSHIP_NO_PERMISSION

</Tabs.Tab>

<Tabs.Tab>

import com.authzed.api.v1.PermissionService;
import com.authzed.api.v1.PermissionsServiceGrpc;
import com.authzed.grpcutil.BearerToken;
import com.authzed.api.v1.Core.*;
import io.grpc.*;

public class App {
  public static void main(String[] args) {
    ManagedChannel channel = ManagedChannelBuilder
            .forTarget("grpc.authzed.com:50051")
            .usePlaintext()
            .build();

    BearerToken bearerToken = new BearerToken("t_your_token_here_1234567deadbeef");

    PermissionsServiceGrpc.PermissionsServiceBlockingStub permissionsService = PermissionsServiceGrpc.newBlockingStub(channel)
            .withCallCredentials(bearerToken);

    ZedToken zedToken = ZedToken.newBuilder()
            .setToken("zed_token_value")
            .build();

    PermissionService.CheckPermissionRequest request = PermissionService.CheckPermissionRequest.newBuilder()
            .setConsistency(
                    PermissionService.Consistency.newBuilder()
                            .setAtLeastAsFresh(zedToken)
                            .build())
            .setResource(
                    ObjectReference.newBuilder()
                            .setObjectType("post")
                            .setObjectId("1")
                            .build())
            .setSubject(
                    SubjectReference.newBuilder()
                            .setObject(
                                    ObjectReference.newBuilder()
                                            .setObjectType("user")
                                            .setObjectId("emilia")
                                            .build())
                            .build())
            .setPermission("read")
            .build();

    PermissionService.CheckPermissionResponse response;
    try {
      response = permissionsService.checkPermission(request);
      response.getPermissionship();
    } catch (Exception e) {
      // Uh oh!
    }
  }
}

</Tabs.Tab>