junobuild_shared/
controllers.rs1use crate::constants_internal::REVOKED_CONTROLLERS;
2use crate::env::{CONSOLE, OBSERVATORY};
3use crate::errors::{
4 JUNO_ERROR_CONTROLLERS_ANONYMOUS_NOT_ALLOWED, JUNO_ERROR_CONTROLLERS_MAX_NUMBER,
5 JUNO_ERROR_CONTROLLERS_REVOKED_NOT_ALLOWED,
6};
7use crate::types::interface::SetController;
8use crate::types::state::{Controller, ControllerId, ControllerScope, Controllers, UserId};
9use crate::utils::{principal_anonymous, principal_equal, principal_not_anonymous};
10use candid::Principal;
11use ic_cdk::api::{is_controller as is_canister_controller, time};
12use ic_cdk::id;
13use std::collections::HashMap;
14
15pub fn init_admin_controllers(new_controllers: &[UserId]) -> Controllers {
23 let mut controllers: Controllers = Controllers::new();
24
25 let controller_data: SetController = SetController {
26 metadata: HashMap::new(),
27 expires_at: None,
28 scope: ControllerScope::Admin,
29 };
30
31 set_controllers(new_controllers, &controller_data, &mut controllers);
32
33 controllers
34}
35
36pub fn set_controllers(
43 new_controllers: &[UserId],
44 controller_data: &SetController,
45 controllers: &mut Controllers,
46) {
47 for controller_id in new_controllers {
48 let existing_controller = controllers.get(controller_id);
49
50 let now = time();
51
52 let created_at: u64 = match existing_controller {
53 None => now,
54 Some(existing_controller) => existing_controller.created_at,
55 };
56
57 let updated_at: u64 = now;
58
59 let controller: Controller = Controller {
60 metadata: controller_data.metadata.clone(),
61 created_at,
62 updated_at,
63 expires_at: controller_data.expires_at,
64 scope: controller_data.scope.clone(),
65 };
66
67 controllers.insert(*controller_id, controller);
68 }
69}
70
71pub fn delete_controllers(remove_controllers: &[UserId], controllers: &mut Controllers) {
77 for c in remove_controllers {
78 controllers.remove(c);
79 }
80}
81
82pub fn controller_can_write(caller: UserId, controllers: &Controllers) -> bool {
91 principal_not_anonymous(caller)
92 && (caller_is_self(caller)
93 || controllers
94 .iter()
95 .any(|(&controller_id, controller)| match controller.scope {
96 ControllerScope::Submit => false,
97 _ => principal_equal(controller_id, caller),
98 }))
99}
100
101pub fn is_controller(caller: UserId, controllers: &Controllers) -> bool {
110 principal_not_anonymous(caller)
111 && (caller_is_self(caller)
112 || controllers
113 .iter()
114 .any(|(&controller_id, _)| principal_equal(controller_id, caller)))
115}
116
117pub fn is_admin_controller(caller: UserId, controllers: &Controllers) -> bool {
126 is_canister_controller(&caller)
127 && principal_not_anonymous(caller)
128 && controllers
129 .iter()
130 .any(|(&controller_id, controller)| match controller.scope {
131 ControllerScope::Admin => principal_equal(controller_id, caller),
132 _ => false,
133 })
134}
135
136pub fn into_controller_ids(controllers: &Controllers) -> Vec<ControllerId> {
144 controllers
145 .clone()
146 .into_keys()
147 .collect::<Vec<ControllerId>>()
148}
149
150pub fn assert_max_number_of_controllers(
160 current_controllers: &Controllers,
161 controllers_ids: &[ControllerId],
162 max_controllers: usize,
163) -> Result<(), String> {
164 let current_controller_ids = into_controller_ids(current_controllers);
165
166 let new_controller_ids = controllers_ids.iter().copied().filter(|id| {
167 !current_controller_ids
168 .iter()
169 .any(|current_id| current_id == id)
170 });
171
172 if current_controller_ids.len() + new_controller_ids.count() > max_controllers {
173 return Err(format!(
174 "{} ({})",
175 JUNO_ERROR_CONTROLLERS_MAX_NUMBER, max_controllers
176 ));
177 }
178
179 Ok(())
180}
181
182pub fn assert_controllers(controllers_ids: &[ControllerId]) -> Result<(), String> {
190 assert_no_anonymous_controller(controllers_ids)?;
191 assert_no_revoked_controller(controllers_ids)?;
192
193 Ok(())
194}
195
196fn assert_no_anonymous_controller(controllers_ids: &[ControllerId]) -> Result<(), String> {
204 let has_anonymous = controllers_ids
205 .iter()
206 .any(|controller_id| principal_anonymous(*controller_id));
207
208 match has_anonymous {
209 true => Err(JUNO_ERROR_CONTROLLERS_ANONYMOUS_NOT_ALLOWED.to_string()),
210 false => Ok(()),
211 }
212}
213
214fn assert_no_revoked_controller(controllers_ids: &[ControllerId]) -> Result<(), String> {
222 let has_revoked = controllers_ids.iter().any(controller_revoked);
224
225 match has_revoked {
226 true => Err(JUNO_ERROR_CONTROLLERS_REVOKED_NOT_ALLOWED.to_string()),
227 false => Ok(()),
228 }
229}
230
231pub fn caller_is_console(caller: UserId) -> bool {
239 let console = Principal::from_text(CONSOLE).unwrap();
240
241 principal_equal(caller, console)
242}
243
244pub fn caller_is_observatory(caller: UserId) -> bool {
252 let observatory = Principal::from_text(OBSERVATORY).unwrap();
253
254 principal_equal(caller, observatory)
255}
256
257pub fn caller_is_self(caller: UserId) -> bool {
265 let itself = id();
266
267 principal_equal(caller, itself)
268}
269
270pub fn filter_admin_controllers(controllers: &Controllers) -> Controllers {
278 #[allow(clippy::match_like_matches_macro)]
279 controllers
280 .clone()
281 .into_iter()
282 .filter(|(_, controller)| match controller.scope {
283 ControllerScope::Admin => true,
284 _ => false,
285 })
286 .collect()
287}
288
289fn controller_revoked(controller_id: &ControllerId) -> bool {
290 REVOKED_CONTROLLERS.iter().any(|revoked_controller_id| {
291 principal_equal(
292 Principal::from_text(revoked_controller_id).unwrap(),
293 *controller_id,
294 )
295 })
296}