oso_cloud/
local_filtering.rs

1use serde::{Deserialize, Serialize};
2
3use crate::{Oso, Value};
4
5type Result<T> = core::result::Result<T, crate::Error>;
6
7/// An [`Oso`] wrapper for using [Local Authorization] with decentralized data.
8///
9/// [Local Authorization]: https://www.osohq.com/docs/app-integration/integrate-authorization/filter-lists#list-filtering-with-local-data
10#[derive(Clone, Debug)]
11pub struct LocalFilteringHandle<S> {
12    oso: Oso,
13    data_bindings: S,
14}
15
16impl Oso {
17    /// Convert into a [`LocalFilteringHandle`].
18    pub fn into_local_filtering_handle<S>(self, data_bindings: S) -> LocalFilteringHandle<S> {
19        LocalFilteringHandle {
20            oso: self,
21            data_bindings,
22        }
23    }
24}
25
26impl<S> LocalFilteringHandle<S> {
27    /// Get the underlying [`Oso`] client.
28    pub fn oso(&self) -> &Oso {
29        &self.oso
30    }
31}
32
33#[derive(Deserialize)]
34struct LocalQueryResult {
35    sql: String,
36}
37
38impl<S: AsRef<str>> LocalFilteringHandle<S> {
39    /// Fetches a query that can be run against your database to determine whether
40    /// an actor can perform an action on a resource.
41    ///
42    /// The query will always return a single record with a single boolean column
43    /// named `allowed`, indicating whether or not the action is permitted.
44    ///
45    /// Example output:
46    /// | allowed |
47    /// |---------|
48    /// | true    |
49    pub async fn authorize_local(
50        &self,
51        actor: impl Into<Value<'_>>,
52        action: &str,
53        resource: impl Into<Value<'_>>,
54    ) -> Result<String> {
55        #[derive(Debug, Serialize)]
56        struct AuthorizeRequest<'a> {
57            actor_type: &'a str,
58            actor_id: &'a str,
59            action: &'a str,
60            resource_type: &'a str,
61            resource_id: &'a str,
62        }
63        #[derive(Debug, Serialize)]
64        struct LocalAuthQuery<'a> {
65            query: AuthorizeRequest<'a>,
66            data_bindings: &'a str,
67        }
68
69        let actor = actor.into();
70        let resource = resource.into();
71        let (Some(actor_type), Some(actor_id)) = (actor.type_.as_ref(), actor.id.as_ref()) else {
72            return Err(crate::Error::Input(
73                "Actor must be a concrete value. Try `oso.query` if you want to get all permitted actors".to_owned(),
74            ));
75        };
76        let (Some(resource_type), Some(resource_id)) = (resource.type_.as_ref(), resource.id.as_ref()) else {
77            return Err(crate::Error::Input(
78                "Resource must be a concrete value. Try `oso.list` if you want to get all allowed resources".to_owned(),
79            ));
80        };
81
82        let query = AuthorizeRequest {
83            actor_type,
84            actor_id,
85            action,
86            resource_type,
87            resource_id,
88        };
89
90        let body = LocalAuthQuery {
91            query,
92            data_bindings: self.data_bindings.as_ref(),
93        };
94
95        let resp: LocalQueryResult = self.oso.client.post("authorize_query", &body, false).await?;
96
97        Ok(resp.sql)
98    }
99
100    /// Fetches a filter that can be applied to a database query to return just
101    /// the resources on which an actor can perform an action.
102    pub async fn list_local(
103        &self,
104        actor: impl Into<Value<'_>>,
105        action: &str,
106        resource_type: &str,
107        column: &str,
108    ) -> Result<String> {
109        #[derive(Debug, Serialize)]
110        struct ListRequest<'a> {
111            actor_type: &'a str,
112            actor_id: &'a str,
113            action: &'a str,
114            resource_type: &'a str,
115        }
116        #[derive(Debug, Serialize)]
117        struct LocalListQuery<'a> {
118            query: ListRequest<'a>,
119            column: &'a str,
120            data_bindings: &'a str,
121        }
122
123        let actor = actor.into();
124
125        let (Some(actor_type), Some(actor_id)) = (actor.type_.as_ref(), actor.id.as_ref()) else {
126            return Err(crate::Error::Input(
127                "Actor must be a concrete value. Try `oso.query` if you want to get all permitted actors".to_owned(),
128            ));
129        };
130
131        let query = ListRequest {
132            actor_type,
133            actor_id,
134            action,
135            resource_type,
136        };
137
138        let body = LocalListQuery {
139            query,
140            column,
141            data_bindings: self.data_bindings.as_ref(),
142        };
143
144        let resp: LocalQueryResult = self.oso.client.post("list_query", &body, false).await?;
145        Ok(resp.sql)
146    }
147}