aboutsummaryrefslogtreecommitdiff
path: root/crates/secd/src/client/spice/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--crates/secd/src/client/spice/mod.rs154
1 files changed, 154 insertions, 0 deletions
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(),
+ }
+ }
+}