rws_server/authz/mod.rs
1//! Authorization (18.14.0, #32): who the caller is ([`Identity`]), and —
2//! from later tranches — what they may do, as relationship-based access
3//! control (Zanzibar-style tuples and a schema).
4//!
5//! Every authenticator in rws hands the authenticated caller to what runs
6//! after it as one [`Identity`]: `JwtLayer`, `OidcAuth`, `SamlSp`,
7//! `BasicAuthLayer`, `ForwardAuthLayer` (with
8//! [`identity_header`](crate::auth::forward::ForwardAuthLayer::identity_header))
9//! and MCP bearer / OAuth tokens. A handler reads it with
10//! [`Identity::current`]:
11//!
12//! ```rust,no_run
13//! # extern crate rws_server as rust_web_server;
14//! use rust_web_server::app::App;
15//! use rust_web_server::authz::Identity;
16//! use rust_web_server::response::{Response, STATUS_CODE_REASON_PHRASE};
17//!
18//! let app = App::with_state(()).get("/me", |_, _, _, _| match Identity::current() {
19//! Some(me) => Response::text(STATUS_CODE_REASON_PHRASE.n200_ok, me.subject()),
20//! None => Response::text(STATUS_CODE_REASON_PHRASE.n401_unauthorized, "who are you?"),
21//! });
22//! ```
23//!
24//! The identity is not a request header: the authenticator runs the rest of
25//! the request inside [`with_identity`], so a client can't send one.
26
27#[doc(hidden)]
28pub mod config;
29pub(crate) mod identity;
30mod layer;
31pub mod rebac;
32#[cfg(any(feature = "model-sqlite", feature = "model-postgres", feature = "model-mysql"))]
33pub mod sql;
34
35pub use identity::{with_identity, Identity};
36pub use layer::{AuthzLayer, ObjectTemplate};
37#[cfg(any(feature = "model-sqlite", feature = "model-postgres", feature = "model-mysql"))]
38pub use sql::SqlStore;
39pub use rebac::{AuthzError, Authorizer, MemoryStore, ObjectRef, Schema, Subject, Tuple, TupleFilter, TupleStore};
40#[cfg(feature = "http2")]
41pub use identity::scope_identity;
42
43#[cfg(any(test, feature = "test-support"))]
44#[doc(hidden)]
45pub mod test_support {
46 use crate::application::Application;
47 use crate::request::Request;
48 use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
49 use crate::server::ConnectionInfo;
50
51 /// Answers with the caller: `<subject> via <source>`, or `anonymous`.
52 #[doc(hidden)]
53 pub struct WhoAmI;
54
55 impl Application for WhoAmI {
56 fn execute(&self, _request: &Request, _connection: &ConnectionInfo) -> Result<Response, String> {
57 let who = super::Identity::current().map_or("anonymous".to_string(), |i| format!("{} via {}", i.subject(), i.source()));
58 Ok(Response::text(STATUS_CODE_REASON_PHRASE.n200_ok, &who))
59 }
60 }
61
62 #[doc(hidden)]
63 pub fn body(response: &Response) -> String {
64 String::from_utf8_lossy(&response.content_range_list.first().map(|c| c.body.clone()).unwrap_or_default()).to_string()
65 }
66}
67
68#[cfg(test)]
69mod tests;