use tari_engine_types::ownership::Ownership;
use tari_template_lib::types::{
NonFungibleAddress,
SubstateOwnerRule,
access_rules::{
AccessRule,
RequireRule,
ResourceAccessRules,
ResourceAuthAction,
RestrictedAccessRule,
RuleRequirement,
},
};
use crate::{
runtime::{ActionIdent, AuthorizationScope, RuntimeError, working_state::WorkingState},
state_store::StateReader,
};
pub struct Authorization<'a, TStore> {
state: &'a WorkingState<TStore>,
}
impl<'a, TStore: StateReader> Authorization<'a, TStore> {
pub(super) fn new(state: &'a WorkingState<TStore>) -> Self {
Self { state }
}
pub fn check_current_component_access_rules(&self, method: &str) -> Result<(), RuntimeError> {
let locked = self
.state
.current_call_scope()?
.get_current_component_lock()
.ok_or_else(|| RuntimeError::InvariantError {
function: "check_component_access_rules",
details: "No current component lock in call scope".to_string(),
})?;
let component = self.state.get_component(locked)?;
let scope = self.state.current_call_scope()?.auth_scope();
if check_ownership(self.state, scope, component.as_ownership())? {
return Ok(());
}
let component_address =
locked
.substate_id()
.as_component_address()
.ok_or_else(|| RuntimeError::InvariantError {
function: "check_component_access_rules",
details: format!("Expected a component address, got {}", locked.substate_id()),
})?;
let access_rule = component.access_rules().get_method_access_rule(method);
if !self.check_access_rule(access_rule)? {
return Err(RuntimeError::AccessDenied {
action_ident: ActionIdent::ComponentCallMethod {
component_address,
method: method.to_string(),
},
});
}
Ok(())
}
pub fn check_resource_access_rules(
&self,
action: ResourceAuthAction,
resource_ownership: Ownership<'_>,
resource_access_rules: &ResourceAccessRules,
) -> Result<(), RuntimeError> {
let scope = self.state.current_call_scope()?.auth_scope();
if !action.is_recall() && check_ownership(self.state, scope, resource_ownership)? {
return Ok(());
}
let rule = resource_access_rules.get_access_rule(&action);
if !check_access_rule(self.state, scope, rule)? {
return Err(RuntimeError::AccessDenied {
action_ident: action.into(),
});
}
Ok(())
}
pub fn check_access_rule(&self, rule: &AccessRule) -> Result<bool, RuntimeError> {
let scope = self.state.current_call_scope()?.auth_scope();
check_access_rule(self.state, scope, rule)
}
pub fn require_ownership<A: Into<ActionIdent>>(
&self,
action: A,
ownership: Ownership<'_>,
) -> Result<(), RuntimeError> {
if !check_ownership(self.state, self.state.current_call_scope()?.auth_scope(), ownership)? {
return Err(RuntimeError::AccessDeniedOwnerRequired { action: action.into() });
}
Ok(())
}
}
fn check_ownership<TStore: StateReader>(
state: &WorkingState<TStore>,
scope: &AuthorizationScope,
ownership: Ownership<'_>,
) -> Result<bool, RuntimeError> {
match ownership.owner_rule.as_ref() {
SubstateOwnerRule::None => Ok(false),
SubstateOwnerRule::ByAccessRule(rule) => check_access_rule(state, scope, rule),
SubstateOwnerRule::ByPublicKey(key) => {
let owner_proof = NonFungibleAddress::from_public_key(*key);
Ok(scope.contains_badge(&owner_proof))
},
}
}
fn check_access_rule<TStore: StateReader>(
state: &WorkingState<TStore>,
scope: &AuthorizationScope,
rule: &AccessRule,
) -> Result<bool, RuntimeError> {
match rule {
AccessRule::AllowAll => Ok(true),
AccessRule::DenyAll => Ok(false),
AccessRule::Restricted(rule) => check_restricted_access_rule(state, scope, rule),
}
}
fn check_restricted_access_rule<TStore: StateReader>(
state: &WorkingState<TStore>,
scope: &AuthorizationScope,
rule: &RestrictedAccessRule,
) -> Result<bool, RuntimeError> {
match rule {
RestrictedAccessRule::Require(rule) => check_require_rule(state, scope, rule),
RestrictedAccessRule::AnyOf(rules) => {
for rule in rules {
if check_restricted_access_rule(state, scope, rule)? {
return Ok(true);
}
}
Ok(false)
},
RestrictedAccessRule::AllOf(rules) => {
if rules.is_empty() {
return Ok(false);
}
for rule in rules {
if !check_restricted_access_rule(state, scope, rule)? {
return Ok(false);
}
}
Ok(true)
},
}
}
fn check_require_rule<TStore: StateReader>(
state: &WorkingState<TStore>,
scope: &AuthorizationScope,
rule: &RequireRule,
) -> Result<bool, RuntimeError> {
match rule {
RequireRule::Require(requirement) => check_requirement(state, scope, requirement),
RequireRule::AnyOf(requirements) => {
for requirement in requirements {
if check_requirement(state, scope, requirement)? {
return Ok(true);
}
}
Ok(false)
},
RequireRule::AllOf(requirements) => {
if requirements.is_empty() {
return Ok(false);
}
for requirement in requirements {
if !check_requirement(state, scope, requirement)? {
return Ok(false);
}
}
Ok(true)
},
RequireRule::MOfN(n, requirements) => {
if *n == 0 {
return Ok(true);
}
let mut satisfied = 0u16;
for requirement in requirements {
if check_requirement(state, scope, requirement)? {
satisfied += 1;
if satisfied == *n {
return Ok(true);
}
}
}
Ok(false)
},
}
}
fn check_requirement<TStore: StateReader>(
state: &WorkingState<TStore>,
scope: &AuthorizationScope,
requirement: &RuleRequirement,
) -> Result<bool, RuntimeError> {
match requirement {
RuleRequirement::Resource(resx) => {
if scope.contains_badge_of_resource(resx) {
return Ok(true);
}
for proof_id in scope.proofs() {
let proof = state.get_proof(*proof_id)?;
if resx == proof.resource_address() {
return Ok(true);
}
}
Ok(false)
},
RuleRequirement::NonFungibleAddress(addr) => {
if scope.contains_badge(addr) {
return Ok(true);
}
for proof_id in scope.proofs() {
let proof = state.get_proof(*proof_id)?;
if addr.resource_address() == proof.resource_address() &&
proof.non_fungible_token_ids().contains(addr.id())
{
return Ok(true);
}
}
Ok(false)
},
RuleRequirement::ScopedToComponent(address) => Ok(state.current_component()? == Some(*address)),
RuleRequirement::ScopedToTemplate(address) => {
let (current, _) = state.current_template()?;
Ok(current == address)
},
}
}