|
| 1 | +//! Authentication tests |
| 2 | +//! |
| 3 | +//! Tests for SIP authentication handling, including Via header parameter updates |
| 4 | +
|
| 5 | +use crate::dialog::authenticate::{handle_client_authenticate, Credential}; |
| 6 | +use crate::transaction::{ |
| 7 | + endpoint::EndpointBuilder, |
| 8 | + key::{TransactionKey, TransactionRole}, |
| 9 | + transaction::Transaction, |
| 10 | +}; |
| 11 | +use crate::transport::TransportLayer; |
| 12 | +use rsip::headers::*; |
| 13 | +use rsip::prelude::{HeadersExt, ToTypedHeader}; |
| 14 | +use rsip::{Request, Response, StatusCode}; |
| 15 | +use tokio_util::sync::CancellationToken; |
| 16 | + |
| 17 | +async fn create_test_endpoint() -> crate::Result<crate::transaction::endpoint::Endpoint> { |
| 18 | + let token = CancellationToken::new(); |
| 19 | + let tl = TransportLayer::new(token.child_token()); |
| 20 | + let endpoint = EndpointBuilder::new() |
| 21 | + .with_user_agent("rsipstack-test") |
| 22 | + .with_transport_layer(tl) |
| 23 | + .build(); |
| 24 | + Ok(endpoint) |
| 25 | +} |
| 26 | + |
| 27 | +fn create_request_with_branch(branch: &str) -> Request { |
| 28 | + Request { |
| 29 | + method: rsip::Method::Register, |
| 30 | + uri: rsip::Uri::try_from("sip:example.com:5060").unwrap(), |
| 31 | + headers: vec![ |
| 32 | + Via::new(&format!( |
| 33 | + "SIP/2.0/UDP alice.example.com:5060;branch={}", |
| 34 | + branch |
| 35 | + )) |
| 36 | + .into(), |
| 37 | + CSeq::new("1 REGISTER").into(), |
| 38 | + From::new("Alice <sip:alice@example.com>;tag=1928301774").into(), |
| 39 | + To::new("Bob <sip:bob@example.com>").into(), |
| 40 | + CallId::new("a84b4c76e66710@pc33.atlanta.com").into(), |
| 41 | + MaxForwards::new("70").into(), |
| 42 | + ] |
| 43 | + .into(), |
| 44 | + version: rsip::Version::V2, |
| 45 | + body: vec![], |
| 46 | + } |
| 47 | +} |
| 48 | + |
| 49 | +fn create_401_response() -> Response { |
| 50 | + Response { |
| 51 | + status_code: StatusCode::Unauthorized, |
| 52 | + version: rsip::Version::V2, |
| 53 | + headers: vec![ |
| 54 | + Via::new("SIP/2.0/UDP alice.example.com:5060;branch=z9hG4bKnashds").into(), |
| 55 | + CSeq::new("1 REGISTER").into(), |
| 56 | + From::new("Alice <sip:alice@example.com>;tag=1928301774").into(), |
| 57 | + To::new("Bob <sip:bob@example.com>").into(), |
| 58 | + CallId::new("a84b4c76e66710@pc33.atlanta.com").into(), |
| 59 | + WwwAuthenticate::new( |
| 60 | + r#"Digest realm="example.com", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", algorithm=MD5, qop="auth""#, |
| 61 | + ) |
| 62 | + .into(), |
| 63 | + ] |
| 64 | + .into(), |
| 65 | + body: vec![], |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +#[tokio::test] |
| 70 | +async fn test_authenticate_via_header_branch_update() -> crate::Result<()> { |
| 71 | + let endpoint = create_test_endpoint().await?; |
| 72 | + |
| 73 | + // Create a request with a specific branch parameter |
| 74 | + let original_branch = "z9hG4bKoriginal123"; |
| 75 | + let original_req = create_request_with_branch(original_branch); |
| 76 | + |
| 77 | + // Verify the original request has the branch |
| 78 | + let original_via = original_req |
| 79 | + .via_header() |
| 80 | + .expect("Request should have Via header") |
| 81 | + .typed() |
| 82 | + .expect("Via header should be parseable"); |
| 83 | + let original_branch_param = original_via |
| 84 | + .params |
| 85 | + .iter() |
| 86 | + .find(|p| matches!(p, rsip::Param::Branch(_))) |
| 87 | + .expect("Original request should have branch parameter"); |
| 88 | + let original_branch_value = match original_branch_param { |
| 89 | + rsip::Param::Branch(b) => b.to_string(), |
| 90 | + _ => unreachable!(), |
| 91 | + }; |
| 92 | + assert_eq!(original_branch_value, original_branch); |
| 93 | + |
| 94 | + // Create transaction |
| 95 | + let key = TransactionKey::from_request(&original_req, TransactionRole::Client)?; |
| 96 | + let tx = Transaction::new_client(key, original_req, endpoint.inner.clone(), None); |
| 97 | + |
| 98 | + // Create 401 response |
| 99 | + let resp = create_401_response(); |
| 100 | + |
| 101 | + // Create credential |
| 102 | + let cred = Credential { |
| 103 | + username: "alice".to_string(), |
| 104 | + password: "secret123".to_string(), |
| 105 | + realm: None, |
| 106 | + }; |
| 107 | + |
| 108 | + // Call handle_client_authenticate |
| 109 | + let new_tx = handle_client_authenticate(2, tx, resp, &cred).await?; |
| 110 | + |
| 111 | + // Verify the new request has updated Via header |
| 112 | + let new_via = new_tx |
| 113 | + .original |
| 114 | + .via_header() |
| 115 | + .expect("New request should have Via header") |
| 116 | + .typed() |
| 117 | + .expect("Via header should be parseable"); |
| 118 | + |
| 119 | + // Verify old branch is removed |
| 120 | + let old_branch_exists = new_via |
| 121 | + .params |
| 122 | + .iter() |
| 123 | + .any(|p| matches!(p, rsip::Param::Branch(b) if b.to_string() == original_branch_value)); |
| 124 | + assert!( |
| 125 | + !old_branch_exists, |
| 126 | + "Old branch parameter should be removed from Via header" |
| 127 | + ); |
| 128 | + |
| 129 | + // Verify new branch is added (and different from old one) |
| 130 | + let new_branch_param = new_via |
| 131 | + .params |
| 132 | + .iter() |
| 133 | + .find(|p| matches!(p, rsip::Param::Branch(_))) |
| 134 | + .expect("New request should have a new branch parameter"); |
| 135 | + let new_branch_value = match new_branch_param { |
| 136 | + rsip::Param::Branch(b) => b.to_string(), |
| 137 | + _ => unreachable!(), |
| 138 | + }; |
| 139 | + assert_ne!( |
| 140 | + new_branch_value, original_branch_value, |
| 141 | + "New branch should be different from old branch" |
| 142 | + ); |
| 143 | + assert!( |
| 144 | + new_branch_value.starts_with("z9hG4bK"), |
| 145 | + "New branch should start with z9hG4bK" |
| 146 | + ); |
| 147 | + |
| 148 | + // Verify rport parameter is added |
| 149 | + let has_rport = new_via.params.iter().any( |
| 150 | + |p| matches!(p, rsip::Param::Other(key, _) if key.value().eq_ignore_ascii_case("rport")), |
| 151 | + ); |
| 152 | + assert!( |
| 153 | + has_rport, |
| 154 | + "Via header should have rport parameter after authentication" |
| 155 | + ); |
| 156 | + |
| 157 | + Ok(()) |
| 158 | +} |
0 commit comments