diff --git a/Cargo.lock b/Cargo.lock index da4d6ea..9973a9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,6 +204,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.96", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.96", +] + [[package]] name = "deranged" version = "0.3.11" @@ -214,6 +249,37 @@ dependencies = [ "serde", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.96", +] + [[package]] name = "digest" version = "0.9.0" @@ -542,6 +608,22 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "handlebars" +version = "6.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098" +dependencies = [ + "derive_builder", + "log", + "num-order", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror 2.0.12", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -710,6 +792,12 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.3" @@ -829,6 +917,21 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-modular" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" + +[[package]] +name = "num-order" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" +dependencies = [ + "num-modular", +] + [[package]] name = "object" version = "0.36.7" @@ -937,6 +1040,7 @@ dependencies = [ "cookie", "fastly 0.11.2", "futures", + "handlebars", "hex", "hmac", "log", @@ -1134,6 +1238,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index 07523ab..5341132 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,4 @@ futures = "0.3" tokio = { version = "1.0", features = ["sync", "macros", "io-util", "rt", "time"] } url = "2.4.1" config = "0.15.11" +handlebars = "6.3.2" diff --git a/README.md b/README.md index 84aebe0..6495672 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ git clone git@github.com:IABTechLab/trusted-server.git :information_source: Note that you’ll have to edit the following files for your setup: - fastly.toml (service ID, author, description) -- Potsi.toml (KV store ID names) +- potsi.toml (KV store ID names) ### Build diff --git a/potsi.toml b/potsi.toml index fcf2f38..ae26266 100644 --- a/potsi.toml +++ b/potsi.toml @@ -9,3 +9,11 @@ server_url = "http://68.183.113.79:8000/openrtb2/auction" counter_store = "jevans_synth_id_counter" opid_store = "jevans_synth_id_opid" secret_key = "potsi" +# Possible values +# - "client_ip" +# - "user_agent" +# - "first_party_id" +# - "auth_user_id" +# - "publisher_domain" +# - "accept_language" +template = "{{ client_ip }}:{{ user_agent }}:{{ first_party_id }}:{{ auth_user_id }}:{{ publisher_domain }}:{{ accept_language }}" diff --git a/src/constants.rs b/src/constants.rs index 1b99afe..106d454 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,2 +1,3 @@ -pub const SYNTH_HEADER_FRESH: &str = "X-Synthetic-Fresh"; -pub const SYNTH_HEADER_POTSI: &str = "X-Synthetic-Potsi"; +pub const SYNTHETIC_HEADER_FRESH: &str = "X-Synthetic-Fresh"; +pub const SYNTHETIC_HEADER_POTSI: &str = "X-Synthetic-Potsi"; +pub const SYNTHETIC_HEADER_PUB_USER_ID: &str = "X-Pub-User-ID"; diff --git a/src/main.rs b/src/main.rs index 5cf0f28..8abde4e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use std::env; mod constants; mod cookies; -use constants::{SYNTH_HEADER_FRESH, SYNTH_HEADER_POTSI}; +use constants::{SYNTHETIC_HEADER_FRESH, SYNTHETIC_HEADER_POTSI}; mod models; use models::AdResponse; mod prebid; @@ -60,7 +60,7 @@ fn handle_main_page(settings: &Settings, req: Request) -> Result Result Result Result { println!("Received response from Prebid Server"); diff --git a/src/prebid.rs b/src/prebid.rs index bd2640c..ca5d8d0 100644 --- a/src/prebid.rs +++ b/src/prebid.rs @@ -2,7 +2,7 @@ use fastly::http::{header, Method}; use fastly::{Error, Request, Response}; use serde_json::json; -use crate::constants::{SYNTH_HEADER_FRESH, SYNTH_HEADER_POTSI}; +use crate::constants::{SYNTHETIC_HEADER_FRESH, SYNTHETIC_HEADER_POTSI}; use crate::settings::Settings; use crate::synthetic::generate_synthetic_id; @@ -31,7 +31,7 @@ impl PrebidRequest { pub fn new(settings: &Settings, req: &Request) -> Result { // Get the POTSI ID from header (which we just set in handle_prebid_test) let synthetic_id = req - .get_header(SYNTH_HEADER_POTSI) + .get_header(SYNTHETIC_HEADER_POTSI) .and_then(|h| h.to_str().ok()) .map(|s| s.to_string()) .unwrap_or_else(|| generate_synthetic_id(settings, req)); @@ -89,12 +89,16 @@ impl PrebidRequest { /// /// # Returns /// * `Result` - Prebid Server response or error - pub async fn send_bid_request(&self, incoming_req: &Request) -> Result { - let mut req = Request::new(Method::POST, "http://68.183.113.79:8000/openrtb2/auction"); + pub async fn send_bid_request( + &self, + settings: &Settings, + incoming_req: &Request, + ) -> Result { + let mut req = Request::new(Method::POST, settings.prebid.server_url.to_owned()); // Get and store the POTSI ID value from the incoming request let potsi_id = incoming_req - .get_header(SYNTH_HEADER_POTSI) + .get_header(SYNTHETIC_HEADER_POTSI) .and_then(|h| h.to_str().ok()) .map(|s| s.to_string()) .unwrap_or_else(|| self.synthetic_id.clone()); @@ -165,8 +169,8 @@ impl PrebidRequest { req.set_header(header::CONTENT_TYPE, "application/json"); req.set_header("X-Forwarded-For", &self.client_ip); req.set_header(header::ORIGIN, &self.origin); - req.set_header(SYNTH_HEADER_FRESH, &self.synthetic_id); - req.set_header(SYNTH_HEADER_POTSI, &potsi_id); + req.set_header(SYNTHETIC_HEADER_FRESH, &self.synthetic_id); + req.set_header(SYNTHETIC_HEADER_POTSI, &potsi_id); println!( "Sending prebid request with Fresh ID: {} and POTSI ID: {}", diff --git a/src/settings.rs b/src/settings.rs index 62acc4e..efd2af6 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -21,6 +21,7 @@ pub struct Synthetic { pub counter_store: String, pub opid_store: String, pub secret_key: String, + pub template: String, } #[derive(Debug, Deserialize)] diff --git a/src/synthetic.rs b/src/synthetic.rs index 00b744f..64371ab 100644 --- a/src/synthetic.rs +++ b/src/synthetic.rs @@ -1,9 +1,11 @@ use fastly::http::header; use fastly::Request; +use handlebars::Handlebars; use hmac::{Hmac, Mac}; +use serde_json::json; use sha2::Sha256; -use crate::constants::SYNTH_HEADER_POTSI; +use crate::constants::{SYNTHETIC_HEADER_POTSI, SYNTHETIC_HEADER_PUB_USER_ID}; use crate::cookies::handle_request_cookies; use crate::settings::Settings; @@ -13,34 +15,37 @@ type HmacSha256 = Hmac; pub fn generate_synthetic_id(settings: &Settings, req: &Request) -> String { let user_agent = req .get_header(header::USER_AGENT) - .map(|h| h.to_str().unwrap_or("Unknown")); + .map(|h| h.to_str().unwrap_or("unknown")); let first_party_id = handle_request_cookies(req).and_then(|jar| { jar.get("pub_userid") .map(|cookie| cookie.value().to_string()) }); let auth_user_id = req - .get_header("X-Pub-User-ID") + .get_header(SYNTHETIC_HEADER_PUB_USER_ID) .map(|h| h.to_str().unwrap_or("anonymous")); let publisher_domain = req .get_header(header::HOST) - .map(|h| h.to_str().unwrap_or("unknown.com")); + .map(|h| h.to_str().unwrap_or("unknown")); let client_ip = req.get_client_ip_addr().map(|ip| ip.to_string()); let accept_language = req .get_header(header::ACCEPT_LANGUAGE) .and_then(|h| h.to_str().ok()) .map(|lang| lang.split(',').next().unwrap_or("unknown")); - let input_string = format!( - "{}:{}:{}:{}:{}:{}", - client_ip.unwrap_or("unknown".to_string()), - user_agent.unwrap_or("unknown"), - first_party_id.unwrap_or("anonymous".to_string()), - auth_user_id.unwrap_or("anonymous"), - publisher_domain.unwrap_or("unknown.com"), - accept_language.unwrap_or("unknown") - ); + let handlebars = Handlebars::new(); + let data = &json!({ + "client_ip": client_ip.unwrap_or("unknown".to_string()), + "user_agent": user_agent.unwrap_or("unknown"), + "first_party_id": first_party_id.unwrap_or("anonymous".to_string()), + "auth_user_id": auth_user_id.unwrap_or("anonymous"), + "publisher_domain": publisher_domain.unwrap_or("unknown.com"), + "accept_language": accept_language.unwrap_or("unknown") + }); - log::info!("Input string for fresh ID: {}", input_string); + let input_string = handlebars + .render_template(&settings.synthetic.template, data) + .unwrap(); + println!("Input string for fresh ID: {} {}", input_string, data); let mut mac = HmacSha256::new_from_slice(settings.synthetic.secret_key.as_bytes()) .expect("HMAC can take key of any size"); @@ -56,7 +61,7 @@ pub fn generate_synthetic_id(settings: &Settings, req: &Request) -> String { pub fn get_or_generate_synthetic_id(settings: &Settings, req: &Request) -> String { // First try to get existing POTSI ID from header if let Some(potsi) = req - .get_header(SYNTH_HEADER_POTSI) + .get_header(SYNTHETIC_HEADER_POTSI) .and_then(|h| h.to_str().ok()) .map(|s| s.to_string()) { @@ -112,6 +117,7 @@ mod tests { counter_store: "https://example.com".to_string(), opid_store: "https://example.com".to_string(), secret_key: "secret_key".to_string(), + template: "{{ client_ip }}:{{ user_agent }}:{{ first_party_id }}:{{ auth_user_id }}:{{ publisher_domain }}:{{ accept_language }}".to_string(), }, } } @@ -128,6 +134,7 @@ mod tests { ]); let synthetic_id = generate_synthetic_id(&settings, &req); + print!("Generated synthetic ID: {}", synthetic_id); assert_eq!( synthetic_id, "07cd73bb8c7db39753ab6b10198b10c3237a3f5a6d2232c6ce578f2c2a623e56" @@ -137,7 +144,7 @@ mod tests { #[test] fn test_get_or_generate_synthetic_id_with_header() { let settings = create_settings(); - let req = create_test_request(vec![(SYNTH_HEADER_POTSI, "existing_potsi_id")]); + let req = create_test_request(vec![(SYNTHETIC_HEADER_POTSI, "existing_potsi_id")]); let synthetic_id = get_or_generate_synthetic_id(&settings, &req); assert_eq!(synthetic_id, "existing_potsi_id");