use actix_csrf_middleware::{
CSRF_PRE_SESSION_KEY, CsrfMiddleware, CsrfMiddlewareConfig, CsrfPattern, CsrfToken,
DEFAULT_CSRF_ANON_TOKEN_KEY, DEFAULT_CSRF_TOKEN_KEY, DEFAULT_SESSION_ID_KEY,
};
use actix_http::Request;
use actix_http::body::{BoxBody, EitherBody};
#[cfg(feature = "actix-session")]
use actix_session::{
SessionMiddleware, config::CookieContentSecurity, storage::CookieSessionStore,
};
use actix_web::cookie::Cookie;
#[cfg(feature = "actix-session")]
use actix_web::cookie::{Key, SameSite};
use actix_web::dev::{Service, ServiceResponse};
use actix_web::{App, HttpResponse, test, web};
use hmac::Hmac;
use sha2::Sha256;
#[allow(dead_code)]
pub type HmacSha256 = Hmac<Sha256>;
#[allow(dead_code)]
pub const HMAC_SECRET: &[u8] = b"secret-key";
#[cfg(feature = "actix-session")]
pub fn test_key() -> Key {
Key::generate()
}
#[allow(dead_code)]
pub async fn build_app(
cfg: CsrfMiddlewareConfig,
) -> impl Service<Request, Response = ServiceResponse<EitherBody<BoxBody>>, Error = actix_web::Error>
{
test::init_service({
let app = App::new().wrap(CsrfMiddleware::new(cfg));
#[cfg(feature = "actix-session")]
let app = app.wrap(get_session_middleware());
app.configure(configure_routes)
})
.await
}
#[allow(dead_code)]
pub fn configure_routes(cfg: &mut web::ServiceConfig) {
cfg.route(
"/form",
web::get().to(|csrf: CsrfToken| async move {
HttpResponse::Ok().body(format!("token:{}", csrf.0))
}),
)
.route(
"/submit",
web::post().to(|_csrf: CsrfToken| async move { HttpResponse::Ok().body("OK") }),
)
.route(
"/submit_get",
web::get().to(|| async move { HttpResponse::Ok().body("GET_OK") }),
);
}
#[cfg(feature = "actix-session")]
fn get_session_middleware() -> SessionMiddleware<CookieSessionStore> {
SessionMiddleware::builder(CookieSessionStore::default(), test_key())
.cookie_content_security(CookieContentSecurity::Private)
.cookie_secure(false) .cookie_http_only(true)
.cookie_same_site(SameSite::Lax) .build()
}
#[allow(dead_code)]
pub fn config_for_with_secret(pattern: CsrfPattern, secret_key: &[u8]) -> CsrfMiddlewareConfig {
match pattern {
#[cfg(feature = "actix-session")]
CsrfPattern::SynchronizerToken => CsrfMiddlewareConfig::synchronizer_token(secret_key),
CsrfPattern::DoubleSubmitCookie => CsrfMiddlewareConfig::double_submit_cookie(secret_key),
}
}
#[allow(dead_code)]
pub async fn token_and_cookies_for<S>(
app: &S,
pattern: &CsrfPattern,
) -> (String, Vec<Cookie<'static>>)
where
S: Service<Request, Response = ServiceResponse<EitherBody<BoxBody>>, Error = actix_web::Error>,
{
match pattern {
CsrfPattern::DoubleSubmitCookie => {
let req = test::TestRequest::get().uri("/form").to_request();
let resp = test::call_service(&app, req).await;
let token_cookie = resp
.response()
.cookies()
.find(|c| {
c.name() == DEFAULT_CSRF_TOKEN_KEY || c.name() == DEFAULT_CSRF_ANON_TOKEN_KEY
})
.map(|c| c.into_owned())
.expect("token cookie present");
let session_cookie = resp
.response()
.cookies()
.find(|c| c.name() == DEFAULT_SESSION_ID_KEY || c.name() == CSRF_PRE_SESSION_KEY)
.map(|c| c.into_owned())
.expect("session/pre-session cookie present");
let body = test::read_body(resp).await;
let token = String::from_utf8(body.to_vec()).unwrap();
let token = token.strip_prefix("token:").unwrap().to_string();
(token, vec![token_cookie, session_cookie])
}
#[cfg(feature = "actix-session")]
CsrfPattern::SynchronizerToken => {
let req1 = test::TestRequest::get().uri("/form").to_request();
let resp1 = test::call_service(&app, req1).await;
let session_cookie = resp1
.response()
.cookies()
.find(|c| c.name() == DEFAULT_SESSION_ID_KEY)
.map(|c| c.into_owned())
.expect("session cookie present");
let req2 = test::TestRequest::get()
.uri("/form")
.cookie(session_cookie.clone())
.to_request();
let resp2 = test::call_service(&app, req2).await;
let session_cookie2 = resp2
.response()
.cookies()
.find(|c| c.name() == DEFAULT_SESSION_ID_KEY)
.map(|c| c.into_owned())
.unwrap_or_else(|| session_cookie.clone());
let body2 = test::read_body(resp2).await;
let token2 = String::from_utf8(body2.to_vec()).unwrap();
let token2 = token2.strip_prefix("token:").unwrap().to_string();
(token2, vec![session_cookie2])
}
}
}
#[allow(unused_macros)]
macro_rules! for_patterns {
($name_double:ident, $name_sync:ident, $body:expr_2021) => {
#[actix_web::test]
async fn $name_double() {
let cfg = common::config_for_with_secret(
actix_csrf_middleware::CsrfPattern::DoubleSubmitCookie,
&get_secret_key(),
);
let app = common::build_app(cfg).await;
$body(actix_csrf_middleware::CsrfPattern::DoubleSubmitCookie, &app).await;
}
#[cfg(feature = "actix-session")]
#[actix_web::test]
async fn $name_sync() {
let cfg = common::config_for_with_secret(
actix_csrf_middleware::CsrfPattern::SynchronizerToken,
&get_secret_key(),
);
let app = common::build_app(cfg).await;
$body(actix_csrf_middleware::CsrfPattern::SynchronizerToken, &app).await;
}
};
}