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 cached_at: OffsetDateTime,
121 pub document: InspectDocument,
122 pub final_url: Url,
123 pub headers: HeaderMap,
124}
125
126#[async_trait]
127pub trait IdentityStore: Send + Sync {
128 async fn find(&self, service_did: &str) -> Result<Option<AgentIdentity>, AgentError>;
129 async fn save(&self, identity: AgentIdentity) -> Result<(), AgentError>;
130}
131
132#[async_trait]
133pub trait CredentialStore: Send + Sync {
134 async fn delete(&self, service_did: &str, credential_id: &str) -> Result<(), AgentError>;
135 async fn find(
136 &self,
137 service_did: &str,
138 credential_id: &str,
139 ) -> Result<Option<CredentialRecord>, AgentError>;
140 async fn list(&self, service_did: &str) -> Result<Vec<CredentialRecord>, AgentError>;
141 async fn save(&self, credential: CredentialRecord) -> Result<(), AgentError>;
142}
143
144#[async_trait]
145pub trait InspectCache: Send + Sync {
146 async fn delete(&self, inspect_url: &Url) -> Result<(), AgentError>;
147 async fn find(&self, inspect_url: &Url) -> Result<Option<InspectCacheEntry>, AgentError>;
148 async fn save(&self, inspect_url: &Url, entry: InspectCacheEntry) -> Result<(), AgentError>;
149}
150
151#[derive(Clone)]
152pub struct ClientOptions {
153 pub allow_insecure_loopback: bool,
154 pub assertion_lifetime: Duration,
155 pub clock: Option<Arc<dyn Clock>>,
156 pub command_transport: Option<Arc<dyn HttpTransport>>,
157 pub credential_store: Option<Arc<dyn CredentialStore>>,
158 pub delay: Option<Arc<dyn Delay>>,
159 pub identity_provider: Arc<dyn IdentityProvider>,
160 pub identity_store: Option<Arc<dyn IdentityStore>>,
161 pub idempotency_keys: Option<Arc<dyn IdempotencyKeyProvider>>,
162 pub inspect_cache: Option<Arc<dyn InspectCache>>,
163 pub inspect_transport: Option<Arc<dyn HttpTransport>>,
164 pub maximum_response_bytes: usize,
165 pub request_timeout: Duration,
166}
167
168impl ClientOptions {
169 pub fn new(identity_provider: Arc<dyn IdentityProvider>) -> Self {
170 Self {
171 allow_insecure_loopback: false,
172 assertion_lifetime: aep_core::MAX_ASSERTION_LIFETIME,
173 clock: None,
174 command_transport: None,
175 credential_store: None,
176 delay: None,
177 identity_provider,
178 identity_store: None,
179 idempotency_keys: None,
180 inspect_cache: None,
181 inspect_transport: None,
182 maximum_response_bytes: 1 << 20,
183 request_timeout: Duration::from_secs(30),
184 }
185 }
186}
187
188#[derive(Clone, Debug, PartialEq)]
189pub struct CommandResult<T> {
190 pub body: T,
191 pub status: u16,
192 pub url: Url,
193}
194
195#[derive(Clone, Debug, Default, PartialEq)]
196pub struct EnrollOptions {
197 pub claims: Option<ClaimValues>,
198 pub idempotency_key: Option<String>,
199}
200
201#[derive(Clone, Debug, Default, Eq, PartialEq)]
202pub struct GrantOptions {
203 pub grant_type: Option<GrantType>,
204 pub idempotency_key: Option<String>,
205 pub preferred_grant_types: Vec<GrantType>,
206 pub requested_scopes: Vec<String>,
207}
208
209#[derive(Clone, PartialEq)]
210pub struct GrantResult {
211 pub credential: Option<BuiltInGrantResponse>,
212 pub grant_type: GrantType,
213 pub raw: Value,
214}
215
216impl fmt::Debug for GrantResult {
217 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
218 formatter
219 .debug_struct("GrantResult")
220 .field("credential", &self.credential)
221 .field("grant_type", &self.grant_type)
222 .field("raw", &"[REDACTED]")
223 .finish()
224 }
225}
226
227#[derive(Clone, Debug, Default, Eq, PartialEq)]
228pub struct RevokeOptions {
229 pub all_grant_types: bool,
230 pub credential_id: Option<String>,
231 pub grant_type: Option<GrantType>,
232 pub idempotency_key: Option<String>,
233}
234
235#[derive(Clone, Copy, Debug, Eq, PartialEq)]
236pub struct WaitOptions {
237 pub interval: Duration,
238 pub timeout: Duration,
239}
240
241impl Default for WaitOptions {
242 fn default() -> Self {
243 Self {
244 interval: Duration::from_secs(1),
245 timeout: Duration::from_secs(30),
246 }
247 }
248}
249
250#[derive(Clone, PartialEq)]
251pub struct AuthenticationOptions {
252 pub carrier: aep_core::AuthorizationCarrier,
253 pub client_assertion_only: bool,
254 pub credential_id: Option<String>,
255 pub grant_type: Option<GrantType>,
256 pub resource: Url,
257}
258
259impl fmt::Debug for AuthenticationOptions {
260 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
261 formatter
262 .debug_struct("AuthenticationOptions")
263 .field("carrier", &self.carrier)
264 .field("client_assertion_only", &self.client_assertion_only)
265 .field("credential_id", &self.credential_id)
266 .field("grant_type", &self.grant_type)
267 .field("resource", &"[REDACTED]")
268 .finish()
269 }
270}
271
272#[derive(Clone, PartialEq)]
273pub struct AuthenticationResult {
274 pub headers: HeaderMap,
275 pub method: AuthenticationMethod,
276}
277
278impl fmt::Debug for AuthenticationResult {
279 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
280 formatter
281 .debug_struct("AuthenticationResult")
282 .field("headers", &"[REDACTED]")
283 .field("method", &self.method)
284 .finish()
285 }
286}
287
288pub(crate) fn assertion_operation(command: &Command) -> Option<AssertionOperation> {
289 match command {
290 Command::Enroll => Some(AssertionOperation::Enroll),
291 Command::Grant => Some(AssertionOperation::Grant),
292 Command::Revoke => Some(AssertionOperation::Revoke),
293 Command::Status => Some(AssertionOperation::Status),
294 Command::Inspect | Command::Other(_) => None,
295 }
296}
297
298pub(crate) fn authorization(
299 carrier: aep_core::AuthorizationCarrier,
300 scheme: aep_core::CredentialScheme,
301 credentials: String,
302) -> ProtectedResourceAuthorization {
303 ProtectedResourceAuthorization {
304 carrier,
305 scheme,
306 credentials,
307 }
308}