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
use super::CapSecret;
use crate::zome::FunctionName;
use crate::zome::ZomeName;
use holo_hash::*;
use holochain_serialized_bytes::SerializedBytes;
use serde::Deserialize;
use serde::Serialize;
use std::collections::BTreeMap;
use std::collections::BTreeSet;

/// Represents a _potentially_ valid access grant to a zome call.
/// Zome call response will be Unauthorized without a valid grant.
///
/// The CapGrant is not always a dedicated entry in the chain.
/// Notably AgentPubKey entries in the current chain act like root access to local zome calls.
///
/// A `CapGrant` is valid if it matches the function, agent and secret for a given zome call.
///
/// See `.is_valid()`
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[allow(clippy::large_enum_variant)]
pub enum CapGrant {
    /// Grants the capability of calling every extern to the calling agent, provided the calling
    /// agent is the local chain author.
    /// This grant is compared to the current `Entry::Agent` entry on the source chain.
    ChainAuthor(AgentPubKey),

    /// Any agent other than the chain author is attempting to call an extern.
    /// The pubkey of the calling agent is secured by the cryptographic handshake at the network
    /// layer and the caller must provide a secret that we check for in a private entry in the
    /// local chain.
    RemoteAgent(ZomeCallCapGrant),
}

impl From<holo_hash::AgentPubKey> for CapGrant {
    fn from(agent_hash: holo_hash::AgentPubKey) -> Self {
        CapGrant::ChainAuthor(agent_hash)
    }
}

#[derive(Default, PartialEq, Eq, Debug, Clone, serde::Serialize, serde::Deserialize)]
/// @todo Ability to forcibly curry payloads into functions that are called with a claim.
pub struct CurryPayloads(pub BTreeMap<GrantedFunction, SerializedBytes>);

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
/// The entry for the ZomeCall capability grant.
/// This data is committed to the callee's source chain as a private entry.
/// The remote calling agent must provide a secret and we source their pubkey from the active
/// network connection. This must match the strictness of the CapAccess.
pub struct ZomeCallCapGrant {
    /// A string by which to later query for saved grants.
    /// This does not need to be unique within a source chain.
    pub tag: String,
    /// Specifies who may claim this capability, and by what means
    pub access: CapAccess,
    /// Set of functions to which this capability grants ZomeCall access
    pub functions: GrantedFunctions,
    // @todo the payloads to curry to the functions
    // pub curry_payloads: CurryPayloads,
}

impl ZomeCallCapGrant {
    /// Constructor
    pub fn new(
        tag: String,
        access: CapAccess,
        functions: GrantedFunctions,
        // @todo curry_payloads: CurryPayloads,
    ) -> Self {
        Self {
            tag,
            access,
            functions,
            // @todo curry_payloads,
        }
    }
}

impl From<ZomeCallCapGrant> for CapGrant {
    /// Create a new ZomeCall capability grant
    fn from(zccg: ZomeCallCapGrant) -> Self {
        CapGrant::RemoteAgent(zccg)
    }
}

impl CapGrant {
    /// Given a grant, is it valid in isolation?
    /// In a world of CRUD, some new entry might update or delete an existing one, but we can check
    /// if a grant is valid in a standalone way.
    pub fn is_valid(
        &self,
        check_function: &GrantedFunction,
        check_agent: &AgentPubKey,
        check_secret: Option<&CapSecret>,
    ) -> bool {
        match self {
            // Grant is always valid if the author matches the check agent.
            CapGrant::ChainAuthor(author) => author == check_agent,
            // Otherwise we need to do more work…
            CapGrant::RemoteAgent(ZomeCallCapGrant {
                access, functions, ..
            }) => {
                // The checked function needs to be in the grant…
                functions.contains(check_function)
                // The agent needs to be valid…
                && match access {
                    // The grant is assigned so the agent needs to match…
                    CapAccess::Assigned { assignees, .. } => assignees.contains(check_agent),
                    // The grant has no assignees so is always valid…
                    _ => true,
                }
                // The secret needs to match…
                && match access {
                    // Unless the extern is unrestricted.
                    CapAccess::Unrestricted => true,
                    // note the PartialEq implementation is constant time for secrets
                    CapAccess::Transferable { secret, .. } => check_secret.map(|given| secret == given).unwrap_or(false),
                    CapAccess::Assigned { secret, .. } => check_secret.map(|given| secret == given).unwrap_or(false),
                }
            }
        }
    }
}

/// Represents access requirements for capability grants.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum CapAccess {
    /// No restriction: callable by anyone.
    Unrestricted,
    /// Callable by anyone who can provide the secret.
    Transferable {
        /// The secret.
        secret: CapSecret,
    },
    /// Callable by anyone in the list of assignees who possesses the secret.
    Assigned {
        /// The secret.
        secret: CapSecret,
        /// Agents who can use this grant.
        assignees: BTreeSet<AgentPubKey>,
    },
}

/// Implements ().into() shorthand for CapAccess::Unrestricted
impl From<()> for CapAccess {
    fn from(_: ()) -> Self {
        Self::Unrestricted
    }
}

/// Implements secret.into() shorthand for CapAccess::Transferable(secret)
impl From<CapSecret> for CapAccess {
    fn from(secret: CapSecret) -> Self {
        Self::Transferable { secret }
    }
}

/// Implements (secret, assignees).into() shorthand for CapAccess::Assigned { secret, assignees }
impl From<(CapSecret, BTreeSet<AgentPubKey>)> for CapAccess {
    fn from((secret, assignees): (CapSecret, BTreeSet<AgentPubKey>)) -> Self {
        Self::Assigned { secret, assignees }
    }
}

/// Implements (secret, agent_pub_key).into() shorthand for
/// CapAccess::Assigned { secret, assignees: hashset!{ agent } }
impl From<(CapSecret, AgentPubKey)> for CapAccess {
    fn from((secret, assignee): (CapSecret, AgentPubKey)) -> Self {
        let mut assignees = BTreeSet::new();
        assignees.insert(assignee);
        Self::from((secret, assignees))
    }
}

/// a single zome/function pair
pub type GrantedFunction = (ZomeName, FunctionName);
/// A collection of zome/function pairs
pub type GrantedFunctions = BTreeSet<GrantedFunction>;