Skip to content

Commit

Permalink
H-3885: Implement lookup API for SpiceDB resources and subjects (#6096)
Browse files Browse the repository at this point in the history
  • Loading branch information
TimDiekmann authored Jan 9, 2025
1 parent 3e3799b commit f4fc1ba
Show file tree
Hide file tree
Showing 9 changed files with 415 additions and 14 deletions.
38 changes: 36 additions & 2 deletions libs/@local/graph/authorization/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ use crate::{
CheckError, CheckResponse, ModifyRelationError, ModifyRelationshipOperation, ReadError,
},
schema::{
AccountGroupPermission, AccountGroupRelationAndSubject, DataTypePermission,
DataTypeRelationAndSubject, EntityPermission, EntityRelationAndSubject,
AccountGroupPermission, AccountGroupRelationAndSubject, AccountIdOrPublic,
DataTypePermission, DataTypeRelationAndSubject, EntityPermission, EntityRelationAndSubject,
EntityTypePermission, EntityTypeRelationAndSubject, PropertyTypePermission,
PropertyTypeRelationAndSubject, WebPermission, WebRelationAndSubject,
},
Expand Down Expand Up @@ -116,6 +116,20 @@ pub trait AuthorizationApi: Send + Sync {
consistency: Consistency<'_>,
) -> impl Future<Output = Result<CheckResponse, Report<CheckError>>> + Send;

fn get_entities(
&self,
actor: AccountId,
permission: EntityPermission,
consistency: Consistency<'_>,
) -> impl Future<Output = Result<Vec<EntityUuid>, Report<ReadError>>> + Send;

fn get_entity_accounts(
&self,
entity: EntityUuid,
permission: EntityPermission,
consistency: Consistency<'_>,
) -> impl Future<Output = Result<Vec<AccountIdOrPublic>, Report<ReadError>>> + Send;

fn modify_entity_relations(
&mut self,
relationships: impl IntoIterator<
Expand Down Expand Up @@ -516,6 +530,26 @@ impl<A: AuthorizationApi> AuthorizationApi for &mut A {
.get_data_type_relations(data_type, consistency)
.await
}

async fn get_entities(
&self,
actor: AccountId,
permission: EntityPermission,
consistency: Consistency<'_>,
) -> Result<Vec<EntityUuid>, Report<ReadError>> {
(**self).get_entities(actor, permission, consistency).await
}

async fn get_entity_accounts(
&self,
entity: EntityUuid,
permission: EntityPermission,
consistency: Consistency<'_>,
) -> Result<Vec<AccountIdOrPublic>, Report<ReadError>> {
(**self)
.get_entity_accounts(entity, permission, consistency)
.await
}
}

/// Managed pool to keep track about [`AuthorizationApi`]s.
Expand Down
102 changes: 102 additions & 0 deletions libs/@local/graph/authorization/src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,33 @@ pub trait ZanzibarBackend {
impl Serialize + Send + Sync,
>,
) -> impl Future<Output = Result<DeleteRelationshipResponse, Report<DeleteRelationshipError>>> + Send;

fn lookup_resources<O>(
&self,
subject: &(
impl Subject<Resource: Resource<Kind: Serialize, Id: Serialize>, Relation: Serialize> + Sync
),
permission: &(impl Serialize + Permission<O> + Sync),
resource_kind: &O::Kind,
consistency: Consistency<'_>,
) -> impl Future<Output = Result<Vec<O::Id>, Report<ReadError>>> + Send
where
for<'de> O: Resource<Kind: Serialize + Sync, Id: Deserialize<'de> + Send> + Send;

fn lookup_subjects<S, O>(
&self,
subject_type: &<S::Resource as Resource>::Kind,
subject_relation: Option<&S::Relation>,
permission: &(impl Serialize + Permission<O> + Sync),
resource: &O,
consistency: Consistency<'_>,
) -> impl Future<Output = Result<Vec<<S::Resource as Resource>::Id>, Report<ReadError>>> + Send
where
for<'de> S: Subject<
Resource: Resource<Kind: Serialize + Sync, Id: Deserialize<'de> + Send>,
Relation: Serialize + Sync,
> + Send,
O: Resource<Kind: Serialize, Id: Serialize> + Sync;
}

impl<Z: ZanzibarBackend + Send + Sync> ZanzibarBackend for &mut Z {
Expand Down Expand Up @@ -344,6 +371,48 @@ impl<Z: ZanzibarBackend + Send + Sync> ZanzibarBackend for &mut Z {
) -> Result<DeleteRelationshipResponse, Report<DeleteRelationshipError>> {
ZanzibarBackend::delete_relations(&mut **self, filter).await
}

async fn lookup_resources<O>(
&self,
subject: &(
impl Subject<Resource: Resource<Kind: Serialize, Id: Serialize>, Relation: Serialize> + Sync
),
permission: &(impl Serialize + Permission<O> + Sync),
resource_kind: &O::Kind,
consistency: Consistency<'_>,
) -> Result<Vec<O::Id>, Report<ReadError>>
where
for<'de> O: Resource<Kind: Serialize + Sync, Id: Deserialize<'de> + Send> + Send,
{
ZanzibarBackend::lookup_resources(&**self, subject, permission, resource_kind, consistency)
.await
}

async fn lookup_subjects<S, O>(
&self,
subject_type: &<S::Resource as Resource>::Kind,
subject_relation: Option<&S::Relation>,
permission: &(impl Serialize + Permission<O> + Sync),
resource: &O,
consistency: Consistency<'_>,
) -> Result<Vec<<S::Resource as Resource>::Id>, Report<ReadError>>
where
for<'de> S: Subject<
Resource: Resource<Kind: Serialize + Sync, Id: Deserialize<'de> + Send>,
Relation: Serialize + Sync,
> + Send,
O: Resource<Kind: Serialize, Id: Serialize> + Sync,
{
ZanzibarBackend::lookup_subjects::<S, O>(
&**self,
subject_type,
subject_relation,
permission,
resource,
consistency,
)
.await
}
}

impl ZanzibarBackend for NoAuthorization {
Expand Down Expand Up @@ -439,6 +508,39 @@ impl ZanzibarBackend for NoAuthorization {
deleted_at: Zookie::empty(),
})
}

async fn lookup_resources<O>(
&self,
_: &(
impl Subject<Resource: Resource<Kind: Serialize, Id: Serialize>, Relation: Serialize> + Sync
),
_: &(impl Serialize + Permission<O> + Sync),
_: &O::Kind,
_: Consistency<'_>,
) -> Result<Vec<O::Id>, Report<ReadError>>
where
for<'de> O: Resource<Kind: Serialize + Sync, Id: Deserialize<'de> + Send> + Send,
{
Ok(Vec::new())
}

async fn lookup_subjects<S, O>(
&self,
_: &<S::Resource as Resource>::Kind,
_: Option<&S::Relation>,
_: &(impl Serialize + Permission<O> + Sync),
_: &O,
_: Consistency<'_>,
) -> Result<Vec<<S::Resource as Resource>::Id>, Report<ReadError>>
where
for<'de> S: Subject<
Resource: Resource<Kind: Serialize + Sync, Id: Deserialize<'de> + Send>,
Relation: Serialize + Sync,
> + Send,
O: Resource<Kind: Serialize, Id: Serialize> + Sync,
{
Ok(Vec::new())
}
}

/// Return value for [`ZanzibarBackend::import_schema`].
Expand Down
121 changes: 120 additions & 1 deletion libs/@local/graph/authorization/src/backend/spicedb/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::{
DeleteRelationshipResponse, ExportSchemaError, ExportSchemaResponse, ImportSchemaError,
ImportSchemaResponse, ModifyRelationshipError, ModifyRelationshipOperation,
ModifyRelationshipResponse, ReadError, SpiceDbOpenApi, ZanzibarBackend,
spicedb::model::{self, Permissionship, RpcError},
spicedb::model::{self, LookupPermissionship, Permissionship, RpcError},
},
zanzibar::{
Consistency, Permission,
Expand Down Expand Up @@ -600,4 +600,123 @@ impl ZanzibarBackend for SpiceDbOpenApi {
})
.change_context(DeleteRelationshipError)
}

async fn lookup_resources<O>(
&self,
subject: &(
impl Subject<Resource: Resource<Kind: Serialize, Id: Serialize>, Relation: Serialize> + Sync
),
permission: &(impl Serialize + Permission<O> + Sync),
resource_kind: &O::Kind,
consistency: Consistency<'_>,
) -> Result<Vec<O::Id>, Report<ReadError>>
where
for<'de> O: Resource<Kind: Serialize + Sync, Id: Deserialize<'de> + Send> + Send,
{
#[derive(Serialize)]
#[serde(
rename_all = "camelCase",
bound = "
O: Serialize,
R: Serialize,
S: Subject<Resource: Resource<Kind: Serialize, Id: Serialize>, Relation: Serialize>"
)]
struct LookupResourcesRequest<'a, O, R, S> {
consistency: model::Consistency<'a>,
resource_object_type: &'a O,
permission: &'a R,
#[serde(with = "super::serde::subject_ref")]
subject: &'a S,
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct LookupResourcesResponse<O> {
resource_object_id: O,
#[serde(rename = "permissionship")]
_permissionship: LookupPermissionship,
}

self.stream::<LookupResourcesResponse<O::Id>, _>(
"/v1/permissions/resources",
&LookupResourcesRequest {
consistency: model::Consistency::from(consistency),
resource_object_type: resource_kind,
permission,
subject,
},
)
.await
.change_context(ReadError)?
.map_ok(|response| response.resource_object_id)
.map_err(|error| error.change_context(ReadError))
.try_collect()
.await
}

async fn lookup_subjects<S, O>(
&self,
subject_type: &<S::Resource as Resource>::Kind,
subject_relation: Option<&S::Relation>,
permission: &(impl Serialize + Permission<O> + Sync),
resource: &O,
consistency: Consistency<'_>,
) -> Result<Vec<<S::Resource as Resource>::Id>, Report<ReadError>>
where
for<'de> S: Subject<
Resource: Resource<Kind: Serialize + Sync, Id: Deserialize<'de> + Send>,
Relation: Serialize + Sync,
> + Send,
O: Resource<Kind: Serialize, Id: Serialize> + Sync,
{
#[derive(Serialize)]
#[serde(
rename_all = "camelCase",
bound = "
O: Resource<Kind: Serialize, Id: Serialize>,
R: Serialize,
S: Serialize,
SR: Serialize"
)]
struct LookupSubjectsRequest<'a, O, R, S, SR> {
consistency: model::Consistency<'a>,
#[serde(with = "super::serde::resource_ref")]
resource: &'a O,
permission: &'a R,
subject_object_type: &'a S,
#[serde(skip_serializing_if = "Option::is_none")]
optional_subject_relation: Option<&'a SR>,
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct ResolvedObject<O> {
subject_object_id: O,
#[serde(rename = "permissionship")]
_permissionship: LookupPermissionship,
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct LookupSubjectsResponse<O> {
subject: ResolvedObject<O>,
}

self.stream::<LookupSubjectsResponse<<S::Resource as Resource>::Id>, _>(
"/v1/permissions/subjects",
&LookupSubjectsRequest {
consistency: model::Consistency::from(consistency),
resource,
permission,
subject_object_type: subject_type,
optional_subject_relation: subject_relation,
},
)
.await
.change_context(ReadError)?
.map_ok(|response| response.subject.subject_object_id)
.map_err(|error| error.change_context(ReadError))
.try_collect()
.await
}
}
4 changes: 2 additions & 2 deletions libs/@local/graph/authorization/src/backend/spicedb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ impl SpiceDbOpenApi {
///
/// # Panics
///
/// - Panics if `key` is not a valid value for the token
/// - if `key` is not a valid value for the token
///
/// # Errors
///
/// - Errors if the client could not be built
/// - if the client could not be built
pub fn new(
base_path: impl Into<String>,
key: Option<&str>,
Expand Down
6 changes: 6 additions & 0 deletions libs/@local/graph/authorization/src/backend/spicedb/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ pub(crate) enum Permissionship {
Conditional,
}

#[derive(Debug, Copy, Clone, Deserialize)]
pub(crate) enum LookupPermissionship {
#[serde(rename = "LOOKUP_PERMISSIONSHIP_HAS_PERMISSION")]
HasPermission,
}

impl From<Permissionship> for bool {
fn from(permissionship: Permissionship) -> Self {
match permissionship {
Expand Down
25 changes: 22 additions & 3 deletions libs/@local/graph/authorization/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ use std::collections::HashMap;

pub use self::api::{AuthorizationApi, AuthorizationApiPool};
use crate::schema::{
AccountGroupRelationAndSubject, DataTypePermission, DataTypeRelationAndSubject,
EntityRelationAndSubject, EntityTypePermission, EntityTypeRelationAndSubject,
PropertyTypePermission, PropertyTypeRelationAndSubject, WebRelationAndSubject,
AccountGroupRelationAndSubject, AccountIdOrPublic, DataTypePermission,
DataTypeRelationAndSubject, EntityRelationAndSubject, EntityTypePermission,
EntityTypeRelationAndSubject, PropertyTypePermission, PropertyTypeRelationAndSubject,
WebRelationAndSubject,
};

mod api;
Expand Down Expand Up @@ -307,6 +308,24 @@ impl AuthorizationApi for NoAuthorization {
) -> Result<Vec<DataTypeRelationAndSubject>, Report<ReadError>> {
Ok(Vec::new())
}

async fn get_entities(
&self,
_: AccountId,
_: EntityPermission,
_: Consistency<'_>,
) -> Result<Vec<EntityUuid>, Report<ReadError>> {
Ok(Vec::new())
}

async fn get_entity_accounts(
&self,
_: EntityUuid,
_: EntityPermission,
_: Consistency<'_>,
) -> Result<Vec<AccountIdOrPublic>, Report<ReadError>> {
Ok(Vec::new())
}
}

impl<A> AuthorizationApiPool for A
Expand Down
Loading

0 comments on commit f4fc1ba

Please sign in to comment.