Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initial OpenFGA v2beta1 protobuf API development #127

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions openfga/v2beta1/openfga.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
syntax = "proto3";

package openfga.v2beta1;

Check failure on line 3 in openfga/v2beta1/openfga.proto

View workflow job for this annotation

GitHub Actions / build

Multiple packages "openfga.v2beta1,watch.v2beta1" detected within directory "openfga/v2beta1".

import "google/protobuf/struct.proto";

message RelationshipTuple {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WHat do you think about naming them RelationshipTuple and RelationshipTupleWithCondition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So invert them?

I think the API more unanymously operates on a RelationshipTuple which can optionally have a condition. For example the WriteRelationshipTuples API, the ReadRelationshipTuples API, etc.. The exception are those APIs that do not need a condition in the tuple representation whatsoever. So I think I lean toward leaving as is.


Subject subject = 1;

string relation = 2;

Object object = 3;

RelationshipCondition condition = 4;
}

message RelationshipCondition {
string name = 1;
google.protobuf.Struct context = 2;
}

message RelationshipTupleWithoutCondition {
Subject subject = 1;
string relation = 2;
Object object = 3;
}

message Subject {
oneof subject_ref{
Object object = 1;
TypedWildcard typed_wildcard = 2;
SubjectSet subject_set = 3;
}
}

message Object {
string type = 1;
string id = 2;
}

// TypedWildcard represents the public wildcard of the specified object type.
// For example, `user:*` represents all objects of the `user` object type.
message TypedWildcard {
string type = 1;
}

message SubjectSet {
Object object = 1;
string relation = 2;
}

message AuthorizationModel {
string id = 1;
map<string, Relation> relations = 2;
map<string, Condition> conditions = 3;
}

message Relation {

// The name of the relation.
string name = 1;

// The relationship rewrite rule for the relation.
RelationRewrite rewrite = 2;

// A list of type restrictions that apply to the relation.
repeated TypeRestriction type_restrictions = 3;
}

// RelationRewrite represents a relationship rule that is used to rewrite or define a relation
// in terms of a direct relationship or some other rewritten relationship definition including
// through set operations involving union, intersection, and exclusion.
message RelationRewrite {
oneof rewrite_rule {
DirectRelationship direct = 1;
ComputedRelationship computed = 2;
TupleToSubjectSetRelationship tuple_to_subjectset = 3;
// union, intersection, exclusion
}
}

// DirectRelationship represents a directly assignable relationship.
//
// These relationships are the kinds of relationships that are permissible for writes to an
// OpenFGA store. The kinds of objects that can be assignable to the directly assignable
// relationship are based on the relations type restrictions.
message DirectRelationship {}

// ComputedRelationship represents a relationship that is rewritten through a recomputed relation.
//
// For example, if a relation `viewer` is rewritten through a computed relationship `editor`. In
// Zanzibar nomenclature this is identical to computed userset rewrites.
message ComputedRelationship {
string relation = 1;
}

// TupleToSubjectSetRelationship represents a relationship
//
// In Zanzibar nomenclature this is identical to tuple to userset rewrites.
message TupleToSubjectSetRelationship {
string tupleset_relation = 1;
ComputedRelationship computed_relationship = 2;
}

// TypeRestriction represents a relationship constraint that applies to a directly
// assignable relationship. A directl relationship is only permissible if the type
// restriction allows it.
message TypeRestriction {
oneof type_reference {
UnconditionedObjectTypeRestriction unconditioned_object_type_reference = 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the separation? also all can be conditioned not just object type

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes the API more strongly typed. A type restriction can reference a direct object type with no condition or one explicitly with a condition. The later requires the condition to be defined, the former does not. To uniquely distinguish these cases we make the protobuf API strongly typed to represent the difference.

ConditionedObjectTypeRestriction conditioned_object_type_reference = 2;
TypedWildcard typed_wildcard_reference = 3;
SubjectSetTypeRestriction subject_set_reference = 4;
}
}

// UnconditionedObjectTypeRestriction represents a type restriction that enforces a relationship with
// only a specific object type and is not conditioned on anything.
message UnconditionedObjectTypeRestriction {
string type = 1;
}

// ConditionedObjectTypeRestriction represents a type restriction that enforces a relationship with
// a specific object type and is conditioned on the provided condition name.
message ConditionedObjectTypeRestriction {
string type = 1;
string condition = 2;
}

// SubjectSetTypeRestriction represents a type restriction that references a relation defined on a specific
// object type. For example `group#member` or `team#admin`.
message SubjectSetTypeRestriction {
string type = 1;
string relation = 2;
}

message Condition {
string name = 1;
string expression = 2;
map<string, ConditionParameterTypeRef> parameters = 3;
}

message ConditionParameterTypeRef {
enum TypeName {
TYPE_NAME_UNSPECIFIED = 0;
TYPE_NAME_ANY = 1;
TYPE_NAME_BOOL = 2;
TYPE_NAME_STRING = 3;
TYPE_NAME_INT = 4;
TYPE_NAME_UINT = 5;
TYPE_NAME_DOUBLE = 6;
TYPE_NAME_DURATION = 7;
TYPE_NAME_TIMESTAMP = 8;
TYPE_NAME_MAP = 9;
TYPE_NAME_LIST = 10;
TYPE_NAME_IPADDRESS = 11;
}

TypeName type_name = 1;

repeated ConditionParameterTypeRef generic_types = 2;
}
118 changes: 118 additions & 0 deletions openfga/v2beta1/openfga_service.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
syntax = "proto3";

package openfga.v2beta1;

Check failure on line 3 in openfga/v2beta1/openfga_service.proto

View workflow job for this annotation

GitHub Actions / build

Multiple packages "openfga.v2beta1,watch.v2beta1" detected within directory "openfga/v2beta1".

import "google/protobuf/struct.proto";
import "google/rpc/status.proto";
import "openfga/v2beta1/openfga.proto";

service OpenFGAService {
rpc Check(CheckRequest) returns (CheckResponse);
rpc BatchCheck(BatchCheckRequest) returns (BatchCheckResponse);

rpc ListObjects(ListObjectsRequest) returns (ListObjectsResponse);

rpc DeleteRelationshipTuples(DeleteRelationshipTuplesRequest) returns (DeleteRelationshipTuplesResponse);
rpc WriteRelationshipTuples(WriteRelationshipTuplesRequest) returns (WriteRelationshipTuplesResponse);
rpc ReadRelationshipTuples(ReadRelationshipTuplesRequest) returns (ReadRelationshipTuplesResponse);

rpc WriteModel(WriteModelRequest) returns (WriteModelResponse);
rpc ReadModel(ReadModelRequest) returns (ReadModelResponse);
rpc ListModels(ListModelsRequest) returns (ListModelsResponse);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReadChanges?
Read?
BatchCheck?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More to come 😄 . Just started with some.

}

message CheckRequest{
string store_id = 1;
string model_id = 2;
Subject subject = 3;
string relation = 4;
Object object = 5;
repeated RelationshipTuple contextual_tuples = 6;
google.protobuf.Struct context = 7;
}

message CheckResponse{}

message BatchCheckRequest {

// One or more individual Check requests to evaluate in the batch.
repeated BatchCheckRequestItem requests = 1;
}

message BatchCheckResponse {

// The response pairings for each request item and the result/response it produced.
repeated BatchCheckResponsePair response_pairs = 1;
}

// BatchCheckRequestItem represents a single Check request item used in a BatchCheckRequest.
message BatchCheckRequestItem {
Subject subject = 1;
string relation = 2;
Object object = 3;
repeated RelationshipTuple contextual_tuples = 4;
google.protobuf.Struct context = 5;
}

// BatchCheckResponseItem represents an individual response for a singular BatchCheckRequestItem produced
// by a BatchCheck RPC.
message BatchCheckResponseItem {
CheckResponse response = 1;
}

// BatchCheckResponsePair represents the pairing of an individual BatchCheckRequestItem in a
// BatchCheckRequest and the result which the individual request produced. Each BatchCheckRequestItem
// can produce either a response item or an error.
message BatchCheckResponsePair {
BatchCheckRequestItem request = 1;

oneof result {
BatchCheckResponseItem response_item = 2;
google.rpc.Status error = 3;
}
}

message ListObjectsRequest{
string store_id = 1;
string model_id = 2;
Subject subject = 3;
string relation = 4;
string object_type = 5;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, maybe:
object: {
type: ...
}

Copy link
Contributor Author

@jon-whit jon-whit Dec 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would imply another message definition of the form

message ObjectWithoutId {
    string type = 1;
}

That's a bit overloaded and adds message level indirection for little to no gain. string object_type points to the Object.Type field. Accepting the string is more straightforward.

RelationshipTuple contextual_tuples = 6;
google.protobuf.Struct context = 7;
}
message ListObjectsResponse{}

message DeleteRelationshipTuplesRequest{
string store_id = 1;
}
message DeleteRelationshipTuplesResponse{}

message WriteRelationshipTuplesRequest{
string store_id = 1;
}
message WriteRelationshipTuplesResponse{}

message ReadRelationshipTuplesRequest {
string store_id = 1;
}

message ReadRelationshipTuplesResponse {
repeated RelationshipTuple relationship_tuples = 1;
}

message WriteModelRequest{
string store_id = 1;
}
message WriteModelResponse{}

message ReadModelRequest{
string store_id = 1;
string model_id = 2;
}
message ReadModelResponse{}

message ListModelsRequest{
string store_id = 1;
}
message ListModelsResponse{}
12 changes: 12 additions & 0 deletions openfga/v2beta1/watch_service.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
syntax = "proto3";

package watch.v2beta1;

Check failure on line 3 in openfga/v2beta1/watch_service.proto

View workflow job for this annotation

GitHub Actions / build

Multiple packages "openfga.v2beta1,watch.v2beta1" detected within directory "openfga/v2beta1".

Check failure on line 3 in openfga/v2beta1/watch_service.proto

View workflow job for this annotation

GitHub Actions / build

Files with package "watch.v2beta1" must be within a directory "watch/v2beta1" relative to root but were in directory "openfga/v2beta1".

service WatchService {

// WatchChanges implements a streaming watch over an FGA store's changelog
rpc WatchChanges(WatchChangesRequest) returns (stream WatchChangesResponse);
}

message WatchChangesRequest {}
message WatchChangesResponse {}
Loading