diff options
Diffstat (limited to '')
| -rw-r--r-- | crates/secd/src/client/mod.rs | 1 | ||||
| -rw-r--r-- | crates/secd/src/client/spice/mod.rs | 154 |
2 files changed, 155 insertions, 0 deletions
diff --git a/crates/secd/src/client/mod.rs b/crates/secd/src/client/mod.rs index e5272fd..709ecad 100644 --- a/crates/secd/src/client/mod.rs +++ b/crates/secd/src/client/mod.rs @@ -1,2 +1,3 @@ pub(crate) mod email; +pub(crate) mod spice; pub(crate) mod store; diff --git a/crates/secd/src/client/spice/mod.rs b/crates/secd/src/client/spice/mod.rs new file mode 100644 index 0000000..d3ca30d --- /dev/null +++ b/crates/secd/src/client/spice/mod.rs @@ -0,0 +1,154 @@ +// TODO: This whole thing depends on having spice server running...which I do not want +// in a public secd library (or really at all). We will eventually get rid of this in +// favor of a light weight solution that leverages the Zanzibar API but disregards the +// scaling part. + +pub mod spice { + tonic::include_proto!("authzed.api.v1"); +} + +use spice::permissions_service_client::PermissionsServiceClient; +use spice::schema_service_client::SchemaServiceClient; +use spice::WriteSchemaRequest; +use std::env::var; +use tonic::metadata::MetadataValue; +use tonic::transport::Channel; +use tonic::{Request, Status}; + +use crate::auth::z::{self, Subject}; +use crate::client::spice::spice::{ + relationship_update, ObjectReference, Relationship, RelationshipUpdate, SubjectReference, +}; +use crate::{ENV_SPICE_SECRET, ENV_SPICE_SERVER}; + +use self::spice::check_permission_response::Permissionship; +use self::spice::{consistency, CheckPermissionRequest, Consistency, WriteRelationshipsRequest}; + +#[derive(Debug, thiserror::Error, derive_more::Display)] +pub enum SpiceError { + TonicTransport(#[from] tonic::transport::Error), + TonicStatus(#[from] tonic::Status), +} + +pub(crate) struct Spice { + channel: Channel, + secret: String, +} + +impl Spice { + pub async fn new() -> Self { + let secret = + var(ENV_SPICE_SECRET).expect("initialization error: Failed to find SPICE_SECRET"); + let server = + var(ENV_SPICE_SERVER).expect("initialization error: Failed to find SPICE_SERVER"); + + let channel = Channel::from_shared(server) + .expect("invalid SPICE_SERVER uri") + .connect() + .await + .expect("initialization error: Spice failed to connect to DB."); + + Spice { channel, secret } + } + + pub async fn check_permission(&self, r: &z::Relationship) -> Result<bool, SpiceError> { + let mut client = + PermissionsServiceClient::with_interceptor(self.channel.clone(), |req: Request<()>| { + self.intercept(req) + }); + + let request = tonic::Request::new(CheckPermissionRequest { + consistency: Some(Consistency { + requirement: Some(consistency::Requirement::MinimizeLatency(true)), + }), + resource: Some(ObjectReference::from(&r.object)), + permission: r.relation.clone(), + subject: Some(SubjectReference::from(&r.subject)), + context: None, + }); + + let response = client.check_permission(request).await?.into_inner(); + + Ok(match Permissionship::from_i32(response.permissionship) { + Some(Permissionship::HasPermission) => true, + _ => false, + }) + } + + pub async fn write_relationship(&self, rs: &[z::Relationship]) -> Result<(), SpiceError> { + let mut client = + PermissionsServiceClient::with_interceptor(self.channel.clone(), |req: Request<()>| { + self.intercept(req) + }); + + let request = tonic::Request::new(WriteRelationshipsRequest { + updates: rs + .into_iter() + .map(|t| RelationshipUpdate { + operation: (relationship_update::Operation::Touch as i32), + relationship: Some(Relationship { + resource: Some(ObjectReference::from(&t.object)), + relation: t.relation.clone(), + subject: Some(SubjectReference::from(&t.subject)), + optional_caveat: None, + }), + }) + .collect(), + optional_preconditions: vec![], + }); + + client.write_relationships(request).await?; + + Ok(()) + } + + pub async fn write_schema(&self, schema: &str) -> Result<(), SpiceError> { + let mut client = + SchemaServiceClient::with_interceptor(self.channel.clone(), |req: Request<()>| { + self.intercept(req) + }); + let request = tonic::Request::new(WriteSchemaRequest { + schema: schema.into(), + }); + + client.write_schema(request).await?; + + Ok(()) + } + + fn intercept(&self, mut req: Request<()>) -> Result<Request<()>, Status> { + req.metadata_mut().insert( + "authorization", + MetadataValue::from_str(&format!("Bearer {}", self.secret)).unwrap(), + ); + Ok(req) + } +} + +impl From<&z::Subject> for SubjectReference { + fn from(s: &z::Subject) -> Self { + let tup = match s { + Subject::User(u) => (u.0.clone(), u.1.clone().to_string(), "".to_string()), + Subject::UserSet { user, relation } => { + (user.0.clone(), user.1.clone().to_string(), relation.clone()) + } + }; + + SubjectReference { + object: Some(ObjectReference { + object_type: tup.0, + object_id: tup.1, + }), + optional_relation: tup.2, + } + } +} + +impl From<&z::Object> for ObjectReference { + fn from(o: &z::Object) -> Self { + ObjectReference { + object_type: o.0.clone(), + object_id: o.1.clone().to_string(), + } + } +} |
