systemprompt_security/authz/parent_chain/
sources.rs1use std::collections::{BTreeMap, BTreeSet};
11
12use systemprompt_identifiers::{MarketplaceId, PluginId, SkillId};
13use systemprompt_models::services::{MarketplaceMemberKind, ServicesConfig};
14
15use crate::authz::marketplace_floor::active_marketplace;
16use crate::authz::types::EntityKind;
17
18#[derive(Debug, Clone)]
19pub struct MarketplaceSource {
20 pub id: MarketplaceId,
21 pub fallback_default_included: Option<bool>,
22}
23
24#[derive(Debug, Clone, Default)]
25pub struct ChainSources {
26 pub marketplace: Option<MarketplaceSource>,
27 pub plugins: BTreeSet<PluginId>,
28 pub skill_owners: BTreeMap<SkillId, BTreeSet<PluginId>>,
29 pub marketplace_members: BTreeMap<EntityKind, BTreeSet<String>>,
30}
31
32impl ChainSources {
33 #[must_use]
34 pub fn from_services(services: &ServicesConfig) -> Self {
35 let Some(marketplace) = active_marketplace(services) else {
36 return Self::default();
37 };
38 let plugins = services.marketplace_plugin_configs(marketplace);
39
40 let mut skill_owners: BTreeMap<SkillId, BTreeSet<PluginId>> = BTreeMap::new();
41 for plugin in &plugins {
42 for skill in services.plugin_selected_skill_ids(plugin) {
43 skill_owners
44 .entry(SkillId::new(skill))
45 .or_default()
46 .insert(plugin.id.clone());
47 }
48 }
49
50 let marketplace_members = [
51 (EntityKind::Agent, MarketplaceMemberKind::Agents),
52 (EntityKind::McpServer, MarketplaceMemberKind::McpServers),
53 ]
54 .into_iter()
55 .map(|(kind, member_kind)| {
56 (
57 kind,
58 marketplace
59 .members(member_kind)
60 .include
61 .iter()
62 .cloned()
63 .collect(),
64 )
65 })
66 .collect();
67
68 Self {
69 marketplace: Some(MarketplaceSource {
70 id: marketplace.id.clone(),
71 fallback_default_included: Some(marketplace.access.default_included),
72 }),
73 plugins: plugins.iter().map(|plugin| plugin.id.clone()).collect(),
74 skill_owners,
75 marketplace_members,
76 }
77 }
78
79 #[must_use]
80 pub fn plugin_ids_to_load(&self) -> Vec<String> {
81 let mut ids: BTreeSet<&str> = self.plugins.iter().map(PluginId::as_str).collect();
82 for owners in self.skill_owners.values() {
83 ids.extend(owners.iter().map(PluginId::as_str));
84 }
85 ids.into_iter().map(str::to_owned).collect()
86 }
87
88 #[must_use]
89 pub fn is_marketplace_member(&self, kind: EntityKind, id: &str) -> bool {
90 self.marketplace_members
91 .get(&kind)
92 .is_some_and(|members| members.contains(id))
93 }
94}