From 31de872c30df687ed59a8edd6e3aaa4ffd845f8d Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Mon, 6 Jan 2025 15:58:26 -0500 Subject: [PATCH] Promote the reflection APIs into stable --- authzed/api/v1/experimental_service.proto | 20 +- authzed/api/v1/schema_service.proto | 237 ++++++++++++++++++++++ 2 files changed, 245 insertions(+), 12 deletions(-) diff --git a/authzed/api/v1/experimental_service.proto b/authzed/api/v1/experimental_service.proto index d261f1d..5f81a91 100644 --- a/authzed/api/v1/experimental_service.proto +++ b/authzed/api/v1/experimental_service.proto @@ -55,48 +55,44 @@ service ExperimentalService { option deprecated = true; } - // EXPERIMENTAL: ReflectSchema is an API that allows clients to reflect the schema stored in - // SpiceDB. This is useful for clients that need to introspect the schema of a SpiceDB instance. + // DEPRECATED: Promoted to ReflectSchema in the stable API. rpc ExperimentalReflectSchema(ExperimentalReflectSchemaRequest) returns (ExperimentalReflectSchemaResponse) { option (google.api.http) = { post: "/v1/experimental/reflectschema" body: "*" }; + option deprecated = true; } - // EXPERIMENTAL: ComputablePermissions is an API that allows clients to request the set of - // permissions that compute based off a relation. For example, if a schema has a relation - // `viewer` and a permission `view` defined as `permission view = viewer + editor`, then the - // computable permissions for the relation `viewer` will include `view`. + // DEPRECATED: Promoted to ComputablePermissions in the stable API. rpc ExperimentalComputablePermissions(ExperimentalComputablePermissionsRequest) returns (ExperimentalComputablePermissionsResponse) { option (google.api.http) = { post: "/v1/experimental/permissions/computable" body: "*" }; + option deprecated = true; } - // EXPERIMENTAL: DependentRelations is an API that allows clients to request the set of - // relations and permissions that used to compute a permission, recursively. It is the - // inverse of the ComputablePermissions API. + // DEPRECATED: Promoted to DependentRelations in the stable API. rpc ExperimentalDependentRelations(ExperimentalDependentRelationsRequest) returns (ExperimentalDependentRelationsResponse) { option (google.api.http) = { post: "/v1/experimental/permissions/dependent" body: "*" }; + option deprecated = true; } - // EXPERIMENTAL: DiffSchema is an API that allows clients to request the difference between the - // specified schema and the schema stored in SpiceDB. This is useful for clients that need to - // introspect the schema of a SpiceDB instance. + // DEPRECATED: Promoted to DiffSchema in the stable API. rpc ExperimentalDiffSchema(ExperimentalDiffSchemaRequest) returns (ExperimentalDiffSchemaResponse) { option (google.api.http) = { post: "/v1/experimental/diffschema" body: "*" }; + option deprecated = true; } // EXPERIMENTAL: RegisterRelationshipCounter registers a new filter for counting relationships. A filter must be registered before diff --git a/authzed/api/v1/schema_service.proto b/authzed/api/v1/schema_service.proto index 08c3a66..be5ebc8 100644 --- a/authzed/api/v1/schema_service.proto +++ b/authzed/api/v1/schema_service.proto @@ -9,6 +9,7 @@ import "google/api/annotations.proto"; import "validate/validate.proto"; import "authzed/api/v1/core.proto"; +import "authzed/api/v1/permission_service.proto"; // SchemaService implements operations on a Permissions System's Schema. service SchemaService { @@ -31,6 +32,49 @@ service SchemaService { body: "*" }; } + + // ReflectSchema reflects the current schema stored in SpiceDB, returning a structural + // form of the schema for use by client tooling. + rpc ReflectSchema(ReflectSchemaRequest) + returns (ReflectSchemaResponse) { + option (google.api.http) = { + post: "/v1/schema/reflectschema" + body: "*" + }; + } + + // ComputablePermissions returns the set of permissions that compute based off a relation + // in the current schema. For example, if the schema has a relation `viewer` and a permission + // `view` defined as `permission view = viewer + editor`, then the + // computable permissions for the relation `viewer` will include `view`. + rpc ComputablePermissions(ComputablePermissionsRequest) + returns (ComputablePermissionsResponse) { + option (google.api.http) = { + post: "/v1/schema/permissions/computable" + body: "*" + }; + } + + // DependentRelations returns the set of relations and permissions that used + // to compute a permission, recursively, in the current schema. It is the + // inverse of the ComputablePermissions API. + rpc DependentRelations(DependentRelationsRequest) + returns (DependentRelationsResponse) { + option (google.api.http) = { + post: "/v1/schema/permissions/dependent" + body: "*" + }; + } + + // DiffSchema returns the difference between the specified schema and the current + // schema stored in SpiceDB. + rpc DiffSchema(DiffSchemaRequest) + returns (DiffSchemaResponse) { + option (google.api.http) = { + post: "/v1/schema/diffschema" + body: "*" + }; + } } // ReadSchemaRequest returns the schema from the database. @@ -60,3 +104,196 @@ message WriteSchemaResponse { // written_at is the ZedToken at which the schema was written. ZedToken written_at = 1 [ (validate.rules).message.required = true ]; } + +// Reflection types //////////////////////////////////////////// + +message ReflectSchemaRequest { + Consistency consistency = 1; + + // optional_filters defines optional filters that are applied in + // an OR fashion to the schema, before being returned + repeated ReflectionSchemaFilter optional_filters = 2; +} + +message ReflectSchemaResponse { + // definitions are the definitions defined in the schema. + repeated ReflectionDefinition definitions = 1; + + // caveats are the caveats defined in the schema. + repeated ReflectionCaveat caveats = 2; + + // read_at is the ZedToken at which the schema was read. + ZedToken read_at = 3; +} + +// ReflectionSchemaFilter is a filter that can be applied to the schema on reflection. +message ReflectionSchemaFilter { + // optional_definition_name_filter is a prefix that is matched against the definition name. + string optional_definition_name_filter = 1; + + // optional_caveat_name_filter is a prefix that is matched against the caveat name. + string optional_caveat_name_filter = 2; + + // optional_relation_name_filter is a prefix that is matched against the relation name. + string optional_relation_name_filter = 3; + + // optional_permission_name_filter is a prefix that is matched against the permission name. + string optional_permission_name_filter = 4; +} + +// ReflectionDefinition is the representation of a definition in the schema. +message ReflectionDefinition { + string name = 1; + + // comment is a human-readable comments on the definition. Will include + // delimiter characters. + string comment = 2; + + repeated ReflectionRelation relations = 3; + repeated ReflectionPermission permissions = 4; +} + +// ReflectionCaveat is the representation of a caveat in the schema. +message ReflectionCaveat { + string name = 1; + + // comment is a human-readable comments on the caveat. Will include + // delimiter characters. + string comment = 2; + + repeated ReflectionCaveatParameter parameters = 3; + string Expression = 4; +} + +// ReflectionCaveatParameter is the representation of a parameter in a caveat. +message ReflectionCaveatParameter { + string name = 1; + + // type is the type of the parameter. Will be a string representing the + // type, e.g. `int` or `list` + string type = 2; + string parent_caveat_name = 3; +} + +// ReflectionRelation is the representation of a relation in the schema. +message ReflectionRelation { + string name = 1; + string comment = 2; + string parent_definition_name = 3; + repeated ReflectionTypeReference subject_types = 4; +} + +// ReflectionTypeReference is the representation of a type reference in the schema. +message ReflectionTypeReference { + // subject_definition_name is the name of the subject's definition. + string subject_definition_name = 1; + + // optional_caveat_name is the name of the caveat that is applied to the subject, if any. + string optional_caveat_name = 2; + + oneof typeref { + // is_terminal_subject is true if the subject is terminal, meaning it is referenced directly vs a sub-relation. + bool is_terminal_subject = 3; + + // optional_relation_name is the name of the relation that is applied to the subject, if any. + string optional_relation_name = 4; + + // is_public_wildcard is true if the subject is a public wildcard. + bool is_public_wildcard = 5; + } +} + +// ReflectionPermission is the representation of a permission in the schema. +message ReflectionPermission { + string name = 1; + + // comment is a human-readable comments on the permission. Will include + // delimiter characters. + string comment = 2; + string parent_definition_name = 3; +} + +message ComputablePermissionsRequest { + Consistency consistency = 1; + string definition_name = 2; + string relation_name = 3; + + // optional_definition_name_match is a prefix that is matched against the definition name(s) + // for the permissions returned. + // If not specified, will be ignored. + string optional_definition_name_filter = 4; +} + +// ReflectionRelationReference is a reference to a relation or permission in the schema. +message ReflectionRelationReference { + string definition_name = 1; + string relation_name = 2; + bool is_permission = 3; +} + +message ComputablePermissionsResponse { + repeated ReflectionRelationReference permissions = 1; + + // read_at is the ZedToken at which the schema was read. + ZedToken read_at = 2; +} + +message DependentRelationsRequest { + Consistency consistency = 1; + string definition_name = 2; + string permission_name = 3; +} + +message DependentRelationsResponse { + repeated ReflectionRelationReference relations = 1; + + // read_at is the ZedToken at which the schema was read. + ZedToken read_at = 2; +} + +message DiffSchemaRequest { + Consistency consistency = 1; + string comparison_schema = 2; +} + +message DiffSchemaResponse { + repeated ReflectionSchemaDiff diffs = 1; + + // read_at is the ZedToken at which the schema was read. + ZedToken read_at = 2; +} + +message ReflectionRelationSubjectTypeChange { + ReflectionRelation relation = 1; + ReflectionTypeReference changed_subject_type = 2; +} + +message ReflectionCaveatParameterTypeChange { + ReflectionCaveatParameter parameter = 1; + string previous_type = 2; +} + +// ReflectionSchemaDiff is the representation of a diff between two schemas. +message ReflectionSchemaDiff { + oneof diff { + ReflectionDefinition definition_added = 1; + ReflectionDefinition definition_removed = 2; + ReflectionDefinition definition_doc_comment_changed = 3; + ReflectionRelation relation_added = 4; + ReflectionRelation relation_removed = 5; + ReflectionRelation relation_doc_comment_changed = 6; + ReflectionRelationSubjectTypeChange relation_subject_type_added = 7; + ReflectionRelationSubjectTypeChange relation_subject_type_removed = 8; + ReflectionPermission permission_added = 9; + ReflectionPermission permission_removed = 10; + ReflectionPermission permission_doc_comment_changed = 11; + ReflectionPermission permission_expr_changed = 12; + ReflectionCaveat caveat_added = 13; + ReflectionCaveat caveat_removed = 14; + ReflectionCaveat caveat_doc_comment_changed = 15; + ReflectionCaveat caveat_expr_changed = 16; + ReflectionCaveatParameter caveat_parameter_added = 17; + ReflectionCaveatParameter caveat_parameter_removed = 18; + ReflectionCaveatParameterTypeChange caveat_parameter_type_changed = 19; + } +}