willow_data_model/authorisation/mod.rs
1//! Authorisation of entries.
2//!
3//! 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`]).
4
5#[cfg(feature = "dev")]
6use arbitrary::Arbitrary;
7
8use crate::{
9 entry::{Entry, Entrylike, EntrylikeExt},
10 prelude::{Coordinatelike, Keylike, Namespaced},
11};
12
13/// A trait for [AuthorisationTokens](https://willowprotocol.org/specs/data-model/index.html#AuthorisationToken).
14///
15/// 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).
16pub trait AuthorisationToken<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD>:
17 Sized
18{
19 /// 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.
20 type Ingredients;
21
22 /// Everything that can go wrong when trying to create a new authorisation token from some [`Ingredients`](AuthorisationToken::Ingredients) for some entry.
23 type CreationError;
24
25 /// Creates an authorisation token for the given entry, if possible.
26 fn new_for_entry<E>(
27 entry: &E,
28 ingredients: &Self::Ingredients,
29 ) -> Result<Self, Self::CreationError>
30 where
31 E: EntrylikeExt<MCL, MCC, MPL, N, S, PD> + ?Sized;
32
33 /// Determines whether `self` [authorises](https://willowprotocol.org/specs/data-model/index.html#is_authorised_write) the given entry.
34 fn does_authorise<E>(&self, entry: &E) -> bool
35 where
36 E: EntrylikeExt<MCL, MCC, MPL, N, S, PD> + ?Sized;
37}
38
39/// 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.
40///
41/// ```
42/// # #[cfg(feature = "dev")] {
43/// use willow_data_model::prelude::*;
44/// use willow_data_model::test_parameters::*;
45///
46/// let entry = Entry::builder()
47/// .namespace_id(Family)
48/// .subspace_id(Alfie)
49/// .path(Path::<4, 4, 4>::new())
50/// .timestamp(12345)
51/// .payload_digest(Spades)
52/// .payload_length(2)
53/// .build().unwrap();
54///
55/// let pae = PossiblyAuthorisedEntry {
56/// entry: entry.clone(),
57/// authorisation_token: TestSubspaceSignature::new_for_entry(&entry, &AlfieSecret).unwrap(),
58/// };
59/// assert!(pae.into_authorised_entry().is_ok());
60/// # }
61/// ```
62///
63/// [Specification](https://willowprotocol.org/specs/data-model/index.html#PossiblyAuthorisedEntry)
64#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
65#[cfg_attr(feature = "dev", derive(Arbitrary))]
66pub struct PossiblyAuthorisedEntry<
67 const MCL: usize,
68 const MCC: usize,
69 const MPL: usize,
70 N,
71 S,
72 PD,
73 AT,
74> {
75 /// The entry.
76 pub entry: Entry<MCL, MCC, MPL, N, S, PD>,
77 /// The authorisation token, which may or may not [authorise](https://willowprotocol.org/specs/data-model/index.html#is_authorised_write) the entry.
78 pub authorisation_token: AT,
79}
80
81impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>
82 PossiblyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
83{
84 /// Checks whether `self.authorisation_token` [authorise](https://willowprotocol.org/specs/data-model/index.html#is_authorised_write) `self.entry`. If so, converts self into an [`AuthorisedEntry`], otherwise returns `Err(self)`.
85 ///
86 /// ```
87 /// # #[cfg(feature = "dev")] {
88 /// use willow_data_model::prelude::*;
89 /// use willow_data_model::test_parameters::*;
90 ///
91 /// let entry = Entry::builder()
92 /// .namespace_id(Family)
93 /// .subspace_id(Alfie)
94 /// .path(Path::<4, 4, 4>::new())
95 /// .timestamp(12345)
96 /// .payload_digest(Spades)
97 /// .payload_length(2)
98 /// .build().unwrap();
99 ///
100 /// let pae1 = PossiblyAuthorisedEntry {
101 /// entry: entry.clone(),
102 /// authorisation_token: TestSubspaceSignature::new_for_entry(&entry, &AlfieSecret).unwrap(),
103 /// };
104 /// assert!(pae1.into_authorised_entry().is_ok());
105 ///
106 /// let pae2 = PossiblyAuthorisedEntry {
107 /// entry: entry.clone(),
108 /// authorisation_token: BettySignature,
109 /// };
110 /// assert!(pae2.into_authorised_entry().is_err());
111 /// # }
112 /// ```
113 pub fn into_authorised_entry(self) -> Result<AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>, Self>
114 where
115 AT: AuthorisationToken<MCL, MCC, MPL, N, S, PD>,
116 {
117 if self.authorisation_token.does_authorise(&self.entry) {
118 Ok(AuthorisedEntry {
119 entry: self.entry,
120 authorisation_token: self.authorisation_token,
121 })
122 } else {
123 Err(self)
124 }
125 }
126
127 /// 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`.
128 ///
129 /// ```
130 /// # #[cfg(feature = "dev")] {
131 /// use willow_data_model::prelude::*;
132 /// use willow_data_model::test_parameters::*;
133 ///
134 /// let entry = Entry::builder()
135 /// .namespace_id(Family)
136 /// .subspace_id(Alfie)
137 /// .path(Path::<4, 4, 4>::new())
138 /// .timestamp(12345)
139 /// .payload_digest(Spades)
140 /// .payload_length(2)
141 /// .build().unwrap();
142 ///
143 /// let pae = PossiblyAuthorisedEntry {
144 /// entry: entry.clone(),
145 /// authorisation_token: TestSubspaceSignature::new_for_entry(&entry, &AlfieSecret).unwrap(),
146 /// };
147 /// let authed = unsafe { pae.into_authorised_entry_unchecked() };
148 /// assert_eq!(authed.as_entry(), &entry);
149 /// assert_eq!(authed.as_authorisation_token(), &AlfieSignature);
150 /// # }
151 /// ```
152 ///
153 /// #### Safety
154 ///
155 /// Undefined behaviour may occur if `self.authorisation_token.does_authorise(&self.entry)` is `false`. If it returns `true`, this method is safe to call.
156 pub unsafe fn into_authorised_entry_unchecked(
157 self,
158 ) -> AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT> {
159 AuthorisedEntry {
160 entry: self.entry,
161 authorisation_token: self.authorisation_token,
162 }
163 }
164}
165
166impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT> Keylike<MCL, MCC, MPL, S>
167 for PossiblyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
168{
169 fn wdm_subspace_id(&self) -> &S {
170 self.entry.wdm_subspace_id()
171 }
172
173 fn wdm_path(&self) -> &crate::prelude::Path<MCL, MCC, MPL> {
174 self.entry.wdm_path()
175 }
176}
177
178impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>
179 Coordinatelike<MCL, MCC, MPL, S> for PossiblyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
180{
181 fn wdm_timestamp(&self) -> crate::Timestamp {
182 self.entry.wdm_timestamp()
183 }
184}
185
186impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT> Namespaced<N>
187 for PossiblyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
188{
189 fn wdm_namespace_id(&self) -> &N {
190 self.entry.wdm_namespace_id()
191 }
192}
193
194impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>
195 Entrylike<MCL, MCC, MPL, N, S, PD> for PossiblyAuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
196{
197 fn wdm_payload_length(&self) -> u64 {
198 self.entry.wdm_payload_length()
199 }
200
201 fn wdm_payload_digest(&self) -> &PD {
202 self.entry.wdm_payload_digest()
203 }
204}
205
206/// An entry, together with an authorisation token that [authorises](https://willowprotocol.org/specs/data-model/index.html#is_authorised_write) the entry.
207///
208/// There are three 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`]).
209///
210/// ```
211/// # #[cfg(feature = "dev")] {
212/// use willow_data_model::prelude::*;
213/// use willow_data_model::test_parameters::*;
214///
215/// let entry = Entry::builder()
216/// .namespace_id(Family)
217/// .subspace_id(Alfie)
218/// .path(Path::<4, 4, 4>::new())
219/// .timestamp(12345)
220/// .payload_digest(Spades)
221/// .payload_length(2)
222/// .build().unwrap();
223///
224/// let authed = entry.into_authorised_entry::<TestSubspaceSignature>(&AlfieSecret).unwrap();
225/// assert_eq!(authed.as_authorisation_token(), &AlfieSignature);
226/// # }
227/// ```
228///
229/// [Specification](https://willowprotocol.org/specs/data-model/index.html#AuthorisedEntry)
230#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
231#[cfg_attr(feature = "dev", derive(Arbitrary))]
232pub struct AuthorisedEntry<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT> {
233 entry: Entry<MCL, MCC, MPL, N, S, PD>,
234 authorisation_token: AT,
235}
236
237impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>
238 AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
239{
240 /// Consumes self, returning the entry and the authorisation token.
241 pub fn into_parts(self) -> (Entry<MCL, MCC, MPL, N, S, PD>, AT) {
242 (self.entry, self.authorisation_token)
243 }
244
245 /// Returns a reference to the entry.
246 pub fn as_entry(&self) -> &Entry<MCL, MCC, MPL, N, S, PD> {
247 &self.entry
248 }
249
250 /// Returns a mutable reference to the entry.
251 pub fn as_mut_entry(&mut self) -> &mut Entry<MCL, MCC, MPL, N, S, PD> {
252 &mut self.entry
253 }
254
255 /// Returns a reference to the authorisation token.
256 pub fn as_authorisation_token(&self) -> &AT {
257 &self.authorisation_token
258 }
259
260 /// Returns a mutable reference to the authorisation token.
261 pub fn as_mut_authorisation_token(&mut self) -> &mut AT {
262 &mut self.authorisation_token
263 }
264}
265
266impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT> Keylike<MCL, MCC, MPL, S>
267 for AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
268{
269 fn wdm_subspace_id(&self) -> &S {
270 self.entry.wdm_subspace_id()
271 }
272
273 fn wdm_path(&self) -> &crate::prelude::Path<MCL, MCC, MPL> {
274 self.entry.wdm_path()
275 }
276}
277
278impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>
279 Coordinatelike<MCL, MCC, MPL, S> for AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
280{
281 fn wdm_timestamp(&self) -> crate::Timestamp {
282 self.entry.wdm_timestamp()
283 }
284}
285
286impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT> Namespaced<N>
287 for AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
288{
289 fn wdm_namespace_id(&self) -> &N {
290 self.entry.wdm_namespace_id()
291 }
292}
293
294impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>
295 Entrylike<MCL, MCC, MPL, N, S, PD> for AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
296{
297 fn wdm_payload_length(&self) -> u64 {
298 self.entry.wdm_payload_length()
299 }
300
301 fn wdm_payload_digest(&self) -> &PD {
302 self.entry.wdm_payload_digest()
303 }
304}