actionable/
statement.rs

1use std::{
2    borrow::Cow,
3    collections::HashMap,
4    convert::TryFrom,
5    fmt::{Display, Formatter, Write},
6    hash::Hash,
7};
8
9use serde::{Deserialize, Serialize};
10
11use super::{Action, ActionName};
12
13/// A statement of permissions. A statement describes whether one or more
14/// `actions` should be `allowed` to be taken against `resources`.
15#[derive(Clone, Debug, Serialize, Deserialize)]
16#[must_use]
17pub struct Statement {
18    /// The list of resources this statement applies to.
19    pub resources: Vec<ResourceName<'static>>,
20    /// The list of actions this statement applies to.
21    pub actions: Option<ActionNameList>,
22    /// Any configured values for these resources.
23    pub configuration: Option<HashMap<String, Configuration>>,
24}
25
26impl Statement {
27    /// Returns a statement that allows [`ActionNameList::All`] against
28    /// [`ResourceName::any()`].
29    pub fn allow_all_for_any_resource() -> Self {
30        Self::for_any().allowing_all()
31    }
32
33    /// Returns an empty statement for a resource named `name`.
34    pub fn for_resource(name: impl Into<ResourceName<'static>>) -> Self {
35        Self {
36            resources: vec![name.into()],
37            actions: None,
38            configuration: None,
39        }
40    }
41
42    /// Returns an empty statement for [`ResourceName::any()`].
43    pub fn for_any() -> Self {
44        Self {
45            resources: vec![ResourceName::any()],
46            actions: None,
47            configuration: None,
48        }
49    }
50
51    /// Returns an empty statement for a resources named `names`.
52    pub fn for_resources<II: IntoIterator<Item = ResourceName<'static>>>(names: II) -> Self {
53        Self {
54            resources: names.into_iter().collect(),
55            actions: None,
56            configuration: None,
57        }
58    }
59
60    /// Allows `action` to be performed.
61    pub fn allow<A: Action>(&mut self, action: &A) {
62        match &mut self.actions {
63            Some(ActionNameList::All) => {}
64            Some(ActionNameList::List(names)) => {
65                names.push(action.name());
66            }
67            None => {
68                self.actions = Some(ActionNameList::List(vec![action.name()]));
69            }
70        }
71    }
72
73    /// Allows `action` and returns self.
74    pub fn allowing<A: Action>(mut self, action: &A) -> Self {
75        self.allow(action);
76        self
77    }
78
79    /// Allows [`ActionNameList::All`].
80    pub fn allow_all(&mut self) {
81        self.actions = Some(ActionNameList::All);
82    }
83
84    /// Allows [`ActionNameList::All`] and returns self.
85    pub fn allowing_all(mut self) -> Self {
86        self.allow_all();
87        self
88    }
89
90    /// Sets `configuration` for `key` for the resources in this statement.
91    pub fn configure<S: Into<String>, C: Into<Configuration>>(&mut self, key: S, configuration: C) {
92        let configurations = self.configuration.get_or_insert_with(HashMap::default);
93        configurations.insert(key.into(), configuration.into());
94    }
95
96    /// Configures `configuration` for `key` and returns self.
97    pub fn with<S: Into<String>, C: Into<Configuration>>(
98        mut self,
99        key: S,
100        configuration: C,
101    ) -> Self {
102        self.configure(key, configuration);
103        self
104    }
105}
106
107/// A single element of a [`ResourceName`]
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub enum Identifier<'a> {
110    /// When checking for allowed permissions, allow any match where this
111    /// identifier is used.
112    Any,
113    /// An integer identifier.
114    Integer(u64),
115    /// A string identifier.
116    String(Cow<'a, str>),
117    /// A binary identifier.
118    Bytes(Cow<'a, [u8]>),
119}
120
121impl<'a> Hash for Identifier<'a> {
122    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
123        // To ensure matching the implementation of Eq, we need to hash
124        // everything to bytes in the same way they would be compared.
125        match self {
126            Identifier::Any => {
127                // We get to pick an arbitrary hash for this value. It only
128                // needs to be self consistent. A null byte is likely to be
129                // unique in terms of produced hash values.
130                state.write_u8(0);
131            }
132            Identifier::Integer(int) => {
133                state.write(&int.to_be_bytes());
134            }
135            Identifier::String(string) => {
136                state.write(string.as_bytes());
137            }
138            Identifier::Bytes(bytes) => {
139                state.write(bytes);
140            }
141        }
142    }
143}
144
145#[test]
146fn identifier_hash_tests() {
147    fn hash(identifier: &Identifier<'_>) -> u64 {
148        use std::hash::Hasher;
149        let mut hasher = std::collections::hash_map::DefaultHasher::default();
150        identifier.hash(&mut hasher);
151        hasher.finish()
152    }
153
154    let integer_a = Identifier::from(u64::from_be_bytes(*b"helloooo"));
155    let string_a = Identifier::from("helloooo");
156    let bytes_a = Identifier::from(b"helloooo");
157    let string_b = Identifier::from("woooorld");
158
159    assert_eq!(hash(&Identifier::Any), hash(&Identifier::Any));
160    assert_eq!(hash(&string_a), hash(&string_a));
161    assert_eq!(hash(&integer_a), hash(&string_a));
162    assert_eq!(hash(&bytes_a), hash(&string_a));
163    assert_ne!(hash(&string_a), hash(&string_b));
164    assert_ne!(hash(&integer_a), hash(&string_b));
165    assert_ne!(hash(&bytes_a), hash(&string_b));
166}
167
168impl<'a> Eq for Identifier<'a> {}
169
170impl<'a> PartialEq for Identifier<'a> {
171    fn eq(&self, other: &Self) -> bool {
172        match other {
173            Self::Any => matches!(self, Self::Any),
174            Self::Integer(int) => self.eq_int(*int),
175            Self::String(string) => self.eq_str(string),
176            Self::Bytes(bytes) => self.eq_bytes(bytes),
177        }
178    }
179}
180
181impl<'a> Identifier<'a> {
182    /// Convert this identifier to an un-borrowed identifier.
183    #[must_use]
184    pub fn to_owned(&self) -> Identifier<'static> {
185        match self {
186            Self::Any => Identifier::Any,
187            Self::Integer(value) => Identifier::Integer(*value),
188            Self::String(value) => Identifier::String(Cow::Owned(value.to_string())),
189            Self::Bytes(value) => Identifier::Bytes(Cow::Owned(value.to_vec())),
190        }
191    }
192
193    fn eq_int(&self, other: u64) -> bool {
194        match self {
195            Identifier::Any => false,
196            Identifier::Integer(int) => *int == other,
197            Identifier::String(string) => {
198                let other = other.to_be_bytes();
199                string.as_bytes() == other
200            }
201            Identifier::Bytes(bytes) => {
202                let other = other.to_be_bytes();
203                **bytes == other
204            }
205        }
206    }
207
208    fn eq_str(&self, other: &str) -> bool {
209        match self {
210            Identifier::Any => false,
211            Identifier::Integer(int) => {
212                let int = int.to_be_bytes();
213                int == other.as_bytes()
214            }
215            Identifier::String(string) => string == other,
216            Identifier::Bytes(bytes) => &**bytes == other.as_bytes(),
217        }
218    }
219
220    fn eq_bytes(&self, other: &[u8]) -> bool {
221        match self {
222            Identifier::Any => false,
223            Identifier::Integer(int) => {
224                let int = int.to_be_bytes();
225                int == other
226            }
227            Identifier::String(string) => string.as_bytes() == other,
228            Identifier::Bytes(bytes) => &**bytes == other,
229        }
230    }
231}
232
233#[test]
234fn identifier_equality_tests() {
235    let integer_a = Identifier::from(u64::from_be_bytes(*b"helloooo"));
236    let integer_b = Identifier::from(u64::from_be_bytes(*b"woooorld"));
237    let string_a = Identifier::from("helloooo");
238    let string_b = Identifier::from("woooorld");
239    let bytes_a = Identifier::from(b"helloooo");
240    let bytes_b = Identifier::from(b"woooorld");
241
242    // Integer on left
243    assert_ne!(integer_a, Identifier::Any);
244    assert_eq!(integer_a, integer_a);
245    assert_eq!(integer_a, string_a);
246    assert_eq!(integer_a, bytes_a);
247    assert_ne!(integer_a, integer_b);
248    assert_ne!(integer_a, string_b);
249    assert_ne!(integer_a, bytes_b);
250
251    // String on left
252    assert_ne!(string_a, Identifier::Any);
253    assert_eq!(string_a, integer_a);
254    assert_eq!(string_a, string_a);
255    assert_eq!(string_a, bytes_a);
256    assert_ne!(string_a, integer_b);
257    assert_ne!(string_a, string_b);
258    assert_ne!(string_a, bytes_b);
259
260    // Bytes on left
261    assert_ne!(bytes_a, Identifier::Any);
262    assert_eq!(bytes_a, integer_a);
263    assert_eq!(bytes_a, string_a);
264    assert_eq!(bytes_a, bytes_a);
265    assert_ne!(bytes_a, integer_b);
266    assert_ne!(bytes_a, string_b);
267    assert_ne!(bytes_a, bytes_b);
268
269    // Any on left
270    assert_eq!(Identifier::Any, Identifier::Any);
271    assert_ne!(Identifier::Any, integer_a);
272    assert_ne!(Identifier::Any, string_a);
273    assert_ne!(Identifier::Any, bytes_a);
274}
275
276impl<'a> Display for Identifier<'a> {
277    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278        match self {
279            Self::Any => f.write_char('*'),
280            Self::Integer(integer) => integer.fmt(f),
281            Self::String(string) => string.fmt(f),
282            Self::Bytes(bytes) => {
283                f.write_char('$')?;
284                for byte in bytes.iter() {
285                    write!(f, "{:02x}", byte)?;
286                }
287                Ok(())
288            }
289        }
290    }
291}
292
293#[test]
294fn identifier_display_tests() {
295    assert_eq!(Identifier::Any.to_string(), "*");
296    assert_eq!(Identifier::from(1).to_string(), "1");
297    assert_eq!(Identifier::from("string").to_string(), "string");
298    assert_eq!(Identifier::from(b"bytes").to_string(), "$6279746573");
299}
300
301impl<'a> From<u64> for Identifier<'a> {
302    fn from(id: u64) -> Self {
303        Self::Integer(id)
304    }
305}
306
307impl<'a> From<&'a str> for Identifier<'a> {
308    fn from(id: &'a str) -> Self {
309        Self::String(Cow::Borrowed(id))
310    }
311}
312
313impl<'a> From<&'a String> for Identifier<'a> {
314    fn from(id: &'a String) -> Self {
315        Self::from(id.as_str())
316    }
317}
318
319impl<'a> From<String> for Identifier<'a> {
320    fn from(id: String) -> Self {
321        Self::String(Cow::Owned(id))
322    }
323}
324
325impl<'a, const N: usize> From<&'a [u8; N]> for Identifier<'a> {
326    fn from(id: &'a [u8; N]) -> Self {
327        Self::from(&id[..])
328    }
329}
330
331impl<'a, const N: usize> From<[u8; N]> for Identifier<'a> {
332    fn from(id: [u8; N]) -> Self {
333        Self::from(id.to_vec())
334    }
335}
336
337impl<'a> From<&'a [u8]> for Identifier<'a> {
338    fn from(id: &'a [u8]) -> Self {
339        Self::Bytes(Cow::Borrowed(id))
340    }
341}
342
343impl<'a> From<&'a Vec<u8>> for Identifier<'a> {
344    fn from(id: &'a Vec<u8>) -> Self {
345        Self::from(id.clone())
346    }
347}
348
349impl<'a> From<Vec<u8>> for Identifier<'a> {
350    fn from(id: Vec<u8>) -> Self {
351        Self::Bytes(Cow::Owned(id))
352    }
353}
354
355#[test]
356fn identifier_from_tests() {
357    assert_eq!(Identifier::from(1).to_string(), "1");
358    assert_eq!(Identifier::from("string").to_string(), "string");
359    assert_eq!(
360        Identifier::from(&String::from("string")).to_string(),
361        "string"
362    );
363    assert_eq!(
364        Identifier::from(String::from("string")).to_string(),
365        "string"
366    );
367    // This calls through to from(&[u8])
368    assert_eq!(Identifier::from(b"bytes").to_string(), "$6279746573");
369    // This calls through to from(Vec<u8>)
370    assert_eq!(Identifier::from(*b"bytes").to_string(), "$6279746573");
371    assert_eq!(
372        Identifier::from(&b"bytes".to_vec()).to_string(),
373        "$6279746573"
374    );
375}
376
377/// A list of [`ActionName`]s.
378#[derive(Clone, Debug, Serialize, Deserialize)]
379pub enum ActionNameList {
380    /// A specific list of names.
381    List(Vec<ActionName>),
382    /// All actions.
383    All,
384}
385
386impl<T> From<T> for ActionNameList
387where
388    T: Action,
389{
390    fn from(action: T) -> Self {
391        Self::List(vec![action.name()])
392    }
393}
394
395impl<T> From<Vec<T>> for ActionNameList
396where
397    T: Action,
398{
399    fn from(actions: Vec<T>) -> Self {
400        Self::List(actions.into_iter().map(|action| action.name()).collect())
401    }
402}
403
404/// A configured value for a resource.
405#[derive(Debug, Clone, Serialize, Deserialize)]
406pub enum Configuration {
407    /// An unsigned integer configuration value.
408    Unsigned(u64),
409    /// A signed integer configuration value.
410    Signed(i64),
411    /// A string configuration value.
412    String(String),
413}
414
415impl Configuration {
416    /// Evaluates the contents of this configuration as a signed integer.
417    /// Returns None if unable to convert safely.
418    #[must_use]
419    pub fn to_signed(&self) -> Option<i64> {
420        match self {
421            Configuration::Unsigned(unsigned) => i64::try_from(*unsigned).ok(),
422            Configuration::Signed(signed) => Some(*signed),
423            Configuration::String(string) => string.parse().ok(),
424        }
425    }
426
427    /// Evaluates the contents of this configuration as an unsigned integer.
428    /// Returns None if unable to convert safely.
429    #[must_use]
430    pub fn to_unsigned(&self) -> Option<u64> {
431        match self {
432            Configuration::Unsigned(unsigned) => Some(*unsigned),
433            Configuration::Signed(signed) => u64::try_from(*signed).ok(),
434            Configuration::String(string) => string.parse().ok(),
435        }
436    }
437}
438
439impl Display for Configuration {
440    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
441        match self {
442            Configuration::Unsigned(unsigned) => unsigned.fmt(f),
443            Configuration::Signed(signed) => signed.fmt(f),
444            Configuration::String(string) => string.fmt(f),
445        }
446    }
447}
448
449impl From<u64> for Configuration {
450    fn from(value: u64) -> Self {
451        Self::Unsigned(value)
452    }
453}
454
455impl From<i64> for Configuration {
456    fn from(value: i64) -> Self {
457        Self::Signed(value)
458    }
459}
460
461impl From<String> for Configuration {
462    fn from(value: String) -> Self {
463        Self::String(value)
464    }
465}
466
467impl<'a> From<&'a str> for Configuration {
468    fn from(value: &'a str) -> Self {
469        Self::String(value.to_string())
470    }
471}
472
473/// A unique name/identifier of a resource.
474#[derive(Default, Debug, Clone, Serialize, Deserialize)]
475pub struct ResourceName<'a>(Vec<Identifier<'a>>);
476
477impl<'a> ResourceName<'a> {
478    /// Convert a borrowed name to an un-borrwed name.
479    #[must_use]
480    pub fn to_owned(&self) -> ResourceName<'static> {
481        ResourceName(self.0.iter().map(Identifier::to_owned).collect())
482    }
483}
484
485impl<'a> Display for ResourceName<'a> {
486    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
487        for (index, identifier) in self.0.iter().enumerate() {
488            if index > 0 {
489                f.write_char('.')?;
490            }
491
492            identifier.fmt(f)?;
493        }
494
495        Ok(())
496    }
497}
498
499impl<'a> ResourceName<'a> {
500    /// Creates a `ResourceName` that matches any identifier.
501    #[must_use]
502    pub fn any() -> Self {
503        Self::named(Identifier::Any)
504    }
505
506    /// Creates a `ResourceName` with `name`.
507    #[must_use]
508    pub fn named<I: Into<Identifier<'a>>>(name: I) -> Self {
509        Self(vec![name.into()])
510    }
511
512    /// Adds another name segment.
513    #[must_use]
514    pub fn and<I: Into<Identifier<'a>>>(mut self, name: I) -> Self {
515        self.0.push(name.into());
516        self
517    }
518}
519
520impl<'a> AsRef<[Identifier<'a>]> for ResourceName<'a> {
521    fn as_ref(&self) -> &[Identifier<'a>] {
522        &self.0
523    }
524}
525
526impl<'a> IntoIterator for ResourceName<'a> {
527    type IntoIter = std::vec::IntoIter<Identifier<'a>>;
528    type Item = Identifier<'a>;
529
530    fn into_iter(self) -> Self::IntoIter {
531        self.0.into_iter()
532    }
533}
534
535impl<'b, 'a> From<&'b [Identifier<'a>]> for ResourceName<'a> {
536    fn from(parts: &'b [Identifier<'a>]) -> Self {
537        Self(parts.to_vec())
538    }
539}
540
541impl<'a> From<&'a str> for ResourceName<'a> {
542    fn from(name: &'a str) -> Self {
543        Self(vec![Identifier::from(name)])
544    }
545}
546
547impl<'a> From<u64> for ResourceName<'a> {
548    fn from(name: u64) -> Self {
549        Self(vec![Identifier::from(name)])
550    }
551}