oso-cloud 0.5.4

Oso Cloud client
Documentation
use serde::{Deserialize, Serialize};

use crate::{Oso, Value};

type Result<T> = core::result::Result<T, crate::Error>;

/// An [`Oso`] wrapper for using [Local Authorization] with decentralized data.
///
/// [Local Authorization]: https://www.osohq.com/docs/app-integration/integrate-authorization/filter-lists#list-filtering-with-local-data
#[derive(Clone, Debug)]
pub struct LocalFilteringHandle<S> {
    oso: Oso,
    data_bindings: S,
}

impl Oso {
    /// Convert into a [`LocalFilteringHandle`].
    pub fn into_local_filtering_handle<S>(self, data_bindings: S) -> LocalFilteringHandle<S> {
        LocalFilteringHandle {
            oso: self,
            data_bindings,
        }
    }
}

impl<S> LocalFilteringHandle<S> {
    /// Get the underlying [`Oso`] client.
    pub fn oso(&self) -> &Oso {
        &self.oso
    }
}

#[derive(Deserialize)]
struct LocalQueryResult {
    sql: String,
}

impl<S: AsRef<str>> LocalFilteringHandle<S> {
    /// Fetches a query that can be run against your database to determine whether
    /// an actor can perform an action on a resource.
    ///
    /// The query will always return a single record with a single boolean column
    /// named `allowed`, indicating whether or not the action is permitted.
    ///
    /// Example output:
    /// | allowed |
    /// |---------|
    /// | true    |
    pub async fn authorize_local(
        &self,
        actor: impl Into<Value<'_>>,
        action: &str,
        resource: impl Into<Value<'_>>,
    ) -> Result<String> {
        #[derive(Debug, Serialize)]
        struct AuthorizeRequest<'a> {
            actor_type: &'a str,
            actor_id: &'a str,
            action: &'a str,
            resource_type: &'a str,
            resource_id: &'a str,
        }
        #[derive(Debug, Serialize)]
        struct LocalAuthQuery<'a> {
            query: AuthorizeRequest<'a>,
            data_bindings: &'a str,
        }

        let actor = actor.into();
        let resource = resource.into();
        let (Some(actor_type), Some(actor_id)) = (actor.type_.as_ref(), actor.id.as_ref()) else {
            return Err(crate::Error::Input(
                "Actor must be a concrete value. Try `oso.query` if you want to get all permitted actors".to_owned(),
            ));
        };
        let (Some(resource_type), Some(resource_id)) = (resource.type_.as_ref(), resource.id.as_ref()) else {
            return Err(crate::Error::Input(
                "Resource must be a concrete value. Try `oso.list` if you want to get all allowed resources".to_owned(),
            ));
        };

        let query = AuthorizeRequest {
            actor_type,
            actor_id,
            action,
            resource_type,
            resource_id,
        };

        let body = LocalAuthQuery {
            query,
            data_bindings: self.data_bindings.as_ref(),
        };

        let resp: LocalQueryResult = self.oso.client.post("authorize_query", &body, false).await?;

        Ok(resp.sql)
    }

    /// Fetches a filter that can be applied to a database query to return just
    /// the resources on which an actor can perform an action.
    pub async fn list_local(
        &self,
        actor: impl Into<Value<'_>>,
        action: &str,
        resource_type: &str,
        column: &str,
    ) -> Result<String> {
        #[derive(Debug, Serialize)]
        struct ListRequest<'a> {
            actor_type: &'a str,
            actor_id: &'a str,
            action: &'a str,
            resource_type: &'a str,
        }
        #[derive(Debug, Serialize)]
        struct LocalListQuery<'a> {
            query: ListRequest<'a>,
            column: &'a str,
            data_bindings: &'a str,
        }

        let actor = actor.into();

        let (Some(actor_type), Some(actor_id)) = (actor.type_.as_ref(), actor.id.as_ref()) else {
            return Err(crate::Error::Input(
                "Actor must be a concrete value. Try `oso.query` if you want to get all permitted actors".to_owned(),
            ));
        };

        let query = ListRequest {
            actor_type,
            actor_id,
            action,
            resource_type,
        };

        let body = LocalListQuery {
            query,
            column,
            data_bindings: self.data_bindings.as_ref(),
        };

        let resp: LocalQueryResult = self.oso.client.post("list_query", &body, false).await?;
        Ok(resp.sql)
    }
}