ankurah_core/
policy.rs

1use crate::{
2    entity::Entity,
3    error::ValidationError,
4    node::{ContextData, Node, NodeInner},
5    property::PropertyError,
6    proto::{self},
7    storage::StorageEngine,
8};
9use ankql::{ast::Predicate, error::ParseError};
10use ankurah_proto::Attested;
11use async_trait::async_trait;
12use thiserror::Error;
13use tracing::debug;
14/// The result of a policy check. Currently just Allow/Deny, but will support Trace in the future
15#[derive(Debug, Error)]
16pub enum AccessDenied {
17    #[error("Access denied by policy: {0}")]
18    ByPolicy(&'static str),
19    #[error("Access denied by collection: {0}")]
20    CollectionDenied(proto::CollectionId),
21    #[error("Access denied by property error: {0}")]
22    PropertyError(Box<PropertyError>),
23    #[error("Access denied by parse error: {0}")]
24    ParseError(ParseError),
25    #[error("Insufficient attestation")]
26    InsufficientAttestation,
27}
28
29impl From<PropertyError> for AccessDenied {
30    fn from(error: PropertyError) -> Self { AccessDenied::PropertyError(Box::new(error)) }
31}
32impl From<ParseError> for AccessDenied {
33    fn from(error: ParseError) -> Self { AccessDenied::ParseError(error) }
34}
35
36#[cfg(feature = "wasm")]
37impl From<AccessDenied> for wasm_bindgen::JsValue {
38    fn from(error: AccessDenied) -> Self { wasm_bindgen::JsValue::from_str(&error.to_string()) }
39}
40
41impl AccessDenied {}
42
43/// PolicyAgents control access to resources, by:
44/// - signing requests which are sent to other nodes - this may come in the form of a bearer token, or a signature, or some other arbitrary method of authentication as defined by the PolicyAgent
45/// - checking access for requests. If approved, yield a ContextData
46/// - attesting events for requests that were approved
47/// - validating attestations for events
48#[async_trait]
49pub trait PolicyAgent: Clone + Send + Sync + 'static {
50    /// The context type that will be used for all resource requests.
51    /// This will typically represent a user or service account.
52    type ContextData: ContextData;
53
54    /// Create relevant auth data for a given request
55    /// This could be a JWT or a cryptographic signature, or some other arbitrary method of authentication as defined by the PolicyAgent
56    fn sign_request<SE: StorageEngine>(
57        &self,
58        node: &NodeInner<SE, Self>,
59        cdata: &Self::ContextData,
60        request: &proto::NodeRequest,
61    ) -> proto::AuthData;
62
63    /// Reverse of sign_request. This will typically parse + validate the auth data and return a ContextData if valid
64    /// optionally, the PolicyAgent may introspect the request directly for signature validation, or other policy checks
65    /// Note that check_read and check_write will be called with the ContextData as well if the request is approved
66    /// Meaning that the PolicyAgent need not necessarily introspect the request directly here if it doesn't want to.
67    async fn check_request<SE: StorageEngine>(
68        &self,
69        node: &Node<SE, Self>,
70        auth: &proto::AuthData,
71        request: &proto::NodeRequest,
72    ) -> Result<Self::ContextData, ValidationError>
73    where
74        Self: Sized;
75
76    /// Check the event and optionally return an attestation
77    /// This could be used to attest that the event has passed the policy check for a given context
78    /// or you could just return None if you don't want to attest to the event
79    fn check_event<SE: StorageEngine>(
80        &self,
81        node: &Node<SE, Self>,
82        cdata: &Self::ContextData,
83        entity: &Entity,
84        event: &proto::Event,
85    ) -> Result<Option<proto::Attestation>, AccessDenied>;
86
87    /// Validate an event attestation
88    /// This could be used to validate that the event has sufficient attestation as to be trusted
89    fn validate_received_event<SE: StorageEngine>(
90        &self,
91        node: &Node<SE, Self>,
92        received_from_node: &proto::EntityId,
93        event: &Attested<proto::Event>,
94    ) -> Result<(), AccessDenied>;
95
96    /// Attest a state which the caller asserts is valid. Implementation may return None if no attestation is required
97    fn attest_state<SE: StorageEngine>(&self, node: &Node<SE, Self>, state: &proto::EntityState) -> Option<proto::Attestation>;
98
99    fn validate_received_state<SE: StorageEngine>(
100        &self,
101        node: &Node<SE, Self>,
102        received_from_node: &proto::EntityId,
103        state: &Attested<proto::EntityState>,
104    ) -> Result<(), AccessDenied>;
105
106    // For checking if a context can access a collection
107    // For checking if a context can access a collection
108    fn can_access_collection(&self, data: &Self::ContextData, collection: &proto::CollectionId) -> Result<(), AccessDenied>;
109
110    /// Filter a predicate based on the context data
111    fn filter_predicate(
112        &self,
113        data: &Self::ContextData,
114        collection: &proto::CollectionId,
115        predicate: Predicate,
116    ) -> Result<Predicate, AccessDenied>;
117
118    /// Check if a context can read an entity
119    /// If the policy agent wants to inspect the entity state, it can do so with either TemporaryEntity::new or entityset.with_state
120    /// Optimization: Consider adding a common trait implemented by Entity and TemporaryEntity returned by entityset.get_evaluation_entity that
121    /// returns a real entity if resident, falling back to a temporary entity if not. (as the former case would save cycles creating/populating the backends)
122    fn check_read(
123        &self,
124        data: &Self::ContextData,
125        id: &proto::EntityId,
126        collection: &proto::CollectionId,
127        state: &proto::State,
128    ) -> Result<(), AccessDenied>;
129
130    /// Check if a context can read an event
131    fn check_read_event(&self, data: &Self::ContextData, event: &Attested<proto::Event>) -> Result<(), AccessDenied>;
132
133    /// Check if a context can edit an entity
134    fn check_write(&self, data: &Self::ContextData, entity: &Entity, event: Option<&proto::Event>) -> Result<(), AccessDenied>;
135
136    // fn check_write_event(&self, data: &Self::ContextData, entity: &Entity, event: &proto::Event) -> Result<(), AccessDenied>;
137
138    // // For checking if a context can subscribe to changes
139    // fn can_subscribe(&self, data: &Self::ContextData, collection: &CollectionId, predicate: &Predicate) -> AccessResult;
140
141    // // For checking if a context can communicate with another node
142    // fn can_communicate_with_node(&self, data: &Self::ContextData, node_id: &ID) -> AccessResult;
143}
144
145/// A policy agent that allows all operations
146#[derive(Clone)]
147pub struct PermissiveAgent {}
148
149impl Default for PermissiveAgent {
150    fn default() -> Self { Self::new() }
151}
152
153impl PermissiveAgent {
154    pub fn new() -> Self { Self {} }
155}
156
157#[async_trait]
158impl PolicyAgent for PermissiveAgent {
159    type ContextData = &'static DefaultContext;
160
161    /// Create relevant auth data for a given request
162    fn sign_request<SE: StorageEngine>(
163        &self,
164        _node: &NodeInner<SE, Self>,
165        _cdata: &Self::ContextData,
166        _request: &proto::NodeRequest,
167    ) -> proto::AuthData {
168        debug!("PermissiveAgent sign_request: {:?}", _request);
169        proto::AuthData(vec![])
170    }
171
172    /// Validate auth data and yield the context data if valid
173    async fn check_request<SE: StorageEngine>(
174        &self,
175        _node: &Node<SE, Self>,
176        _auth: &proto::AuthData,
177        _request: &proto::NodeRequest,
178    ) -> Result<Self::ContextData, ValidationError> {
179        Ok(DEFAULT_CONTEXT)
180    }
181
182    /// Create an attestation for an event
183    fn check_event<SE: StorageEngine>(
184        &self,
185        _node: &Node<SE, Self>,
186        _cdata: &Self::ContextData,
187        _entity: &Entity,
188        _event: &proto::Event,
189    ) -> Result<Option<proto::Attestation>, AccessDenied> {
190        Ok(None)
191    }
192
193    fn validate_received_event<SE: StorageEngine>(
194        &self,
195        _node: &Node<SE, Self>,
196        _from_node: &proto::EntityId,
197        _event: &proto::Attested<proto::Event>,
198    ) -> Result<(), AccessDenied> {
199        Ok(())
200    }
201
202    fn attest_state<SE: StorageEngine>(&self, _node: &Node<SE, Self>, _state: &proto::EntityState) -> Option<proto::Attestation> {
203        // This PolicyAgent does not require attestation, so we return None
204        // Client/Server policy agents may also return None and defer to the server identity to validate the received state
205        None
206    }
207
208    fn validate_received_state<SE: StorageEngine>(
209        &self,
210        _node: &Node<SE, Self>,
211        _from_node: &proto::EntityId,
212        _state: &Attested<proto::EntityState>,
213    ) -> Result<(), AccessDenied> {
214        // This PolicyAgent does not require validation, so we return Ok
215        // Client/Server policy agents may use the _from_node to validate the received state rather than an attestation
216        Ok(())
217    }
218
219    fn can_access_collection(&self, _context: &Self::ContextData, _collection: &proto::CollectionId) -> Result<(), AccessDenied> { Ok(()) }
220
221    fn check_read(
222        &self,
223        _context: &Self::ContextData,
224        _id: &proto::EntityId,
225        _collection: &proto::CollectionId,
226        _state: &proto::State,
227    ) -> Result<(), AccessDenied> {
228        // If your policy agent wants to inspect the entity properties, it can do so with either TemporaryEntity::new or entityset.with_state
229        Ok(())
230    }
231
232    fn check_read_event(&self, _context: &Self::ContextData, _event: &Attested<proto::Event>) -> Result<(), AccessDenied> {
233        // TODO - think about the best way to get the entity properties for cases where we want to inspect
234        // presumably this would need to be changed to async, and we'd need a way to retrieve entity state from storage, or possibly even a remote node
235        Ok(())
236    }
237
238    fn check_write(&self, _context: &Self::ContextData, _entity: &Entity, _event: Option<&proto::Event>) -> Result<(), AccessDenied> {
239        Ok(())
240    }
241
242    fn filter_predicate(
243        &self,
244        _context: &Self::ContextData,
245        _collection: &proto::CollectionId,
246        predicate: Predicate,
247    ) -> Result<Predicate, AccessDenied> {
248        Ok(predicate)
249    }
250
251    // fn can_read_entity(&self, _context: &Self::ContextData, _entity: &Entity) -> AccessResult { AccessResult::Allow }
252
253    // fn can_modify_entity(&self, _context: &Self::ContextData, _collection: &CollectionId, _id: &ID) -> AccessResult { AccessResult::Allow }
254
255    // fn can_create_in_collection(&self, _context: &Self::ContextData, _collection: &CollectionId) -> AccessResult { AccessResult::Allow }
256
257    // fn can_subscribe(&self, _context: &Self::ContextData, _collection: &CollectionId, _predicate: &Predicate) -> AccessResult {
258    //     AccessResult::Allow
259    // }
260
261    // fn can_communicate_with_node(&self, _context: &Self::ContextData, _node_id: &ID) -> AccessResult { AccessResult::Allow }
262}
263
264/// A default context that is used when no context is needed
265
266pub struct DefaultContext {}
267pub static DEFAULT_CONTEXT: &DefaultContext = &DefaultContext {};
268
269impl Default for DefaultContext {
270    fn default() -> Self { Self::new() }
271}
272
273impl DefaultContext {
274    pub fn new() -> Self { Self {} }
275}
276
277#[async_trait]
278impl ContextData for &'static DefaultContext {}