From 4d77e26e1e82221949d534c78685108dbb3d2b3b Mon Sep 17 00:00:00 2001 From: tglman Date: Fri, 25 Mar 2022 18:25:38 +0000 Subject: [PATCH] add support for session access from guards (#234) --- actix-session/CHANGES.md | 4 +- actix-session/src/lib.rs | 97 +++++++++++++++++++++++++++++++- actix-session/src/session_ext.rs | 7 +++ 3 files changed, 105 insertions(+), 3 deletions(-) diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index ba4a99160..516fd4b24 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,11 +1,13 @@ # Changes ## Unreleased - 2021-xx-xx -### Fixed +- Implement `SessionExt` for `GuardContext`. [#234] - Do not leak internal implementation details to callers when errors occur. [#236] +[#234]: https://github.com/actix/actix-extras/pull/234 [#236]: https://github.com/actix/actix-extras/pull/236 + ## 0.6.1 - 2022-03-21 - No significant changes since `0.6.0`. diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index a10e65ecb..67d5c92a5 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -193,13 +193,14 @@ pub mod test_helpers { *policy, ) .await; + acceptance_tests::guard(store_builder.clone(), *policy).await; } } mod acceptance_tests { use actix_web::{ dev::Service, - middleware, test, + guard, middleware, test, web::{self, get, post, resource, Bytes}, App, HttpResponse, Result, }; @@ -209,7 +210,7 @@ pub mod test_helpers { use crate::{ middleware::SessionLength, storage::SessionStore, test_helpers::key, - CookieContentSecurity, Session, SessionMiddleware, + CookieContentSecurity, Session, SessionExt, SessionMiddleware, }; pub(super) async fn basic_workflow( @@ -308,6 +309,91 @@ pub mod test_helpers { assert_eq!(cookie_2.max_age(), Some(Duration::seconds(60))); } + pub(super) async fn guard(store_builder: F, policy: CookieContentSecurity) + where + Store: SessionStore + 'static, + F: Fn() -> Store + Clone + Send + 'static, + { + let srv = actix_test::start(move || { + App::new() + .wrap( + SessionMiddleware::builder(store_builder(), key()) + .cookie_name("test-session".into()) + .cookie_content_security(policy) + .session_length(SessionLength::Predetermined { + max_session_length: Some(time::Duration::days(7)), + }) + .build(), + ) + .wrap(middleware::Logger::default()) + .service(resource("/").route(get().to(index))) + .service(resource("/do_something").route(post().to(do_something))) + .service(resource("/login").route(post().to(login))) + .service(resource("/logout").route(post().to(logout))) + .service( + web::scope("/protected") + .guard(guard::fn_guard(|g| { + g.get_session().get::("user_id").unwrap().is_some() + })) + .service(resource("/count").route(get().to(count))), + ) + }); + + // Step 1: GET without session info + // - response should be a unsuccessful status + let req_1 = srv.get("/protected/count").send(); + let resp_1 = req_1.await.unwrap(); + assert!(!resp_1.status().is_success()); + + // Step 2: POST to login + // - set-cookie actix-session will be in response (session cookie #1) + // - updates session state: {"counter": 0, "user_id": "ferris"} + let req_2 = srv.post("/login").send_json(&json!({"user_id": "ferris"})); + let resp_2 = req_2.await.unwrap(); + let cookie_1 = resp_2 + .cookies() + .unwrap() + .clone() + .into_iter() + .find(|c| c.name() == "test-session") + .unwrap(); + + // Step 3: POST to do_something + // - adds new session state: {"counter": 1, "user_id": "ferris" } + // - set-cookie actix-session should be in response (session cookie #2) + // - response should be: {"counter": 1, "user_id": None} + let req_3 = srv.post("/do_something").cookie(cookie_1.clone()).send(); + let mut resp_3 = req_3.await.unwrap(); + let result_3 = resp_3.json::().await.unwrap(); + assert_eq!( + result_3, + IndexResponse { + user_id: Some("ferris".into()), + counter: 1 + } + ); + let cookie_2 = resp_3 + .cookies() + .unwrap() + .clone() + .into_iter() + .find(|c| c.name() == "test-session") + .unwrap(); + + // Step 4: GET using a existing user id + // - response should be: {"counter": 3, "user_id": "ferris"} + let req_4 = srv.get("/protected/count").cookie(cookie_2.clone()).send(); + let mut resp_4 = req_4.await.unwrap(); + let result_4 = resp_4.json::().await.unwrap(); + assert_eq!( + result_4, + IndexResponse { + user_id: Some("ferris".into()), + counter: 1 + } + ); + } + pub(super) async fn complex_workflow( store_builder: F, is_invalidation_supported: bool, @@ -549,6 +635,13 @@ pub mod test_helpers { Ok(HttpResponse::Ok().json(&IndexResponse { user_id, counter })) } + async fn count(session: Session) -> Result { + let user_id: Option = session.get::("user_id").unwrap(); + let counter: i32 = session.get::("counter").unwrap().unwrap(); + + Ok(HttpResponse::Ok().json(&IndexResponse { user_id, counter })) + } + #[derive(Deserialize)] struct Identity { user_id: String, diff --git a/actix-session/src/session_ext.rs b/actix-session/src/session_ext.rs index 00d799c11..1cff2b472 100644 --- a/actix-session/src/session_ext.rs +++ b/actix-session/src/session_ext.rs @@ -1,5 +1,6 @@ use actix_web::{ dev::{ServiceRequest, ServiceResponse}, + guard::GuardContext, HttpMessage, HttpRequest, }; @@ -29,3 +30,9 @@ impl SessionExt for ServiceResponse { self.request().get_session() } } + +impl<'a> SessionExt for GuardContext<'a> { + fn get_session(&self) -> Session { + Session::get_session(&mut *self.req_data_mut()) + } +}