Skip to main content

aep_agent/
types.rs

1use std::{collections::BTreeMap, fmt, sync::Arc, time::Duration};
2
3use aep_core::{
4    AssertionOperation, AuthenticationMethod, BuiltInGrantResponse, ClaimValues,
5    ClientAssertionClaims, Command, GrantType, HttpTransport, IdentityMethod, InspectDocument,
6    ProtectedResourceAuthorization, SigningAlgorithm,
7};
8use async_trait::async_trait;
9use http::HeaderMap;
10use serde_json::Value;
11use time::OffsetDateTime;
12use url::Url;
13
14use crate::AgentError;
15
16#[derive(Clone, Debug, PartialEq)]
17pub struct AgentIdentity {
18    pub agent_did: String,
19    pub identity_method: IdentityMethod,
20    pub service_did: String,
21    pub signing_algorithms: Vec<SigningAlgorithm>,
22    pub metadata: BTreeMap<String, String>,
23}
24
25#[derive(Clone, Debug, PartialEq)]
26pub struct IdentityRequest {
27    pub inspection: Inspection,
28}
29
30#[async_trait]
31pub trait AssertionSigner: Send + Sync {
32    async fn sign(
33        &self,
34        claims: &ClientAssertionClaims,
35        algorithms: &[SigningAlgorithm],
36    ) -> Result<String, AgentError>;
37}
38
39#[async_trait]
40pub trait IdentityProvider: Send + Sync {
41    async fn get_or_create_identity(
42        &self,
43        request: IdentityRequest,
44    ) -> Result<AgentIdentity, AgentError>;
45    async fn signer_for(
46        &self,
47        identity: &AgentIdentity,
48    ) -> Result<Arc<dyn AssertionSigner>, AgentError>;
49}
50
51#[derive(Clone, Debug, Eq, PartialEq)]
52pub struct OperationKey {
53    pub command: Command,
54    pub credential_id: Option<String>,
55    pub grant_type: Option<GrantType>,
56    pub service_did: String,
57    pub service_url: Url,
58}
59
60#[async_trait]
61pub trait IdempotencyKeyProvider: Send + Sync {
62    async fn create_key(&self, operation: &OperationKey) -> Result<String, AgentError>;
63}
64
65pub trait Clock: Send + Sync {
66    fn now(&self) -> OffsetDateTime;
67}
68
69#[async_trait]
70pub trait Delay: Send + Sync {
71    async fn sleep(&self, duration: Duration);
72}
73
74#[derive(Clone, PartialEq)]
75pub struct CredentialRecord {
76    pub credential_id: String,
77    pub expires_at: OffsetDateTime,
78    pub grant_type: GrantType,
79    pub issued_at: OffsetDateTime,
80    pub payload: Value,
81    pub service_did: String,
82    pub service_url: Url,
83}
84
85impl fmt::Debug for CredentialRecord {
86    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
87        formatter
88            .debug_struct("CredentialRecord")
89            .field("credential_id", &self.credential_id)
90            .field("expires_at", &self.expires_at)
91            .field("grant_type", &self.grant_type)
92            .field("issued_at", &self.issued_at)
93            .field("payload", &"[REDACTED]")
94            .field("service_did", &self.service_did)
95            .field("service_url", &"[REDACTED]")
96            .finish()
97    }
98}
99
100#[derive(Clone, Debug, PartialEq)]
101pub struct Inspection {
102    pub cache_control: Option<String>,
103    pub document: InspectDocument,
104    pub etag: Option<String>,
105    pub final_url: Url,
106    pub inspect_url: Url,
107    pub last_modified: Option<String>,
108    pub service_url: Url,
109}
110
111impl Inspection {
112    pub fn command_url(&self, command: &Command) -> Result<Url, AgentError> {
113        let path = aep_core::command_path_from_inspect(&self.document, command)?;
114        Ok(self.service_url.join(&path)?)
115    }
116}
117
118#[derive(Clone, Debug, PartialEq)]
119pub struct InspectCacheEntry {
120    pub cache_control: Option<String>,
121    pub cached_at: OffsetDateTime,
122    pub document: InspectDocument,
123    pub etag: Option<String>,
124    pub final_url: Url,
125    pub last_modified: Option<String>,
126}
127
128#[async_trait]
129pub trait IdentityStore: Send + Sync {
130    async fn find(&self, service_did: &str) -> Result<Option<AgentIdentity>, AgentError>;
131    async fn save(&self, identity: AgentIdentity) -> Result<(), AgentError>;
132}
133
134#[async_trait]
135pub trait CredentialStore: Send + Sync {
136    async fn delete(&self, service_did: &str, credential_id: &str) -> Result<(), AgentError>;
137    async fn find(
138        &self,
139        service_did: &str,
140        credential_id: &str,
141    ) -> Result<Option<CredentialRecord>, AgentError>;
142    async fn list(&self, service_did: &str) -> Result<Vec<CredentialRecord>, AgentError>;
143    async fn save(&self, credential: CredentialRecord) -> Result<(), AgentError>;
144}
145
146#[async_trait]
147pub trait InspectCache: Send + Sync {
148    async fn delete(&self, inspect_url: &Url) -> Result<(), AgentError>;
149    async fn find(&self, inspect_url: &Url) -> Result<Option<InspectCacheEntry>, AgentError>;
150    async fn save(&self, inspect_url: &Url, entry: InspectCacheEntry) -> Result<(), AgentError>;
151}
152
153#[derive(Clone)]
154pub struct ClientOptions {
155    pub allow_insecure_loopback: bool,
156    pub assertion_lifetime: Duration,
157    pub clock: Option<Arc<dyn Clock>>,
158    pub command_transport: Option<Arc<dyn HttpTransport>>,
159    pub credential_store: Option<Arc<dyn CredentialStore>>,
160    pub delay: Option<Arc<dyn Delay>>,
161    pub identity_provider: Arc<dyn IdentityProvider>,
162    pub identity_store: Option<Arc<dyn IdentityStore>>,
163    pub idempotency_keys: Option<Arc<dyn IdempotencyKeyProvider>>,
164    pub inspect_cache: Option<Arc<dyn InspectCache>>,
165    pub inspect_transport: Option<Arc<dyn HttpTransport>>,
166    pub maximum_response_bytes: usize,
167    pub request_timeout: Duration,
168}
169
170impl ClientOptions {
171    pub fn new(identity_provider: Arc<dyn IdentityProvider>) -> Self {
172        Self {
173            allow_insecure_loopback: false,
174            assertion_lifetime: aep_core::MAX_ASSERTION_LIFETIME,
175            clock: None,
176            command_transport: None,
177            credential_store: None,
178            delay: None,
179            identity_provider,
180            identity_store: None,
181            idempotency_keys: None,
182            inspect_cache: None,
183            inspect_transport: None,
184            maximum_response_bytes: 1 << 20,
185            request_timeout: Duration::from_secs(30),
186        }
187    }
188}
189
190#[derive(Clone, Debug, PartialEq)]
191pub struct CommandResult<T> {
192    pub body: T,
193    pub status: u16,
194    pub url: Url,
195}
196
197#[derive(Clone, Debug, Default, PartialEq)]
198pub struct EnrollOptions {
199    pub claims: Option<ClaimValues>,
200    pub idempotency_key: Option<String>,
201}
202
203#[derive(Clone, Debug, Default, Eq, PartialEq)]
204pub struct GrantOptions {
205    pub grant_type: Option<GrantType>,
206    pub idempotency_key: Option<String>,
207    pub preferred_grant_types: Vec<GrantType>,
208    pub requested_scopes: Vec<String>,
209}
210
211#[derive(Clone, PartialEq)]
212pub struct GrantResult {
213    pub credential: Option<BuiltInGrantResponse>,
214    pub grant_type: GrantType,
215    pub raw: Value,
216}
217
218impl fmt::Debug for GrantResult {
219    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
220        formatter
221            .debug_struct("GrantResult")
222            .field("credential", &self.credential)
223            .field("grant_type", &self.grant_type)
224            .field("raw", &"[REDACTED]")
225            .finish()
226    }
227}
228
229#[derive(Clone, Debug, Default, Eq, PartialEq)]
230pub struct RevokeOptions {
231    pub all_grant_types: bool,
232    pub credential_id: Option<String>,
233    pub grant_type: Option<GrantType>,
234    pub idempotency_key: Option<String>,
235}
236
237#[derive(Clone, Copy, Debug, Eq, PartialEq)]
238pub struct WaitOptions {
239    pub interval: Duration,
240    pub timeout: Duration,
241}
242
243impl Default for WaitOptions {
244    fn default() -> Self {
245        Self {
246            interval: Duration::from_secs(1),
247            timeout: Duration::from_secs(30),
248        }
249    }
250}
251
252#[derive(Clone, PartialEq)]
253pub struct AuthenticationOptions {
254    pub carrier: aep_core::AuthorizationCarrier,
255    pub client_assertion_only: bool,
256    pub credential_id: Option<String>,
257    pub grant_type: Option<GrantType>,
258    pub resource: Url,
259}
260
261impl fmt::Debug for AuthenticationOptions {
262    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
263        formatter
264            .debug_struct("AuthenticationOptions")
265            .field("carrier", &self.carrier)
266            .field("client_assertion_only", &self.client_assertion_only)
267            .field("credential_id", &self.credential_id)
268            .field("grant_type", &self.grant_type)
269            .field("resource", &"[REDACTED]")
270            .finish()
271    }
272}
273
274#[derive(Clone, PartialEq)]
275pub struct AuthenticationResult {
276    pub headers: HeaderMap,
277    pub method: AuthenticationMethod,
278}
279
280impl fmt::Debug for AuthenticationResult {
281    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
282        formatter
283            .debug_struct("AuthenticationResult")
284            .field("headers", &"[REDACTED]")
285            .field("method", &self.method)
286            .finish()
287    }
288}
289
290pub(crate) fn assertion_operation(command: &Command) -> Option<AssertionOperation> {
291    match command {
292        Command::Enroll => Some(AssertionOperation::Enroll),
293        Command::Grant => Some(AssertionOperation::Grant),
294        Command::Revoke => Some(AssertionOperation::Revoke),
295        Command::Status => Some(AssertionOperation::Status),
296        Command::Inspect | Command::Other(_) => None,
297    }
298}
299
300pub(crate) fn authorization(
301    carrier: aep_core::AuthorizationCarrier,
302    scheme: aep_core::CredentialScheme,
303    credentials: String,
304) -> ProtectedResourceAuthorization {
305    ProtectedResourceAuthorization {
306        carrier,
307        scheme,
308        credentials,
309    }
310}