diff --git a/up-subscription/src/handlers/subscribe.rs b/up-subscription/src/handlers/subscribe.rs index f879195..1274e5d 100644 --- a/up-subscription/src/handlers/subscribe.rs +++ b/up-subscription/src/handlers/subscribe.rs @@ -59,6 +59,11 @@ impl RequestHandler for SubscriptionRequestHandler { )); }; + // [impl->dsn~usubscription-subscribe-invalid-topic~1] + helpers::validate_uri(topic).map_err(|e| { + ServiceInvocationError::InvalidArgument(format!("Invalid topic uri '{topic}': {e}")) + })?; + // Provisionally compute milliseconds to subscription expiry, from protobuf.google.Timestamp input in second granularity (we ignore the nanos). // Likely to change in the future, when we get rid of the protobuf.google.Timestamp type and track in milliseconds throughought. let expiry: Option = @@ -116,9 +121,13 @@ impl RequestHandler for SubscriptionRequestHandler { #[cfg(test)] mod tests { + use std::str::FromStr; + use super::*; + use test_case::test_case; use tokio::sync::mpsc::{self}; - use up_rust::core::usubscription::State; + + use up_rust::{core::usubscription::State, UUri}; use crate::{helpers, tests::test_lib}; @@ -387,4 +396,37 @@ mod tests { assert!(result.is_err_and(|e| matches!(e, ServiceInvocationError::InvalidArgument(_)))); } + + // [utest->dsn~usubscription-subscribe-invalid-topic~1] + #[test_case("up:/0/0/0"; "Bad topic UUri")] + #[test_case("up://*/100000/1/8AC7"; "Wildcard authority in topic UUri")] + #[test_case("up://LOCAL/FFFF0000/1/8AC7"; "Wildcard entity id in topic UUri")] + #[test_case("up://LOCAL/100000/1/FFFF"; "Wildcard resource id in topic UUri")] + #[tokio::test] + async fn test_invalid_topic_uri(topic: &str) { + helpers::init_once(); + + // create request and other required object(s) + let topic = UUri::from_str(topic).expect("Test parameter UUri failed to parse"); + let subscribe_request = test_lib::helpers::subscription_request(topic, None); + let request_payload = UPayload::try_from_protobuf(subscribe_request.clone()).unwrap(); + let message_attributes = UAttributes { + source: Some(test_lib::helpers::subscriber_uri1()).into(), + ..Default::default() + }; + let (subscription_sender, _) = mpsc::channel::(1); + + // create handler and perform tested operation + let request_handler = SubscriptionRequestHandler::new(subscription_sender); + + let result = request_handler + .handle_request( + up_rust::core::usubscription::RESOURCE_ID_SUBSCRIBE, + &message_attributes, + Some(request_payload), + ) + .await; + + assert!(result.is_err_and(|err| matches!(err, ServiceInvocationError::InvalidArgument(_)))); + } } diff --git a/up-subscription/src/handlers/unregister_for_notifications.rs b/up-subscription/src/handlers/unregister_for_notifications.rs index b7753a6..2b149fe 100644 --- a/up-subscription/src/handlers/unregister_for_notifications.rs +++ b/up-subscription/src/handlers/unregister_for_notifications.rs @@ -46,13 +46,27 @@ impl RequestHandler for UnregisterNotificationsRequestHandler { request_payload: Option, ) -> Result, ServiceInvocationError> { // [impl->dsn~usubscription-unregister-notifications-protobuf~1] - let (_subscription_request, source) = helpers::extract_inputs::( + let (notification_request, source) = helpers::extract_inputs::( RESOURCE_ID_UNREGISTER_FOR_NOTIFICATIONS, resource_id, &request_payload, message_attributes, )?; + let Some(topic) = notification_request.topic.as_ref() else { + return Err(ServiceInvocationError::InvalidArgument( + "No topic defined in request".to_string(), + )); + }; + + // [impl->dsn~usubscription-unregister-notifications-invalid-topic~1] + helpers::validate_uri(topic).map_err(|e| { + ServiceInvocationError::InvalidArgument(format!("Invalid topic uri '{topic}': {e}")) + })?; + + // TODO: can/should we actually use the topic alongside the subscriber, for notification-removal? + // UGH - this entire notifications storage thing doesn't work, can only store one topic per subscriber atm. Needs fixed... + // Interact with notification manager backend let se = NotificationEvent::RemoveNotifyee { subscriber: source.clone(), @@ -78,8 +92,12 @@ impl RequestHandler for UnregisterNotificationsRequestHandler { #[cfg(test)] mod tests { use super::*; + use std::str::FromStr; + use test_case::test_case; use tokio::sync::mpsc::{self}; + use up_rust::UUri; + use crate::{helpers, tests::test_lib}; // [utest->dsn~usubscription-unregister-notifications-protobuf~1] @@ -236,4 +254,38 @@ mod tests { assert!(result.is_err_and(|err| matches!(err, ServiceInvocationError::InvalidArgument(_)))); } + + // [utest->dsn~usubscription-unregister-notifications-invalid-topic~1] + #[test_case("up:/0/0/0"; "Bad topic UUri")] + #[test_case("up://*/100000/1/8AC7"; "Wildcard authority in topic UUri")] + #[test_case("up://LOCAL/FFFF0000/1/8AC7"; "Wildcard entity id in topic UUri")] + #[test_case("up://LOCAL/100000/1/FFFF"; "Wildcard resource id in topic UUri")] + #[tokio::test] + async fn test_invalid_topic_uri(topic: &str) { + helpers::init_once(); + + // create request and other required object(s) + let topic = UUri::from_str(topic).expect("Test parameter UUri failed to parse"); + // create request and other required object(s) + let subscribe_request = test_lib::helpers::subscription_request(topic, None); + let request_payload = UPayload::try_from_protobuf(subscribe_request.clone()).unwrap(); + let message_attributes = UAttributes { + source: Some(test_lib::helpers::subscriber_uri1()).into(), + ..Default::default() + }; + let (subscription_sender, _) = mpsc::channel::(1); + + // create handler and perform tested operation + let request_handler = UnregisterNotificationsRequestHandler::new(subscription_sender); + + let result = request_handler + .handle_request( + up_rust::core::usubscription::RESOURCE_ID_UNREGISTER_FOR_NOTIFICATIONS, + &message_attributes, + Some(request_payload), + ) + .await; + + assert!(result.is_err_and(|err| matches!(err, ServiceInvocationError::InvalidArgument(_)))); + } } diff --git a/up-subscription/src/handlers/unsubscribe.rs b/up-subscription/src/handlers/unsubscribe.rs index 4c9dc28..0142cd6 100644 --- a/up-subscription/src/handlers/unsubscribe.rs +++ b/up-subscription/src/handlers/unsubscribe.rs @@ -58,6 +58,11 @@ impl RequestHandler for UnubscribeRequestHandler { )); }; + // [impl->dsn~usubscription-unsubscribe-invalid-topic~1] + helpers::validate_uri(topic).map_err(|e| { + ServiceInvocationError::InvalidArgument(format!("Invalid topic uri '{topic}': {e}")) + })?; + let (respond_to, receive_from) = oneshot::channel::(); let se = SubscriptionEvent::RemoveSubscription { subscriber: source.clone(), @@ -91,8 +96,11 @@ impl RequestHandler for UnubscribeRequestHandler { #[cfg(test)] mod tests { use super::*; + use std::str::FromStr; + use test_case::test_case; use tokio::sync::mpsc::{self}; - use up_rust::core::usubscription::State; + + use up_rust::{core::usubscription::State, UUri}; use crate::{helpers, tests::test_lib}; @@ -258,4 +266,37 @@ mod tests { assert!(result.is_err_and(|err| matches!(err, ServiceInvocationError::InvalidArgument(_)))); } + + // [utest->dsn~usubscription-unsubscribe-invalid-topic~1] + #[test_case("up:/0/0/0"; "Bad topic UUri")] + #[test_case("up://*/100000/1/8AC7"; "Wildcard authority in topic UUri")] + #[test_case("up://LOCAL/FFFF0000/1/8AC7"; "Wildcard entity id in topic UUri")] + #[test_case("up://LOCAL/100000/1/FFFF"; "Wildcard resource id in topic UUri")] + #[tokio::test] + async fn test_invalid_topic_uri(topic: &str) { + helpers::init_once(); + + // create request and other required object(s) + let topic = UUri::from_str(topic).expect("Test parameter UUri failed to parse"); + let subscribe_request = test_lib::helpers::subscription_request(topic, None); + let request_payload = UPayload::try_from_protobuf(subscribe_request.clone()).unwrap(); + let message_attributes = UAttributes { + source: Some(test_lib::helpers::subscriber_uri1()).into(), + ..Default::default() + }; + let (subscription_sender, _) = mpsc::channel::(1); + + // create handler and perform tested operation + let request_handler = UnubscribeRequestHandler::new(subscription_sender); + + let result = request_handler + .handle_request( + up_rust::core::usubscription::RESOURCE_ID_UNSUBSCRIBE, + &message_attributes, + Some(request_payload), + ) + .await; + + assert!(result.is_err_and(|err| matches!(err, ServiceInvocationError::InvalidArgument(_)))); + } }