Skip to main content

shared/extensions/
manager.rs

1use crate::{
2    State,
3    extensions::{
4        ConstructedExtension, ExtensionPermissionsBuilder, ExtensionRouteBuilder,
5        commands::CliCommandGroupBuilder,
6    },
7    settings::ExtensionPermissions,
8};
9use futures_util::FutureExt;
10use std::{
11    borrow::Cow,
12    collections::{BTreeMap, HashSet},
13    panic::AssertUnwindSafe,
14    sync::Arc,
15};
16use tokio::sync::{RwLock, RwLockReadGuard};
17use utoipa_axum::router::OpenApiRouter;
18
19fn panic_message(payload: &(dyn std::any::Any + Send)) -> Cow<'_, str> {
20    if let Some(s) = payload.downcast_ref::<&str>() {
21        (*s).into()
22    } else if let Some(s) = payload.downcast_ref::<String>() {
23        s.as_str().into()
24    } else {
25        "Unknown panic".into()
26    }
27}
28
29pub struct ExtensionManager {
30    vec: RwLock<Vec<ConstructedExtension>>,
31    disabled: parking_lot::RwLock<Vec<compact_str::CompactString>>,
32    failed: parking_lot::RwLock<BTreeMap<compact_str::CompactString, String>>,
33}
34
35impl ExtensionManager {
36    pub fn new(vec: Vec<ConstructedExtension>) -> Self {
37        Self {
38            vec: RwLock::new(vec),
39            disabled: parking_lot::RwLock::new(Vec::new()),
40            failed: parking_lot::RwLock::new(BTreeMap::new()),
41        }
42    }
43
44    /// Sets the extensions whose entrypoints are skipped, this has to happen before `init`
45    /// and before extension migrations are collected.
46    pub fn set_disabled(&self, disabled: Vec<compact_str::CompactString>) {
47        *self.disabled.write() = disabled;
48    }
49
50    #[inline]
51    pub fn disabled(&self) -> Vec<compact_str::CompactString> {
52        self.disabled.read().clone()
53    }
54
55    #[inline]
56    pub fn is_disabled(&self, package_name: &str) -> bool {
57        self.disabled.read().iter().any(|d| d == package_name)
58    }
59
60    /// Extensions whose entrypoints were skipped at boot because one of them panicked or
61    /// their routes or CLI commands conflict, mapped to the reason.
62    #[inline]
63    pub fn failed(&self) -> BTreeMap<compact_str::CompactString, String> {
64        self.failed.read().clone()
65    }
66
67    #[inline]
68    pub fn is_failed(&self, package_name: &str) -> bool {
69        self.failed.read().contains_key(package_name)
70    }
71
72    /// Extensions that are not running, either disabled or failed to start.
73    pub fn inactive(&self) -> Vec<compact_str::CompactString> {
74        let mut inactive = self.disabled();
75        for package_name in self.failed.read().keys() {
76            if !inactive.contains(package_name) {
77                inactive.push(package_name.clone());
78            }
79        }
80
81        inactive
82    }
83
84    fn fail(&self, package_name: &str, stage: &str, reason: &str) {
85        let reason = format!("{stage}: {reason}");
86        tracing::error!(
87            extension = %package_name,
88            "extension failed to start, skipping its entrypoints: {reason}"
89        );
90
91        self.failed.write().insert(package_name.into(), reason);
92    }
93
94    /// Runs the entrypoints of every enabled extension and merges their routes into `router`,
95    /// which already holds the core routes. `mount` turns a single extension's route builder
96    /// into a router that can be merged into `router`.
97    ///
98    /// An extension whose entrypoint panics, or whose routes conflict with the core or an
99    /// earlier extension, is marked as failed and everything it registered is rolled back,
100    /// instead of panicking the panel.
101    pub async fn init(
102        &self,
103        state: State,
104        mut router: OpenApiRouter<State>,
105        mount: impl Fn(ExtensionRouteBuilder) -> OpenApiRouter<State>,
106    ) -> (
107        OpenApiRouter<State>,
108        super::background_tasks::BackgroundTaskBuilder,
109        super::shutdown_handlers::ShutdownHandlerBuilder,
110    ) {
111        let mut email_templates_builder =
112            super::email_templates::ExtensionEmailTemplateBuilder::default();
113        let background_tasks_builder =
114            super::background_tasks::BackgroundTaskBuilder::new(state.clone());
115        let shutdown_handlers_builder =
116            super::shutdown_handlers::ShutdownHandlerBuilder::new(state.clone());
117        let mut permissions_builder = ExtensionPermissionsBuilder::new(
118            crate::permissions::BASE_USER_PERMISSIONS.clone(),
119            crate::permissions::BASE_ADMIN_PERMISSIONS.clone(),
120            crate::permissions::BASE_SERVER_PERMISSIONS.clone(),
121        );
122
123        let mut settings_map = crate::settings::Settings::fetch_settings_map(&state.database)
124            .await
125            .unwrap_or_else(|err| {
126                tracing::error!("failed to fetch settings for extension settings probe: {err:#}");
127                Default::default()
128            });
129
130        for ext in self.vec.read().await.iter() {
131            if self.is_disabled(ext.package_name) {
132                tracing::info!(extension = %ext.package_name, "extension is disabled, skipping its entrypoints");
133                continue;
134            }
135            if self.is_failed(ext.package_name) {
136                continue;
137            }
138
139            match AssertUnwindSafe(async {
140                let deserializer = ext.settings_deserializer(state.clone()).await;
141                deserializer
142                    .deserialize_boxed(crate::extensions::settings::SettingsDeserializer::new(
143                        state.database.clone(),
144                        ext.package_name,
145                        &mut settings_map,
146                    ))
147                    .await
148                    .map(|_| deserializer)
149            })
150            .catch_unwind()
151            .await
152            {
153                Ok(Ok(deserializer)) => {
154                    crate::settings::SETTINGS_DESER_EXTENSIONS
155                        .write()
156                        .insert(ext.package_name, deserializer);
157                }
158                Ok(Err(err)) => self.fail(
159                    ext.package_name,
160                    "settings_deserializer",
161                    &format!("{err:#}"),
162                ),
163                Err(err) => self.fail(
164                    ext.package_name,
165                    "settings_deserializer",
166                    &panic_message(&*err),
167                ),
168            }
169        }
170        state.settings.invalidate_cache().await;
171
172        let mut contributed_permissions = BTreeMap::new();
173
174        for ext in self.vec.write().await.iter_mut() {
175            if self.is_disabled(ext.package_name) || self.is_failed(ext.package_name) {
176                continue;
177            }
178
179            let package_name = ext.package_name;
180            let ext = match Arc::get_mut(&mut ext.extension) {
181                Some(ext) => ext,
182                None => {
183                    panic!(
184                        "Failed to get mutable reference to extension {package_name}. This should NEVER happen."
185                    );
186                }
187            };
188
189            let task_names = background_tasks_builder.task_names().await;
190            let handler_names = shutdown_handlers_builder.handler_names().await;
191            let stage = std::cell::Cell::new("initialize");
192
193            let result = AssertUnwindSafe(async {
194                ext.initialize(state.clone()).await;
195
196                stage.set("initialize_router");
197                let route_builder = ext
198                    .initialize_router(state.clone(), ExtensionRouteBuilder::new(state.clone()))
199                    .await;
200                stage.set("registering routes");
201                let router = router.clone().merge(mount(route_builder));
202
203                stage.set("initialize_email_templates");
204                let email_templates = ext
205                    .initialize_email_templates(state.clone(), email_templates_builder.clone())
206                    .await;
207                stage.set("initialize_background_tasks");
208                ext.initialize_background_tasks(state.clone(), background_tasks_builder.share())
209                    .await;
210                stage.set("initialize_shutdown_handlers");
211                ext.initialize_shutdown_handlers(state.clone(), shutdown_handlers_builder.share())
212                    .await;
213                stage.set("initialize_permissions");
214                let permissions = ext
215                    .initialize_permissions(state.clone(), permissions_builder.clone())
216                    .await;
217
218                (router, email_templates, permissions)
219            })
220            .catch_unwind()
221            .await;
222
223            match result {
224                Ok((extension_router, email_templates, permissions)) => {
225                    router = extension_router;
226                    email_templates_builder = email_templates;
227                    contributed_permissions.insert(
228                        compact_str::CompactString::from(package_name),
229                        permissions.contributions_since(&permissions_builder.snapshot()),
230                    );
231                    permissions_builder = permissions;
232                }
233                Err(err) => {
234                    background_tasks_builder.rollback(&task_names).await;
235                    shutdown_handlers_builder.rollback(&handler_names).await;
236                    crate::settings::SETTINGS_DESER_EXTENSIONS
237                        .write()
238                        .remove(package_name);
239
240                    self.fail(package_name, stage.get(), &panic_message(&*err));
241                }
242            }
243        }
244
245        let (templates, variables) = email_templates_builder.finish();
246        *state.mail.templates.templates.write() = templates;
247        *state.mail.templates.variables.write() = variables;
248
249        crate::permissions::USER_PERMISSIONS
250            .write()
251            .replace(permissions_builder.user_permissions);
252        crate::permissions::ADMIN_PERMISSIONS
253            .write()
254            .replace(permissions_builder.admin_permissions);
255        crate::permissions::SERVER_PERMISSIONS
256            .write()
257            .replace(permissions_builder.server_permissions);
258
259        self.apply_permission_snapshots(&state, contributed_permissions)
260            .await;
261
262        (router, background_tasks_builder, shutdown_handlers_builder)
263    }
264
265    /// Keeps the permissions of installed extensions valid across a disable/enable cycle:
266    /// what an enabled extension contributed is remembered, what a disabled one contributed
267    /// is fed back as inert so roles, subusers and api keys holding it still validate.
268    async fn apply_permission_snapshots(
269        &self,
270        state: &State,
271        contributed: BTreeMap<compact_str::CompactString, ExtensionPermissions>,
272    ) {
273        let stored = match state.settings.get().await {
274            Ok(settings) => settings.extension_permissions.clone(),
275            Err(err) => {
276                tracing::error!("failed to read stored extension permissions: {:#?}", err);
277                return;
278            }
279        };
280
281        let mut snapshots = BTreeMap::new();
282        let mut inert_user = HashSet::new();
283        let mut inert_admin = HashSet::new();
284        let mut inert_server = HashSet::new();
285
286        for ext in self.vec.read().await.iter() {
287            let package_name = compact_str::CompactString::from(ext.package_name);
288
289            let permissions = match contributed.get(&package_name) {
290                Some(permissions) => permissions.clone(),
291                None => match stored.get(&package_name) {
292                    Some(permissions) => {
293                        inert_user.extend(permissions.user.iter().map(ToString::to_string));
294                        inert_admin.extend(permissions.admin.iter().map(ToString::to_string));
295                        inert_server.extend(permissions.server.iter().map(ToString::to_string));
296
297                        permissions.clone()
298                    }
299                    None => continue,
300                },
301            };
302
303            snapshots.insert(package_name, permissions);
304        }
305
306        tracing::debug!(
307            count = inert_user.len() + inert_admin.len() + inert_server.len(),
308            "keeping permissions of disabled extensions grantable"
309        );
310
311        crate::permissions::USER_PERMISSIONS
312            .write()
313            .set_inert(inert_user);
314        crate::permissions::ADMIN_PERMISSIONS
315            .write()
316            .set_inert(inert_admin);
317        crate::permissions::SERVER_PERMISSIONS
318            .write()
319            .set_inert(inert_server);
320
321        if snapshots != stored
322            && let Err(err) = state.settings.set_extension_permissions(&snapshots).await
323        {
324            tracing::error!("failed to store extension permissions: {:#?}", err);
325        }
326    }
327
328    /// Registers the CLI commands of every extension. An extension whose `initialize_cli` panics
329    /// or registers an already taken command is marked as failed, which also skips its other
330    /// entrypoints in `init`.
331    pub async fn init_cli(
332        &self,
333        env: Option<&Arc<crate::env::Env>>,
334        mut builder: CliCommandGroupBuilder,
335    ) -> CliCommandGroupBuilder {
336        for ext in self.vec.write().await.iter_mut() {
337            let package_name = ext.package_name;
338            let ext = match Arc::get_mut(&mut ext.extension) {
339                Some(ext) => ext,
340                None => {
341                    panic!(
342                        "Failed to get mutable reference to extension {package_name}. This should NEVER happen."
343                    );
344                }
345            };
346
347            let result = AssertUnwindSafe(
348                ext.initialize_cli(env, CliCommandGroupBuilder::new(package_name, "")),
349            )
350            .catch_unwind()
351            .await;
352
353            let reason = match result {
354                Ok(commands) => match builder.merge(commands) {
355                    Ok(()) => continue,
356                    Err(reason) => reason,
357                },
358                Err(err) => panic_message(&*err).into_owned(),
359            };
360
361            self.fail(package_name, "initialize_cli", &reason);
362        }
363
364        builder
365    }
366
367    #[inline]
368    pub async fn extensions(&self) -> RwLockReadGuard<'_, Vec<ConstructedExtension>> {
369        self.vec.read().await
370    }
371
372    #[inline]
373    pub fn blocking_extensions(&self) -> RwLockReadGuard<'_, Vec<ConstructedExtension>> {
374        self.vec.blocking_read()
375    }
376
377    pub async fn call(
378        &self,
379        name: impl AsRef<str>,
380        args: &[super::ExtensionCallValue],
381    ) -> Option<super::ExtensionCallValue> {
382        for ext in self.extensions().await.iter() {
383            if self.is_disabled(ext.package_name) || self.is_failed(ext.package_name) {
384                continue;
385            }
386
387            if let Some(ret) = ext.process_call(name.as_ref(), args).await {
388                return Some(ret);
389            }
390        }
391
392        None
393    }
394}