capgrok/
capability.rs

1use crate::{Error, Namespace, Set};
2use std::collections::{BTreeMap, HashMap};
3
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7fn eq_set_is_empty<T: Eq>(s: &Set<T>) -> bool {
8    s.as_ref().is_empty()
9}
10
11/// Representation of a delegated Capability.
12#[derive(Clone, Default, Debug, Serialize, Deserialize)]
13pub struct Capability {
14    /// The default actions that are allowed globally within this namespace.
15    #[serde(default, skip_serializing_if = "eq_set_is_empty", rename = "def")]
16    pub default_actions: Set<String>,
17    /// The actions that are allowed for the given target within this namespace.
18    #[serde(default, skip_serializing_if = "BTreeMap::is_empty", rename = "tar")]
19    pub targeted_actions: BTreeMap<String, Set<String>>,
20    /// Any additional information that is needed for the verifier to understand this Capability.
21    ///
22    /// This data is not encoded in the SIWE statement, so it must not contain any information that
23    /// the verifier could use to extend the functionality defined by this capability. A good
24    /// example of information you might encode here is the Cid of a previous delegation that this
25    /// Capability is chaining from.
26    #[serde(default, skip_serializing_if = "HashMap::is_empty", rename = "ext")]
27    pub extra_fields: HashMap<String, Value>,
28}
29
30impl Capability {
31    /// Check if a particular action is allowed for the specified target, or is allowed globally.
32    pub fn can(&self, target: &str, action: &str) -> bool {
33        self.can_default(action)
34            || self
35                .targeted_actions
36                .get(target)
37                .map(|actions| actions.as_ref().contains_alike(action))
38                .unwrap_or(false)
39    }
40
41    /// Check if a particular actions is allowed globally.
42    pub fn can_default(&self, action: &str) -> bool {
43        self.default_actions.as_ref().contains_alike(action)
44    }
45
46    pub(crate) fn encode(&self) -> Result<String, Error> {
47        serde_json::to_vec(self)
48            .map_err(Error::Ser)
49            .map(|bytes| base64::encode_config(bytes, base64::URL_SAFE_NO_PAD))
50    }
51
52    pub(crate) fn decode(encoded: &str) -> Result<Self, Error> {
53        base64::decode_config(encoded, base64::URL_SAFE_NO_PAD)
54            .map_err(Error::Base64Decode)
55            .and_then(|bytes| serde_json::from_slice(&bytes).map_err(Error::De))
56    }
57
58    pub(crate) fn to_statement_lines<'l>(
59        &'l self,
60        namespace: &'l Namespace,
61    ) -> impl Iterator<Item = String> + 'l {
62        let default_actions = std::iter::once(self.default_actions.as_ref().join(", "))
63            .filter(|actions| !actions.is_empty())
64            .map(move |actions| format!("{}: {} for any.", namespace, actions));
65
66        let action_sets: Set<&[String]> =
67            self.targeted_actions.values().map(AsRef::as_ref).collect();
68
69        let targeted_actions = action_sets.into_iter().map(move |action_set| {
70            let targets = self
71                .targeted_actions
72                .iter()
73                .filter(|(_, actions)| actions.as_ref() == action_set)
74                .map(|(target, _)| target.as_ref())
75                .collect::<Vec<&str>>();
76            format!(
77                "{}: {} for {}.",
78                namespace,
79                action_set.join(", "),
80                targets.join(", ")
81            )
82        });
83
84        default_actions.chain(targeted_actions)
85    }
86}
87
88trait Contains<T: ?Sized> {
89    fn contains_alike(&self, other: &T) -> bool;
90}
91
92impl Contains<str> for [String] {
93    fn contains_alike(&self, other: &str) -> bool {
94        self.iter().any(|i| i == other)
95    }
96}