Skip to content

Commit 04c8b96

Browse files
committed
introduces the gRPC API for Authzed Materialize
This is published as v0 and is considered alpha version
1 parent 9c4c264 commit 04c8b96

File tree

2 files changed

+244
-0
lines changed

2 files changed

+244
-0
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
syntax = "proto3";
2+
package authzed.api.materialize.v0;
3+
4+
import "authzed/api/v1/core.proto";
5+
import "google/protobuf/struct.proto";
6+
import "validate/validate.proto";
7+
8+
option go_package = "github.com/authzed/authzed-go/proto/authzed/api/v0/materialize";
9+
option java_package = "com.authzed.api.materialize.v0";
10+
option java_multiple_files = true;
11+
12+
service WatchPermissionsService {
13+
// WatchPermissions returns a stream of PermissionChange events for the given permissions.
14+
//
15+
// WatchPermissions is a long-running RPC, and will stream events until the client
16+
// closes the connection or the server terminates the stream. The consumer is responsible of
17+
// keeping track of the last seen revision and resuming the stream from that point in the event
18+
// of disconnection or client-side restarts.
19+
//
20+
// The API does not offer a sharding mechanism and thus there should only be one consumer per target system.
21+
// Implementing an active-active HA consumer setup over the same target system will require coordinating which
22+
// revisions have been consumed in order to prevent transitioning to an inconsistent state.
23+
//
24+
// Usage of WatchPermissions requires it to be explicitly enabled on the service,
25+
// requires more resources and is less performant than WatchPermissionsSets. It's usage
26+
// is only recommended when performing the set intersections of WatchPermissionSets in the client side is not viable
27+
// or there is a strict application requirement to use consume the computed permissions.
28+
rpc WatchPermissions(WatchPermissionsRequest) returns (stream WatchPermissionsResponse) {}
29+
}
30+
31+
message WatchPermissionsRequest {
32+
// permissions is a list of permissions to watch for changes. At least one permission must be specified
33+
repeated WatchedPermission permissions = 1;
34+
// optional_starting_after is the revision token to start watching from. If not provided, the stream
35+
// will start from the current revision at the moment of the request.
36+
authzed.api.v1.ZedToken optional_starting_after = 2;
37+
}
38+
39+
message WatchedPermission {
40+
// resource_type is the type of the resource to watch for changes.
41+
string resource_type = 1;
42+
// permission is the permission to watch for changes.
43+
string permission = 2;
44+
// subject_type is the type of the subject to watch for changes.
45+
string subject_type = 3;
46+
// optional_subject_relation is the relation on the subject to watch for changes.
47+
string optional_subject_relation = 4;
48+
}
49+
50+
message WatchPermissionsResponse {
51+
oneof response {
52+
// change is the computed permission delta that has occurred as result of a mutation in origin SpiceDB.
53+
// The consumer should apply this change to the current state of the computed permissions in their target system.
54+
// Once an event arrives with completed_revision instead, the consumer shall consider there are not more changes
55+
// originating from that revision.
56+
//
57+
// The consumer should keep track of the revision in order to resume streaming in the event of consumer restarts.
58+
PermissionChange change = 1;
59+
60+
// completed_revision is the revision token that indicates all changes originating from a revision have been
61+
// streamed and thus the revision should be considered completed. It may also be
62+
// received without accompanying set of changes, indicating that a mutation in the origin SpiceDB cluster did
63+
// not yield any effective changes in the computed permissions
64+
authzed.api.v1.ZedToken completed_revision = 2;
65+
}
66+
}
67+
68+
message PermissionChange {
69+
enum Permissionship {
70+
PERMISSIONSHIP_UNSPECIFIED = 0;
71+
PERMISSIONSHIP_NO_PERMISSION = 1;
72+
PERMISSIONSHIP_HAS_PERMISSION = 2;
73+
PERMISSIONSHIP_CONDITIONAL_PERMISSION = 3;
74+
}
75+
76+
// revision represents the revision at which the change occurred.
77+
authzed.api.v1.ZedToken revision = 1;
78+
79+
// resource is the resource that the permission change is related to.
80+
authzed.api.v1.ObjectReference resource = 2;
81+
// permission is the permission that has changed.
82+
string permission = 3;
83+
// subject is the subject that the permission change is related to.
84+
authzed.api.v1.SubjectReference subject = 4;
85+
// permissionship is the new permissionship of the subject over the resource after the change.
86+
Permissionship permissionship = 5;
87+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
syntax = "proto3";
2+
package authzed.api.materialize.v0;
3+
4+
import "authzed/api/v1/core.proto";
5+
import "google/protobuf/struct.proto";
6+
import "google/protobuf/duration.proto";
7+
import "validate/validate.proto";
8+
import "authzed/api/v0/materialize/watchpermissions.proto";
9+
10+
option go_package = "github.com/authzed/authzed-go/proto/authzed/api/v0/materialize";
11+
option java_package = "com.authzed.api.materialize.v0";
12+
option java_multiple_files = true;
13+
14+
service WatchPermissionSetsService {
15+
// WatchPermissionSets returns a stream of changes to the sets which can be used to compute the watched permissions.
16+
//
17+
// WatchPermissionSets lets consumers achieve the same thing as WatchPermissions, but trades off a simpler usage model with
18+
// significantly lower computational requirements. Unlike WatchPermissions, this method returns changes to the sets of permissions,
19+
// rather than the individual permissions. Permission sets are a normalized form of the computed permissions, which
20+
// means that the consumer must perform an extra computation over this representation to obtain the final computed
21+
// permissions, typically by intersecting the provided sets.
22+
//
23+
// For example, this would look like a JOIN between the
24+
// materialize permission sets table in a target relation database, the table with the resources to authorize access
25+
// to, and the table with the subject (e.g. a user).
26+
//
27+
// In exchange, the number of changes issued by WatchPermissionSets will be several orders of magnitude less than those
28+
// emitted by WatchPermissions, which has several implications:
29+
// - significantly less resources to compute the sets
30+
// - significantly less messages to stream over the network
31+
// - significantly less events to ingest on the consumer side
32+
// - less ingestion lag from the origin SpiceDB mutation
33+
//
34+
// The type of scenarios WatchPermissionSets is particularly well suited is when a single change
35+
// in the origin SpiceDB can yield millions of changes. For example, in the GitHub authorization model, assigning a role
36+
// to a top-level team of an organization with hundreds of thousands of employees can lead to an explosion of
37+
// permission change events that would require a lot of computational resources to process, both on Materialize and
38+
// the consumer side.
39+
//
40+
// WatchPermissionSets is thus recommended for any larger scale use case where the fan-out in permission changes that
41+
// emerges from a specific schema and data shape is too large to handle effectively.
42+
//
43+
// The API does not offer a sharding mechanism and thus there should only be one consumer per target system.
44+
// Implementing an active-active HA consumer setup over the same target system will require coordinating which
45+
// revisions have been consumed in order to prevent transitioning to an inconsistent state.
46+
rpc WatchPermissionSets(WatchPermissionSetsRequest) returns (stream WatchPermissionSetsResponse) {}
47+
48+
// LookupPermissionSets returns the current state of the permission sets which can be used to derive the computed permissions.
49+
// It's typically used to backfill the state of the permission sets in the consumer side.
50+
//
51+
// It's a cursored API and the consumer is responsible to keep track of the cursor and use it on each subsequent call.
52+
// Each stream will return <N> permission sets defined by the specified request limit. The server will keep streaming until
53+
// the sets per stream is hit, or the current state of the sets is reached,
54+
// whatever happens first, and then close the stream. The server will indicate there are no more changes to stream
55+
// through the `completed_members` in the cursor.
56+
//
57+
// There may be many elements to stream, and so the consumer should be prepared to resume the stream from the last
58+
// cursor received. Once completed, the consumer may start streaming permission set changes using WatchPermissionSets
59+
// and the revision token from the last LookupPermissionSets response.
60+
rpc LookupPermissionSets(LookupPermissionSetsRequest) returns (stream LookupPermissionSetsResponse) {}
61+
}
62+
63+
message WatchPermissionSetsRequest {
64+
// optional_starting_after is used to specify the SpiceDB revision to start watching from.
65+
// If not specified, the watch will start from the current SpiceDB revision time of the request ("head revision").
66+
authzed.api.v1.ZedToken optional_starting_after = 1;
67+
}
68+
69+
message WatchPermissionSetsResponse {
70+
oneof response {
71+
// change is the permission set delta that has occurred as result of a mutation in origin SpiceDB.
72+
// The consumer should apply this change to the current state of the permission sets in their target system.
73+
// Once an event arrives with completed_revision instead, the consumer shall consider the set of
74+
// changes originating from that revision completed.
75+
//
76+
// The consumer should keep track of the revision in order to resume streaming in the event of consumer restarts.
77+
PermissionSetChange change = 1;
78+
79+
// completed_revision is the revision token that indicates the completion of a set of changes. It may also be
80+
// received without accompanying set of changes, indicating that a mutation in the origin SpiceDB cluster did
81+
// not yield any effective changes in the permission sets
82+
authzed.api.v1.ZedToken completed_revision = 2;
83+
}
84+
}
85+
86+
message Cursor {
87+
// limit is the number of permission sets to stream over a single LookupPermissionSets call that was requested.
88+
uint32 limit = 1;
89+
// token is the snapshot revision at which the cursor was computed.
90+
authzed.api.v1.ZedToken token = 4;
91+
// starting_index is an offset of the permission set represented by this cursor
92+
uint32 starting_index = 5;
93+
// completed_members is a boolean flag that indicates that the cursor has reached the end of the permission sets
94+
bool completed_members = 6;
95+
}
96+
97+
message LookupPermissionSetsRequest {
98+
// limit is the number of permission sets to stream over a single LookupPermissionSets. Once the limit is reached,
99+
// the server will close the stream. If more permission sets are available, the consume should open a new stream
100+
// providing optional_starting_after_cursor, using the cursor from the last response.
101+
uint32 limit = 1;
102+
// optional_starting_after_cursor is used to specify the offset to start streaming permission sets from.
103+
Cursor optional_starting_after_cursor = 4;
104+
}
105+
106+
message LookupPermissionSetsResponse {
107+
// change represents the permission set delta necessary to transition an uninitialized target system to
108+
// a specific snapshot revision. In practice it's not different from the WatchPermissionSetsResponse.change, except
109+
// all changes will be of time SET_OPERATION_ADDED because it's assumed there is no known previous state.
110+
//
111+
// Applying the deltas to a previously initialized target system would yield incorrect results.
112+
PermissionSetChange change = 1;
113+
// cursor points to a specific permission set in a revision.
114+
// The consumer should keep track of the cursor in order to resume streaming in the event of consumer restarts. This
115+
// is particularly important in backfill scenarios that may take hours or event days to complete.
116+
Cursor cursor = 2;
117+
}
118+
119+
message PermissionSetChange {
120+
enum SetOperation {
121+
SET_OPERATION_UNSPECIFIED = 0;
122+
SET_OPERATION_ADDED = 1;
123+
SET_OPERATION_REMOVED = 2;
124+
}
125+
126+
// revision represents the revision at which the permission set change occurred.
127+
authzed.api.v1.ZedToken at_revision = 1;
128+
// operation represents the type of set operation that took place as part of the change
129+
SetOperation operation = 2;
130+
// parent_set represents the permission set parent of either another set or a member
131+
SetReference parent_set = 3;
132+
133+
oneof child {
134+
// child_set represents the scenario where another set is considered member of the parent set
135+
SetReference child_set = 4;
136+
// child_member represents the scenario where an specific object is considered member of the parent set
137+
MemberReference child_member = 5;
138+
}
139+
}
140+
141+
message SetReference {
142+
// object_type is the type of object in a permission set
143+
string object_type = 1;
144+
// object_id is the ID of a permission set
145+
string object_id = 2;
146+
// permission_or_relation is the permission or relation referenced by this permission set
147+
string permission_or_relation = 3;
148+
}
149+
150+
message MemberReference {
151+
// object_type is the type of object of a permission set member
152+
string object_type = 1;
153+
// object_id is the ID of a permission set member
154+
string object_id = 2;
155+
// optional_permission_or_relation is the permission or relation referenced by this permission set member
156+
string optional_permission_or_relation = 3;
157+
}

0 commit comments

Comments
 (0)