1use crate::{
2 access::{AccessError, AccessRuleError, AccessRuleResult},
3 cdk::{api::canister_self, api::is_controller as caller_is_controller, types::Principal},
4 config::Config,
5 ops::runtime::env::EnvOps,
6};
7use thiserror::Error as ThisError;
8
9#[derive(Debug, ThisError)]
14pub enum EnvAccessError {
15 #[error("this endpoint is only available on the prime subnet")]
16 NotPrimeSubnet,
17
18 #[error("this endpoint is only available on prime root")]
19 NotPrimeRoot,
20
21 #[error("operation must be called from the root canister")]
22 NotRoot,
23
24 #[error("operation cannot be called from the root canister")]
25 IsRoot,
26
27 #[error("access dependency unavailable: {0}")]
28 DependencyUnavailable(String),
29}
30
31#[allow(clippy::unused_async)]
36pub async fn is_root() -> Result<(), AccessError> {
37 let root_pid = EnvOps::root_pid()
38 .map_err(|_| EnvAccessError::DependencyUnavailable("root pid unavailable".to_string()))?;
39
40 if root_pid == canister_self() {
41 Ok(())
42 } else {
43 Err(EnvAccessError::NotRoot.into())
44 }
45}
46
47#[allow(clippy::unused_async)]
48pub async fn is_not_root() -> Result<(), AccessError> {
49 let root_pid = EnvOps::root_pid()
50 .map_err(|_| EnvAccessError::DependencyUnavailable("root pid unavailable".to_string()))?;
51
52 if root_pid == canister_self() {
53 Err(EnvAccessError::IsRoot.into())
54 } else {
55 Ok(())
56 }
57}
58
59#[allow(clippy::unused_async)]
60pub async fn is_prime_root() -> Result<(), AccessError> {
61 if EnvOps::is_prime_root() {
62 Ok(())
63 } else {
64 Err(EnvAccessError::NotPrimeRoot.into())
65 }
66}
67
68#[allow(clippy::unused_async)]
69pub async fn is_prime_subnet() -> Result<(), AccessError> {
70 if EnvOps::is_prime_subnet() {
71 Ok(())
72 } else {
73 Err(EnvAccessError::NotPrimeSubnet.into())
74 }
75}
76
77pub(crate) fn require_root() -> Result<(), AccessError> {
79 let root_pid = EnvOps::snapshot()
80 .root_pid
81 .ok_or_else(|| EnvAccessError::DependencyUnavailable("root pid unavailable".to_string()))?;
82
83 if root_pid == canister_self() {
84 Ok(())
85 } else {
86 Err(EnvAccessError::NotRoot.into())
87 }
88}
89
90pub(crate) fn deny_root() -> Result<(), AccessError> {
92 let root_pid = EnvOps::snapshot()
93 .root_pid
94 .ok_or_else(|| EnvAccessError::DependencyUnavailable("root pid unavailable".to_string()))?;
95
96 if root_pid == canister_self() {
97 Err(EnvAccessError::IsRoot.into())
98 } else {
99 Ok(())
100 }
101}
102
103#[must_use]
110pub fn is_controller(caller: Principal) -> AccessRuleResult {
111 Box::pin(async move {
112 if caller_is_controller(&caller) {
113 Ok(())
114 } else {
115 Err(AccessRuleError::NotController(caller).into())
116 }
117 })
118}
119
120#[must_use]
123pub fn is_whitelisted(caller: Principal) -> AccessRuleResult {
124 Box::pin(async move {
125 let cfg = Config::try_get().ok_or_else(|| {
126 AccessRuleError::DependencyUnavailable("config not initialized".to_string())
127 })?;
128
129 if !cfg.is_whitelisted(&caller) {
130 return Err(AccessRuleError::NotWhitelisted(caller).into());
131 }
132
133 Ok(())
134 })
135}