openfga_client/lib.rs
1#![warn(
2 missing_debug_implementations,
3 rust_2018_idioms,
4 unreachable_pub,
5 clippy::pedantic
6)]
7#![forbid(unsafe_code)]
8
9//! # OpenFGA Rust Client
10//!
11//! [](https://crates.io/crates/openfga-client)
12//! [](https://opensource.org/licenses/Apache-2.0)
13//! [](https://github.com/vakamo-labs/openfga-client/actions/workflows/ci.yaml)
14//!
15//! OpenFGA Rust Client is a type-safe client for OpenFGA with optional Authorization Model management and Authentication (Bearer or Client Credentials).
16//!
17//! ## Features
18//!
19//! * Type-safe client for OpenFGA (gRPC) build on `tonic`
20//! * (JSON) Serialization and deserialization for Authorization Models in addition to protobuf Messages
21//! * Uses `vendored-protoc` for well-known types - Rust files are pre-generated.
22//! * Optional Authorization Model management with Migration hooks. Ideal for stateless deployments. State is managed exclusively in OpenFGA. This enables fully automated model management by your Application without re-writing of Authorization Models on startup.
23//! * Optional Authentication (Bearer or Client Credentials) via the [Middle Crate](https://crates.io/crates/middle). (Feature: `auth-middle`)
24//! * Convenience functions like `read_all_tuples` (handles pagination), `get_store_by_name` and more.
25//!
26//! # Usage
27//!
28//! ## Basic Usage
29//! ```no_run
30//! use openfga_client::client::OpenFgaServiceClient;
31//! use tonic::transport::Channel;
32//!
33//! #[tokio::main]
34//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
35//! let endpoint = "http://localhost:8081";
36//! let service_client = OpenFgaServiceClient::connect(endpoint).await?;
37//!
38//! // Use the client to interact with OpenFGA
39//! Ok(())
40//! }
41//! ```
42//!
43//! ## Bearer Token Authentication (API-Key)
44//! ```no_run
45//! use openfga_client::{client::BasicOpenFgaServiceClient, url};
46//!
47//! fn main() -> Result<(), Box<dyn std::error::Error>> {
48//! let endpoint = url::Url::parse("http://localhost:8081")?;
49//! let token = "your-bearer-token";
50//! let service_client = BasicOpenFgaServiceClient::new_with_basic_auth(endpoint, token)?;
51//!
52//! // Use the client to interact with OpenFGA
53//! Ok(())
54//! }
55//! ```
56//!
57//! ## Client Credential Authentication
58//! ```no_run
59//! use openfga_client::client::BasicOpenFgaServiceClient;
60//! use url::Url;
61//!
62//! #[tokio::main]
63//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
64//! let endpoint = Url::parse("http://localhost:8081")?;
65//! let client_id = "your-client-id";
66//! let client_secret = "your-client-secret";
67//! let token_endpoint = Url::parse("http://localhost:8081/token")?;
68//! let scopes = vec!["scope1", "scope2"];
69//! let service_client = BasicOpenFgaServiceClient::new_with_client_credentials(endpoint, client_id, client_secret, token_endpoint, &scopes).await?;
70//!
71//! // Use the client to interact with OpenFGA
72//! Ok(())
73//! }
74//! ```
75//!
76//! ## Authorization Model Management and Migration
77//!
78//! For more details please check the [`TupleModelManager`](`migration::TupleModelManager`).
79//!
80//! Requires the following as part of the Authorization model:
81//! ```text
82//! type auth_model_id
83//! type model_version
84//! relations
85//! define openfga_id: [auth_model_id]
86//! define exists: [auth_model_id:*]
87//! ```
88//!
89//! Usage:
90//! ```no_run
91//! use openfga_client::client::{OpenFgaServiceClient, TupleKeyWithoutCondition};
92//! use openfga_client::migration::{AuthorizationModelVersion, MigrationFn, TupleModelManager};
93//! use openfga_client::tonic::codegen::StdError;
94//!
95//! #[allow(clippy::unused_async)]
96//! async fn v1_1_migration(
97//! client: OpenFgaServiceClient<tonic::transport::Channel>,
98//! ) -> std::result::Result<(), StdError> {
99//! let _ = client;
100//! Ok(())
101//! }
102//!
103//! #[tokio::main]
104//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
105//! let endpoint = "http://localhost:8081";
106//! let mut service_client = OpenFgaServiceClient::connect(endpoint).await?;
107//!
108//! let store_name = "my-store";
109//! let model_prefix = "my-model";
110//!
111//! let mut manager = TupleModelManager::new(service_client.clone(), store_name, model_prefix)
112//! // Migrations are executed in order for models that have not been previously migrated.
113//! // First model - version 1.0
114//! .add_model(
115//! serde_json::from_str(include_str!("../tests/model-manager/v1.0/schema.json"))?,
116//! AuthorizationModelVersion::new(1, 0),
117//! // For major version upgrades, this is where tuple migrations go.
118//! None::<MigrationFn<_>>,
119//! None::<MigrationFn<_>>,
120//! )
121//! // Second model - version 1.1
122//! .add_model(
123//! serde_json::from_str(include_str!("../tests/model-manager/v1.1/schema.json"))?,
124//! AuthorizationModelVersion::new(1, 1),
125//! // For major version upgrades, this is where tuple migrations go.
126//! Some(v1_1_migration),
127//! None::<MigrationFn<_>>,
128//! );
129//!
130//! // Perform the migration if necessary
131//! manager.migrate().await?;
132//!
133//! let store_id = service_client
134//! .get_store_by_name(store_name)
135//! .await?
136//! .expect("Store found")
137//! .id;
138//! let authorization_model_id = manager
139//! .get_authorization_model_id(AuthorizationModelVersion::new(1, 1))
140//! .await?
141//! .expect("Authorization model found");
142//! let client = service_client.into_client(&store_id, &authorization_model_id);
143//!
144//! // Use the client.
145//! // `store_id` and `authorization_model_id` are stored in the client and attached to all requests.
146//! let page_size = 100;
147//! let continuation_token = None;
148//! let _tuples = client
149//! .read(
150//! page_size,
151//! TupleKeyWithoutCondition {
152//! user: "user:peter".to_string(),
153//! relation: "owner".to_string(),
154//! object: "organization:my-org".to_string(),
155//! },
156//! continuation_token,
157//! )
158//! .await?;
159//!
160//! Ok(())
161//! }
162//! ```
163//!
164//! ## License
165//! This project is licensed under the Apache-2.0 License. See the LICENSE file for details.
166//!
167//! ## Contributing
168//! Contributions are welcome! Please open an issue or submit a pull request on GitHub.
169
170pub use prost_types;
171pub use prost_wkt_types;
172pub use tonic;
173pub mod display;
174pub mod error;
175pub mod migration;
176pub use url;
177
178mod client_ext;
179mod conversions;
180mod model_client;
181
182mod generated {
183 #![allow(clippy::all)]
184 #![allow(clippy::pedantic)]
185
186 include!("gen/openfga.v1.rs");
187}
188
189pub mod client {
190 //! Contains clients to connect to OpenFGA:
191 //!
192 //! * [`OpenFgaServiceClient`] is the generated client that allows full control over all parameters.
193 //! * [`OpenFgaClient`] is a wrapper around the generated client, that provides a more convenient interface and adds `store_id`, `authorization_model_id` and `consistency` to all requests.
194 //!
195 pub use open_fga_service_client::OpenFgaServiceClient;
196
197 #[cfg(feature = "auth-middle")]
198 pub use super::client_ext::{BasicAuthLayer, BasicOpenFgaServiceClient};
199 #[cfg(feature = "auth-middle")]
200 pub use super::model_client::BasicOpenFgaClient;
201 pub use super::{generated::*, model_client::OpenFgaClient};
202}
203
204#[cfg(test)]
205mod test_json_serde {
206 use super::client::*;
207
208 fn test_authorization_model_serde(schema: &str) {
209 let schema_json: serde_json::Value = schema.parse().unwrap();
210 let schema: AuthorizationModel = serde_json::from_value(schema_json.clone()).unwrap();
211 let value = serde_json::to_value(&schema).unwrap();
212 assert_eq!(schema_json, value);
213 }
214 #[test]
215 fn test_serde_custom_roles() {
216 test_authorization_model_serde(include_str!(
217 "../tests/sample-store/custom-roles/schema.json"
218 ));
219 }
220
221 #[test]
222 fn test_serde_entitlements() {
223 test_authorization_model_serde(include_str!(
224 "../tests/sample-store/entitlements/schema.json"
225 ));
226 }
227
228 #[test]
229 fn test_serde_expenses() {
230 test_authorization_model_serde(include_str!("../tests/sample-store/expenses/schema.json"));
231 }
232
233 // gdrive, github, iot, issue-tracker, modular, slack
234 #[test]
235 fn test_serde_gdrive() {
236 test_authorization_model_serde(include_str!("../tests/sample-store/gdrive/schema.json"));
237 }
238
239 #[test]
240 fn test_serde_github() {
241 test_authorization_model_serde(include_str!("../tests/sample-store/github/schema.json"));
242 }
243
244 #[test]
245 fn test_serde_iot() {
246 test_authorization_model_serde(include_str!("../tests/sample-store/iot/schema.json"));
247 }
248
249 #[test]
250 fn test_serde_modular() {
251 test_authorization_model_serde(include_str!("../tests/sample-store/modular/schema.json"));
252 }
253
254 #[test]
255 fn test_serde_slack() {
256 test_authorization_model_serde(include_str!("../tests/sample-store/slack/schema.json"));
257 }
258}