willow-data-model 0.7.0

The core datatypes of Willow, an eventually consistent data store with improved distributed deletion.
Documentation
//! Authorisation of entries.
//!
//! This module provides the [`AuthorisationToken`] trait for types which can serve as willow [AuthorisationTokens](https://willowprotocol.org/specs/data-model/index.html#AuthorisationToken). It further provides types for [PossiblyAuthorisedEntries](https://willowprotocol.org/specs/data-model/index.html#PossiblyAuthorisedEntry) ([`PossiblyAuthorisedEntry`]) and [AuthorisedEntries](https://willowprotocol.org/specs/data-model/index.html#AuthorisedEntry) ([`AuthorisedEntry`]).

#[cfg(feature = "dev")]
use arbitrary::Arbitrary;

use crate::{
    entry::{Entry, Entrylike, EntrylikeExt},
    prelude::{Coordinatelike, Keylike, Namespaced},
};

/// A trait for [AuthorisationTokens](https://willowprotocol.org/specs/data-model/index.html#AuthorisationToken).
///
/// This trait serves a dual role: it describes both how to authorise entries (i.e., how to create valid authorisation tokens) and how to verify authorisation tokens (i.e., how to compute the [is_authorised_write](https://willowprotocol.org/specs/data-model/index.html#is_authorised_write) function of the spec).
pub trait AuthorisationToken<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD>:
    Sized
{
    /// The type of the information you need in order to authorise an entry. For example, in [Meadowcap](https://willowprotocol.org/specs/meadowcap/), this would be a pair of a capability and a secret key.
    type Ingredients;

    /// Everything that can go wrong when trying to create a new authorisation token from some [`Ingredients`](AuthorisationToken::Ingredients) for some entry.
    type CreationError;

    /// Creates an authorisation token for the given entry, if possible.
    fn new_for_entry<E>(
        entry: &E,
        ingredients: &Self::Ingredients,
    ) -> Result<Self, Self::CreationError>
    where
        E: EntrylikeExt<MCL, MCC, MPL, N, S, PD> + ?Sized;

    /// Determines whether `self` [authorises](https://willowprotocol.org/specs/data-model/index.html#is_authorised_write) the given entry.
    fn does_authorise<E>(&self, entry: &E) -> bool
    where
        E: EntrylikeExt<MCL, MCC, MPL, N, S, PD> + ?Sized;
}

/// An entry, together with an authorisation token that may or may not [authorise](https://willowprotocol.org/specs/data-model/index.html#is_authorised_write) the entry.
///
/// ```
/// # #[cfg(feature = "dev")] {
/// use willow_data_model::prelude::*;
/// use willow_data_model::test_parameters::*;
///
/// let entry = Entry::builder()
///     .namespace_id(Family)
///     .subspace_id(Alfie)
///     .path(Path::<4, 4, 4>::new())
///     .timestamp(12345)
///     .payload_digest(Spades)
///     .payload_length(2)
///     .build();
///
/// let pae = PossiblyAuthorisedEntry {
///     entry: entry.clone(),
///     authorisation_token: TestSubspaceSignature::new_for_entry(&entry, &AlfieSecret).unwrap(),
/// };
/// assert!(pae.into_authorised_entry().is_ok());
/// # }
/// ```
///
/// [Specification](https://willowprotocol.org/specs/data-model/index.html#PossiblyAuthorisedEntry)
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
#[cfg_attr(feature = "dev", derive(Arbitrary))]
pub struct PossiblyAuthorisedEntry<
    const MCL: usize,
    const MCC: usize,
    const MPL: usize,
    N,
    S,
    PD,
    AT,
> {
    /// The entry.
    pub entry: Entry<MCL, MCC, MPL, N, S, PD>,
    /// The authorisation token, which may or may not [authorise](https://willowprotocol.org/specs/data-model/index.html#is_authorised_write) the entry.
    pub authorisation_token: AT,
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>
    PossiblyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
{
    /// Checks whether `self.authorisation_token` [authorises](https://willowprotocol.org/specs/data-model/index.html#is_authorised_write) `self.entry`. If so, converts self into an [`AuthorisedEntry`], otherwise returns `Err(self)`.
    ///
    /// ```
    /// # #[cfg(feature = "dev")] {
    /// use willow_data_model::prelude::*;
    /// use willow_data_model::test_parameters::*;
    ///
    /// let entry = Entry::builder()
    ///     .namespace_id(Family)
    ///     .subspace_id(Alfie)
    ///     .path(Path::<4, 4, 4>::new())
    ///     .timestamp(12345)
    ///     .payload_digest(Spades)
    ///     .payload_length(2)
    ///     .build();
    ///
    /// let pae1 = PossiblyAuthorisedEntry {
    ///     entry: entry.clone(),
    ///     authorisation_token: TestSubspaceSignature::new_for_entry(&entry, &AlfieSecret).unwrap(),
    /// };
    /// assert!(pae1.into_authorised_entry().is_ok());
    ///
    /// let pae2 = PossiblyAuthorisedEntry {
    ///     entry: entry.clone(),
    ///     authorisation_token: BettySignature,
    /// };
    /// assert!(pae2.into_authorised_entry().is_err());
    /// # }
    /// ```
    pub fn into_authorised_entry(self) -> Result<AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>, Self>
    where
        AT: AuthorisationToken<MCL, MCC, MPL, N, S, PD>,
    {
        if self.authorisation_token.does_authorise(&self.entry) {
            Ok(AuthorisedEntry {
                entry: self.entry,
                authorisation_token: self.authorisation_token,
            })
        } else {
            Err(self)
        }
    }

    /// Converts self into an [`AuthorisedEntry`], without checking if `self.authorisation_token` [authorise](https://willowprotocol.org/specs/data-model/index.html#is_authorised_write) `self.entry`.
    ///
    /// ```
    /// # #[cfg(feature = "dev")] {
    /// use willow_data_model::prelude::*;
    /// use willow_data_model::test_parameters::*;
    ///
    /// let entry = Entry::builder()
    ///     .namespace_id(Family)
    ///     .subspace_id(Alfie)
    ///     .path(Path::<4, 4, 4>::new())
    ///     .timestamp(12345)
    ///     .payload_digest(Spades)
    ///     .payload_length(2)
    ///     .build();
    ///
    /// let pae = PossiblyAuthorisedEntry {
    ///     entry: entry.clone(),
    ///     authorisation_token: TestSubspaceSignature::new_for_entry(&entry, &AlfieSecret).unwrap(),
    /// };
    /// let authed = unsafe { pae.into_authorised_entry_unchecked() };
    /// assert_eq!(authed.entry(), &entry);
    /// assert_eq!(authed.authorisation_token(), &AlfieSignature);
    /// # }
    /// ```
    ///
    /// #### Safety
    ///
    /// Undefined behaviour may occur if `self.authorisation_token.does_authorise(&self.entry)` is `false`. If it returns `true`, this method is safe to call.
    pub unsafe fn into_authorised_entry_unchecked(
        self,
    ) -> AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT> {
        AuthorisedEntry {
            entry: self.entry,
            authorisation_token: self.authorisation_token,
        }
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT> Keylike<MCL, MCC, MPL, S>
    for PossiblyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
{
    fn wdm_subspace_id(&self) -> &S {
        self.entry.wdm_subspace_id()
    }

    fn wdm_path(&self) -> &crate::prelude::Path<MCL, MCC, MPL> {
        self.entry.wdm_path()
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>
    Coordinatelike<MCL, MCC, MPL, S> for PossiblyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
{
    fn wdm_timestamp(&self) -> crate::Timestamp {
        self.entry.wdm_timestamp()
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT> Namespaced<N>
    for PossiblyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
{
    fn wdm_namespace_id(&self) -> &N {
        self.entry.wdm_namespace_id()
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>
    Entrylike<MCL, MCC, MPL, N, S, PD> for PossiblyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
{
    fn wdm_payload_length(&self) -> u64 {
        self.entry.wdm_payload_length()
    }

    fn wdm_payload_digest(&self) -> &PD {
        self.entry.wdm_payload_digest()
    }
}

/// An entry, together with an authorisation token that [authorises](https://willowprotocol.org/specs/data-model/index.html#is_authorised_write) the entry.
///
/// There are two typical scenarios for creating these: authorising an [`Entry`] yourself (via [`Entry::into_authorised_entry`] or [`crate::entry::EntrylikeExt::wdm_authorise`]), or receiving and verifying an untrusted authorisation token ([`PossiblyAuthorisedEntry::into_authorised_entry`]).
///
/// ```
/// # #[cfg(feature = "dev")] {
/// use willow_data_model::prelude::*;
/// use willow_data_model::test_parameters::*;
///
/// let entry = Entry::builder()
///     .namespace_id(Family)
///     .subspace_id(Alfie)
///     .path(Path::<4, 4, 4>::new())
///     .timestamp(12345)
///     .payload_digest(Spades)
///     .payload_length(2)
///     .build();
///
/// let authed = entry.into_authorised_entry::<TestSubspaceSignature>(&AlfieSecret).unwrap();
/// assert_eq!(authed.authorisation_token(), &AlfieSignature);
/// # }
/// ```
///
/// [Specification](https://willowprotocol.org/specs/data-model/index.html#AuthorisedEntry)
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
#[cfg_attr(feature = "dev", derive(Arbitrary))]
pub struct AuthorisedEntry<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT> {
    entry: Entry<MCL, MCC, MPL, N, S, PD>,
    authorisation_token: AT,
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>
    AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
{
    /// Consumes self, returning the entry and the authorisation token.
    pub fn into_parts(self) -> (Entry<MCL, MCC, MPL, N, S, PD>, AT) {
        (self.entry, self.authorisation_token)
    }

    /// Returns a reference to the entry.
    pub fn entry(&self) -> &Entry<MCL, MCC, MPL, N, S, PD> {
        &self.entry
    }

    /// Returns a reference to the authorisation token.
    pub fn authorisation_token(&self) -> &AT {
        &self.authorisation_token
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT> Keylike<MCL, MCC, MPL, S>
    for AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
{
    fn wdm_subspace_id(&self) -> &S {
        self.entry.wdm_subspace_id()
    }

    fn wdm_path(&self) -> &crate::prelude::Path<MCL, MCC, MPL> {
        self.entry.wdm_path()
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>
    Coordinatelike<MCL, MCC, MPL, S> for AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
{
    fn wdm_timestamp(&self) -> crate::Timestamp {
        self.entry.wdm_timestamp()
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT> Namespaced<N>
    for AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
{
    fn wdm_namespace_id(&self) -> &N {
        self.entry.wdm_namespace_id()
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>
    Entrylike<MCL, MCC, MPL, N, S, PD> for AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
{
    fn wdm_payload_length(&self) -> u64 {
        self.entry.wdm_payload_length()
    }

    fn wdm_payload_digest(&self) -> &PD {
        self.entry.wdm_payload_digest()
    }
}