1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
use crate::{entry::Entry, error::HolochainError};

use holochain_persistence_api::cas::content::{Address, AddressableContent};

use holochain_json_api::{error::JsonError, json::JsonString};

use std::{collections::BTreeMap, str::FromStr};

//--------------------------------------------------------------------------------------------------
// CapabilityType
//--------------------------------------------------------------------------------------------------

/// Enum for CapabilityType.  Public capabilities require public grant token.  Transferable
/// capabilities require a token, but don't limit the capability to specific agent(s);
/// this functions like a password in that you can give the token to someone else and it works.
/// Assigned capabilities check the request's signature against the list of agents to which
/// the capability has been granted.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash)]
pub enum CapabilityType {
    #[serde(rename = "public")]
    Public,
    #[serde(rename = "transferable")]
    Transferable,
    #[serde(rename = "assigned")]
    Assigned,
}

impl Default for CapabilityType {
    fn default() -> CapabilityType {
        CapabilityType::Assigned
    }
}

#[derive(Debug, PartialEq)]
/// Enumeration of all Capabilities known and used by HC Core
/// Enumeration converts to str
pub enum ReservedCapabilityId {
    /// used for identifying the default public capability
    Public,
}

impl FromStr for ReservedCapabilityId {
    type Err = &'static str;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "hc_public" => Ok(ReservedCapabilityId::Public),
            _ => Err("Cannot convert string to ReservedCapabilityId"),
        }
    }
}

impl ReservedCapabilityId {
    pub fn as_str(&self) -> &'static str {
        match *self {
            ReservedCapabilityId::Public => "hc_public",
        }
    }
}

pub type CapTokenValue = Address;

/// a collection functions by zome name that are authorized within a capability
pub type CapFunctions = BTreeMap<String, Vec<String>>;

/// System entry to hold a capability token claim for use as a caller
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, DefaultJson, Eq)]
pub struct CapTokenClaim {
    id: String,
    grantor: Address,
    token: CapTokenValue,
}

impl CapTokenClaim {
    pub fn new(id: String, grantor: Address, token: CapTokenValue) -> Self {
        CapTokenClaim { id, grantor, token }
    }
    pub fn token(&self) -> CapTokenValue {
        self.token.clone()
    }
    pub fn id(&self) -> String {
        self.id.clone()
    }
    pub fn grantor(&self) -> Address {
        self.grantor.clone()
    }
}

/// System entry to hold a capabilities granted by the callee
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, DefaultJson, Eq)]
pub struct CapTokenGrant {
    id: String,
    assignees: Option<Vec<Address>>,
    functions: CapFunctions,
}

impl CapTokenGrant {
    fn new(id: &str, assignees: Option<Vec<Address>>, functions: CapFunctions) -> Self {
        CapTokenGrant {
            id: String::from(id),
            assignees,
            functions,
        }
    }

    pub fn create(
        id: &str,
        cap_type: CapabilityType,
        assignees: Option<Vec<Address>>,
        functions: CapFunctions,
    ) -> Result<Self, HolochainError> {
        let assignees = CapTokenGrant::valid(cap_type, assignees)?;
        Ok(CapTokenGrant::new(id, assignees, functions))
    }

    // internal check that type and assignees are valid for create
    fn valid(
        cap_type: CapabilityType,
        assignees: Option<Vec<Address>>,
    ) -> Result<Option<Vec<Address>>, HolochainError> {
        if (cap_type == CapabilityType::Public || cap_type == CapabilityType::Transferable)
            && (assignees.is_some() && !assignees.clone().unwrap().is_empty())
        {
            return Err(HolochainError::new(
                "there must be no assignees for public or transferable grants",
            ));
        }
        match cap_type {
            CapabilityType::Assigned => {
                if assignees.is_none() || assignees.clone().unwrap().is_empty() {
                    return Err(HolochainError::new(
                        "Assigned grant must have 1 or more assignees",
                    ));
                }
                Ok(assignees)
            }
            CapabilityType::Public => Ok(None),
            CapabilityType::Transferable => Ok(Some(Vec::new())),
        }
    }

    pub fn id(&self) -> String {
        self.id.to_string()
    }

    // the token value is address of the entry, so we can just build it
    // and take the address.
    pub fn token(&self) -> CapTokenValue {
        let addr: Address = Entry::CapTokenGrant((*self).clone()).address();
        addr
    }

    pub fn cap_type(&self) -> CapabilityType {
        match self.assignees() {
            None => CapabilityType::Public,
            Some(vec) => {
                if vec.is_empty() {
                    CapabilityType::Transferable
                } else {
                    CapabilityType::Assigned
                }
            }
        }
    }

    pub fn assignees(&self) -> Option<Vec<Address>> {
        self.assignees.clone()
    }

    pub fn functions(&self) -> CapFunctions {
        self.functions.clone()
    }
}

#[cfg(test)]
pub mod tests {
    use super::*;

    #[test]
    /// test that ReservedCapabilityId can be created from a canonical string
    fn test_reserved_capid_from_str() {
        assert_eq!(
            Ok(ReservedCapabilityId::Public),
            ReservedCapabilityId::from_str("hc_public"),
        );
        assert_eq!(
            Err("Cannot convert string to ReservedCapabilityId"),
            ReservedCapabilityId::from_str("foo"),
        );
    }

    #[test]
    /// test that a canonical string can be created from ReservedCapabilityId
    fn test_reserved_capid_as_str() {
        assert_eq!(ReservedCapabilityId::Public.as_str(), "hc_public");
    }

    #[test]
    fn test_new_cap_token_claim_entry() {
        let token = Address::from("fake");
        let grantor = Address::from("fake grantor");
        let claim = CapTokenClaim::new("foo".to_string(), grantor.clone(), token.clone());
        assert_eq!(claim.id(), "foo".to_string());
        assert_eq!(claim.grantor(), grantor);
        assert_eq!(claim.token(), token);
    }

    #[test]
    fn test_new_cap_token_grant_entry() {
        let empty_functions = CapFunctions::new();
        let grant = CapTokenGrant::new("foo", None, empty_functions.clone());
        assert_eq!(grant.cap_type(), CapabilityType::Public);
        assert_eq!(grant.id(), "foo".to_string());
        let grant = CapTokenGrant::new("", Some(Vec::new()), empty_functions.clone());
        assert_eq!(grant.cap_type(), CapabilityType::Transferable);
        let test_address = Address::new();
        let grant = CapTokenGrant::new(
            "",
            Some(vec![test_address.clone()]),
            empty_functions.clone(),
        );
        assert_eq!(grant.cap_type(), CapabilityType::Assigned);
        assert_eq!(grant.assignees().unwrap()[0], test_address)
    }

    #[test]
    fn test_cap_grant_valid() {
        assert!(CapTokenGrant::valid(CapabilityType::Public, None).is_ok());
        assert!(CapTokenGrant::valid(CapabilityType::Public, Some(Vec::new())).is_ok());
        assert!(CapTokenGrant::valid(CapabilityType::Public, Some(vec![Address::new()])).is_err());
        assert!(CapTokenGrant::valid(CapabilityType::Transferable, None).is_ok());
        assert!(CapTokenGrant::valid(CapabilityType::Transferable, Some(Vec::new())).is_ok());
        assert!(
            CapTokenGrant::valid(CapabilityType::Transferable, Some(vec![Address::new()])).is_err()
        );
        assert!(CapTokenGrant::valid(CapabilityType::Assigned, None).is_err());
        assert!(CapTokenGrant::valid(CapabilityType::Assigned, Some(Vec::new())).is_err());
        assert!(CapTokenGrant::valid(CapabilityType::Assigned, Some(vec![Address::new()])).is_ok());
    }

    #[test]
    fn test_create_cap_token_grant_entry() {
        let some_fn = String::from("some_fn");
        let mut example_functions = CapFunctions::new();
        example_functions.insert("some_zome".to_string(), vec![some_fn]);
        let maybe_grant = CapTokenGrant::create(
            "foo",
            CapabilityType::Public,
            None,
            example_functions.clone(),
        );
        assert!(maybe_grant.is_ok());
        let grant = maybe_grant.unwrap();
        assert_eq!(grant.id, "foo".to_string());
        assert_eq!(grant.cap_type(), CapabilityType::Public);
        assert_eq!(grant.functions(), example_functions.clone());

        let maybe_grant = CapTokenGrant::create(
            "foo",
            CapabilityType::Transferable,
            Some(Vec::new()),
            example_functions.clone(),
        );
        assert!(maybe_grant.is_ok());
        let grant = maybe_grant.unwrap();
        assert_eq!(grant.cap_type(), CapabilityType::Transferable);

        let test_address = Address::new();

        let maybe_grant = CapTokenGrant::create(
            "foo",
            CapabilityType::Public,
            Some(vec![test_address.clone()]),
            example_functions.clone(),
        );
        assert!(maybe_grant.is_err());
        let maybe_grant = CapTokenGrant::create(
            "foo",
            CapabilityType::Transferable,
            None,
            example_functions.clone(),
        );
        assert!(maybe_grant.is_ok());
        let grant = maybe_grant.unwrap();
        assert_eq!(grant.cap_type(), CapabilityType::Transferable);

        let maybe_grant = CapTokenGrant::create(
            "foo",
            CapabilityType::Assigned,
            Some(vec![test_address.clone()]),
            example_functions.clone(),
        );
        assert!(maybe_grant.is_ok());
        let grant = maybe_grant.unwrap();
        assert_eq!(grant.cap_type(), CapabilityType::Assigned);
        assert_eq!(grant.assignees().unwrap()[0], test_address)
    }
}