use core::future::Future;
use std::sync::Arc;
use mkit_core::protocol::PackKey;
use crate::error::{Redacted, ServerError};
use crate::op::{AuthzFacts, Operation};
use crate::quota::{QuotaCharge, QuotaLimits, QuotaScope};
use crate::rt::{MaybeSend, MaybeSync};
use crate::store::BlobKey;
pub trait Authorizer: MaybeSend + MaybeSync {
fn is_open(&self) -> bool {
false
}
fn authorize(
&self,
op: &Operation,
) -> impl Future<Output = Result<AuthzFacts, ServerError>> + MaybeSend;
}
#[derive(Clone)]
#[non_exhaustive]
pub struct AdmissionInput<'a> {
pub op: &'a Operation,
pub declared_bytes: u64,
pub pack_id: Option<PackKey>,
pub creates_namespace: bool,
pub creates_repo: bool,
pub new_to_repo_bytes: Option<u64>,
pub idempotency_key: Option<&'a str>,
pub write_quota: Option<QuotaLimits>,
pub audience: Option<&'a str>,
pub credential_headers: &'a [CredentialHeader],
}
impl core::fmt::Debug for AdmissionInput<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("AdmissionInput")
.field("op", &self.op)
.field("declared_bytes", &self.declared_bytes)
.field("pack_id", &self.pack_id)
.field("audience", &self.audience)
.finish_non_exhaustive()
}
}
#[derive(Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct CredentialHeader {
pub name: String,
pub value: Redacted,
}
impl CredentialHeader {
#[must_use]
pub fn new(name: impl Into<String>, value: Redacted) -> Self {
Self {
name: name.into(),
value,
}
}
}
impl core::fmt::Debug for CredentialHeader {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CredentialHeader")
.field("name", &self.name)
.finish_non_exhaustive()
}
}
impl<'a> AdmissionInput<'a> {
#[must_use]
pub fn new(op: &'a Operation) -> Self {
Self {
op,
declared_bytes: 0,
pack_id: None,
creates_namespace: op.creation.namespace,
creates_repo: op.creation.repo,
new_to_repo_bytes: None,
idempotency_key: op.auth.as_ref().map(|auth| auth.nonce.as_str()),
write_quota: None,
audience: None,
credential_headers: &[],
}
}
}
pub const ADMISSION_EXPOSE_HEADERS: [&str; 4] = [
"WWW-Authenticate",
"PAYMENT-REQUIRED",
"Payment-Receipt",
"PAYMENT-RESPONSE",
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Challenge {
pub scheme: String,
pub value: String,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum AdmissionDecision {
#[non_exhaustive]
Allow {
charges: Vec<QuotaCharge>,
reservation: Option<String>,
response_headers: Vec<(String, String)>,
external_ref: Option<String>,
},
#[non_exhaustive]
Challenge {
challenges: Vec<Challenge>,
description: String,
response_headers: Vec<(String, String)>,
},
Deny(ServerError),
}
impl AdmissionDecision {
#[must_use]
pub fn allow(charges: Vec<QuotaCharge>) -> Self {
Self::Allow {
charges,
reservation: None,
response_headers: Vec::new(),
external_ref: None,
}
}
#[must_use]
pub fn with_reservation(mut self, id: impl Into<String>) -> Self {
if let Self::Allow { reservation, .. } = &mut self {
*reservation = Some(id.into());
}
self
}
#[must_use]
pub fn with_response_header(
mut self,
name: impl Into<String>,
value: impl Into<String>,
) -> Self {
match &mut self {
Self::Allow {
response_headers, ..
}
| Self::Challenge {
response_headers, ..
} => {
response_headers.push((name.into(), value.into()));
}
Self::Deny(_) => {}
}
self
}
#[must_use]
pub fn with_external_ref(mut self, reference: impl Into<String>) -> Self {
if let Self::Allow { external_ref, .. } = &mut self {
*external_ref = Some(reference.into());
}
self
}
#[must_use]
pub fn challenge(challenges: Vec<Challenge>, description: impl Into<String>) -> Self {
Self::Challenge {
challenges,
description: description.into(),
response_headers: Vec::new(),
}
}
#[must_use]
pub fn deny(message: impl Into<String>) -> Self {
Self::Deny(ServerError::permission_denied(message.into()))
}
}
pub trait Admission: MaybeSend + MaybeSync {
fn is_default(&self) -> bool {
false
}
fn admit(
&self,
input: &AdmissionInput<'_>,
) -> impl Future<Output = Result<AdmissionDecision, ServerError>> + MaybeSend;
}
pub trait PreReceive: MaybeSend + MaybeSync {
fn check(
&self,
op: &Operation,
pack: Option<&BlobKey>,
) -> impl Future<Output = Result<(), ServerError>> + MaybeSend;
}
pub trait ReceiptSigner: MaybeSend + MaybeSync {
fn sign(&self, op: &Operation) -> impl Future<Output = Option<Vec<u8>>> + MaybeSend;
}
use super::durable_outcome::{DeliveryError, Outcome};
pub trait OutcomeSink: MaybeSend + MaybeSync {
fn deliver(
&self,
outcome: &Outcome,
) -> impl Future<Output = Result<(), DeliveryError>> + MaybeSend;
fn deliver_batch(
&self,
outcomes: &[Outcome],
) -> impl Future<Output = Vec<Result<(), DeliveryError>>> + MaybeSend {
async move {
let mut results = Vec::with_capacity(outcomes.len());
for outcome in outcomes {
results.push(self.deliver(outcome).await);
}
results
}
}
}
impl<T: OutcomeSink> OutcomeSink for Arc<T> {
async fn deliver(&self, outcome: &Outcome) -> Result<(), DeliveryError> {
T::deliver(self, outcome).await
}
async fn deliver_batch(&self, outcomes: &[Outcome]) -> Vec<Result<(), DeliveryError>> {
T::deliver_batch(self, outcomes).await
}
}
pub trait HookSet: MaybeSend + MaybeSync {
type Az: Authorizer;
type Ad: Admission;
type Pr: PreReceive;
type Rs: ReceiptSigner;
type Os: OutcomeSink;
fn authorizer(&self) -> &Self::Az;
fn admission(&self) -> &Self::Ad;
fn pre_receive(&self) -> &Self::Pr;
fn receipts(&self) -> &Self::Rs;
fn outcomes(&self) -> &Self::Os;
}
#[derive(Debug, Clone, Default)]
pub struct Hooks<
Az = OpenAuthorizer,
Ad = DefaultAdmission,
Pr = NoPreReceive,
Rs = NoReceipts,
Os = NoOutcomes,
> {
pub authorizer: Az,
pub admission: Ad,
pub pre_receive: Pr,
pub receipts: Rs,
pub outcomes: Os,
}
impl Hooks {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl<Az, Ad, Pr, Rs, Os> HookSet for Hooks<Az, Ad, Pr, Rs, Os>
where
Az: Authorizer,
Ad: Admission,
Pr: PreReceive,
Rs: ReceiptSigner,
Os: OutcomeSink,
{
type Az = Az;
type Ad = Ad;
type Pr = Pr;
type Rs = Rs;
type Os = Os;
fn authorizer(&self) -> &Az {
&self.authorizer
}
fn admission(&self) -> &Ad {
&self.admission
}
fn pre_receive(&self) -> &Pr {
&self.pre_receive
}
fn receipts(&self) -> &Rs {
&self.receipts
}
fn outcomes(&self) -> &Os {
&self.outcomes
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct OpenAuthorizer;
impl Authorizer for OpenAuthorizer {
fn is_open(&self) -> bool {
true
}
async fn authorize(&self, _op: &Operation) -> Result<AuthzFacts, ServerError> {
Ok(AuthzFacts::default())
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DefaultAdmission;
impl Admission for DefaultAdmission {
fn is_default(&self) -> bool {
true
}
async fn admit(&self, input: &AdmissionInput<'_>) -> Result<AdmissionDecision, ServerError> {
let charges = match (input.write_quota, &input.op.auth) {
(Some(limits), Some(auth)) if input.op.procedure().is_write() => vec![QuotaCharge {
scope: QuotaScope::for_signer(&input.op.repo.namespace, &auth.signer),
bytes: input.declared_bytes,
limits,
}],
_ => Vec::new(),
};
Ok(AdmissionDecision::allow(charges))
}
}
#[derive(Debug, Clone)]
pub enum Choice<L, R> {
Left(L),
Right(R),
}
impl<L: Authorizer, R: Authorizer> Authorizer for Choice<L, R> {
fn is_open(&self) -> bool {
match self {
Self::Left(l) => l.is_open(),
Self::Right(r) => r.is_open(),
}
}
async fn authorize(&self, op: &Operation) -> Result<AuthzFacts, ServerError> {
match self {
Self::Left(l) => l.authorize(op).await,
Self::Right(r) => r.authorize(op).await,
}
}
}
impl<L: Admission, R: Admission> Admission for Choice<L, R> {
fn is_default(&self) -> bool {
match self {
Self::Left(l) => l.is_default(),
Self::Right(r) => r.is_default(),
}
}
async fn admit(&self, input: &AdmissionInput<'_>) -> Result<AdmissionDecision, ServerError> {
match self {
Self::Left(l) => l.admit(input).await,
Self::Right(r) => r.admit(input).await,
}
}
}
impl<L: OutcomeSink, R: OutcomeSink> OutcomeSink for Choice<L, R> {
async fn deliver(&self, outcome: &Outcome) -> Result<(), DeliveryError> {
match self {
Self::Left(l) => l.deliver(outcome).await,
Self::Right(r) => r.deliver(outcome).await,
}
}
async fn deliver_batch(&self, outcomes: &[Outcome]) -> Vec<Result<(), DeliveryError>> {
match self {
Self::Left(l) => l.deliver_batch(outcomes).await,
Self::Right(r) => r.deliver_batch(outcomes).await,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoPreReceive;
impl PreReceive for NoPreReceive {
async fn check(&self, _op: &Operation, _pack: Option<&BlobKey>) -> Result<(), ServerError> {
Ok(())
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoReceipts;
impl ReceiptSigner for NoReceipts {
async fn sign(&self, _op: &Operation) -> Option<Vec<u8>> {
None
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoOutcomes;
impl OutcomeSink for NoOutcomes {
async fn deliver(&self, _row: &Outcome) -> Result<(), DeliveryError> {
Ok(())
}
}