radix_engine/system/system_modules/auth/
authorization.rs

1use crate::blueprints::resource::AuthZone;
2use crate::errors::RuntimeError;
3use crate::internal_prelude::*;
4use crate::kernel::kernel_api::KernelSubstateApi;
5use crate::object_modules::role_assignment::{
6    RoleAssignmentAccessRuleEntryPayload, RoleAssignmentOwnerFieldPayload,
7};
8use crate::system::system_modules::auth::{
9    AuthorityListAuthorizationResult, AuthorizationCheckResult,
10};
11use crate::system::system_substates::FieldSubstate;
12use crate::system::system_substates::KeyValueEntrySubstate;
13use num_traits::Zero;
14use radix_engine_interface::api::{LockFlags, ModuleId, SystemObjectApi};
15use radix_engine_interface::blueprints::resource::*;
16use radix_native_sdk::resource::{NativeNonFungibleProof, NativeProof};
17use sbor::rust::ops::Fn;
18
19pub struct Authorization;
20
21impl Authorization {
22    fn proof_matches<Y: SystemObjectApi<RuntimeError> + KernelSubstateApi<L>, L: Default>(
23        resource_rule: &ResourceOrNonFungible,
24        proof: &Proof,
25        api: &mut Y,
26    ) -> Result<bool, RuntimeError> {
27        match resource_rule {
28            ResourceOrNonFungible::NonFungible(non_fungible_global_id) => {
29                let proof_resource_address = proof.resource_address(api)?;
30                Ok(
31                    proof_resource_address == non_fungible_global_id.resource_address()
32                        && proof
33                            .non_fungible_local_ids(api)?
34                            .contains(non_fungible_global_id.local_id()),
35                )
36            }
37            ResourceOrNonFungible::Resource(resource_address) => {
38                let proof_resource_address = proof.resource_address(api)?;
39                Ok(proof_resource_address == *resource_address)
40            }
41        }
42    }
43
44    fn global_auth_zone_matches<Y: KernelSubstateApi<L>, L: Default>(
45        api: &mut Y,
46        auth_zone_id: &NodeId,
47        check: &impl Fn(
48            &[Proof],
49            &BTreeSet<ResourceAddress>,
50            BTreeSet<NonFungibleGlobalId>,
51            &mut Y,
52        ) -> Result<bool, RuntimeError>,
53    ) -> Result<bool, RuntimeError> {
54        let mut pass = false;
55        let mut current_auth_zone_id = *auth_zone_id;
56        let mut handles = Vec::new();
57        loop {
58            // Load auth zone
59            let handle = api.kernel_open_substate(
60                &current_auth_zone_id,
61                MAIN_BASE_PARTITION,
62                &AuthZoneField::AuthZone.into(),
63                LockFlags::read_only(),
64                L::default(),
65            )?;
66            let auth_zone = api
67                .kernel_read_substate(handle)?
68                .as_typed::<FieldSubstate<AuthZone>>()
69                .unwrap()
70                .into_payload();
71            handles.push(handle);
72
73            {
74                let mut implicit_non_fungible_proofs = BTreeSet::new();
75                let simulate_all_proofs_under_resources =
76                    auth_zone.simulate_all_proofs_under_resources();
77
78                implicit_non_fungible_proofs
79                    .extend(auth_zone.implicit_non_fungible_proofs().clone());
80
81                let proofs = auth_zone.proofs();
82
83                // Check
84                if check(
85                    proofs,
86                    simulate_all_proofs_under_resources,
87                    implicit_non_fungible_proofs,
88                    api,
89                )? {
90                    pass = true;
91                    break;
92                }
93            }
94
95            if let Some(id) = auth_zone.parent {
96                current_auth_zone_id = id.into();
97            } else {
98                break;
99            }
100        }
101
102        for handle in handles {
103            api.kernel_close_substate(handle)?;
104        }
105
106        Ok(pass)
107    }
108
109    fn auth_zone_stack_matches<Y: KernelSubstateApi<L>, L: Default>(
110        auth_zone: &NodeId,
111        api: &mut Y,
112        check: impl Fn(
113            &[Proof],
114            &BTreeSet<ResourceAddress>,
115            BTreeSet<NonFungibleGlobalId>,
116            &mut Y,
117        ) -> Result<bool, RuntimeError>,
118    ) -> Result<bool, RuntimeError> {
119        let handle = api.kernel_open_substate(
120            &auth_zone,
121            MAIN_BASE_PARTITION,
122            &AuthZoneField::AuthZone.into(),
123            LockFlags::read_only(),
124            L::default(),
125        )?;
126
127        // Using this block structure to be able to ensure handle is freed
128        // The suggested Rust pattern seems to be to use RAII pattern + Drop but
129        // at the moment this does not seem practical to be able to implement
130        let rtn = (|| -> Result<bool, RuntimeError> {
131            let auth_zone = api
132                .kernel_read_substate(handle)?
133                .as_typed::<FieldSubstate<AuthZone>>()
134                .unwrap()
135                .into_payload();
136
137            // Check local implicit non fungible proofs
138            let local_implicit_non_fungible_proofs = auth_zone.local_implicit_non_fungible_proofs();
139            if !local_implicit_non_fungible_proofs.is_empty() {
140                if check(&[], &btreeset!(), local_implicit_non_fungible_proofs, api)? {
141                    return Ok(true);
142                }
143            }
144
145            // Check global caller's full auth zone
146            if let Some((_, global_caller_leaf_auth_zone_reference)) = &auth_zone.global_caller {
147                if Self::global_auth_zone_matches(
148                    api,
149                    &global_caller_leaf_auth_zone_reference.0,
150                    &check,
151                )? {
152                    return Ok(true);
153                }
154            }
155
156            // Check current caller's full auth zone
157            // We ignore the current frame's authzone since it is not relevant
158            if let Some(parent) = auth_zone.parent {
159                if Self::global_auth_zone_matches(api, &parent.0, &check)? {
160                    return Ok(true);
161                }
162            }
163
164            Ok(false)
165        })()?;
166
167        api.kernel_close_substate(handle)?;
168
169        Ok(rtn)
170    }
171
172    fn auth_zone_stack_has_amount<
173        Y: SystemObjectApi<RuntimeError> + KernelSubstateApi<L>,
174        L: Default,
175    >(
176        auth_zone: &NodeId,
177        resource: &ResourceAddress,
178        amount: Decimal,
179        api: &mut Y,
180    ) -> Result<bool, RuntimeError> {
181        Self::auth_zone_stack_matches(auth_zone, api, |proofs, _, _, api| {
182            // TODO: revisit this and decide if we need to check the composite max amount rather than just each proof individually
183            for p in proofs {
184                if Self::proof_matches(&ResourceOrNonFungible::Resource(*resource), p, api)?
185                    && p.amount(api)? >= amount
186                {
187                    return Ok(true);
188                }
189            }
190
191            Ok(false)
192        })
193    }
194
195    fn auth_zone_stack_matches_rule<
196        Y: SystemObjectApi<RuntimeError> + KernelSubstateApi<L>,
197        L: Default,
198    >(
199        auth_zone: &NodeId,
200        resource_rule: &ResourceOrNonFungible,
201        api: &mut Y,
202    ) -> Result<bool, RuntimeError> {
203        Self::auth_zone_stack_matches(
204            auth_zone,
205            api,
206            |proofs, virtual_resources, virtual_non_fungibles, api| {
207                if let ResourceOrNonFungible::NonFungible(non_fungible_global_id) = resource_rule {
208                    if virtual_non_fungibles.contains(non_fungible_global_id) {
209                        return Ok(true);
210                    }
211
212                    if virtual_resources.contains(&non_fungible_global_id.resource_address()) {
213                        return Ok(true);
214                    }
215                }
216
217                for p in proofs {
218                    if Self::proof_matches(resource_rule, p, api)? {
219                        return Ok(true);
220                    }
221                }
222
223                Ok(false)
224            },
225        )
226    }
227
228    pub fn verify_proof_rule<
229        Y: SystemObjectApi<RuntimeError> + KernelSubstateApi<L>,
230        L: Default,
231    >(
232        auth_zone: &NodeId,
233        requirement_rule: &BasicRequirement,
234        api: &mut Y,
235    ) -> Result<bool, RuntimeError> {
236        match requirement_rule {
237            BasicRequirement::Require(resource) => {
238                if Self::auth_zone_stack_matches_rule(auth_zone, resource, api)? {
239                    Ok(true)
240                } else {
241                    Ok(false)
242                }
243            }
244            BasicRequirement::AmountOf(amount, resource) => {
245                if Self::auth_zone_stack_has_amount(auth_zone, resource, *amount, api)? {
246                    Ok(true)
247                } else {
248                    Ok(false)
249                }
250            }
251            BasicRequirement::AllOf(resources) => {
252                for resource in resources {
253                    if !Self::auth_zone_stack_matches_rule(auth_zone, resource, api)? {
254                        return Ok(false);
255                    }
256                }
257
258                Ok(true)
259            }
260            BasicRequirement::AnyOf(resources) => {
261                for resource in resources {
262                    if Self::auth_zone_stack_matches_rule(auth_zone, resource, api)? {
263                        return Ok(true);
264                    }
265                }
266
267                Ok(false)
268            }
269            BasicRequirement::CountOf(count, resources) => {
270                if count.is_zero() {
271                    return Ok(true);
272                }
273
274                let mut left = count.clone();
275                for resource in resources {
276                    if Self::auth_zone_stack_matches_rule(auth_zone, resource, api)? {
277                        left -= 1;
278                        if left == 0 {
279                            return Ok(true);
280                        }
281                    }
282                }
283                Ok(false)
284            }
285        }
286    }
287
288    pub fn verify_auth_rule<Y: SystemObjectApi<RuntimeError> + KernelSubstateApi<L>, L: Default>(
289        auth_zone: &NodeId,
290        requirement_rule: &CompositeRequirement,
291        api: &mut Y,
292    ) -> Result<AuthorizationCheckResult, RuntimeError> {
293        match requirement_rule {
294            CompositeRequirement::BasicRequirement(rule) => {
295                if Self::verify_proof_rule(auth_zone, rule, api)? {
296                    Ok(AuthorizationCheckResult::Authorized)
297                } else {
298                    Ok(AuthorizationCheckResult::Failed(vec![]))
299                }
300            }
301            CompositeRequirement::AnyOf(rules) => {
302                for r in rules {
303                    let rtn = Self::verify_auth_rule(auth_zone, r, api)?;
304                    if matches!(rtn, AuthorizationCheckResult::Authorized) {
305                        return Ok(rtn);
306                    }
307                }
308                Ok(AuthorizationCheckResult::Failed(vec![]))
309            }
310            CompositeRequirement::AllOf(rules) => {
311                for r in rules {
312                    let rtn = Self::verify_auth_rule(auth_zone, r, api)?;
313                    if matches!(rtn, AuthorizationCheckResult::Failed(..)) {
314                        return Ok(rtn);
315                    }
316                }
317
318                return Ok(AuthorizationCheckResult::Authorized);
319            }
320        }
321    }
322
323    pub fn check_authorization_against_role_key_internal<
324        Y: SystemObjectApi<RuntimeError> + KernelSubstateApi<L>,
325        L: Default,
326    >(
327        auth_zone: &NodeId,
328        role_assignment_of: &GlobalAddress,
329        key: &ModuleRoleKey,
330        api: &mut Y,
331    ) -> Result<AuthorizationCheckResult, RuntimeError> {
332        let access_rule = if key.key.key.eq(SELF_ROLE) {
333            rule!(require(global_caller(role_assignment_of.clone())))
334        } else {
335            let handle = api.kernel_open_substate_with_default(
336                role_assignment_of.as_node_id(),
337                ROLE_ASSIGNMENT_BASE_PARTITION
338                    .at_offset(ROLE_ASSIGNMENT_ROLE_DEF_PARTITION_OFFSET)
339                    .unwrap(),
340                &SubstateKey::Map(scrypto_encode(&key).unwrap()),
341                LockFlags::read_only(),
342                Some(|| {
343                    let kv_entry = KeyValueEntrySubstate::<()>::default();
344                    IndexedScryptoValue::from_typed(&kv_entry)
345                }),
346                L::default(),
347            )?;
348            let substate: KeyValueEntrySubstate<RoleAssignmentAccessRuleEntryPayload> =
349                api.kernel_read_substate(handle)?.as_typed().unwrap();
350            api.kernel_close_substate(handle)?;
351
352            match substate.into_value() {
353                Some(access_rule) => access_rule.fully_update_and_into_latest_version(),
354                None => {
355                    let handle = api.kernel_open_substate(
356                        role_assignment_of.as_node_id(),
357                        ROLE_ASSIGNMENT_BASE_PARTITION
358                            .at_offset(ROLE_ASSIGNMENT_FIELDS_PARTITION_OFFSET)
359                            .unwrap(),
360                        &SubstateKey::Field(0u8),
361                        LockFlags::read_only(),
362                        L::default(),
363                    )?;
364
365                    let owner_role_substate: FieldSubstate<RoleAssignmentOwnerFieldPayload> =
366                        api.kernel_read_substate(handle)?.as_typed().unwrap();
367                    api.kernel_close_substate(handle)?;
368                    owner_role_substate
369                        .into_payload()
370                        .fully_update_and_into_latest_version()
371                        .owner_role_entry
372                        .rule
373                }
374            }
375        };
376
377        Self::check_authorization_against_access_rule(api, auth_zone, &access_rule)
378    }
379
380    pub fn check_authorization_against_access_rule<
381        Y: SystemObjectApi<RuntimeError> + KernelSubstateApi<L>,
382        L: Default,
383    >(
384        api: &mut Y,
385        auth_zone: &NodeId,
386        rule: &AccessRule,
387    ) -> Result<AuthorizationCheckResult, RuntimeError> {
388        match rule {
389            AccessRule::Protected(rule_node) => {
390                let mut rtn = Self::verify_auth_rule(auth_zone, rule_node, api)?;
391                match &mut rtn {
392                    AuthorizationCheckResult::Authorized => {}
393                    AuthorizationCheckResult::Failed(stack) => {
394                        stack.push(rule.clone());
395                    }
396                }
397                Ok(rtn)
398            }
399            AccessRule::AllowAll => Ok(AuthorizationCheckResult::Authorized),
400            AccessRule::DenyAll => Ok(AuthorizationCheckResult::Failed(vec![rule.clone()])),
401        }
402    }
403
404    pub fn check_authorization_against_role_list<
405        Y: SystemObjectApi<RuntimeError> + KernelSubstateApi<L>,
406        L: Default,
407    >(
408        auth_zone: &NodeId,
409        role_assignment_of: &GlobalAddress,
410        module: ModuleId,
411        role_list: &RoleList,
412        api: &mut Y,
413    ) -> Result<AuthorityListAuthorizationResult, RuntimeError> {
414        let mut failed = Vec::new();
415
416        for key in &role_list.list {
417            let module_role_key = ModuleRoleKey::new(module, key.key.as_str());
418            let result = Self::check_authorization_against_role_key_internal(
419                &auth_zone,
420                role_assignment_of,
421                &module_role_key,
422                api,
423            )?;
424            match result {
425                AuthorizationCheckResult::Authorized => {
426                    return Ok(AuthorityListAuthorizationResult::Authorized)
427                }
428                AuthorizationCheckResult::Failed(stack) => {
429                    failed.push((key.clone(), stack));
430                }
431            }
432        }
433
434        Ok(AuthorityListAuthorizationResult::Failed(failed))
435    }
436}