junobuild_shared/
controllers.rs

1use crate::constants_internal::REVOKED_CONTROLLERS;
2use crate::env::{CONSOLE, OBSERVATORY};
3use crate::types::interface::SetController;
4use crate::types::state::{Controller, ControllerId, ControllerScope, Controllers, UserId};
5use crate::utils::{principal_anonymous, principal_equal, principal_not_anonymous};
6use candid::Principal;
7use ic_cdk::api::{is_controller as is_canister_controller, time};
8use ic_cdk::id;
9use std::collections::HashMap;
10
11/// Initializes a set of controllers with default administrative scope.
12///
13/// # Arguments
14/// - `new_controllers`: Slice of `UserId` representing the new controllers to be initialized.
15///
16/// # Returns
17/// A `Controllers` collection populated with the specified new controllers.
18pub fn init_controllers(new_controllers: &[UserId]) -> Controllers {
19    let mut controllers: Controllers = Controllers::new();
20
21    let controller_data: SetController = SetController {
22        metadata: HashMap::new(),
23        expires_at: None,
24        scope: ControllerScope::Admin,
25    };
26
27    set_controllers(new_controllers, &controller_data, &mut controllers);
28
29    controllers
30}
31
32/// Sets or updates controllers with specified data.
33///
34/// # Arguments
35/// - `new_controllers`: Slice of `UserId` for the controllers to be set or updated.
36/// - `controller_data`: `SetController` data to apply to the controllers.
37/// - `controllers`: Mutable reference to the current set of controllers to update.
38pub fn set_controllers(
39    new_controllers: &[UserId],
40    controller_data: &SetController,
41    controllers: &mut Controllers,
42) {
43    for controller_id in new_controllers {
44        let existing_controller = controllers.get(controller_id);
45
46        let now = time();
47
48        let created_at: u64 = match existing_controller {
49            None => now,
50            Some(existing_controller) => existing_controller.created_at,
51        };
52
53        let updated_at: u64 = now;
54
55        let controller: Controller = Controller {
56            metadata: controller_data.metadata.clone(),
57            created_at,
58            updated_at,
59            expires_at: controller_data.expires_at,
60            scope: controller_data.scope.clone(),
61        };
62
63        controllers.insert(*controller_id, controller);
64    }
65}
66
67/// Removes specified controllers from the set.
68///
69/// # Arguments
70/// - `remove_controllers`: Slice of `UserId` for the controllers to be removed.
71/// - `controllers`: Mutable reference to the current set of controllers to update.
72pub fn delete_controllers(remove_controllers: &[UserId], controllers: &mut Controllers) {
73    for c in remove_controllers {
74        controllers.remove(c);
75    }
76}
77
78/// Checks if a caller is a controller.
79///
80/// # Arguments
81/// - `caller`: `UserId` of the caller.
82/// - `controllers`: Reference to the current set of controllers.
83///
84/// # Returns
85/// `true` if the caller is a controller (not anonymous, calling itself or one of the known controllers), otherwise `false`.
86pub fn is_controller(caller: UserId, controllers: &Controllers) -> bool {
87    principal_not_anonymous(caller)
88        && (caller_is_self(caller)
89            || controllers
90                .iter()
91                .any(|(&controller_id, _)| principal_equal(controller_id, caller)))
92}
93
94/// Checks if a caller is an admin controller.
95///
96/// # Arguments
97/// - `caller`: `UserId` of the caller.
98/// - `controllers`: Reference to the current set of controllers.
99///
100/// # Returns
101/// `true` if the caller is an admin controller, otherwise `false`.
102pub fn is_admin_controller(caller: UserId, controllers: &Controllers) -> bool {
103    is_canister_controller(&caller)
104        && principal_not_anonymous(caller)
105        && controllers
106            .iter()
107            .any(|(&controller_id, controller)| match controller.scope {
108                ControllerScope::Write => false,
109                ControllerScope::Admin => principal_equal(controller_id, caller),
110            })
111}
112
113/// Converts the controllers set into a vector of controller IDs.
114///
115/// # Arguments
116/// - `controllers`: Reference to the current set of controllers.
117///
118/// # Returns
119/// A vector of `ControllerId`.
120pub fn into_controller_ids(controllers: &Controllers) -> Vec<ControllerId> {
121    controllers
122        .clone()
123        .into_keys()
124        .collect::<Vec<ControllerId>>()
125}
126
127/// Asserts that the number of controllers does not exceed the maximum allowed.
128///
129/// # Arguments
130/// - `current_controllers`: Reference to the current set of controllers.
131/// - `controllers_ids`: Slice of `ControllerId` representing the controllers to be added.
132/// - `max_controllers`: Maximum number of allowed controllers.
133///
134/// # Returns
135/// `Ok(())` if the operation is successful, or `Err(String)` if the maximum is exceeded.
136pub fn assert_max_number_of_controllers(
137    current_controllers: &Controllers,
138    controllers_ids: &[ControllerId],
139    max_controllers: usize,
140) -> Result<(), String> {
141    let current_controller_ids = into_controller_ids(current_controllers);
142
143    let new_controller_ids = controllers_ids.iter().copied().filter(|id| {
144        !current_controller_ids
145            .iter()
146            .any(|current_id| current_id == id)
147    });
148
149    if current_controller_ids.len() + new_controller_ids.count() > max_controllers {
150        return Err(format!(
151            "Maximum number of controllers ({}) is already reached.",
152            max_controllers
153        ));
154    }
155
156    Ok(())
157}
158
159/// Asserts that the controller IDs are not anonymous and not revoked.
160///
161/// # Arguments
162/// - `controllers_ids`: Slice of `ControllerId` to validate.
163///
164/// # Returns
165/// `Ok(())` if no anonymous and no revoked IDs are present, or `Err(String)` if any are found.
166pub fn assert_controllers(controllers_ids: &[ControllerId]) -> Result<(), String> {
167    assert_no_anonymous_controller(controllers_ids)?;
168    assert_no_revoked_controller(controllers_ids)?;
169
170    Ok(())
171}
172
173/// Asserts that no controller IDs are anonymous.
174///
175/// # Arguments
176/// - `controllers_ids`: Slice of `ControllerId` to validate.
177///
178/// # Returns
179/// `Ok(())` if no anonymous IDs are present, or `Err(String)` if any are found.
180fn assert_no_anonymous_controller(controllers_ids: &[ControllerId]) -> Result<(), String> {
181    let has_anonymous = controllers_ids
182        .iter()
183        .any(|controller_id| principal_anonymous(*controller_id));
184
185    match has_anonymous {
186        true => Err("Anonymous controller not allowed.".to_string()),
187        false => Ok(()),
188    }
189}
190
191/// Asserts that no controller IDs are revoked for security reason.
192///
193/// # Arguments
194/// - `controllers_ids`: Slice of `ControllerId` to validate.
195///
196/// # Returns
197/// `Ok(())` if no revoked IDs are present, or `Err(String)` if any are found.
198fn assert_no_revoked_controller(controllers_ids: &[ControllerId]) -> Result<(), String> {
199    // We treat revoked controllers as anonymous controllers.
200    let has_revoked = controllers_ids.iter().any(controller_revoked);
201
202    match has_revoked {
203        true => Err("Revoked controller not allowed.".to_string()),
204        false => Ok(()),
205    }
206}
207
208/// Checks if the caller is the console.
209///
210/// # Arguments
211/// - `caller`: `UserId` of the caller.
212///
213/// # Returns
214/// `true` if the caller matches the console's principal, otherwise `false`.
215pub fn caller_is_console(caller: UserId) -> bool {
216    let console = Principal::from_text(CONSOLE).unwrap();
217
218    principal_equal(caller, console)
219}
220
221/// Checks if the caller is the observatory.
222///
223/// # Arguments
224/// - `caller`: `UserId` of the caller.
225///
226/// # Returns
227/// `true` if the caller matches the observatory's principal, otherwise `false`.
228pub fn caller_is_observatory(caller: UserId) -> bool {
229    let observatory = Principal::from_text(OBSERVATORY).unwrap();
230
231    principal_equal(caller, observatory)
232}
233
234/// Checks if the caller is the canister itself.
235///
236/// # Arguments
237/// - `caller`: `UserId` of the caller.
238///
239/// # Returns
240/// `true` if the caller is calling itself, if the canister is the caller, otherwise `false`.
241pub fn caller_is_self(caller: UserId) -> bool {
242    let itself = id();
243
244    principal_equal(caller, itself)
245}
246
247/// Filters the set of controllers, returning only those with administrative scope.
248///
249/// # Arguments
250/// - `controllers`: Reference to the current set of controllers.
251///
252/// # Returns
253/// A `Controllers` collection containing only admin controllers.
254pub fn filter_admin_controllers(controllers: &Controllers) -> Controllers {
255    controllers
256        .clone()
257        .into_iter()
258        .filter(|(_, controller)| match controller.scope {
259            ControllerScope::Write => false,
260            ControllerScope::Admin => true,
261        })
262        .collect()
263}
264
265fn controller_revoked(controller_id: &ControllerId) -> bool {
266    REVOKED_CONTROLLERS.iter().any(|revoked_controller_id| {
267        principal_equal(
268            Principal::from_text(revoked_controller_id).unwrap(),
269            *controller_id,
270        )
271    })
272}