systemprompt_security/authz/parent_chain/
sources.rs1use std::collections::{BTreeMap, BTreeSet};
15
16use systemprompt_identifiers::{MarketplaceId, PluginId, SkillId};
17use systemprompt_models::services::{MarketplaceConfig, MarketplaceMemberKind, ServicesConfig};
18
19use crate::authz::types::EntityKind;
20
21static EMPTY_MARKETPLACES: BTreeSet<MarketplaceId> = BTreeSet::new();
22
23#[derive(Debug, Clone)]
24pub struct MarketplaceSource {
25 pub id: MarketplaceId,
26 pub fallback_default_included: Option<bool>,
27}
28
29#[derive(Debug, Clone, Default)]
30pub struct ChainSources {
31 pub marketplaces: BTreeMap<MarketplaceId, MarketplaceSource>,
32 pub plugins: BTreeMap<PluginId, BTreeSet<MarketplaceId>>,
33 pub skill_owners: BTreeMap<SkillId, BTreeSet<PluginId>>,
34 pub marketplace_members: BTreeMap<EntityKind, BTreeMap<String, BTreeSet<MarketplaceId>>>,
35}
36
37impl ChainSources {
38 #[must_use]
39 pub fn from_services(services: &ServicesConfig) -> Self {
40 let mut out = Self::default();
41 for marketplace in services.enabled_marketplaces() {
42 out.absorb(services, marketplace);
43 }
44 out
45 }
46
47 fn absorb(&mut self, services: &ServicesConfig, marketplace: &MarketplaceConfig) {
48 let id = marketplace.id.clone();
49 self.marketplaces.insert(
50 id.clone(),
51 MarketplaceSource {
52 id: id.clone(),
53 fallback_default_included: Some(marketplace.access.default_included),
54 },
55 );
56
57 for plugin in services.marketplace_plugin_configs(marketplace) {
58 self.plugins
59 .entry(plugin.id.clone())
60 .or_default()
61 .insert(id.clone());
62 for skill in services.plugin_selected_skill_ids(plugin) {
63 self.skill_owners
64 .entry(SkillId::new(skill))
65 .or_default()
66 .insert(plugin.id.clone());
67 }
68 }
69
70 for (kind, member_kind, catalogue) in [
71 (
72 EntityKind::Agent,
73 MarketplaceMemberKind::Agents,
74 services.agents.keys().cloned().collect::<Vec<String>>(),
75 ),
76 (
77 EntityKind::McpServer,
78 MarketplaceMemberKind::McpServers,
79 services
80 .mcp_servers
81 .keys()
82 .cloned()
83 .collect::<Vec<String>>(),
84 ),
85 ] {
86 let include = &marketplace.members(member_kind).include;
87 let members: Vec<String> = if include.is_empty() {
88 catalogue
89 } else {
90 include.clone()
91 };
92 let band = self.marketplace_members.entry(kind).or_default();
93 for member in members {
94 band.entry(member).or_default().insert(id.clone());
95 }
96 }
97 }
98
99 #[must_use]
100 pub fn plugin_ids_to_load(&self) -> Vec<String> {
101 let mut ids: BTreeSet<&str> = self.plugins.keys().map(PluginId::as_str).collect();
102 for owners in self.skill_owners.values() {
103 ids.extend(owners.iter().map(PluginId::as_str));
104 }
105 ids.into_iter().map(str::to_owned).collect()
106 }
107
108 #[must_use]
109 pub fn marketplace_ids_to_load(&self) -> Vec<MarketplaceId> {
110 self.marketplaces.keys().cloned().collect()
111 }
112
113 #[must_use]
114 pub fn marketplaces_of(&self, kind: EntityKind, id: &str) -> &BTreeSet<MarketplaceId> {
115 self.marketplace_members
116 .get(&kind)
117 .and_then(|band| band.get(id))
118 .unwrap_or(&EMPTY_MARKETPLACES)
119 }
120
121 #[must_use]
122 pub fn plugin_marketplaces(&self, id: &PluginId) -> &BTreeSet<MarketplaceId> {
123 self.plugins.get(id).unwrap_or(&EMPTY_MARKETPLACES)
124 }
125
126 #[must_use]
127 pub fn is_marketplace_member(&self, kind: EntityKind, id: &str) -> bool {
128 !self.marketplaces_of(kind, id).is_empty()
129 }
130}