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;
91 let members: Vec<String> = if include.is_empty() {
92 catalogue
93 } else {
94 include.clone()
95 };
96 let band = self.marketplace_members.entry(kind).or_default();
97 for member in members {
98 band.entry(member).or_default().insert(id.clone());
99 }
100 }
101 }
102
103 #[must_use]
104 pub fn plugin_ids_to_load(&self) -> Vec<String> {
105 let mut ids: BTreeSet<&str> = self.plugins.keys().map(PluginId::as_str).collect();
106 for owners in self.skill_owners.values() {
107 ids.extend(owners.iter().map(PluginId::as_str));
108 }
109 ids.into_iter().map(str::to_owned).collect()
110 }
111
112 #[must_use]
113 pub fn marketplace_ids_to_load(&self) -> Vec<MarketplaceId> {
114 self.marketplaces.keys().cloned().collect()
115 }
116
117 #[must_use]
118 pub fn marketplaces_of(&self, kind: EntityKind, id: &str) -> &BTreeSet<MarketplaceId> {
119 self.marketplace_members
120 .get(&kind)
121 .and_then(|band| band.get(id))
122 .unwrap_or(&EMPTY_MARKETPLACES)
123 }
124
125 #[must_use]
126 pub fn plugin_marketplaces(&self, id: &PluginId) -> &BTreeSet<MarketplaceId> {
127 self.plugins.get(id).unwrap_or(&EMPTY_MARKETPLACES)
128 }
129
130 #[must_use]
131 pub fn is_marketplace_member(&self, kind: EntityKind, id: &str) -> bool {
132 !self.marketplaces_of(kind, id).is_empty()
133 }
134}