1use core::future::Future;
10use std::sync::Arc;
11
12use mkit_core::protocol::PackKey;
13
14use crate::error::{Redacted, ServerError};
15use crate::op::{AuthzFacts, Operation};
16use crate::quota::{QuotaCharge, QuotaLimits, QuotaScope};
17use crate::rt::{MaybeSend, MaybeSync};
18use crate::store::BlobKey;
19
20pub trait Authorizer: MaybeSend + MaybeSync {
26 fn is_open(&self) -> bool {
28 false
29 }
30
31 fn authorize(
34 &self,
35 op: &Operation,
36 ) -> impl Future<Output = Result<AuthzFacts, ServerError>> + MaybeSend;
37}
38
39#[derive(Clone)]
47#[non_exhaustive]
48pub struct AdmissionInput<'a> {
49 pub op: &'a Operation,
51 pub declared_bytes: u64,
53 pub pack_id: Option<PackKey>,
55 pub creates_namespace: bool,
57 pub creates_repo: bool,
59 pub new_to_repo_bytes: Option<u64>,
61 pub idempotency_key: Option<&'a str>,
63 pub write_quota: Option<QuotaLimits>,
66 pub audience: Option<&'a str>,
68 pub credential_headers: &'a [CredentialHeader],
70}
71
72impl core::fmt::Debug for AdmissionInput<'_> {
73 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
74 f.debug_struct("AdmissionInput")
75 .field("op", &self.op)
76 .field("declared_bytes", &self.declared_bytes)
77 .field("pack_id", &self.pack_id)
78 .field("audience", &self.audience)
79 .finish_non_exhaustive()
80 }
81}
82
83#[derive(Clone, PartialEq, Eq)]
85#[non_exhaustive]
86pub struct CredentialHeader {
87 pub name: String,
89 pub value: Redacted,
91}
92
93impl CredentialHeader {
94 #[must_use]
96 pub fn new(name: impl Into<String>, value: Redacted) -> Self {
97 Self {
98 name: name.into(),
99 value,
100 }
101 }
102}
103
104impl core::fmt::Debug for CredentialHeader {
105 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
106 f.debug_struct("CredentialHeader")
107 .field("name", &self.name)
108 .finish_non_exhaustive()
109 }
110}
111
112impl<'a> AdmissionInput<'a> {
113 #[must_use]
115 pub fn new(op: &'a Operation) -> Self {
116 Self {
117 op,
118 declared_bytes: 0,
119 pack_id: None,
120 creates_namespace: op.creation.namespace,
121 creates_repo: op.creation.repo,
122 new_to_repo_bytes: None,
123 idempotency_key: op.auth.as_ref().map(|auth| auth.nonce.as_str()),
124 write_quota: None,
125 audience: None,
126 credential_headers: &[],
127 }
128 }
129}
130
131pub const ADMISSION_EXPOSE_HEADERS: [&str; 4] = [
134 "WWW-Authenticate",
135 "PAYMENT-REQUIRED",
136 "Payment-Receipt",
137 "PAYMENT-RESPONSE",
138];
139
140#[derive(Debug, Clone, PartialEq, Eq)]
142pub struct Challenge {
143 pub scheme: String,
145 pub value: String,
147}
148
149#[derive(Debug, Clone)]
152#[non_exhaustive]
153pub enum AdmissionDecision {
154 #[non_exhaustive]
157 Allow {
158 charges: Vec<QuotaCharge>,
160 reservation: Option<String>,
162 response_headers: Vec<(String, String)>,
164 external_ref: Option<String>,
166 },
167 #[non_exhaustive]
169 Challenge {
170 challenges: Vec<Challenge>,
172 description: String,
174 response_headers: Vec<(String, String)>,
176 },
177 Deny(ServerError),
179}
180
181impl AdmissionDecision {
182 #[must_use]
184 pub fn allow(charges: Vec<QuotaCharge>) -> Self {
185 Self::Allow {
186 charges,
187 reservation: None,
188 response_headers: Vec::new(),
189 external_ref: None,
190 }
191 }
192
193 #[must_use]
195 pub fn with_reservation(mut self, id: impl Into<String>) -> Self {
196 if let Self::Allow { reservation, .. } = &mut self {
197 *reservation = Some(id.into());
198 }
199 self
200 }
201
202 #[must_use]
204 pub fn with_response_header(
205 mut self,
206 name: impl Into<String>,
207 value: impl Into<String>,
208 ) -> Self {
209 match &mut self {
210 Self::Allow {
211 response_headers, ..
212 }
213 | Self::Challenge {
214 response_headers, ..
215 } => {
216 response_headers.push((name.into(), value.into()));
217 }
218 Self::Deny(_) => {}
219 }
220 self
221 }
222
223 #[must_use]
225 pub fn with_external_ref(mut self, reference: impl Into<String>) -> Self {
226 if let Self::Allow { external_ref, .. } = &mut self {
227 *external_ref = Some(reference.into());
228 }
229 self
230 }
231
232 #[must_use]
234 pub fn challenge(challenges: Vec<Challenge>, description: impl Into<String>) -> Self {
235 Self::Challenge {
236 challenges,
237 description: description.into(),
238 response_headers: Vec::new(),
239 }
240 }
241
242 #[must_use]
244 pub fn deny(message: impl Into<String>) -> Self {
245 Self::Deny(ServerError::permission_denied(message.into()))
246 }
247}
248
249pub trait Admission: MaybeSend + MaybeSync {
251 fn is_default(&self) -> bool {
253 false
254 }
255
256 fn admit(
264 &self,
265 input: &AdmissionInput<'_>,
266 ) -> impl Future<Output = Result<AdmissionDecision, ServerError>> + MaybeSend;
267}
268
269pub trait PreReceive: MaybeSend + MaybeSync {
271 fn check(
273 &self,
274 op: &Operation,
275 pack: Option<&BlobKey>,
276 ) -> impl Future<Output = Result<(), ServerError>> + MaybeSend;
277}
278
279pub trait ReceiptSigner: MaybeSend + MaybeSync {
281 fn sign(&self, op: &Operation) -> impl Future<Output = Option<Vec<u8>>> + MaybeSend;
283}
284
285use super::durable_outcome::{DeliveryError, Outcome};
286
287pub trait OutcomeSink: MaybeSend + MaybeSync {
292 fn deliver(
294 &self,
295 outcome: &Outcome,
296 ) -> impl Future<Output = Result<(), DeliveryError>> + MaybeSend;
297
298 fn deliver_batch(
300 &self,
301 outcomes: &[Outcome],
302 ) -> impl Future<Output = Vec<Result<(), DeliveryError>>> + MaybeSend {
303 async move {
304 let mut results = Vec::with_capacity(outcomes.len());
305 for outcome in outcomes {
306 results.push(self.deliver(outcome).await);
307 }
308 results
309 }
310 }
311}
312
313impl<T: OutcomeSink> OutcomeSink for Arc<T> {
314 async fn deliver(&self, outcome: &Outcome) -> Result<(), DeliveryError> {
315 T::deliver(self, outcome).await
316 }
317
318 async fn deliver_batch(&self, outcomes: &[Outcome]) -> Vec<Result<(), DeliveryError>> {
319 T::deliver_batch(self, outcomes).await
320 }
321}
322
323pub trait HookSet: MaybeSend + MaybeSync {
325 type Az: Authorizer;
327 type Ad: Admission;
329 type Pr: PreReceive;
331 type Rs: ReceiptSigner;
333 type Os: OutcomeSink;
335
336 fn authorizer(&self) -> &Self::Az;
338 fn admission(&self) -> &Self::Ad;
340 fn pre_receive(&self) -> &Self::Pr;
342 fn receipts(&self) -> &Self::Rs;
344 fn outcomes(&self) -> &Self::Os;
346}
347
348#[derive(Debug, Clone, Default)]
350pub struct Hooks<
351 Az = OpenAuthorizer,
352 Ad = DefaultAdmission,
353 Pr = NoPreReceive,
354 Rs = NoReceipts,
355 Os = NoOutcomes,
356> {
357 pub authorizer: Az,
359 pub admission: Ad,
361 pub pre_receive: Pr,
363 pub receipts: Rs,
365 pub outcomes: Os,
367}
368
369impl Hooks {
370 #[must_use]
372 pub fn new() -> Self {
373 Self::default()
374 }
375}
376
377impl<Az, Ad, Pr, Rs, Os> HookSet for Hooks<Az, Ad, Pr, Rs, Os>
378where
379 Az: Authorizer,
380 Ad: Admission,
381 Pr: PreReceive,
382 Rs: ReceiptSigner,
383 Os: OutcomeSink,
384{
385 type Az = Az;
386 type Ad = Ad;
387 type Pr = Pr;
388 type Rs = Rs;
389 type Os = Os;
390
391 fn authorizer(&self) -> &Az {
392 &self.authorizer
393 }
394 fn admission(&self) -> &Ad {
395 &self.admission
396 }
397 fn pre_receive(&self) -> &Pr {
398 &self.pre_receive
399 }
400 fn receipts(&self) -> &Rs {
401 &self.receipts
402 }
403 fn outcomes(&self) -> &Os {
404 &self.outcomes
405 }
406}
407
408#[derive(Debug, Clone, Copy, Default)]
411pub struct OpenAuthorizer;
412
413impl Authorizer for OpenAuthorizer {
414 fn is_open(&self) -> bool {
415 true
416 }
417
418 async fn authorize(&self, _op: &Operation) -> Result<AuthzFacts, ServerError> {
419 Ok(AuthzFacts::default())
420 }
421}
422
423#[derive(Debug, Clone, Copy, Default)]
428pub struct DefaultAdmission;
429
430impl Admission for DefaultAdmission {
431 fn is_default(&self) -> bool {
432 true
433 }
434
435 async fn admit(&self, input: &AdmissionInput<'_>) -> Result<AdmissionDecision, ServerError> {
436 let charges = match (input.write_quota, &input.op.auth) {
437 (Some(limits), Some(auth)) if input.op.procedure().is_write() => vec![QuotaCharge {
438 scope: QuotaScope::for_signer(&input.op.repo.namespace, &auth.signer),
439 bytes: input.declared_bytes,
440 limits,
441 }],
442 _ => Vec::new(),
443 };
444 Ok(AdmissionDecision::allow(charges))
445 }
446}
447
448#[derive(Debug, Clone)]
454pub enum Choice<L, R> {
455 Left(L),
457 Right(R),
459}
460
461impl<L: Authorizer, R: Authorizer> Authorizer for Choice<L, R> {
462 fn is_open(&self) -> bool {
463 match self {
464 Self::Left(l) => l.is_open(),
465 Self::Right(r) => r.is_open(),
466 }
467 }
468
469 async fn authorize(&self, op: &Operation) -> Result<AuthzFacts, ServerError> {
470 match self {
471 Self::Left(l) => l.authorize(op).await,
472 Self::Right(r) => r.authorize(op).await,
473 }
474 }
475}
476
477impl<L: Admission, R: Admission> Admission for Choice<L, R> {
478 fn is_default(&self) -> bool {
479 match self {
480 Self::Left(l) => l.is_default(),
481 Self::Right(r) => r.is_default(),
482 }
483 }
484
485 async fn admit(&self, input: &AdmissionInput<'_>) -> Result<AdmissionDecision, ServerError> {
486 match self {
487 Self::Left(l) => l.admit(input).await,
488 Self::Right(r) => r.admit(input).await,
489 }
490 }
491}
492
493impl<L: OutcomeSink, R: OutcomeSink> OutcomeSink for Choice<L, R> {
494 async fn deliver(&self, outcome: &Outcome) -> Result<(), DeliveryError> {
495 match self {
496 Self::Left(l) => l.deliver(outcome).await,
497 Self::Right(r) => r.deliver(outcome).await,
498 }
499 }
500
501 async fn deliver_batch(&self, outcomes: &[Outcome]) -> Vec<Result<(), DeliveryError>> {
502 match self {
503 Self::Left(l) => l.deliver_batch(outcomes).await,
504 Self::Right(r) => r.deliver_batch(outcomes).await,
505 }
506 }
507}
508
509#[derive(Debug, Clone, Copy, Default)]
511pub struct NoPreReceive;
512
513impl PreReceive for NoPreReceive {
514 async fn check(&self, _op: &Operation, _pack: Option<&BlobKey>) -> Result<(), ServerError> {
515 Ok(())
516 }
517}
518
519#[derive(Debug, Clone, Copy, Default)]
521pub struct NoReceipts;
522
523impl ReceiptSigner for NoReceipts {
524 async fn sign(&self, _op: &Operation) -> Option<Vec<u8>> {
525 None
526 }
527}
528
529#[derive(Debug, Clone, Copy, Default)]
531pub struct NoOutcomes;
532
533impl OutcomeSink for NoOutcomes {
534 async fn deliver(&self, _row: &Outcome) -> Result<(), DeliveryError> {
535 Ok(())
536 }
537}