From 73d2b4b43fba88df509cfff34c2cb6f63cb025a7 Mon Sep 17 00:00:00 2001 From: Johnson Shih Date: Thu, 13 Jul 2023 18:17:17 -0700 Subject: [PATCH 1/8] Enable Onvif discovery handler authenticated discovery Signed-off-by: Johnson Shih --- Cargo.lock | 3 + discovery-handlers/onvif/Cargo.toml | 3 + .../onvif/src/credential_store.rs | 587 ++++++++++++++++++ .../onvif/src/discovery_handler.rs | 10 +- .../onvif/src/discovery_impl.rs | 2 +- .../onvif/src/discovery_utils.rs | 131 ++-- discovery-handlers/onvif/src/lib.rs | 2 + .../onvif/src/username_token.rs | 51 ++ 8 files changed, 749 insertions(+), 40 deletions(-) create mode 100644 discovery-handlers/onvif/src/credential_store.rs create mode 100644 discovery-handlers/onvif/src/username_token.rs diff --git a/Cargo.lock b/Cargo.lock index d95d50d80..1fde8eda6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -450,7 +450,9 @@ dependencies = [ "akri-shared", "anyhow", "async-trait", + "base64 0.13.1", "bytes 1.4.0", + "chrono", "env_logger", "futures-util", "hyper", @@ -460,6 +462,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_yaml", + "sha1 0.6.1", "sxd-document", "sxd-xpath", "tokio 1.26.0", diff --git a/discovery-handlers/onvif/Cargo.toml b/discovery-handlers/onvif/Cargo.toml index 7c59106dc..4512b918b 100644 --- a/discovery-handlers/onvif/Cargo.toml +++ b/discovery-handlers/onvif/Cargo.toml @@ -12,7 +12,9 @@ akri-discovery-utils = { path = "../../discovery-utils" } akri-shared = { path = "../../shared" } anyhow = "1.0.38" async-trait = "0.1.0" +base64 = "0.13.1" bytes = "1.0.1" +chrono = "0.4.10" env_logger = "0.10.0" futures-util = "0.3" hyper = { version = "0.14.11", package = "hyper" } @@ -21,6 +23,7 @@ serde = "1.0.104" serde_json = "1.0.45" serde_yaml = "0.8.11" serde_derive = "1.0.104" +sha1 = "0.6.1" sxd-document = "0.3.0" sxd-xpath = "0.4.0" tokio = { version = "1.0", features = ["time", "net", "sync"] } diff --git a/discovery-handlers/onvif/src/credential_store.rs b/discovery-handlers/onvif/src/credential_store.rs new file mode 100644 index 000000000..3adcfa5c2 --- /dev/null +++ b/discovery-handlers/onvif/src/credential_store.rs @@ -0,0 +1,587 @@ +use akri_discovery_utils::discovery::v0::ByteData; +use std::collections::HashMap; + +/// Key name of device credential list in discoveryProperties +pub const DEVICE_CREDENTIAL_LIST: &str = "device_credential_list"; +/// Key name of device credential ref list in discoveryProperties +pub const DEVICE_CREDENTIAL_REF_LIST: &str = "device_credential_ref_list"; +/// Key name prfix of username credential list in discoveryProperties +pub const DEVICE_CREDENTIAL_USERNAME_PREFIX: &str = "username_"; +/// Key name prfix of password credential list in discoveryProperties +pub const DEVICE_CREDENTIAL_PASSWORD_PREFIX: &str = "password_"; + +#[derive(Serialize, Deserialize, Clone, Debug)] +struct CredentialData { + username: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + password: Option, + #[serde(default = "default_base64encoded")] + base64encoded: bool, +} + +fn default_base64encoded() -> bool { + false +} + +impl CredentialData { + fn get_username(&self) -> String { + self.username.clone() + } + + fn get_password(&self) -> Option { + if self.base64encoded { + self.password + .as_ref() + .and_then(|encoded_data| decode_base64_str(encoded_data)) + } else { + self.password.clone() + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +struct CredentialRefData { + username_ref: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + password_ref: Option, +} + +#[derive(Default)] +pub struct CredentialStore { + credentials: HashMap)>, +} + +impl CredentialStore { + pub fn new(credential_data: &HashMap) -> Self { + let mut store = Self { + credentials: HashMap::new(), + }; + store.process_credential_list(credential_data); + store.process_credential_ref_list(credential_data); + store.process_username_password(credential_data); + store + } + + pub fn get(&self, uuid: &str) -> Option<(String, Option)> { + self.credentials + .get(uuid) + .map(|(n, p)| (n.to_string(), p.as_ref().map(|p| p.to_string()))) + } + + fn process_credential_list(&mut self, credential_data: &HashMap) { + let result = self.process_list_data( + DEVICE_CREDENTIAL_LIST, + credential_data, + |list_content, _credential_data| parse_credential_list(list_content), + ); + self.credentials.extend(result); + } + + fn process_credential_ref_list(&mut self, credential_data: &HashMap) { + let result = self.process_list_data( + DEVICE_CREDENTIAL_REF_LIST, + credential_data, + parse_credential_ref_list, + ); + self.credentials.extend(result); + } + + fn process_list_data( + &mut self, + key: &str, + credential_data: &HashMap, + parse: F, + ) -> HashMap)> + where + F: Fn(&str, &HashMap) -> HashMap)>, + { + parse_list_data(key, credential_data) + .unwrap_or_default() + .into_iter() + .filter_map(|list| credential_data.get(&list).and_then(byte_data_to_str)) + .flat_map(|list_content| parse(list_content, credential_data)) + .collect() + } + + fn process_username_password(&mut self, credential_data: &HashMap) { + let username_list = credential_data + .iter() + .filter_map(|(key, value)| { + key.strip_prefix(DEVICE_CREDENTIAL_USERNAME_PREFIX) + .and_then(|device_uuid| { + let username = byte_data_to_str(value)?; + if !device_uuid.is_empty() { + Some((device_uuid, username)) + } else { + None + } + }) + }) + .collect::>(); + if username_list.is_empty() { + return; + } + + let mut password_list = credential_data + .iter() + .filter_map(|(key, value)| { + key.strip_prefix(DEVICE_CREDENTIAL_PASSWORD_PREFIX) + .and_then(|device_uuid| { + let password = byte_data_to_str(value); + if !device_uuid.is_empty() { + Some((device_uuid, password)) + } else { + None + } + }) + }) + .collect::>>(); + + let result = username_list + .into_iter() + .map(|(device_uuid, username)| { + let password = password_list + .remove(&device_uuid) + .and_then(|opt_s| opt_s.map(|s| s.to_string())); + (device_uuid.to_string(), (username.to_string(), password)) + }) + .collect::)>>(); + self.credentials.extend(result); + } +} + +fn parse_credential_list(credential_list: &str) -> HashMap)> { + serde_json::from_str::>(credential_list) + .unwrap_or_default() + .into_iter() + .map(|(id, cred_data)| (id, (cred_data.get_username(), cred_data.get_password()))) + .collect() +} + +fn parse_credential_ref_list( + credential_ref_list: &str, + credential_data: &HashMap, +) -> HashMap)> { + serde_json::from_str::>(credential_ref_list) + .unwrap_or_default() + .into_iter() + .filter_map(|(id, cred_ref)| { + let username = credential_data + .get(&cred_ref.username_ref) + .and_then(byte_data_to_str) + .map(|n| n.to_string())?; + if username.is_empty() { + return None; + } + + let password = cred_ref + .password_ref + .map(|pwd| { + credential_data + .get(&pwd) + .and_then(byte_data_to_str) + .map(|p| p.to_string()) + }) + .unwrap_or_default(); + Some((id, (username, password))) + }) + .collect() +} + +fn parse_list_data(key: &str, credential_data: &HashMap) -> Option> { + credential_data + .get(key) + .and_then(byte_data_to_str) + .and_then(|list_json_str| serde_json::from_str::>(list_json_str).ok()) +} + +fn byte_data_to_str(byte_data: &ByteData) -> Option<&str> { + byte_data + .vec + .as_ref() + .and_then(|s| std::str::from_utf8(s).ok()) +} + +fn decode_base64_str(encoded_data: &str) -> Option { + let decoded_data = base64::decode(encoded_data).ok()?; + std::str::from_utf8(&decoded_data) + .map(|s| s.to_string()) + .ok() +} + +#[cfg(test)] +mod tests { + use super::*; + struct DeviceCredentialData<'a> { + pub id: &'a str, + pub username: Option<&'a [u8]>, + pub password: Option<&'a [u8]>, + } + + fn generate_list_credential_data( + list_name: &str, + entries: Vec<(&str, Option<&[u8]>)>, + ) -> HashMap { + let credential_list_value = format!( + "[{}]", + entries + .iter() + .map(|(k, _v)| format!(r#""{}""#, k)) + .collect::>() + .join(", ") + ); + + let list_data = HashMap::from([generate_credential_data_entry( + list_name, + Some(credential_list_value.as_bytes()), + )]); + + entries + .into_iter() + .map(|(k, v)| generate_credential_data_entry(k, v)) + .chain(list_data.into_iter()) + .collect::>() + } + + fn generate_username_password_credential_data( + entries: Vec, + ) -> HashMap { + entries + .into_iter() + .flat_map(|entry| { + let username_key = format!("{}{}", DEVICE_CREDENTIAL_USERNAME_PREFIX, entry.id); + let password_key = format!("{}{}", DEVICE_CREDENTIAL_PASSWORD_PREFIX, entry.id); + HashMap::from([ + generate_credential_data_entry(&username_key, entry.username), + generate_credential_data_entry(&password_key, entry.password), + ]) + }) + .collect::>() + } + + fn generate_credential_data_entry(key: &str, value: Option<&[u8]>) -> (String, ByteData) { + ( + key.to_string(), + ByteData { + vec: value.map(|p| p.to_vec()), + }, + ) + } + + #[test] + fn test_credential_store_empty() { + let credential_data = HashMap::new(); + + let credential_store = CredentialStore::new(&credential_data); + assert!(credential_store.credentials.is_empty()); + } + + #[test] + fn test_credential_store_non_utf8_username() { + let test_data = vec![("deviceid_1", vec![200u8, 200u8, 200u8], "password_1")]; + let test_entries = test_data + .iter() + .map(|(id, uname, pwd)| DeviceCredentialData { + id, + username: Some(uname as &[u8]), + password: Some(pwd.as_bytes()), + }) + .collect::>(); + let credential_data = generate_username_password_credential_data(test_entries); + + let credential_store = CredentialStore::new(&credential_data); + assert!(credential_store.credentials.is_empty()); + } + + #[test] + fn test_credential_store_non_utf8_password() { + let test_data = vec![("deviceid_1", "username_1", vec![200u8, 200u8, 200u8])]; + let expected_result = test_data + .iter() + .map(|(id, uname, pwd)| { + ( + id.to_string(), + ( + uname.to_string(), + std::str::from_utf8(pwd).map(|s| s.to_string()).ok(), + ), + ) + }) + .collect::)>>(); + let test_entries = test_data + .iter() + .map(|(id, uname, pwd)| DeviceCredentialData { + id, + username: Some(uname.as_bytes()), + password: Some(pwd as &[u8]), + }) + .collect::>(); + let credential_data = generate_username_password_credential_data(test_entries); + + let credential_store = CredentialStore::new(&credential_data); + assert_eq!(credential_store.credentials, expected_result); + } + + fn build_username_password_data() -> HashMap { + let secret_data = vec![( + "5f5a69c2-e0ae-504f-829b-00fcdab169cc", + "username_5f", + "password_5f", + )]; + let secret_test_data = secret_data + .iter() + .map(|(id, uname, pwd)| DeviceCredentialData { + id, + username: Some(uname.as_bytes()), + password: Some(pwd.as_bytes()), + }) + .collect::>(); + generate_username_password_credential_data(secret_test_data) + } + + fn build_device_credential_list_data() -> HashMap { + let credential_list_key = "device_credential_list"; + let secret_list1_key = "secret_list1"; + let secret_list1_value = r#" + { + "5f5a69c2-e0ae-504f-829b-00fcdab169cc" : + { + "username" : "uname_1", + "password" : "password_1" + }, + "6a67158b-42b1-400b-8afe-1bec9a5d7909": + { + "username" : "uname_3", + "password" : "YWRtaW4=", + "base64encoded": true + } + }"#; + + let secret_list2_key = "secret_list2"; + let secret_list2_value = r#" + { + "7a21dc67-8438-5588-1547-4d1349048438" : + { + "username" : "uname_2", + "password" : "password_2" + } + } + "#; + let secret_list_test_data = vec![ + (secret_list1_key, Some(secret_list1_value.as_bytes())), + (secret_list2_key, Some(secret_list2_value.as_bytes())), + ]; + generate_list_credential_data(credential_list_key, secret_list_test_data) + } + + fn build_device_credential_ref_list_data() -> HashMap { + let credential_ref_list_key = "device_credential_ref_list"; + let secret_ref_list1_key = "secret_ref_list1"; + let secret_ref_list1_value = r#" + { + "5f5a69c2-e0ae-504f-829b-00fcdab169cc" : + { + "username_ref" : "device_1_username", + "password_ref" : "device_1_password" + } + } + "#; + + let secret_ref_list2_key = "secret_ref_list2"; + let secret_ref_list2_value = r#" + { + "7a21dc67-8438-5588-1547-4d1349048438" : + { + "username_ref" : "device_2_username", + "password_ref" : "device_2_password" + } + } + "#; + let secret_ref_username_password = vec![ + ("device_1_username", "user_foo"), + ("device_1_password", "password_foo"), + ("device_2_username", "user_bar"), + ("device_2_password", "password_bar"), + ]; + + let secret_ref_list_test_data = vec![ + ( + secret_ref_list1_key, + Some(secret_ref_list1_value.as_bytes()), + ), + ( + secret_ref_list2_key, + Some(secret_ref_list2_value.as_bytes()), + ), + ]; + let credential_ref_list_data = + generate_list_credential_data(credential_ref_list_key, secret_ref_list_test_data); + secret_ref_username_password + .iter() + .map(|(k, v)| generate_credential_data_entry(k, Some(v.as_bytes()))) + .chain(credential_ref_list_data.into_iter()) + .collect::>() + } + + #[test] + fn test_credential_store_username_password() { + let expected_result = HashMap::from([( + "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), + ("username_5f".to_string(), Some("password_5f".to_string())), + )]); + let credential_data = build_username_password_data(); + + let credential_store = CredentialStore::new(&credential_data); + assert_eq!(credential_store.credentials, expected_result); + } + + #[test] + fn test_credential_store_device_credential_list() { + let expected_result = HashMap::from([ + ( + "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), + ("uname_1".to_string(), Some("password_1".to_string())), + ), + ( + "6a67158b-42b1-400b-8afe-1bec9a5d7909".to_string(), + ("uname_3".to_string(), Some("admin".to_string())), + ), + ( + "7a21dc67-8438-5588-1547-4d1349048438".to_string(), + ("uname_2".to_string(), Some("password_2".to_string())), + ), + ]); + let credential_data = build_device_credential_list_data(); + + let credential_store = CredentialStore::new(&credential_data); + assert_eq!(credential_store.credentials, expected_result); + } + + #[test] + fn test_credential_store_device_credential_ref_list() { + let expected_result = HashMap::from([ + ( + "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), + ("user_foo".to_string(), Some("password_foo".to_string())), + ), + ( + "7a21dc67-8438-5588-1547-4d1349048438".to_string(), + ("user_bar".to_string(), Some("password_bar".to_string())), + ), + ]); + let credential_data = build_device_credential_ref_list_data(); + let credential_store = CredentialStore::new(&credential_data); + + assert_eq!(credential_store.credentials, expected_result); + } + + #[test] + fn test_credential_store_device_credential_list_and_username_password() { + let expected_result = HashMap::from([ + ( + "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), + ("username_5f".to_string(), Some("password_5f".to_string())), + ), + ( + "6a67158b-42b1-400b-8afe-1bec9a5d7909".to_string(), + ("uname_3".to_string(), Some("admin".to_string())), + ), + ( + "7a21dc67-8438-5588-1547-4d1349048438".to_string(), + ("uname_2".to_string(), Some("password_2".to_string())), + ), + ]); + let credential_list_data = build_device_credential_list_data(); + let username_password_data = build_username_password_data(); + let credential_data = HashMap::new() + .into_iter() + .chain(credential_list_data.into_iter()) + .chain(username_password_data.into_iter()) + .collect(); + let credential_store = CredentialStore::new(&credential_data); + + assert_eq!(credential_store.credentials, expected_result); + } + + #[test] + fn test_credential_store_device_credential_list_and_credential_ref_list() { + let expected_result = HashMap::from([ + ( + "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), + ("user_foo".to_string(), Some("password_foo".to_string())), + ), + ( + "6a67158b-42b1-400b-8afe-1bec9a5d7909".to_string(), + ("uname_3".to_string(), Some("admin".to_string())), + ), + ( + "7a21dc67-8438-5588-1547-4d1349048438".to_string(), + ("user_bar".to_string(), Some("password_bar".to_string())), + ), + ]); + let credential_list_data = build_device_credential_list_data(); + let credential_ref_list_data = build_device_credential_ref_list_data(); + let credential_data = HashMap::new() + .into_iter() + .chain(credential_list_data.into_iter()) + .chain(credential_ref_list_data.into_iter()) + .collect(); + let credential_store = CredentialStore::new(&credential_data); + + assert_eq!(credential_store.credentials, expected_result); + } + + #[test] + fn test_credential_store_device_credential_ref_list_and_username_password() { + let expected_result = HashMap::from([ + ( + "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), + ("username_5f".to_string(), Some("password_5f".to_string())), + ), + ( + "7a21dc67-8438-5588-1547-4d1349048438".to_string(), + ("user_bar".to_string(), Some("password_bar".to_string())), + ), + ]); + let credential_ref_list_data = build_device_credential_ref_list_data(); + let username_password_data = build_username_password_data(); + let credential_data = HashMap::new() + .into_iter() + .chain(credential_ref_list_data.into_iter()) + .chain(username_password_data.into_iter()) + .collect(); + let credential_store = CredentialStore::new(&credential_data); + + assert_eq!(credential_store.credentials, expected_result); + } + + #[test] + fn test_credential_store_device_credential_all() { + let expected_result = HashMap::from([ + ( + "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), + ("username_5f".to_string(), Some("password_5f".to_string())), + ), + ( + "6a67158b-42b1-400b-8afe-1bec9a5d7909".to_string(), + ("uname_3".to_string(), Some("admin".to_string())), + ), + ( + "7a21dc67-8438-5588-1547-4d1349048438".to_string(), + ("user_bar".to_string(), Some("password_bar".to_string())), + ), + ]); + let credential_list_data = build_device_credential_list_data(); + let credential_ref_list_data = build_device_credential_ref_list_data(); + let username_password_data = build_username_password_data(); + let credential_data = HashMap::new() + .into_iter() + .chain(credential_list_data.into_iter()) + .chain(credential_ref_list_data.into_iter()) + .chain(username_password_data.into_iter()) + .collect(); + let credential_store = CredentialStore::new(&credential_data); + + assert_eq!(credential_store.credentials, expected_result); + } +} diff --git a/discovery-handlers/onvif/src/discovery_handler.rs b/discovery-handlers/onvif/src/discovery_handler.rs index 0985e65ff..3c003b50e 100644 --- a/discovery-handlers/onvif/src/discovery_handler.rs +++ b/discovery-handlers/onvif/src/discovery_handler.rs @@ -1,3 +1,4 @@ +use super::credential_store::CredentialStore; use super::discovery_impl::util; use super::discovery_utils::{ OnvifQuery, OnvifQueryImpl, ONVIF_DEVICE_IP_ADDRESS_LABEL_ID, @@ -73,6 +74,8 @@ impl DiscoveryHandler for DiscoveryHandlerImpl { let discovery_handler_config: OnvifDiscoveryDetails = deserialize_discovery_details(&discover_request.discovery_details) .map_err(|e| tonic::Status::new(tonic::Code::InvalidArgument, format!("{}", e)))?; + let credential_store = CredentialStore::new(&discover_request.discovery_properties); + let onvif_query = OnvifQueryImpl::new(credential_store); tokio::spawn(async move { let mut previous_cameras = HashMap::new(); let mut filtered_camera_devices = HashMap::new(); @@ -86,7 +89,6 @@ impl DiscoveryHandler for DiscoveryHandlerImpl { break; } let mut changed_camera_list = false; - let onvif_query = OnvifQueryImpl {}; trace!("discover - filters:{:?}", &discovery_handler_config,); let mut socket = util::get_discovery_response_socket().await.unwrap(); @@ -161,7 +163,7 @@ async fn apply_filters( device_service_uri, device_uuid ); let (ip_address, mac_address) = match onvif_query - .get_device_ip_and_mac_address(device_service_uri) + .get_device_ip_and_mac_address(device_service_uri, device_uuid) .await { Ok(ip_and_mac) => ip_and_mac, @@ -243,8 +245,8 @@ mod tests { ) { mock.expect_get_device_ip_and_mac_address() .times(1) - .withf(move |u| u == uri) - .returning(move |_| Ok((ip.to_string(), mac.to_string()))); + .withf(move |u, _uuid| u == uri) + .returning(move |_, _| Ok((ip.to_string(), mac.to_string()))); } fn expected_device(uri: &str, uuid: &str, ip: &str, mac: &str) -> (String, Device) { diff --git a/discovery-handlers/onvif/src/discovery_impl.rs b/discovery-handlers/onvif/src/discovery_impl.rs index 6812babc2..bff8bf906 100644 --- a/discovery-handlers/onvif/src/discovery_impl.rs +++ b/discovery-handlers/onvif/src/discovery_impl.rs @@ -610,7 +610,7 @@ pub mod util { "simple_onvif_discover - uris after filtering by scopes {:?}", filtered_uris ); - let devices = get_responsive_uris(filtered_uris, &OnvifQueryImpl {}).await; + let devices = get_responsive_uris(filtered_uris, &OnvifQueryImpl::default()).await; info!("simple_onvif_discover - devices: {:?}", devices); Ok(devices) } diff --git a/discovery-handlers/onvif/src/discovery_utils.rs b/discovery-handlers/onvif/src/discovery_utils.rs index 7dd9ee172..b94f58b01 100644 --- a/discovery-handlers/onvif/src/discovery_utils.rs +++ b/discovery-handlers/onvif/src/discovery_utils.rs @@ -1,3 +1,5 @@ +use super::credential_store::CredentialStore; +use super::username_token::UsernameToken; use async_trait::async_trait; use futures_util::stream::TryStreamExt; use hyper::Request; @@ -24,6 +26,7 @@ pub trait OnvifQuery { async fn get_device_ip_and_mac_address( &self, service_url: &str, + device_uuid: &str, ) -> Result<(String, String), anyhow::Error>; async fn get_device_service_uri( &self, @@ -39,7 +42,16 @@ pub trait OnvifQuery { async fn is_device_responding(&self, url: &str) -> Result; } -pub struct OnvifQueryImpl {} +#[derive(Default)] +pub struct OnvifQueryImpl { + credential_store: CredentialStore, +} + +impl OnvifQueryImpl { + pub fn new(credential_store: CredentialStore) -> Self { + Self { credential_store } + } +} #[async_trait] impl OnvifQuery for OnvifQueryImpl { @@ -47,9 +59,11 @@ impl OnvifQuery for OnvifQueryImpl { async fn get_device_ip_and_mac_address( &self, service_url: &str, + device_uuid: &str, ) -> Result<(String, String), anyhow::Error> { + let credential = self.credential_store.get(device_uuid); let http = HttpRequest {}; - inner_get_device_ip_and_mac_address(service_url, &http).await + inner_get_device_ip_and_mac_address(service_url, credential, &http).await } /// Gets specific service, like media, from a given ONVIF camera @@ -173,13 +187,18 @@ fn get_action(wsdl: &str, function: &str) -> String { /// Gets the ip and mac address for a given ONVIF camera async fn inner_get_device_ip_and_mac_address( service_url: &str, + credential: Option<(String, Option)>, http: &impl Http, ) -> Result<(String, String), anyhow::Error> { + let username_token = credential.map(|(uname, passwd)| { + UsernameToken::new(uname.as_str(), passwd.unwrap_or_default().as_str()) + }); + let message = get_network_interfaces_message(&username_token); let network_interfaces_xml = match http .post( service_url, &get_action(DEVICE_WSDL, "GetNetworkInterfaces"), - GET_NETWORK_INTERFACES_TEMPLATE, + message.as_str(), ) .await { @@ -229,13 +248,49 @@ async fn inner_get_device_ip_and_mac_address( Ok((ip_address, mac_address)) } +fn get_soap_security_header(username_token: &UsernameToken) -> String { + format!( + r#" + + + {} + {} + {} + {} + + + "#, + username_token.username, + username_token.digest, + username_token.nonce, + username_token.created + ) +} + /// SOAP request body for getting the network interfaces for an ONVIF camera -const GET_NETWORK_INTERFACES_TEMPLATE: &str = r#" - - - - - "#; +fn get_network_interfaces_message(username_token: &Option) -> String { + let security_header = if let Some(username_token) = username_token { + get_soap_security_header(username_token) + } else { + "".to_string() + }; + + format!( + r#" + + + {} + + + + +"#, + security_header + ) +} /// Gets a specific service (like media) uri from an ONVIF camera async fn inner_get_device_service_uri( @@ -285,10 +340,10 @@ async fn inner_get_device_service_uri( /// SOAP request body for getting the supported services' uris for an ONVIF camera const GET_SERVICES_TEMPLATE: &str = r#" - - - - "#; + + + +"#; /// Gets list of media profiles for a given ONVIF camera async fn inner_get_device_profiles( @@ -378,18 +433,18 @@ fn get_stream_uri_message(profile: &str) -> String { format!( r#" - - - - RTP-Unicast - - RTSP - - - {} - - - ;"#, + + + + RTP-Unicast + + RTSP + + + {} + + +;"#, profile ) } @@ -397,10 +452,10 @@ fn get_stream_uri_message(profile: &str) -> String { /// SOAP request body for getting the media profiles for an ONVIF camera const GET_PROFILES_TEMPLATE: &str = r#" - - - - "#; + + + +"#; // const GET_DEVICE_INFORMATION_TEMPLATE: &str = r#" // @@ -411,10 +466,10 @@ const GET_PROFILES_TEMPLATE: &str = r#" - - - - "#; + + + +"#; #[cfg(test)] mod tests { @@ -443,17 +498,20 @@ mod tests { let mut mock = MockHttp::new(); let response = "\ntrueeth000:12:41:5c:a1:a51500false10Fullfalse10Full0true192.168.1.3624false"; + let username_token = None; + let message = get_network_interfaces_message(&username_token); configure_post( &mut mock, "test_inner_get_device_ip_and_mac_address-url", &get_action(DEVICE_WSDL, "GetNetworkInterfaces"), - GET_NETWORK_INTERFACES_TEMPLATE, + &message, response, ); assert_eq!( ("192.168.1.36".to_string(), "00:12:41:5c:a1:a5".to_string()), inner_get_device_ip_and_mac_address( "test_inner_get_device_ip_and_mac_address-url", + None, &mock ) .await @@ -467,11 +525,13 @@ mod tests { let mut mock = MockHttp::new(); let response = "\ntrueeth000:FC:DA:B1:69:CC1500true10.137.185.208010.137.185.20823true\r\n"; + let username_token = None; + let message = get_network_interfaces_message(&username_token); configure_post( &mut mock, "test_inner_get_device_ip_and_mac_address-url", &get_action(DEVICE_WSDL, "GetNetworkInterfaces"), - GET_NETWORK_INTERFACES_TEMPLATE, + &message, response, ); assert_eq!( @@ -481,6 +541,7 @@ mod tests { ), inner_get_device_ip_and_mac_address( "test_inner_get_device_ip_and_mac_address-url", + None, &mock ) .await diff --git a/discovery-handlers/onvif/src/lib.rs b/discovery-handlers/onvif/src/lib.rs index 2fe844b10..228140ed0 100644 --- a/discovery-handlers/onvif/src/lib.rs +++ b/discovery-handlers/onvif/src/lib.rs @@ -1,6 +1,8 @@ +mod credential_store; pub mod discovery_handler; mod discovery_impl; mod discovery_utils; +mod username_token; #[macro_use] extern crate serde_derive; diff --git a/discovery-handlers/onvif/src/username_token.rs b/discovery-handlers/onvif/src/username_token.rs new file mode 100644 index 000000000..4b4a2f48d --- /dev/null +++ b/discovery-handlers/onvif/src/username_token.rs @@ -0,0 +1,51 @@ +#[derive(Default, Debug, Clone)] +pub struct UsernameToken { + pub username: String, + pub nonce: String, + pub digest: String, + pub created: String, +} + +impl UsernameToken { + pub fn new(username: &str, password: &str) -> UsernameToken { + let nonce = uuid::Uuid::new_v4().to_string(); + let created = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true); + Self::generate_token(username, password, &nonce, &created) + } + + fn generate_token(username: &str, password: &str, nonce: &str, created: &str) -> UsernameToken { + let concat = format!("{}{}{}", nonce, created, password); + let digest = { + let mut hasher = sha1::Sha1::new(); + hasher.update(concat.as_bytes()); + hasher.digest().bytes() + }; + + UsernameToken { + username: username.to_string(), + nonce: base64::encode(nonce), + digest: base64::encode(digest), + created: created.to_string(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generate_token() { + let username = "abcdefe"; + let password = "1234567"; + let nonce = "nonce"; + let created = "2000-01-01T12:34:56:789Z"; + + let username_token = UsernameToken::generate_token(username, password, nonce, created); + + assert_eq!(username_token.username, username); + assert_eq!(username_token.created, created); + assert_eq!(username_token.nonce, "bm9uY2U="); + assert_eq!(username_token.digest, "AGsoQQ+qNJu6Ha7h/QAPoQvYcV0="); + } +} From eb368b46b71e8326c82ae3b6667cfd8858f54b28 Mon Sep 17 00:00:00 2001 From: Johnson Shih Date: Tue, 1 Aug 2023 13:16:57 -0700 Subject: [PATCH 2/8] update credential data key format Signed-off-by: Johnson Shih --- .../onvif/src/credential_store.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/discovery-handlers/onvif/src/credential_store.rs b/discovery-handlers/onvif/src/credential_store.rs index 3adcfa5c2..f3c5a8c16 100644 --- a/discovery-handlers/onvif/src/credential_store.rs +++ b/discovery-handlers/onvif/src/credential_store.rs @@ -143,7 +143,12 @@ impl CredentialStore { let password = password_list .remove(&device_uuid) .and_then(|opt_s| opt_s.map(|s| s.to_string())); - (device_uuid.to_string(), (username.to_string(), password)) + // Credential data key is in C_IDENTIFIER format + // convert it back to uuid string format by replacing "_" with "-" + ( + device_uuid.replace('_', "-"), + (username.to_string(), password), + ) }) .collect::)>>(); self.credentials.extend(result); @@ -213,7 +218,7 @@ fn decode_base64_str(encoded_data: &str) -> Option { mod tests { use super::*; struct DeviceCredentialData<'a> { - pub id: &'a str, + pub id: String, pub username: Option<&'a [u8]>, pub password: Option<&'a [u8]>, } @@ -278,11 +283,11 @@ mod tests { #[test] fn test_credential_store_non_utf8_username() { - let test_data = vec![("deviceid_1", vec![200u8, 200u8, 200u8], "password_1")]; + let test_data = vec![("deviceid-1", vec![200u8, 200u8, 200u8], "password_1")]; let test_entries = test_data .iter() .map(|(id, uname, pwd)| DeviceCredentialData { - id, + id: id.replace('-', "_"), username: Some(uname as &[u8]), password: Some(pwd.as_bytes()), }) @@ -295,7 +300,7 @@ mod tests { #[test] fn test_credential_store_non_utf8_password() { - let test_data = vec![("deviceid_1", "username_1", vec![200u8, 200u8, 200u8])]; + let test_data = vec![("deviceid-1", "username_1", vec![200u8, 200u8, 200u8])]; let expected_result = test_data .iter() .map(|(id, uname, pwd)| { @@ -311,7 +316,7 @@ mod tests { let test_entries = test_data .iter() .map(|(id, uname, pwd)| DeviceCredentialData { - id, + id: id.replace('-', "_"), username: Some(uname.as_bytes()), password: Some(pwd as &[u8]), }) @@ -331,7 +336,7 @@ mod tests { let secret_test_data = secret_data .iter() .map(|(id, uname, pwd)| DeviceCredentialData { - id, + id: id.replace('-', "_"), username: Some(uname.as_bytes()), password: Some(pwd.as_bytes()), }) From 1a2f114b02625f74e119a985a8b05dd699223b6c Mon Sep 17 00:00:00 2001 From: Johnson Shih Date: Thu, 3 Aug 2023 09:31:21 -0700 Subject: [PATCH 3/8] discoveryProperties helm support Signed-off-by: Johnson Shih --- .../helm/templates/onvif-configuration.yaml | 36 +++++++++++++++++++ deployment/helm/values.yaml | 3 ++ 2 files changed, 39 insertions(+) diff --git a/deployment/helm/templates/onvif-configuration.yaml b/deployment/helm/templates/onvif-configuration.yaml index c018de01b..ded6fb299 100644 --- a/deployment/helm/templates/onvif-configuration.yaml +++ b/deployment/helm/templates/onvif-configuration.yaml @@ -32,6 +32,42 @@ spec: items: [] {{- end }} discoveryTimeoutSeconds: {{ .Values.onvif.configuration.discoveryDetails.discoveryTimeoutSeconds }} + {{- if .Values.onvif.configuration.discoveryProperties}} + discoveryProperties: + {{- range $property := .Values.onvif.configuration.discoveryProperties }} + - name: {{ $property.name }} + {{- if $property.valueFrom }} + valueFrom: + {{- if $property.valueFrom.secretKeyRef }} + secretKeyRef: + name: {{ $property.valueFrom.secretKeyRef.name }} + {{- if $property.valueFrom.secretKeyRef.namespace }} + namespace: {{ $property.valueFrom.secretKeyRef.namespace }} + {{- end }} + {{- if $property.valueFrom.secretKeyRef.key }} + key: {{ $property.valueFrom.secretKeyRef.key }} + {{- end }} + {{- if hasKey $property.valueFrom.secretKeyRef "optional" }} + optional: {{ $property.valueFrom.secretKeyRef.optional }} + {{- end }} + {{- else if $property.valueFrom.configMapKeyRef}} + configMapKeyRef: + name: {{ $property.valueFrom.configMapKeyRef.name }} + {{- if $property.valueFrom.configMapKeyRef.namespace }} + namespace: {{ $property.valueFrom.configMapKeyRef.namespace }} + {{- end }} + {{- if $property.valueFrom.configMapKeyRef.key }} + key: {{ $property.valueFrom.configMapKeyRef.key }} + {{- end }} + {{- if hasKey $property.valueFrom.configMapKeyRef "optional" }} + optional: {{ $property.configMapKeyRef.optional }} + {{- end }} + {{- end }} + {{- else }} + value: {{ $property.value | quote }} + {{- end }} + {{- end }} + {{- end }} {{- if or .Values.onvif.configuration.brokerPod.image.repository .Values.onvif.configuration.brokerJob.image.repository }} {{- /* Only add brokerSpec if a broker image is provided */}} brokerSpec: diff --git a/deployment/helm/values.yaml b/deployment/helm/values.yaml index 9f6a5865b..364672d82 100644 --- a/deployment/helm/values.yaml +++ b/deployment/helm/values.yaml @@ -425,6 +425,9 @@ onvif: action: Exclude items: [] discoveryTimeoutSeconds: 1 + # discoveryProperties is a map of properties fthat will be passed to discovery handler, + # the properties can be direct specified or read from Secret or ConfigMap + discoveryProperties: # capacity is the capacity for any instances created as a result of # applying this onvif configuration capacity: 1 From 57672e0358db86c1f535aa008c3ca819710179ea Mon Sep 17 00:00:00 2001 From: Johnson Shih Date: Mon, 7 Aug 2023 17:07:55 -0700 Subject: [PATCH 4/8] Add default credential support Signed-off-by: Johnson Shih --- .../onvif/src/credential_store.rs | 132 +++++++++++++++++- 1 file changed, 129 insertions(+), 3 deletions(-) diff --git a/discovery-handlers/onvif/src/credential_store.rs b/discovery-handlers/onvif/src/credential_store.rs index f3c5a8c16..4ac6cec91 100644 --- a/discovery-handlers/onvif/src/credential_store.rs +++ b/discovery-handlers/onvif/src/credential_store.rs @@ -49,16 +49,16 @@ struct CredentialRefData { #[derive(Default)] pub struct CredentialStore { credentials: HashMap)>, + default_credential: Option<(String, Option)>, } impl CredentialStore { pub fn new(credential_data: &HashMap) -> Self { - let mut store = Self { - credentials: HashMap::new(), - }; + let mut store = Self::default(); store.process_credential_list(credential_data); store.process_credential_ref_list(credential_data); store.process_username_password(credential_data); + store.process_default_username_password(credential_data); store } @@ -66,6 +66,7 @@ impl CredentialStore { self.credentials .get(uuid) .map(|(n, p)| (n.to_string(), p.as_ref().map(|p| p.to_string()))) + .or(self.default_credential.clone()) } fn process_credential_list(&mut self, credential_data: &HashMap) { @@ -153,6 +154,23 @@ impl CredentialStore { .collect::)>>(); self.credentials.extend(result); } + + fn process_default_username_password(&mut self, credential_data: &HashMap) { + let username_key = format!("{}{}", DEVICE_CREDENTIAL_USERNAME_PREFIX, "default"); + let password_key = format!("{}{}", DEVICE_CREDENTIAL_PASSWORD_PREFIX, "default"); + + self.default_credential = credential_data + .get(&username_key) + .and_then(byte_data_to_str) + .map(|username| username.to_string()) + .map(|username| { + let password = credential_data + .get(&password_key) + .and_then(byte_data_to_str) + .map(|password| password.to_string()); + (username, password) + }); + } } fn parse_credential_list(credential_list: &str) -> HashMap)> { @@ -275,6 +293,7 @@ mod tests { #[test] fn test_credential_store_empty() { + let _ = env_logger::builder().is_test(true).try_init(); let credential_data = HashMap::new(); let credential_store = CredentialStore::new(&credential_data); @@ -283,6 +302,7 @@ mod tests { #[test] fn test_credential_store_non_utf8_username() { + let _ = env_logger::builder().is_test(true).try_init(); let test_data = vec![("deviceid-1", vec![200u8, 200u8, 200u8], "password_1")]; let test_entries = test_data .iter() @@ -300,6 +320,7 @@ mod tests { #[test] fn test_credential_store_non_utf8_password() { + let _ = env_logger::builder().is_test(true).try_init(); let test_data = vec![("deviceid-1", "username_1", vec![200u8, 200u8, 200u8])]; let expected_result = test_data .iter() @@ -327,6 +348,19 @@ mod tests { assert_eq!(credential_store.credentials, expected_result); } + fn build_default_username_password_data() -> HashMap { + let secret_data = vec![("default", "default_username", "default_password")]; + let secret_test_data = secret_data + .iter() + .map(|(id, uname, pwd)| DeviceCredentialData { + id: id.replace('-', "_"), + username: Some(uname.as_bytes()), + password: Some(pwd.as_bytes()), + }) + .collect::>(); + generate_username_password_credential_data(secret_test_data) + } + fn build_username_password_data() -> HashMap { let secret_data = vec![( "5f5a69c2-e0ae-504f-829b-00fcdab169cc", @@ -428,8 +462,22 @@ mod tests { .collect::>() } + #[test] + fn test_credential_store_default_username_password() { + let _ = env_logger::builder().is_test(true).try_init(); + let expected_result = ( + "default_username".to_string(), + Some("default_password".to_string()), + ); + let credential_data = build_default_username_password_data(); + + let credential_store = CredentialStore::new(&credential_data); + assert_eq!(credential_store.default_credential, Some(expected_result)); + } + #[test] fn test_credential_store_username_password() { + let _ = env_logger::builder().is_test(true).try_init(); let expected_result = HashMap::from([( "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), ("username_5f".to_string(), Some("password_5f".to_string())), @@ -442,6 +490,7 @@ mod tests { #[test] fn test_credential_store_device_credential_list() { + let _ = env_logger::builder().is_test(true).try_init(); let expected_result = HashMap::from([ ( "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), @@ -464,6 +513,7 @@ mod tests { #[test] fn test_credential_store_device_credential_ref_list() { + let _ = env_logger::builder().is_test(true).try_init(); let expected_result = HashMap::from([ ( "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), @@ -482,6 +532,7 @@ mod tests { #[test] fn test_credential_store_device_credential_list_and_username_password() { + let _ = env_logger::builder().is_test(true).try_init(); let expected_result = HashMap::from([ ( "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), @@ -510,6 +561,7 @@ mod tests { #[test] fn test_credential_store_device_credential_list_and_credential_ref_list() { + let _ = env_logger::builder().is_test(true).try_init(); let expected_result = HashMap::from([ ( "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), @@ -538,6 +590,7 @@ mod tests { #[test] fn test_credential_store_device_credential_ref_list_and_username_password() { + let _ = env_logger::builder().is_test(true).try_init(); let expected_result = HashMap::from([ ( "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), @@ -562,6 +615,7 @@ mod tests { #[test] fn test_credential_store_device_credential_all() { + let _ = env_logger::builder().is_test(true).try_init(); let expected_result = HashMap::from([ ( "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), @@ -589,4 +643,76 @@ mod tests { assert_eq!(credential_store.credentials, expected_result); } + + #[test] + fn test_get_credential_found_no_default() { + let _ = env_logger::builder().is_test(true).try_init(); + let credential = ( + "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), + ("username_5f".to_string(), Some("password_5f".to_string())), + ); + let credentials = HashMap::from([credential.clone()]); + let credential_store = CredentialStore { + credentials, + default_credential: None, + }; + let result = credential_store.get(&credential.0); + assert_eq!(result, Some(credential.1)); + } + + #[test] + fn test_get_credential_not_found_no_default() { + let _ = env_logger::builder().is_test(true).try_init(); + let credential = ( + "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), + ("username_5f".to_string(), Some("password_5f".to_string())), + ); + let credentials = HashMap::from([credential]); + let credential_store = CredentialStore { + credentials, + default_credential: None, + }; + let result = credential_store.get("not-exist-uuid"); + assert!(result.is_none()); + } + + #[test] + fn test_get_credential_found_with_default() { + let _ = env_logger::builder().is_test(true).try_init(); + let credential = ( + "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), + ("username_5f".to_string(), Some("password_5f".to_string())), + ); + let default_credential = ( + "default_username".to_string(), + Some("default_password".to_string()), + ); + let credentials = HashMap::from([credential.clone()]); + let credential_store = CredentialStore { + credentials, + default_credential: Some(default_credential), + }; + let result = credential_store.get(&credential.0); + assert_eq!(result, Some(credential.1)); + } + + #[test] + fn test_get_credential_not_found_with_default() { + let _ = env_logger::builder().is_test(true).try_init(); + let credential = ( + "5f5a69c2-e0ae-504f-829b-00fcdab169cc".to_string(), + ("username_5f".to_string(), Some("password_5f".to_string())), + ); + let default_credential = ( + "default_username".to_string(), + Some("default_password".to_string()), + ); + let credentials = HashMap::from([credential]); + let credential_store = CredentialStore { + credentials, + default_credential: Some(default_credential.clone()), + }; + let result = credential_store.get("not-exist-uuid"); + assert_eq!(result, Some(default_credential)); + } } From c61b34f2d6e99caa09b90932372c0c83d4cb69ff Mon Sep 17 00:00:00 2001 From: Johnson Shih Date: Tue, 8 Aug 2023 10:23:33 -0700 Subject: [PATCH 5/8] remove base64encoded default setter Signed-off-by: Johnson Shih --- .../onvif/src/credential_store.rs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/discovery-handlers/onvif/src/credential_store.rs b/discovery-handlers/onvif/src/credential_store.rs index 4ac6cec91..adc70932c 100644 --- a/discovery-handlers/onvif/src/credential_store.rs +++ b/discovery-handlers/onvif/src/credential_store.rs @@ -5,24 +5,24 @@ use std::collections::HashMap; pub const DEVICE_CREDENTIAL_LIST: &str = "device_credential_list"; /// Key name of device credential ref list in discoveryProperties pub const DEVICE_CREDENTIAL_REF_LIST: &str = "device_credential_ref_list"; -/// Key name prfix of username credential list in discoveryProperties +/// Key name prefix of username credential list in discoveryProperties pub const DEVICE_CREDENTIAL_USERNAME_PREFIX: &str = "username_"; -/// Key name prfix of password credential list in discoveryProperties +/// Key name prefix of password credential list in discoveryProperties pub const DEVICE_CREDENTIAL_PASSWORD_PREFIX: &str = "password_"; +/// Key name of default username +pub const DEVICE_CREDENTIAL_DEFAULT_USERNAME: &str = "username_default"; +/// Key name of default password +pub const DEVICE_CREDENTIAL_DEFAULT_PASSWORD: &str = "password_default"; #[derive(Serialize, Deserialize, Clone, Debug)] struct CredentialData { username: String, #[serde(default, skip_serializing_if = "Option::is_none")] password: Option, - #[serde(default = "default_base64encoded")] + #[serde(default)] base64encoded: bool, } -fn default_base64encoded() -> bool { - false -} - impl CredentialData { fn get_username(&self) -> String { self.username.clone() @@ -156,16 +156,13 @@ impl CredentialStore { } fn process_default_username_password(&mut self, credential_data: &HashMap) { - let username_key = format!("{}{}", DEVICE_CREDENTIAL_USERNAME_PREFIX, "default"); - let password_key = format!("{}{}", DEVICE_CREDENTIAL_PASSWORD_PREFIX, "default"); - self.default_credential = credential_data - .get(&username_key) + .get(DEVICE_CREDENTIAL_DEFAULT_USERNAME) .and_then(byte_data_to_str) .map(|username| username.to_string()) .map(|username| { let password = credential_data - .get(&password_key) + .get(DEVICE_CREDENTIAL_DEFAULT_PASSWORD) .and_then(byte_data_to_str) .map(|password| password.to_string()); (username, password) From be3211bef2c6001001384ebdc487936c2cc962ce Mon Sep 17 00:00:00 2001 From: Johnson Shih Date: Tue, 8 Aug 2023 10:44:52 -0700 Subject: [PATCH 6/8] Add comment for username token profile Signed-off-by: Johnson Shih --- discovery-handlers/onvif/src/username_token.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/discovery-handlers/onvif/src/username_token.rs b/discovery-handlers/onvif/src/username_token.rs index 4b4a2f48d..3cbea0538 100644 --- a/discovery-handlers/onvif/src/username_token.rs +++ b/discovery-handlers/onvif/src/username_token.rs @@ -1,3 +1,5 @@ +/// This implements the Username token profile described in ONVIF Core Spec 5.9.4 +/// which is based on [WS-UsernameToken]: https://docs.oasis-open.org/wss/v1.1/wss-v1.1-spec-pr-UsernameTokenProfile-01.htm #[derive(Default, Debug, Clone)] pub struct UsernameToken { pub username: String, From 28a6f0bab8484891220a4e845340e0ee5780bf75 Mon Sep 17 00:00:00 2001 From: Johnson Shih Date: Wed, 9 Aug 2023 10:23:57 -0700 Subject: [PATCH 7/8] refactor default_credential Signed-off-by: Johnson Shih --- .../onvif/src/credential_store.rs | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/discovery-handlers/onvif/src/credential_store.rs b/discovery-handlers/onvif/src/credential_store.rs index adc70932c..a9f99fad0 100644 --- a/discovery-handlers/onvif/src/credential_store.rs +++ b/discovery-handlers/onvif/src/credential_store.rs @@ -13,6 +13,8 @@ pub const DEVICE_CREDENTIAL_PASSWORD_PREFIX: &str = "password_"; pub const DEVICE_CREDENTIAL_DEFAULT_USERNAME: &str = "username_default"; /// Key name of default password pub const DEVICE_CREDENTIAL_DEFAULT_PASSWORD: &str = "password_default"; +/// Name of default credential for querying CredentialStore +pub const DEFAULT_CREDENTIAL_ID: &str = "default"; #[derive(Serialize, Deserialize, Clone, Debug)] struct CredentialData { @@ -49,7 +51,6 @@ struct CredentialRefData { #[derive(Default)] pub struct CredentialStore { credentials: HashMap)>, - default_credential: Option<(String, Option)>, } impl CredentialStore { @@ -65,8 +66,8 @@ impl CredentialStore { pub fn get(&self, uuid: &str) -> Option<(String, Option)> { self.credentials .get(uuid) + .or_else(|| self.credentials.get(DEFAULT_CREDENTIAL_ID)) .map(|(n, p)| (n.to_string(), p.as_ref().map(|p| p.to_string()))) - .or(self.default_credential.clone()) } fn process_credential_list(&mut self, credential_data: &HashMap) { @@ -156,7 +157,7 @@ impl CredentialStore { } fn process_default_username_password(&mut self, credential_data: &HashMap) { - self.default_credential = credential_data + let default_credential = credential_data .get(DEVICE_CREDENTIAL_DEFAULT_USERNAME) .and_then(byte_data_to_str) .map(|username| username.to_string()) @@ -167,6 +168,10 @@ impl CredentialStore { .map(|password| password.to_string()); (username, password) }); + if let Some(credential) = default_credential { + self.credentials + .insert(DEFAULT_CREDENTIAL_ID.to_string(), credential); + } } } @@ -469,7 +474,7 @@ mod tests { let credential_data = build_default_username_password_data(); let credential_store = CredentialStore::new(&credential_data); - assert_eq!(credential_store.default_credential, Some(expected_result)); + assert_eq!(credential_store.get("any_id"), Some(expected_result)); } #[test] @@ -649,10 +654,7 @@ mod tests { ("username_5f".to_string(), Some("password_5f".to_string())), ); let credentials = HashMap::from([credential.clone()]); - let credential_store = CredentialStore { - credentials, - default_credential: None, - }; + let credential_store = CredentialStore { credentials }; let result = credential_store.get(&credential.0); assert_eq!(result, Some(credential.1)); } @@ -665,10 +667,7 @@ mod tests { ("username_5f".to_string(), Some("password_5f".to_string())), ); let credentials = HashMap::from([credential]); - let credential_store = CredentialStore { - credentials, - default_credential: None, - }; + let credential_store = CredentialStore { credentials }; let result = credential_store.get("not-exist-uuid"); assert!(result.is_none()); } @@ -681,14 +680,14 @@ mod tests { ("username_5f".to_string(), Some("password_5f".to_string())), ); let default_credential = ( - "default_username".to_string(), - Some("default_password".to_string()), + "default".to_string(), + ( + "default_username".to_string(), + Some("default_password".to_string()), + ), ); - let credentials = HashMap::from([credential.clone()]); - let credential_store = CredentialStore { - credentials, - default_credential: Some(default_credential), - }; + let credentials = HashMap::from([credential.clone(), default_credential]); + let credential_store = CredentialStore { credentials }; let result = credential_store.get(&credential.0); assert_eq!(result, Some(credential.1)); } @@ -701,15 +700,15 @@ mod tests { ("username_5f".to_string(), Some("password_5f".to_string())), ); let default_credential = ( - "default_username".to_string(), - Some("default_password".to_string()), + "default".to_string(), + ( + "default_username".to_string(), + Some("default_password".to_string()), + ), ); - let credentials = HashMap::from([credential]); - let credential_store = CredentialStore { - credentials, - default_credential: Some(default_credential.clone()), - }; + let credentials = HashMap::from([credential, default_credential.clone()]); + let credential_store = CredentialStore { credentials }; let result = credential_store.get("not-exist-uuid"); - assert_eq!(result, Some(default_credential)); + assert_eq!(result, Some(default_credential.1)); } } From 07e73a2622047c0d7edade918417aff81ff0b7ae Mon Sep 17 00:00:00 2001 From: Johnson Shih Date: Wed, 9 Aug 2023 10:26:06 -0700 Subject: [PATCH 8/8] update patch version Signed-off-by: Johnson Shih --- Cargo.lock | 28 +++++++++---------- agent/Cargo.toml | 2 +- controller/Cargo.toml | 2 +- deployment/helm/Chart.yaml | 4 +-- .../debug-echo-discovery-handler/Cargo.toml | 2 +- .../onvif-discovery-handler/Cargo.toml | 2 +- .../opcua-discovery-handler/Cargo.toml | 2 +- .../udev-discovery-handler/Cargo.toml | 2 +- discovery-handlers/debug-echo/Cargo.toml | 2 +- discovery-handlers/onvif/Cargo.toml | 2 +- discovery-handlers/opcua/Cargo.toml | 2 +- discovery-handlers/udev/Cargo.toml | 2 +- discovery-utils/Cargo.toml | 2 +- samples/brokers/udev-video-broker/Cargo.toml | 2 +- shared/Cargo.toml | 2 +- version.txt | 2 +- webhooks/validating/configuration/Cargo.toml | 2 +- 17 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ed1fea8c..16115f448 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,7 +333,7 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "agent" -version = "0.12.1" +version = "0.12.2" dependencies = [ "akri-debug-echo", "akri-discovery-utils", @@ -402,7 +402,7 @@ dependencies = [ [[package]] name = "akri-debug-echo" -version = "0.12.1" +version = "0.12.2" dependencies = [ "akri-discovery-utils", "akri-shared", @@ -422,7 +422,7 @@ dependencies = [ [[package]] name = "akri-discovery-utils" -version = "0.12.1" +version = "0.12.2" dependencies = [ "akri-shared", "anyhow", @@ -444,7 +444,7 @@ dependencies = [ [[package]] name = "akri-onvif" -version = "0.12.1" +version = "0.12.2" dependencies = [ "akri-discovery-utils", "akri-shared", @@ -475,7 +475,7 @@ dependencies = [ [[package]] name = "akri-opcua" -version = "0.12.1" +version = "0.12.2" dependencies = [ "akri-discovery-utils", "akri-shared", @@ -499,7 +499,7 @@ dependencies = [ [[package]] name = "akri-shared" -version = "0.12.1" +version = "0.12.2" dependencies = [ "anyhow", "async-trait", @@ -528,7 +528,7 @@ dependencies = [ [[package]] name = "akri-udev" -version = "0.12.1" +version = "0.12.2" dependencies = [ "akri-discovery-utils", "anyhow", @@ -1046,7 +1046,7 @@ checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" [[package]] name = "controller" -version = "0.12.1" +version = "0.12.2" dependencies = [ "akri-shared", "anyhow", @@ -1246,7 +1246,7 @@ dependencies = [ [[package]] name = "debug-echo-discovery-handler" -version = "0.12.1" +version = "0.12.2" dependencies = [ "akri-debug-echo", "akri-discovery-utils", @@ -2543,7 +2543,7 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "onvif-discovery-handler" -version = "0.12.1" +version = "0.12.2" dependencies = [ "akri-discovery-utils", "akri-onvif", @@ -2593,7 +2593,7 @@ dependencies = [ [[package]] name = "opcua-discovery-handler" -version = "0.12.1" +version = "0.12.2" dependencies = [ "akri-discovery-utils", "akri-opcua", @@ -4192,7 +4192,7 @@ dependencies = [ [[package]] name = "udev-discovery-handler" -version = "0.12.1" +version = "0.12.2" dependencies = [ "akri-discovery-utils", "akri-udev", @@ -4203,7 +4203,7 @@ dependencies = [ [[package]] name = "udev-video-broker" -version = "0.12.1" +version = "0.12.2" dependencies = [ "akri-shared", "env_logger", @@ -4480,7 +4480,7 @@ dependencies = [ [[package]] name = "webhook-configuration" -version = "0.12.1" +version = "0.12.2" dependencies = [ "actix", "actix-rt 2.7.0", diff --git a/agent/Cargo.toml b/agent/Cargo.toml index 58d7c370c..10b6b4a27 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "agent" -version = "0.12.1" +version = "0.12.2" authors = ["Kate Goldenring ", ""] edition = "2018" rust-version = "1.68.1" diff --git a/controller/Cargo.toml b/controller/Cargo.toml index c4cf90db9..b2d6c1222 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "controller" -version = "0.12.1" +version = "0.12.2" authors = ["", ""] edition = "2018" rust-version = "1.68.1" diff --git a/deployment/helm/Chart.yaml b/deployment/helm/Chart.yaml index 4f98ce22a..1b008c5c0 100644 --- a/deployment/helm/Chart.yaml +++ b/deployment/helm/Chart.yaml @@ -16,9 +16,9 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.12.1 +version: 0.12.2 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 0.12.1 +appVersion: 0.12.2 diff --git a/discovery-handler-modules/debug-echo-discovery-handler/Cargo.toml b/discovery-handler-modules/debug-echo-discovery-handler/Cargo.toml index ccd80f191..769a0b5a5 100644 --- a/discovery-handler-modules/debug-echo-discovery-handler/Cargo.toml +++ b/discovery-handler-modules/debug-echo-discovery-handler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "debug-echo-discovery-handler" -version = "0.12.1" +version = "0.12.2" authors = ["Kate Goldenring "] edition = "2018" rust-version = "1.68.1" diff --git a/discovery-handler-modules/onvif-discovery-handler/Cargo.toml b/discovery-handler-modules/onvif-discovery-handler/Cargo.toml index 100a206f1..82fd9f6de 100644 --- a/discovery-handler-modules/onvif-discovery-handler/Cargo.toml +++ b/discovery-handler-modules/onvif-discovery-handler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onvif-discovery-handler" -version = "0.12.1" +version = "0.12.2" authors = ["Kate Goldenring "] edition = "2018" rust-version = "1.68.1" diff --git a/discovery-handler-modules/opcua-discovery-handler/Cargo.toml b/discovery-handler-modules/opcua-discovery-handler/Cargo.toml index dcf8a1190..71187890f 100644 --- a/discovery-handler-modules/opcua-discovery-handler/Cargo.toml +++ b/discovery-handler-modules/opcua-discovery-handler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "opcua-discovery-handler" -version = "0.12.1" +version = "0.12.2" authors = ["Kate Goldenring "] edition = "2018" rust-version = "1.68.1" diff --git a/discovery-handler-modules/udev-discovery-handler/Cargo.toml b/discovery-handler-modules/udev-discovery-handler/Cargo.toml index 78b00e98a..88c22ed1c 100644 --- a/discovery-handler-modules/udev-discovery-handler/Cargo.toml +++ b/discovery-handler-modules/udev-discovery-handler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "udev-discovery-handler" -version = "0.12.1" +version = "0.12.2" authors = ["Kate Goldenring "] edition = "2018" rust-version = "1.68.1" diff --git a/discovery-handlers/debug-echo/Cargo.toml b/discovery-handlers/debug-echo/Cargo.toml index e8134c8d2..25a36185b 100644 --- a/discovery-handlers/debug-echo/Cargo.toml +++ b/discovery-handlers/debug-echo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "akri-debug-echo" -version = "0.12.1" +version = "0.12.2" authors = ["Kate Goldenring "] edition = "2018" rust-version = "1.68.1" diff --git a/discovery-handlers/onvif/Cargo.toml b/discovery-handlers/onvif/Cargo.toml index d5ba33a41..e627701bf 100644 --- a/discovery-handlers/onvif/Cargo.toml +++ b/discovery-handlers/onvif/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "akri-onvif" -version = "0.12.1" +version = "0.12.2" authors = ["Kate Goldenring "] edition = "2018" rust-version = "1.68.1" diff --git a/discovery-handlers/opcua/Cargo.toml b/discovery-handlers/opcua/Cargo.toml index 84e299930..fb3201194 100644 --- a/discovery-handlers/opcua/Cargo.toml +++ b/discovery-handlers/opcua/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "akri-opcua" -version = "0.12.1" +version = "0.12.2" authors = ["Kate Goldenring "] edition = "2018" rust-version = "1.68.1" diff --git a/discovery-handlers/udev/Cargo.toml b/discovery-handlers/udev/Cargo.toml index cdc1599c1..1ac2c6cc1 100644 --- a/discovery-handlers/udev/Cargo.toml +++ b/discovery-handlers/udev/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "akri-udev" -version = "0.12.1" +version = "0.12.2" authors = ["Kate Goldenring "] edition = "2018" rust-version = "1.68.1" diff --git a/discovery-utils/Cargo.toml b/discovery-utils/Cargo.toml index a6f3e6a04..3ecbd385d 100644 --- a/discovery-utils/Cargo.toml +++ b/discovery-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "akri-discovery-utils" -version = "0.12.1" +version = "0.12.2" authors = ["Kate Goldenring "] edition = "2018" rust-version = "1.68.1" diff --git a/samples/brokers/udev-video-broker/Cargo.toml b/samples/brokers/udev-video-broker/Cargo.toml index 3ff422ab3..85103f07a 100644 --- a/samples/brokers/udev-video-broker/Cargo.toml +++ b/samples/brokers/udev-video-broker/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "udev-video-broker" -version = "0.12.1" +version = "0.12.2" authors = ["Kate Goldenring ", ""] edition = "2018" rust-version = "1.68.1" diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 7843534e3..08f5ef379 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "akri-shared" -version = "0.12.1" +version = "0.12.2" authors = [""] edition = "2018" rust-version = "1.68.1" diff --git a/version.txt b/version.txt index 34a83616b..26acbf080 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.12.1 +0.12.2 diff --git a/webhooks/validating/configuration/Cargo.toml b/webhooks/validating/configuration/Cargo.toml index a5c7fee99..6009f7a69 100644 --- a/webhooks/validating/configuration/Cargo.toml +++ b/webhooks/validating/configuration/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webhook-configuration" -version = "0.12.1" +version = "0.12.2" authors = ["DazWilkin "] edition = "2018" rust-version = "1.68.1"