unleash-edge 19.2.1

Unleash edge is a proxy for Unleash. It can return both evaluated feature toggles as well as the raw data from Unleash's client API
use crate::cli::TrustProxy;
use crate::types::ClientIp;
use actix_http::body::MessageBody;
use actix_http::HttpMessage;
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::web::Data;
use std::net::IpAddr;
use std::str::FromStr;
use tracing::{debug, trace};

pub async fn enrich_with_client_ip(
    req: ServiceRequest,
    srv: crate::middleware::as_async_middleware::Next<impl MessageBody + 'static>,
) -> Result<ServiceResponse<impl MessageBody>, actix_web::Error> {
    debug!("Calling enricher for ip");
    let trust_proxy_config: Option<&Data<TrustProxy>> = req.app_data::<Data<TrustProxy>>();
    let ip = match trust_proxy_config {
        Some(config) => {
            if config.trust_proxy {
                trace!("Trust proxy was configured and enabled");
                req.connection_info().realip_remote_addr().and_then(|r| {
                    trace!("{r}");
                    IpAddr::from_str(r).ok()
                })
            } else {
                trace!("Trust proxy was configured and disabled");
                req.peer_addr().map(|s| s.ip())
            }
        }
        None => {
            trace!("Trust proxy was not configured. Will use peer ip");
            req.peer_addr().map(|s| s.ip())
        }
    };
    if let Some(ip) = ip {
        trace!("Found client ip to be: {}", ip);
        req.extensions_mut().insert(ClientIp { ip });
    }
    srv.call(req).await
}

#[cfg(test)]
mod tests {
    use crate::cli::TrustProxy;
    use crate::middleware::as_async_middleware::as_async_middleware;
    use crate::middleware::enrich_with_client_ip::enrich_with_client_ip;
    use crate::types::{ClientIp, EdgeJsonResult};
    use actix_http::HttpMessage;
    use actix_web::get;
    use actix_web::web::{Data, Json};
    use actix_web::{test, App, HttpRequest};
    use std::net::{IpAddr, SocketAddr};
    use std::str::FromStr;
    use tracing_test::traced_test;

    #[get("/")]
    pub async fn hello_ip(req: HttpRequest) -> EdgeJsonResult<String> {
        let ip = req.extensions().get::<ClientIp>().cloned();
        Ok(Json(ip.map(|ip| ip.to_string()).unwrap_or("No ip".into())))
    }

    #[tokio::test]
    #[traced_test]
    pub async fn should_insert_peer_addr_with_no_trust_configured() {
        let app = test::init_service(
            App::new()
                .wrap(as_async_middleware(enrich_with_client_ip))
                .service(hello_ip),
        )
        .await;

        let req = test::TestRequest::get()
            .uri("/")
            .peer_addr(SocketAddr::new(
                IpAddr::from_str("127.0.0.1").unwrap(),
                1337,
            ))
            .to_request();
        let ip: String = test::call_and_read_body_json(&app, req).await;
        assert_eq!(ip, "127.0.0.1");
    }
    #[tokio::test]
    #[traced_test]
    pub async fn should_insert_conn_info_with_trust_configured() {
        let trust_proxy = TrustProxy {
            trust_proxy: true,
            proxy_trusted_servers: vec![],
        };
        let app = test::init_service(
            App::new()
                .app_data(Data::new(trust_proxy))
                .wrap(as_async_middleware(enrich_with_client_ip))
                .service(hello_ip),
        )
        .await;

        let req = test::TestRequest::get()
            .uri("/")
            .insert_header(("Forwarded", "For=192.168.0.1"))
            .to_request();
        let ip: String = test::call_and_read_body_json(&app, req).await;
        assert_eq!(ip, "192.168.0.1");
    }

    #[tokio::test]
    #[traced_test]
    pub async fn should_insert_conn_info_from_x_forwarded_for_with_trust_configured() {
        let trust_proxy = TrustProxy {
            trust_proxy: true,
            proxy_trusted_servers: vec![],
        };
        let app = test::init_service(
            App::new()
                .app_data(Data::new(trust_proxy))
                .wrap(as_async_middleware(enrich_with_client_ip))
                .service(hello_ip),
        )
        .await;

        let req = test::TestRequest::get()
            .uri("/")
            .insert_header(("X-Forwarded-For", "192.168.0.1,217.0.0.1,255.255.255.0"))
            .to_request();
        let ip: String = test::call_and_read_body_json(&app, req).await;
        assert_eq!(ip, "192.168.0.1");
    }
}