@@ -22,13 +22,24 @@ use actix_web::http::StatusCode;
2222use actix_web:: { HttpRequest , HttpResponse , web} ;
2323use serde:: { Deserialize , Serialize } ;
2424
25+ use crate :: rbac:: { self , Users , role:: Action } ;
26+ use crate :: utils:: actix:: extract_session_key_from_req;
2527use crate :: utils:: get_tenant_id_from_request;
2628use crate :: {
2729 handlers:: DatasetTag ,
2830 parseable:: PARSEABLE ,
2931 storage:: { ObjectStorageError , StreamType } ,
3032} ;
3133
34+ /// Check if the caller is authorized to read a specific stream.
35+ fn can_access_stream ( req : & HttpRequest , stream_name : & str ) -> bool {
36+ let Ok ( key) = extract_session_key_from_req ( req) else {
37+ return false ;
38+ } ;
39+ Users . authorize ( key, Action :: GetStreamInfo , Some ( stream_name) , None )
40+ == rbac:: Response :: Authorized
41+ }
42+
3243#[ derive( Debug , Serialize ) ]
3344#[ serde( rename_all = "camelCase" ) ]
3445struct CorrelatedDataset {
@@ -39,12 +50,19 @@ struct CorrelatedDataset {
3950
4051/// GET /api/v1/datasets/{name}/correlated
4152/// Returns all datasets sharing at least one tag or label with the named dataset.
53+ /// Results are filtered to only include datasets the caller is authorized to read.
4254pub async fn get_correlated_datasets (
4355 req : HttpRequest ,
4456 path : web:: Path < String > ,
4557) -> Result < HttpResponse , DatasetsError > {
4658 let dataset_name = path. into_inner ( ) ;
4759 let tenant_id = get_tenant_id_from_request ( & req) ;
60+
61+ // Authorize caller for the seed dataset
62+ if !can_access_stream ( & req, & dataset_name) {
63+ return Err ( DatasetsError :: DatasetNotFound ( dataset_name) ) ;
64+ }
65+
4866 let stream = PARSEABLE
4967 . get_stream ( & dataset_name, & tenant_id)
5068 . map_err ( |_| DatasetsError :: DatasetNotFound ( dataset_name. clone ( ) ) ) ?;
@@ -63,6 +81,10 @@ pub async fn get_correlated_datasets(
6381 if name == dataset_name {
6482 continue ;
6583 }
84+ // Filter out datasets the caller cannot read
85+ if !can_access_stream ( & req, & name) {
86+ continue ;
87+ }
6688 if let Ok ( s) = PARSEABLE . get_stream ( & name, & tenant_id) {
6789 // Skip internal streams
6890 if s. get_stream_type ( ) == StreamType :: Internal {
@@ -91,6 +113,7 @@ pub async fn get_correlated_datasets(
91113
92114/// GET /api/v1/datasets/tags/{tag}
93115/// Returns all datasets that have the specified tag.
116+ /// Results are filtered to only include datasets the caller is authorized to read.
94117pub async fn get_datasets_by_tag (
95118 req : HttpRequest ,
96119 path : web:: Path < String > ,
@@ -104,6 +127,10 @@ pub async fn get_datasets_by_tag(
104127 let mut matching = Vec :: new ( ) ;
105128
106129 for name in all_streams {
130+ // Filter out datasets the caller cannot read
131+ if !can_access_stream ( & req, & name) {
132+ continue ;
133+ }
107134 if let Ok ( s) = PARSEABLE . get_stream ( & name, & tenant_id) {
108135 if s. get_stream_type ( ) == StreamType :: Internal {
109136 continue ;
0 commit comments