1- use crate :: auth:: { ensure_permission , require_realm_auth} ;
1+ use crate :: auth:: require_realm_auth;
22use crate :: error:: { ErrorResponse , ServerError , ServerResult } ;
33use crate :: server_state:: ServerState ;
44use aruna_core:: NodeId ;
55use aruna_core:: UserId ;
66use aruna_core:: structs:: {
77 AuthContext , NOTIFICATION_WATCH_MAX_PREFIX_LEN , NotificationClass , NotificationKind ,
8- NotificationRecord , Permission , WatchEventKind , WatchEventMask , WatchSubscription ,
8+ NotificationRecord , WatchEventKind , WatchEventMask , WatchSubscription ,
99} ;
1010use aruna_operations:: driver:: DriverContext ;
1111use aruna_operations:: notifications:: dispatch:: {
@@ -15,7 +15,9 @@ use aruna_operations::notifications::dispatch::{
1515} ;
1616use aruna_operations:: notifications:: list:: LIST_NOTIFICATIONS_MAX_LIMIT ;
1717use aruna_operations:: notifications:: mark_read:: MARK_READ_MAX_IDS ;
18- use aruna_operations:: notifications:: watch:: authorization:: watch_permission_path;
18+ use aruna_operations:: notifications:: watch:: authorization:: {
19+ is_watch_authorized, watch_permission_path,
20+ } ;
1921use axum:: extract:: { Path , Query , State } ;
2022use axum:: http:: StatusCode ;
2123use axum:: response:: sse:: { Event , KeepAlive , Sse } ;
@@ -319,15 +321,32 @@ fn notification_response(record: &NotificationRecord) -> NotificationResponse {
319321 response
320322}
321323
324+ /// Creation shares the exact authorization result delivery and enumeration use,
325+ /// so a watch cannot be created on anything they would later refuse. A prefix
326+ /// with no canonical resource identity is a malformed request; everything else a
327+ /// caller may not read answers Forbidden, whether or not it exists.
322328async fn authorize_watch (
323329 state : & ServerState ,
324330 auth : & AuthContext ,
325331 path_prefix : & str ,
326332 event_mask : WatchEventMask ,
327333) -> ServerResult < ( ) > {
328- let permission_path = watch_permission_path ( state. get_realm_id ( ) , path_prefix, event_mask)
329- . ok_or ( ServerError :: BadRequest ) ?;
330- ensure_permission ( state, auth, permission_path, Permission :: READ ) . await
334+ if watch_permission_path ( state. get_realm_id ( ) , path_prefix, event_mask) . is_none ( ) {
335+ return Err ( ServerError :: BadRequest ) ;
336+ }
337+ match is_watch_authorized (
338+ & state. get_ctx ( ) ,
339+ state. get_realm_id ( ) ,
340+ auth. user_id ,
341+ path_prefix,
342+ event_mask,
343+ )
344+ . await
345+ {
346+ Ok ( true ) => Ok ( ( ) ) ,
347+ Ok ( false ) => Err ( ServerError :: Forbidden ) ,
348+ Err ( error) => Err ( ServerError :: InternalError ( error) ) ,
349+ }
331350}
332351
333352#[ utoipa:: path(
@@ -1565,6 +1584,50 @@ mod tests {
15651584 assert_eq ! ( created. path_prefix, path_prefix) ;
15661585 }
15671586
1587+ // A group that does not exist and a group the caller may not read answer
1588+ // identically, so creating a watch is never an existence oracle.
1589+ #[ tokio:: test]
1590+ async fn missing_group_forbidden ( ) {
1591+ let realm_id = realm_id ( 18 ) ;
1592+ let holder = node ( 18 ) ;
1593+ let ( _dir, state) = build_state ( realm_id, holder) . await ;
1594+ install_local_holder_config ( & state, realm_id, holder) . await ;
1595+ let caller = UserId :: new ( Ulid :: r#gen ( ) , realm_id) ;
1596+ let existing_group = Ulid :: r#gen ( ) ;
1597+ install_group_authorization (
1598+ & state,
1599+ realm_id,
1600+ existing_group,
1601+ UserId :: new ( Ulid :: r#gen ( ) , realm_id) ,
1602+ & [ ] ,
1603+ )
1604+ . await ;
1605+
1606+ let missing = create_watch (
1607+ State ( state. clone ( ) ) ,
1608+ Extension ( Some ( auth_for ( caller, realm_id) ) ) ,
1609+ Json ( CreateWatchRequest {
1610+ path_prefix : format ! ( "meta/{}/datasets" , Ulid :: r#gen( ) ) ,
1611+ events : vec ! [ "metadata_created" . to_string( ) ] ,
1612+ } ) ,
1613+ )
1614+ . await
1615+ . expect_err ( "a watch on a group that does not exist must be refused" ) ;
1616+ assert ! ( matches!( missing, ServerError :: Forbidden ) ) ;
1617+
1618+ let unreadable = create_watch (
1619+ State ( state) ,
1620+ Extension ( Some ( auth_for ( caller, realm_id) ) ) ,
1621+ Json ( CreateWatchRequest {
1622+ path_prefix : format ! ( "meta/{existing_group}/datasets" ) ,
1623+ events : vec ! [ "metadata_created" . to_string( ) ] ,
1624+ } ) ,
1625+ )
1626+ . await
1627+ . expect_err ( "a watch on an existing unreadable group must be refused" ) ;
1628+ assert ! ( matches!( unreadable, ServerError :: Forbidden ) ) ;
1629+ }
1630+
15681631 #[ tokio:: test]
15691632 async fn create_watch_rejects_mixed_event_namespaces ( ) {
15701633 let realm_id = realm_id ( 17 ) ;
0 commit comments