Skip to content

Commit 17180c6

Browse files
permission aware dashboard
1 parent 9625acf commit 17180c6

1 file changed

Lines changed: 66 additions & 9 deletions

File tree

pangolin/pangolin_api/src/dashboard_handlers.rs

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -99,20 +99,77 @@ pub async fn get_dashboard_stats(
9999
UserRole::TenantUser => {
100100
let tenant_id = session.tenant_id.unwrap_or_default();
101101

102-
let catalogs = store.list_catalogs(tenant_id).await
102+
// Fetch user permissions for filtering
103+
let permissions = store.list_user_permissions(session.user_id).await
103104
.map_err(ApiError::from)?;
104105

105-
// For now, users verify all counts in tenant.
106-
// TODO: In future, filter by permissions if needed, but count_assets provides O(1) stats.
107-
let namespaces_count = store.count_namespaces(tenant_id).await.unwrap_or(0);
108-
let tables_count = store.count_assets(tenant_id).await.unwrap_or(0);
106+
// Get all catalogs and filter by permissions
107+
let all_catalogs = store.list_catalogs(tenant_id).await
108+
.map_err(ApiError::from)?;
109+
let accessible_catalogs = crate::authz_utils::filter_catalogs(
110+
all_catalogs,
111+
&permissions,
112+
session.role.clone()
113+
);
114+
115+
// Get all warehouses and filter by permissions
116+
// Note: Warehouses don't have direct permission scopes, so we show warehouses
117+
// associated with accessible catalogs
118+
let all_warehouses = store.list_warehouses(tenant_id).await
119+
.map_err(ApiError::from)?;
120+
let accessible_warehouse_names: std::collections::HashSet<_> = accessible_catalogs
121+
.iter()
122+
.filter_map(|c| c.warehouse_name.clone())
123+
.collect();
124+
let accessible_warehouses_count = all_warehouses
125+
.iter()
126+
.filter(|w| accessible_warehouse_names.contains(&w.name))
127+
.count();
128+
129+
// Count accessible namespaces
130+
// Fetch all namespaces and filter by permissions
131+
let catalog_id_map: std::collections::HashMap<_, _> = accessible_catalogs
132+
.iter()
133+
.map(|c| (c.name.clone(), c.id))
134+
.collect();
135+
136+
let mut accessible_namespaces_count = 0;
137+
for catalog in &accessible_catalogs {
138+
if let Ok(namespaces) = store.list_namespaces(tenant_id, &catalog.name, None).await {
139+
let namespace_tuples: Vec<_> = namespaces
140+
.into_iter()
141+
.map(|ns| (ns, catalog.name.clone()))
142+
.collect();
143+
144+
let filtered = crate::authz_utils::filter_namespaces(
145+
namespace_tuples,
146+
&permissions,
147+
session.role.clone(),
148+
&catalog_id_map
149+
);
150+
accessible_namespaces_count += filtered.len();
151+
}
152+
}
153+
154+
// Count accessible tables/assets
155+
// This is more expensive but necessary for accurate counts
156+
let mut accessible_tables_count = 0;
157+
if let Ok(all_assets) = store.search_assets(tenant_id, "", None).await {
158+
let filtered_assets = crate::authz_utils::filter_assets(
159+
all_assets,
160+
&permissions,
161+
session.role.clone(),
162+
&catalog_id_map
163+
);
164+
accessible_tables_count = filtered_assets.len();
165+
}
109166

110167
let stats = DashboardStats {
111-
catalogs_count: catalogs.len(),
112-
tables_count,
113-
namespaces_count,
168+
catalogs_count: accessible_catalogs.len(),
169+
tables_count: accessible_tables_count,
170+
namespaces_count: accessible_namespaces_count,
114171
users_count: 0,
115-
warehouses_count: 0,
172+
warehouses_count: accessible_warehouses_count,
116173
branches_count: 0,
117174
scope: "user".to_string(),
118175
};

0 commit comments

Comments
 (0)