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 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 #[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 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 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 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 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}