use std::{net::IpAddr, time::Instant};
use anyhow::Context as _;
use crate::{
api::http::model::{PgWapSessionManager, Session},
pg_wap::{auth::AuthService, session_manager::WapSessionManager},
};
mod auth;
pub mod session_manager;
pub use auth::AuthInfo;
#[derive(Clone)]
pub struct WapControl {
auth_service: AuthService,
session_manager: WapSessionManager,
data_plane_port: u16,
ap_id: String,
}
impl WapControl {
pub fn new(
session_manager: WapSessionManager,
auth_duration: std::time::Duration,
ap_id: String,
data_plane_port: u16,
) -> Self {
let auth_service = AuthService::new(auth_duration);
Self {
session_manager,
auth_service,
ap_id,
data_plane_port,
}
}
fn ap_id(&self) -> &str {
&self.ap_id
}
}
impl PgWapSessionManager for WapControl {
fn new_session(
&self,
client_ip: IpAddr,
target_domains: &[&str],
) -> Result<Session, anyhow::Error> {
let now = Instant::now();
let auth_info = self
.auth_service
.authenticate(now, client_ip, target_domains);
self.session_manager
.add_session_authentication(auth_info.clone());
let valid_until = chrono::Utc::now()
+ chrono::Duration::from_std(auth_info.valid_until.saturating_duration_since(now))
.context("auth service returned out of bounds duration")?;
tracing::info!(%client_ip, %valid_until, ?target_domains, "Granted IP access");
Ok(Session {
ip: auth_info.ip,
ap_id: self.ap_id().to_string(),
data_plane_port: self.data_plane_port,
target_domains: auth_info.targets,
valid_until,
})
}
}
#[cfg(test)]
mod tests {
use std::{net::IpAddr, time::Duration};
use super::*;
fn control() -> WapControl {
WapControl::new(
WapSessionManager::new(),
Duration::from_secs(60),
"test-wap".to_string(),
8443,
)
}
#[test]
fn new_session_authenticates_for_requested_target_domains() {
let control = control();
let client_ip = IpAddr::from([10, 0, 0, 1]);
let session = control
.new_session(client_ip, &["a.example.com", "b.example.com"])
.expect("new_session");
assert_eq!(
session.target_domains,
vec!["a.example.com".to_string(), "b.example.com".to_string()]
);
assert_eq!(session.ip, client_ip);
assert_eq!(session.data_plane_port, 8443);
let authed = control
.session_manager
.authenticated_sessions_for_ip(client_ip);
assert_eq!(authed.len(), 1);
assert_eq!(
authed[0].targets,
vec!["a.example.com".to_string(), "b.example.com".to_string()]
);
}
#[test]
fn authentication_is_scoped_to_supplied_domains() {
let control = control();
let client_ip = IpAddr::from([10, 0, 0, 2]);
control
.new_session(client_ip, &["a.example.com"])
.expect("new_session");
let authed = control
.session_manager
.authenticated_sessions_for_ip(client_ip);
assert_eq!(authed.len(), 1);
assert!(authed[0].targets.contains(&"a.example.com".to_string()));
assert!(
!authed[0]
.targets
.contains(&"not-requested.example.com".to_string())
);
}
}