actix / actix-extras

@@ -0,0 +1,168 @@
Loading
1 +
use std::rc::Rc;
2 +
3 +
use actix_web::{
4 +
    dev::{Service, ServiceRequest, ServiceResponse, Transform},
5 +
    Error, HttpMessage, Result,
6 +
};
7 +
use futures_util::future::{ready, FutureExt, LocalBoxFuture, Ready};
8 +
9 +
use crate::{identity::IdentityItem, IdentityPolicy};
10 +
11 +
/// Request identity middleware
12 +
///
13 +
/// ```
14 +
/// use actix_web::App;
15 +
/// use actix_identity::{CookieIdentityPolicy, IdentityService};
16 +
///
17 +
/// // create cookie identity backend
18 +
/// let policy = CookieIdentityPolicy::new(&[0; 32])
19 +
///            .name("auth-cookie")
20 +
///            .secure(false);
21 +
///
22 +
/// let app = App::new()
23 +
///     // wrap policy into identity middleware
24 +
///     .wrap(IdentityService::new(policy));
25 +
/// ```
26 +
pub struct IdentityService<T> {
27 +
    backend: Rc<T>,
28 +
}
29 +
30 +
impl<T> IdentityService<T> {
31 +
    /// Create new identity service with specified backend.
32 +
    pub fn new(backend: T) -> Self {
33 +
        IdentityService {
34 +
            backend: Rc::new(backend),
35 +
        }
36 +
    }
37 +
}
38 +
39 +
impl<S, T, B> Transform<S, ServiceRequest> for IdentityService<T>
40 +
where
41 +
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
42 +
    S::Future: 'static,
43 +
    T: IdentityPolicy,
44 +
    B: 'static,
45 +
{
46 +
    type Response = ServiceResponse<B>;
47 +
    type Error = Error;
48 +
    type InitError = ();
49 +
    type Transform = IdentityServiceMiddleware<S, T>;
50 +
    type Future = Ready<Result<Self::Transform, Self::InitError>>;
51 +
52 +
    fn new_transform(&self, service: S) -> Self::Future {
53 +
        ready(Ok(IdentityServiceMiddleware {
54 +
            backend: self.backend.clone(),
55 +
            service: Rc::new(service),
56 +
        }))
57 +
    }
58 +
}
59 +
60 +
pub struct IdentityServiceMiddleware<S, T> {
61 +
    pub(crate) service: Rc<S>,
62 +
    pub(crate) backend: Rc<T>,
63 +
}
64 +
65 +
impl<S, T> Clone for IdentityServiceMiddleware<S, T> {
66 +
    fn clone(&self) -> Self {
67 +
        Self {
68 +
            backend: Rc::clone(&self.backend),
69 +
            service: Rc::clone(&self.service),
70 +
        }
71 +
    }
72 +
}
73 +
74 +
impl<S, T, B> Service<ServiceRequest> for IdentityServiceMiddleware<S, T>
75 +
where
76 +
    B: 'static,
77 +
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
78 +
    S::Future: 'static,
79 +
    T: IdentityPolicy,
80 +
{
81 +
    type Response = ServiceResponse<B>;
82 +
    type Error = Error;
83 +
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
84 +
85 +
    actix_service::forward_ready!(service);
86 +
87 +
    fn call(&self, mut req: ServiceRequest) -> Self::Future {
88 +
        let srv = Rc::clone(&self.service);
89 +
        let backend = Rc::clone(&self.backend);
90 +
        let fut = self.backend.from_request(&mut req);
91 +
92 +
        async move {
93 +
            match fut.await {
94 +
                Ok(id) => {
95 +
                    req.extensions_mut()
96 +
                        .insert(IdentityItem { id, changed: false });
97 +
98 +
                    let mut res = srv.call(req).await?;
99 +
                    let id = res.request().extensions_mut().remove::<IdentityItem>();
100 +
101 +
                    if let Some(id) = id {
102 +
                        match backend.to_response(id.id, id.changed, &mut res).await {
103 +
                            Ok(_) => Ok(res),
104 +
                            Err(e) => Ok(res.error_response(e)),
105 +
                        }
106 +
                    } else {
107 +
                        Ok(res)
108 +
                    }
109 +
                }
110 +
                Err(err) => Ok(req.error_response(err)),
111 +
            }
112 +
        }
113 +
        .boxed_local()
114 +
    }
115 +
}
116 +
117 +
#[cfg(test)]
118 +
mod tests {
119 +
    use std::{rc::Rc, time::Duration};
120 +
121 +
    use actix_service::into_service;
122 +
    use actix_web::{dev, error, test, Error, Result};
123 +
124 +
    use super::*;
125 +
126 +
    #[actix_rt::test]
127 +
    async fn test_borrowed_mut_error() {
128 +
        use futures_util::future::{lazy, ok, Ready};
129 +
130 +
        struct Ident;
131 +
        impl IdentityPolicy for Ident {
132 +
            type Future = Ready<Result<Option<String>, Error>>;
133 +
            type ResponseFuture = Ready<Result<(), Error>>;
134 +
135 +
            fn from_request(&self, _: &mut dev::ServiceRequest) -> Self::Future {
136 +
                ok(Some("test".to_string()))
137 +
            }
138 +
139 +
            fn to_response<B>(
140 +
                &self,
141 +
                _: Option<String>,
142 +
                _: bool,
143 +
                _: &mut dev::ServiceResponse<B>,
144 +
            ) -> Self::ResponseFuture {
145 +
                ok(())
146 +
            }
147 +
        }
148 +
149 +
        let srv = crate::middleware::IdentityServiceMiddleware {
150 +
            backend: Rc::new(Ident),
151 +
            service: Rc::new(into_service(|_: dev::ServiceRequest| async move {
152 +
                actix_rt::time::sleep(Duration::from_secs(100)).await;
153 +
                Err::<dev::ServiceResponse, _>(error::ErrorBadRequest("error"))
154 +
            })),
155 +
        };
156 +
157 +
        let srv2 = srv.clone();
158 +
        let req = test::TestRequest::default().to_srv_request();
159 +
160 +
        actix_rt::spawn(async move {
161 +
            let _ = srv2.call(req).await;
162 +
        });
163 +
164 +
        actix_rt::time::sleep(Duration::from_millis(50)).await;
165 +
166 +
        let _ = lazy(|cx| srv.poll_ready(cx)).await;
167 +
    }
168 +
}

@@ -1,21 +1,17 @@
Loading
1 -
//! Request identity service for Actix applications.
1 +
//! Opinionated request identity service for Actix Web apps.
2 2
//!
3 -
//! [**IdentityService**](struct.IdentityService.html) middleware can be
4 -
//! used with different policies types to store identity information.
3 +
//! [`IdentityService`] middleware can be used with different policies types to store
4 +
//! identity information.
5 5
//!
6 -
//! By default, only cookie identity policy is implemented. Other backend
7 -
//! implementations can be added separately.
6 +
//! A cookie based policy is provided. [`CookieIdentityPolicy`] uses cookies as identity storage.
8 7
//!
9 -
//! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html)
10 -
//! uses cookies as identity storage.
11 -
//!
12 -
//! To access current request identity
13 -
//! [**Identity**](struct.Identity.html) extractor should be used.
8 +
//! To access current request identity, use the [`Identity`] extractor.
14 9
//!
15 10
//! ```
16 11
//! use actix_web::*;
17 12
//! use actix_identity::{Identity, CookieIdentityPolicy, IdentityService};
18 13
//!
14 +
//! #[get("/")]
19 15
//! async fn index(id: Identity) -> String {
20 16
//!     // access request identity
21 17
//!     if let Some(id) = id.identity() {
@@ -25,155 +21,47 @@
Loading
25 21
//!     }
26 22
//! }
27 23
//!
24 +
//! #[post("/login")]
28 25
//! async fn login(id: Identity) -> HttpResponse {
29 26
//!     id.remember("User1".to_owned()); // <- remember identity
30 27
//!     HttpResponse::Ok().finish()
31 28
//! }
32 29
//!
30 +
//! #[post("/logout")]
33 31
//! async fn logout(id: Identity) -> HttpResponse {
34 32
//!     id.forget();                      // <- remove identity
35 33
//!     HttpResponse::Ok().finish()
36 34
//! }
37 35
//!
38 -
//! fn main() {
39 -
//!     let app = App::new().wrap(IdentityService::new(
40 -
//!         // <- create identity middleware
41 -
//!         CookieIdentityPolicy::new(&[0; 32])    // <- create cookie identity policy
42 -
//!               .name("auth-cookie")
43 -
//!               .secure(false)))
44 -
//!         .service(web::resource("/index.html").to(index))
45 -
//!         .service(web::resource("/login.html").to(login))
46 -
//!         .service(web::resource("/logout.html").to(logout));
47 -
//! }
36 +
//! // create cookie identity backend
37 +
//! let policy = CookieIdentityPolicy::new(&[0; 32])
38 +
//!     .name("auth-cookie")
39 +
//!     .secure(false);
40 +
//!
41 +
//! let app = App::new()
42 +
//!     // wrap policy into middleware identity middleware
43 +
//!     .wrap(IdentityService::new(policy))
44 +
//!     .service(services![index, login, logout]);
48 45
//! ```
49 46
50 -
#![deny(rust_2018_idioms)]
47 +
#![deny(rust_2018_idioms, nonstandard_style)]
51 48
52 -
use std::{future::Future, rc::Rc, time::SystemTime};
49 +
use std::future::Future;
53 50
54 -
use actix_service::{Service, Transform};
55 -
use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready};
56 -
use serde::{Deserialize, Serialize};
57 -
use time::Duration;
51 +
use actix_web::{
52 +
    dev::{ServiceRequest, ServiceResponse},
53 +
    Error, HttpMessage, Result,
54 +
};
58 55
59 -
use actix_web::cookie::{Cookie, CookieJar, Key, SameSite};
60 -
use actix_web::dev::{Extensions, Payload, ServiceRequest, ServiceResponse};
61 -
use actix_web::error::{Error, Result};
62 -
use actix_web::http::header::{self, HeaderValue};
63 -
use actix_web::{FromRequest, HttpMessage, HttpRequest};
56 +
mod cookie;
57 +
mod identity;
58 +
mod middleware;
64 59
65 -
/// The extractor type to obtain your identity from a request.
66 -
///
67 -
/// ```rust
68 -
/// use actix_web::*;
69 -
/// use actix_identity::Identity;
70 -
///
71 -
/// fn index(id: Identity) -> Result<String> {
72 -
///     // access request identity
73 -
///     if let Some(id) = id.identity() {
74 -
///         Ok(format!("Welcome! {}", id))
75 -
///     } else {
76 -
///         Ok("Welcome Anonymous!".to_owned())
77 -
///     }
78 -
/// }
79 -
///
80 -
/// fn login(id: Identity) -> HttpResponse {
81 -
///     id.remember("User1".to_owned()); // <- remember identity
82 -
///     HttpResponse::Ok().finish()
83 -
/// }
84 -
///
85 -
/// fn logout(id: Identity) -> HttpResponse {
86 -
///     id.forget(); // <- remove identity
87 -
///     HttpResponse::Ok().finish()
88 -
/// }
89 -
/// # fn main() {}
90 -
/// ```
91 -
#[derive(Clone)]
92 -
pub struct Identity(HttpRequest);
60 +
pub use self::cookie::CookieIdentityPolicy;
61 +
pub use self::identity::Identity;
62 +
pub use self::middleware::IdentityService;
93 63
94 -
impl Identity {
95 -
    /// Return the claimed identity of the user associated request or
96 -
    /// ``None`` if no identity can be found associated with the request.
97 -
    pub fn identity(&self) -> Option<String> {
98 -
        Identity::get_identity(&self.0.extensions())
99 -
    }
100 -
101 -
    /// Remember identity.
102 -
    pub fn remember(&self, identity: String) {
103 -
        if let Some(id) = self.0.extensions_mut().get_mut::<IdentityItem>() {
104 -
            id.id = Some(identity);
105 -
            id.changed = true;
106 -
        }
107 -
    }
108 -
109 -
    /// This method is used to 'forget' the current identity on subsequent
110 -
    /// requests.
111 -
    pub fn forget(&self) {
112 -
        if let Some(id) = self.0.extensions_mut().get_mut::<IdentityItem>() {
113 -
            id.id = None;
114 -
            id.changed = true;
115 -
        }
116 -
    }
117 -
118 -
    fn get_identity(extensions: &Extensions) -> Option<String> {
119 -
        if let Some(id) = extensions.get::<IdentityItem>() {
120 -
            id.id.clone()
121 -
        } else {
122 -
            None
123 -
        }
124 -
    }
125 -
}
126 -
127 -
struct IdentityItem {
128 -
    id: Option<String>,
129 -
    changed: bool,
130 -
}
131 -
132 -
/// Helper trait that allows to get Identity.
133 -
///
134 -
/// It could be used in middleware but identity policy must be set before any other middleware that needs identity
135 -
/// RequestIdentity is implemented both for `ServiceRequest` and `HttpRequest`.
136 -
pub trait RequestIdentity {
137 -
    fn get_identity(&self) -> Option<String>;
138 -
}
139 -
140 -
impl<T> RequestIdentity for T
141 -
where
142 -
    T: HttpMessage,
143 -
{
144 -
    fn get_identity(&self) -> Option<String> {
145 -
        Identity::get_identity(&self.extensions())
146 -
    }
147 -
}
148 -
149 -
/// Extractor implementation for Identity type.
150 -
///
151 -
/// ```rust
152 -
/// # use actix_web::*;
153 -
/// use actix_identity::Identity;
154 -
///
155 -
/// fn index(id: Identity) -> String {
156 -
///     // access request identity
157 -
///     if let Some(id) = id.identity() {
158 -
///         format!("Welcome! {}", id)
159 -
///     } else {
160 -
///         "Welcome Anonymous!".to_owned()
161 -
///     }
162 -
/// }
163 -
/// # fn main() {}
164 -
/// ```
165 -
impl FromRequest for Identity {
166 -
    type Config = ();
167 -
    type Error = Error;
168 -
    type Future = Ready<Result<Identity, Error>>;
169 -
170 -
    #[inline]
171 -
    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
172 -
        ok(Identity(req.clone()))
173 -
    }
174 -
}
175 -
176 -
/// Identity policy definition.
64 +
/// Identity policy.
177 65
pub trait IdentityPolicy: Sized + 'static {
178 66
    /// The return type of the middleware
179 67
    type Future: Future<Output = Result<Option<String>, Error>>;
@@ -182,7 +70,7 @@
Loading
182 70
    type ResponseFuture: Future<Output = Result<(), Error>>;
183 71
184 72
    /// Parse the session from request and load data from a service identity.
185 -
    fn from_request(&self, request: &mut ServiceRequest) -> Self::Future;
73 +
    fn from_request(&self, req: &mut ServiceRequest) -> Self::Future;
186 74
187 75
    /// Write changes to response
188 76
    fn to_response<B>(
@@ -193,582 +81,49 @@
Loading
193 81
    ) -> Self::ResponseFuture;
194 82
}
195 83
196 -
/// Request identity middleware
197 -
///
198 -
/// ```rust
199 -
/// use actix_web::App;
200 -
/// use actix_identity::{CookieIdentityPolicy, IdentityService};
84 +
/// Helper trait that allows to get Identity.
201 85
///
202 -
/// let app = App::new().wrap(IdentityService::new(
203 -
///     // <- create identity middleware
204 -
///     CookieIdentityPolicy::new(&[0; 32])    // <- create cookie session backend
205 -
///           .name("auth-cookie")
206 -
///           .secure(false),
207 -
/// ));
208 -
/// ```
209 -
pub struct IdentityService<T> {
210 -
    backend: Rc<T>,
211 -
}
212 -
213 -
impl<T> IdentityService<T> {
214 -
    /// Create new identity service with specified backend.
215 -
    pub fn new(backend: T) -> Self {
216 -
        IdentityService {
217 -
            backend: Rc::new(backend),
218 -
        }
219 -
    }
220 -
}
221 -
222 -
impl<S, T, B> Transform<S, ServiceRequest> for IdentityService<T>
223 -
where
224 -
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
225 -
    S::Future: 'static,
226 -
    T: IdentityPolicy,
227 -
    B: 'static,
228 -
{
229 -
    type Response = ServiceResponse<B>;
230 -
    type Error = Error;
231 -
    type InitError = ();
232 -
    type Transform = IdentityServiceMiddleware<S, T>;
233 -
    type Future = Ready<Result<Self::Transform, Self::InitError>>;
234 -
235 -
    fn new_transform(&self, service: S) -> Self::Future {
236 -
        ok(IdentityServiceMiddleware {
237 -
            backend: self.backend.clone(),
238 -
            service: Rc::new(service),
239 -
        })
240 -
    }
241 -
}
242 -
243 -
#[doc(hidden)]
244 -
pub struct IdentityServiceMiddleware<S, T> {
245 -
    service: Rc<S>,
246 -
    backend: Rc<T>,
247 -
}
248 -
249 -
impl<S, T> Clone for IdentityServiceMiddleware<S, T> {
250 -
    fn clone(&self) -> Self {
251 -
        Self {
252 -
            backend: Rc::clone(&self.backend),
253 -
            service: Rc::clone(&self.service),
254 -
        }
255 -
    }
86 +
/// It could be used in middleware but identity policy must be set before any other middleware that
87 +
/// needs identity. RequestIdentity is implemented both for `ServiceRequest` and `HttpRequest`.
88 +
pub trait RequestIdentity {
89 +
    fn get_identity(&self) -> Option<String>;
256 90
}
257 91
258 -
impl<S, T, B> Service<ServiceRequest> for IdentityServiceMiddleware<S, T>
92 +
impl<T> RequestIdentity for T
259 93
where
260 -
    B: 'static,
261 -
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
262 -
    S::Future: 'static,
263 -
    T: IdentityPolicy,
94 +
    T: HttpMessage,
264 95
{
265 -
    type Response = ServiceResponse<B>;
266 -
    type Error = Error;
267 -
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
268 -
269 -
    actix_service::forward_ready!(service);
270 -
271 -
    fn call(&self, mut req: ServiceRequest) -> Self::Future {
272 -
        let srv = Rc::clone(&self.service);
273 -
        let backend = Rc::clone(&self.backend);
274 -
        let fut = self.backend.from_request(&mut req);
275 -
276 -
        async move {
277 -
            match fut.await {
278 -
                Ok(id) => {
279 -
                    req.extensions_mut()
280 -
                        .insert(IdentityItem { id, changed: false });
281 -
282 -
                    let mut res = srv.call(req).await?;
283 -
                    let id = res.request().extensions_mut().remove::<IdentityItem>();
284 -
285 -
                    if let Some(id) = id {
286 -
                        match backend.to_response(id.id, id.changed, &mut res).await {
287 -
                            Ok(_) => Ok(res),
288 -
                            Err(e) => Ok(res.error_response(e)),
289 -
                        }
290 -
                    } else {
291 -
                        Ok(res)
292 -
                    }
293 -
                }
294 -
                Err(err) => Ok(req.error_response(err)),
295 -
            }
296 -
        }
297 -
        .boxed_local()
298 -
    }
299 -
}
300 -
301 -
struct CookieIdentityInner {
302 -
    key: Key,
303 -
    key_v2: Key,
304 -
    name: String,
305 -
    path: String,
306 -
    domain: Option<String>,
307 -
    secure: bool,
308 -
    max_age: Option<Duration>,
309 -
    http_only: Option<bool>,
310 -
    same_site: Option<SameSite>,
311 -
    visit_deadline: Option<Duration>,
312 -
    login_deadline: Option<Duration>,
313 -
}
314 -
315 -
#[derive(Deserialize, Serialize, Debug)]
316 -
struct CookieValue {
317 -
    identity: String,
318 -
319 -
    #[serde(skip_serializing_if = "Option::is_none")]
320 -
    login_timestamp: Option<SystemTime>,
321 -
322 -
    #[serde(skip_serializing_if = "Option::is_none")]
323 -
    visit_timestamp: Option<SystemTime>,
324 -
}
325 -
326 -
#[derive(Debug)]
327 -
struct CookieIdentityExtension {
328 -
    login_timestamp: Option<SystemTime>,
329 -
}
330 -
331 -
impl CookieIdentityInner {
332 -
    fn new(key: &[u8]) -> CookieIdentityInner {
333 -
        let key_v2: Vec<u8> = key.iter().chain([1, 0, 0, 0].iter()).cloned().collect();
334 -
        CookieIdentityInner {
335 -
            key: Key::derive_from(key),
336 -
            key_v2: Key::derive_from(&key_v2),
337 -
            name: "actix-identity".to_owned(),
338 -
            path: "/".to_owned(),
339 -
            domain: None,
340 -
            secure: true,
341 -
            max_age: None,
342 -
            http_only: None,
343 -
            same_site: None,
344 -
            visit_deadline: None,
345 -
            login_deadline: None,
346 -
        }
347 -
    }
348 -
349 -
    fn set_cookie<B>(
350 -
        &self,
351 -
        resp: &mut ServiceResponse<B>,
352 -
        value: Option<CookieValue>,
353 -
    ) -> Result<()> {
354 -
        let add_cookie = value.is_some();
355 -
        let val = value.map(|val| {
356 -
            if !self.legacy_supported() {
357 -
                serde_json::to_string(&val)
358 -
            } else {
359 -
                Ok(val.identity)
360 -
            }
361 -
        });
362 -
        let mut cookie =
363 -
            Cookie::new(self.name.clone(), val.unwrap_or_else(|| Ok(String::new()))?);
364 -
        cookie.set_path(self.path.clone());
365 -
        cookie.set_secure(self.secure);
366 -
        cookie.set_http_only(true);
367 -
368 -
        if let Some(ref domain) = self.domain {
369 -
            cookie.set_domain(domain.clone());
370 -
        }
371 -
372 -
        if let Some(max_age) = self.max_age {
373 -
            cookie.set_max_age(max_age);
374 -
        }
375 -
376 -
        if let Some(http_only) = self.http_only {
377 -
            cookie.set_http_only(http_only);
378 -
        }
379 -
380 -
        if let Some(same_site) = self.same_site {
381 -
            cookie.set_same_site(same_site);
382 -
        }
383 -
384 -
        let mut jar = CookieJar::new();
385 -
        let key = if self.legacy_supported() {
386 -
            &self.key
387 -
        } else {
388 -
            &self.key_v2
389 -
        };
390 -
        if add_cookie {
391 -
            jar.private(&key).add(cookie);
392 -
        } else {
393 -
            jar.add_original(cookie.clone());
394 -
            jar.private(&key).remove(cookie);
395 -
        }
396 -
        for cookie in jar.delta() {
397 -
            let val = HeaderValue::from_str(&cookie.to_string())?;
398 -
            resp.headers_mut().append(header::SET_COOKIE, val);
399 -
        }
400 -
        Ok(())
401 -
    }
402 -
403 -
    fn load(&self, req: &ServiceRequest) -> Option<CookieValue> {
404 -
        let cookie = req.cookie(&self.name)?;
405 -
        let mut jar = CookieJar::new();
406 -
        jar.add_original(cookie.clone());
407 -
        let res = if self.legacy_supported() {
408 -
            jar.private(&self.key).get(&self.name).map(|n| CookieValue {
409 -
                identity: n.value().to_string(),
410 -
                login_timestamp: None,
411 -
                visit_timestamp: None,
412 -
            })
413 -
        } else {
414 -
            None
415 -
        };
416 -
        res.or_else(|| {
417 -
            jar.private(&self.key_v2)
418 -
                .get(&self.name)
419 -
                .and_then(|c| self.parse(c))
420 -
        })
421 -
    }
422 -
423 -
    fn parse(&self, cookie: Cookie<'_>) -> Option<CookieValue> {
424 -
        let value: CookieValue = serde_json::from_str(cookie.value()).ok()?;
425 -
        let now = SystemTime::now();
426 -
        if let Some(visit_deadline) = self.visit_deadline {
427 -
            if now.duration_since(value.visit_timestamp?).ok()? > visit_deadline {
428 -
                return None;
429 -
            }
430 -
        }
431 -
        if let Some(login_deadline) = self.login_deadline {
432 -
            if now.duration_since(value.login_timestamp?).ok()? > login_deadline {
433 -
                return None;
434 -
            }
435 -
        }
436 -
        Some(value)
437 -
    }
438 -
439 -
    fn legacy_supported(&self) -> bool {
440 -
        self.visit_deadline.is_none() && self.login_deadline.is_none()
441 -
    }
442 -
443 -
    fn always_update_cookie(&self) -> bool {
444 -
        self.visit_deadline.is_some()
445 -
    }
446 -
447 -
    fn requires_oob_data(&self) -> bool {
448 -
        self.login_deadline.is_some()
449 -
    }
450 -
}
451 -
452 -
/// Use cookies for request identity storage.
453 -
///
454 -
/// The constructors take a key as an argument.
455 -
/// This is the private key for cookie - when this value is changed,
456 -
/// all identities are lost. The constructors will panic if the key is less
457 -
/// than 32 bytes in length.
458 -
///
459 -
/// # Example
460 -
///
461 -
/// ```rust
462 -
/// use actix_web::App;
463 -
/// use actix_identity::{CookieIdentityPolicy, IdentityService};
464 -
///
465 -
/// let app = App::new().wrap(IdentityService::new(
466 -
///     // <- create identity middleware
467 -
///     CookieIdentityPolicy::new(&[0; 32])  // <- construct cookie policy
468 -
///            .domain("www.rust-lang.org")
469 -
///            .name("actix_auth")
470 -
///            .path("/")
471 -
///            .secure(true),
472 -
/// ));
473 -
/// ```
474 -
pub struct CookieIdentityPolicy(Rc<CookieIdentityInner>);
475 -
476 -
impl CookieIdentityPolicy {
477 -
    /// Construct new `CookieIdentityPolicy` instance.
478 -
    ///
479 -
    /// Panics if key length is less than 32 bytes.
480 -
    pub fn new(key: &[u8]) -> CookieIdentityPolicy {
481 -
        CookieIdentityPolicy(Rc::new(CookieIdentityInner::new(key)))
482 -
    }
483 -
484 -
    /// Sets the `path` field in the session cookie being built.
485 -
    pub fn path<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
486 -
        Rc::get_mut(&mut self.0).unwrap().path = value.into();
487 -
        self
488 -
    }
489 -
490 -
    /// Sets the `name` field in the session cookie being built.
491 -
    pub fn name<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
492 -
        Rc::get_mut(&mut self.0).unwrap().name = value.into();
493 -
        self
494 -
    }
495 -
496 -
    /// Sets the `domain` field in the session cookie being built.
497 -
    pub fn domain<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
498 -
        Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into());
499 -
        self
500 -
    }
501 -
502 -
    /// Sets the `secure` field in the session cookie being built.
503 -
    ///
504 -
    /// If the `secure` field is set, a cookie will only be transmitted when the
505 -
    /// connection is secure - i.e. `https`
506 -
    pub fn secure(mut self, value: bool) -> CookieIdentityPolicy {
507 -
        Rc::get_mut(&mut self.0).unwrap().secure = value;
508 -
        self
509 -
    }
510 -
511 -
    /// Sets the `max-age` field in the session cookie being built with given number of seconds.
512 -
    pub fn max_age(self, seconds: i64) -> CookieIdentityPolicy {
513 -
        self.max_age_time(Duration::seconds(seconds))
514 -
    }
515 -
516 -
    /// Sets the `max-age` field in the session cookie being built with `time::Duration`.
517 -
    pub fn max_age_time(mut self, value: Duration) -> CookieIdentityPolicy {
518 -
        Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
519 -
        self
520 -
    }
521 -
522 -
    /// Sets the `http_only` field in the session cookie being built.
523 -
    pub fn http_only(mut self, http_only: bool) -> Self {
524 -
        Rc::get_mut(&mut self.0).unwrap().http_only = Some(http_only);
525 -
        self
526 -
    }
527 -
528 -
    /// Sets the `same_site` field in the session cookie being built.
529 -
    pub fn same_site(mut self, same_site: SameSite) -> Self {
530 -
        Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site);
531 -
        self
532 -
    }
533 -
534 -
    /// Accepts only users whose cookie has been seen before the given deadline
535 -
    ///
536 -
    /// By default visit deadline is disabled.
537 -
    pub fn visit_deadline(mut self, value: Duration) -> CookieIdentityPolicy {
538 -
        Rc::get_mut(&mut self.0).unwrap().visit_deadline = Some(value);
539 -
        self
540 -
    }
541 -
542 -
    /// Accepts only users which has been authenticated before the given deadline
543 -
    ///
544 -
    /// By default login deadline is disabled.
545 -
    pub fn login_deadline(mut self, value: Duration) -> CookieIdentityPolicy {
546 -
        Rc::get_mut(&mut self.0).unwrap().login_deadline = Some(value);
547 -
        self
548 -
    }
549 -
}
550 -
551 -
impl IdentityPolicy for CookieIdentityPolicy {
552 -
    type Future = Ready<Result<Option<String>, Error>>;
553 -
    type ResponseFuture = Ready<Result<(), Error>>;
554 -
555 -
    fn from_request(&self, req: &mut ServiceRequest) -> Self::Future {
556 -
        ok(self.0.load(req).map(
557 -
            |CookieValue {
558 -
                 identity,
559 -
                 login_timestamp,
560 -
                 ..
561 -
             }| {
562 -
                if self.0.requires_oob_data() {
563 -
                    req.extensions_mut()
564 -
                        .insert(CookieIdentityExtension { login_timestamp });
565 -
                }
566 -
                identity
567 -
            },
568 -
        ))
569 -
    }
570 -
571 -
    fn to_response<B>(
572 -
        &self,
573 -
        id: Option<String>,
574 -
        changed: bool,
575 -
        res: &mut ServiceResponse<B>,
576 -
    ) -> Self::ResponseFuture {
577 -
        let _ = if changed {
578 -
            let login_timestamp = SystemTime::now();
579 -
            self.0.set_cookie(
580 -
                res,
581 -
                id.map(|identity| CookieValue {
582 -
                    identity,
583 -
                    login_timestamp: self.0.login_deadline.map(|_| login_timestamp),
584 -
                    visit_timestamp: self.0.visit_deadline.map(|_| login_timestamp),
585 -
                }),
586 -
            )
587 -
        } else if self.0.always_update_cookie() && id.is_some() {
588 -
            let visit_timestamp = SystemTime::now();
589 -
            let login_timestamp = if self.0.requires_oob_data() {
590 -
                let CookieIdentityExtension {
591 -
                    login_timestamp: lt,
592 -
                } = res.request().extensions_mut().remove().unwrap();
593 -
                lt
594 -
            } else {
595 -
                None
596 -
            };
597 -
            self.0.set_cookie(
598 -
                res,
599 -
                Some(CookieValue {
600 -
                    identity: id.unwrap(),
601 -
                    login_timestamp,
602 -
                    visit_timestamp: self.0.visit_deadline.map(|_| visit_timestamp),
603 -
                }),
604 -
            )
605 -
        } else {
606 -
            Ok(())
607 -
        };
608 -
        ok(())
96 +
    fn get_identity(&self) -> Option<String> {
97 +
        Identity::get_identity(&self.extensions())
609 98
    }
610 99
}
611 100
612 101
#[cfg(test)]
613 102
mod tests {
614 -
    use std::borrow::Borrow;
615 -
616 -
    use super::*;
617 -
    use actix_service::into_service;
618 -
    use actix_web::http::StatusCode;
619 -
    use actix_web::test::{self, TestRequest};
620 -
    use actix_web::{error, web, App, Error, HttpResponse};
621 -
622 -
    const COOKIE_KEY_MASTER: [u8; 32] = [0; 32];
623 -
    const COOKIE_NAME: &str = "actix_auth";
624 -
    const COOKIE_LOGIN: &str = "test";
103 +
    use std::time::SystemTime;
625 104
626 -
    #[actix_rt::test]
627 -
    async fn test_identity() {
628 -
        let srv = test::init_service(
629 -
            App::new()
630 -
                .wrap(IdentityService::new(
631 -
                    CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
632 -
                        .domain("www.rust-lang.org")
633 -
                        .name(COOKIE_NAME)
634 -
                        .path("/")
635 -
                        .secure(true),
636 -
                ))
637 -
                .service(web::resource("/index").to(|id: Identity| {
638 -
                    if id.identity().is_some() {
639 -
                        HttpResponse::Created()
640 -
                    } else {
641 -
                        HttpResponse::Ok()
642 -
                    }
643 -
                }))
644 -
                .service(web::resource("/login").to(|id: Identity| {
645 -
                    id.remember(COOKIE_LOGIN.to_string());
646 -
                    HttpResponse::Ok()
647 -
                }))
648 -
                .service(web::resource("/logout").to(|id: Identity| {
649 -
                    if id.identity().is_some() {
650 -
                        id.forget();
651 -
                        HttpResponse::Ok()
652 -
                    } else {
653 -
                        HttpResponse::BadRequest()
654 -
                    }
655 -
                })),
656 -
        )
657 -
        .await;
658 -
        let resp =
659 -
            test::call_service(&srv, TestRequest::with_uri("/index").to_request()).await;
660 -
        assert_eq!(resp.status(), StatusCode::OK);
661 -
662 -
        let resp =
663 -
            test::call_service(&srv, TestRequest::with_uri("/login").to_request()).await;
664 -
        assert_eq!(resp.status(), StatusCode::OK);
665 -
        let c = resp.response().cookies().next().unwrap().to_owned();
105 +
    use actix_web::{dev::ServiceResponse, test, web, App, Error};
666 106
667 -
        let resp = test::call_service(
668 -
            &srv,
669 -
            TestRequest::with_uri("/index")
670 -
                .cookie(c.clone())
671 -
                .to_request(),
672 -
        )
673 -
        .await;
674 -
        assert_eq!(resp.status(), StatusCode::CREATED);
675 -
676 -
        let resp = test::call_service(
677 -
            &srv,
678 -
            TestRequest::with_uri("/logout")
679 -
                .cookie(c.clone())
680 -
                .to_request(),
681 -
        )
682 -
        .await;
683 -
        assert_eq!(resp.status(), StatusCode::OK);
684 -
        assert!(resp.headers().contains_key(header::SET_COOKIE))
685 -
    }
686 -
687 -
    #[actix_rt::test]
688 -
    async fn test_identity_max_age_time() {
689 -
        let duration = Duration::days(1);
690 -
        let srv = test::init_service(
691 -
            App::new()
692 -
                .wrap(IdentityService::new(
693 -
                    CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
694 -
                        .domain("www.rust-lang.org")
695 -
                        .name(COOKIE_NAME)
696 -
                        .path("/")
697 -
                        .max_age_time(duration)
698 -
                        .secure(true),
699 -
                ))
700 -
                .service(web::resource("/login").to(|id: Identity| {
701 -
                    id.remember("test".to_string());
702 -
                    HttpResponse::Ok()
703 -
                })),
704 -
        )
705 -
        .await;
706 -
        let resp =
707 -
            test::call_service(&srv, TestRequest::with_uri("/login").to_request()).await;
708 -
        assert_eq!(resp.status(), StatusCode::OK);
709 -
        assert!(resp.headers().contains_key(header::SET_COOKIE));
710 -
        let c = resp.response().cookies().next().unwrap().to_owned();
711 -
        assert_eq!(duration, c.max_age().unwrap());
712 -
    }
713 -
714 -
    #[actix_rt::test]
715 -
    async fn test_http_only_same_site() {
716 -
        let srv = test::init_service(
717 -
            App::new()
718 -
                .wrap(IdentityService::new(
719 -
                    CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
720 -
                        .domain("www.rust-lang.org")
721 -
                        .name(COOKIE_NAME)
722 -
                        .path("/")
723 -
                        .http_only(true)
724 -
                        .same_site(SameSite::None),
725 -
                ))
726 -
                .service(web::resource("/login").to(|id: Identity| {
727 -
                    id.remember("test".to_string());
728 -
                    HttpResponse::Ok()
729 -
                })),
730 -
        )
731 -
        .await;
732 -
733 -
        let resp =
734 -
            test::call_service(&srv, TestRequest::with_uri("/login").to_request()).await;
107 +
    use super::*;
735 108
736 -
        assert_eq!(resp.status(), StatusCode::OK);
737 -
        assert!(resp.headers().contains_key(header::SET_COOKIE));
109 +
    pub(crate) const COOKIE_KEY_MASTER: [u8; 32] = [0; 32];
110 +
    pub(crate) const COOKIE_NAME: &str = "actix_auth";
111 +
    pub(crate) const COOKIE_LOGIN: &str = "test";
738 112
739 -
        let c = resp.response().cookies().next().unwrap().to_owned();
740 -
        assert!(c.http_only().unwrap());
741 -
        assert_eq!(SameSite::None, c.same_site().unwrap());
113 +
    #[allow(clippy::enum_variant_names)]
114 +
    pub(crate) enum LoginTimestampCheck {
115 +
        NoTimestamp,
116 +
        NewTimestamp,
117 +
        OldTimestamp(SystemTime),
742 118
    }
743 119
744 -
    #[actix_rt::test]
745 -
    async fn test_identity_max_age() {
746 -
        let seconds = 60;
747 -
        let srv = test::init_service(
748 -
            App::new()
749 -
                .wrap(IdentityService::new(
750 -
                    CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
751 -
                        .domain("www.rust-lang.org")
752 -
                        .name(COOKIE_NAME)
753 -
                        .path("/")
754 -
                        .max_age(seconds)
755 -
                        .secure(true),
756 -
                ))
757 -
                .service(web::resource("/login").to(|id: Identity| {
758 -
                    id.remember("test".to_string());
759 -
                    HttpResponse::Ok()
760 -
                })),
761 -
        )
762 -
        .await;
763 -
        let resp =
764 -
            test::call_service(&srv, TestRequest::with_uri("/login").to_request()).await;
765 -
        assert_eq!(resp.status(), StatusCode::OK);
766 -
        assert!(resp.headers().contains_key(header::SET_COOKIE));
767 -
        let c = resp.response().cookies().next().unwrap().to_owned();
768 -
        assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap());
120 +
    #[allow(clippy::enum_variant_names)]
121 +
    pub(crate) enum VisitTimeStampCheck {
122 +
        NoTimestamp,
123 +
        NewTimestamp,
769 124
    }
770 125
771 -
    async fn create_identity_server<
126 +
    pub(crate) async fn create_identity_server<
772 127
        F: Fn(CookieIdentityPolicy) -> CookieIdentityPolicy + Sync + Send + Clone + 'static,
773 128
    >(
774 129
        f: F,
@@ -794,349 +149,4 @@
Loading
794 149
        )
795 150
        .await
796 151
    }
797 -
798 -
    fn legacy_login_cookie(identity: &'static str) -> Cookie<'static> {
799 -
        let mut jar = CookieJar::new();
800 -
        jar.private(&Key::derive_from(&COOKIE_KEY_MASTER))
801 -
            .add(Cookie::new(COOKIE_NAME, identity));
802 -
        jar.get(COOKIE_NAME).unwrap().clone()
803 -
    }
804 -
805 -
    fn login_cookie(
806 -
        identity: &'static str,
807 -
        login_timestamp: Option<SystemTime>,
808 -
        visit_timestamp: Option<SystemTime>,
809 -
    ) -> Cookie<'static> {
810 -
        let mut jar = CookieJar::new();
811 -
        let key: Vec<u8> = COOKIE_KEY_MASTER
812 -
            .iter()
813 -
            .chain([1, 0, 0, 0].iter())
814 -
            .copied()
815 -
            .collect();
816 -
        jar.private(&Key::derive_from(&key)).add(Cookie::new(
817 -
            COOKIE_NAME,
818 -
            serde_json::to_string(&CookieValue {
819 -
                identity: identity.to_string(),
820 -
                login_timestamp,
821 -
                visit_timestamp,
822 -
            })
823 -
            .unwrap(),
824 -
        ));
825 -
        jar.get(COOKIE_NAME).unwrap().clone()
826 -
    }
827 -
828 -
    async fn assert_logged_in(response: ServiceResponse, identity: Option<&str>) {
829 -
        let bytes = test::read_body(response).await;
830 -
        let resp: Option<String> = serde_json::from_slice(&bytes[..]).unwrap();
831 -
        assert_eq!(resp.as_ref().map(|s| s.borrow()), identity);
832 -
    }
833 -
834 -
    fn assert_legacy_login_cookie(response: &mut ServiceResponse, identity: &str) {
835 -
        let mut cookies = CookieJar::new();
836 -
        for cookie in response.headers().get_all(header::SET_COOKIE) {
837 -
            cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
838 -
        }
839 -
        let cookie = cookies
840 -
            .private(&Key::derive_from(&COOKIE_KEY_MASTER))
841 -
            .get(COOKIE_NAME)
842 -
            .unwrap();
843 -
        assert_eq!(cookie.value(), identity);
844 -
    }
845 -
846 -
    #[allow(clippy::enum_variant_names)]
847 -
    enum LoginTimestampCheck {
848 -
        NoTimestamp,
849 -
        NewTimestamp,
850 -
        OldTimestamp(SystemTime),
851 -
    }
852 -
853 -
    #[allow(clippy::enum_variant_names)]
854 -
    enum VisitTimeStampCheck {
855 -
        NoTimestamp,
856 -
        NewTimestamp,
857 -
    }
858 -
859 -
    fn assert_login_cookie(
860 -
        response: &mut ServiceResponse,
861 -
        identity: &str,
862 -
        login_timestamp: LoginTimestampCheck,
863 -
        visit_timestamp: VisitTimeStampCheck,
864 -
    ) {
865 -
        let mut cookies = CookieJar::new();
866 -
        for cookie in response.headers().get_all(header::SET_COOKIE) {
867 -
            cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
868 -
        }
869 -
        let key: Vec<u8> = COOKIE_KEY_MASTER
870 -
            .iter()
871 -
            .chain([1, 0, 0, 0].iter())
872 -
            .copied()
873 -
            .collect();
874 -
        let cookie = cookies
875 -
            .private(&Key::derive_from(&key))
876 -
            .get(COOKIE_NAME)
877 -
            .unwrap();
878 -
        let cv: CookieValue = serde_json::from_str(cookie.value()).unwrap();
879 -
        assert_eq!(cv.identity, identity);
880 -
        let now = SystemTime::now();
881 -
        let t30sec_ago = now - Duration::seconds(30);
882 -
        match login_timestamp {
883 -
            LoginTimestampCheck::NoTimestamp => assert_eq!(cv.login_timestamp, None),
884 -
            LoginTimestampCheck::NewTimestamp => assert!(
885 -
                t30sec_ago <= cv.login_timestamp.unwrap()
886 -
                    && cv.login_timestamp.unwrap() <= now
887 -
            ),
888 -
            LoginTimestampCheck::OldTimestamp(old_timestamp) => {
889 -
                assert_eq!(cv.login_timestamp, Some(old_timestamp))
890 -
            }
891 -
        }
892 -
        match visit_timestamp {
893 -
            VisitTimeStampCheck::NoTimestamp => assert_eq!(cv.visit_timestamp, None),
894 -
            VisitTimeStampCheck::NewTimestamp => assert!(
895 -
                t30sec_ago <= cv.visit_timestamp.unwrap()
896 -
                    && cv.visit_timestamp.unwrap() <= now
897 -
            ),
898 -
        }
899 -
    }
900 -
901 -
    fn assert_no_login_cookie(response: &mut ServiceResponse) {
902 -
        let mut cookies = CookieJar::new();
903 -
        for cookie in response.headers().get_all(header::SET_COOKIE) {
904 -
            cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
905 -
        }
906 -
        assert!(cookies.get(COOKIE_NAME).is_none());
907 -
    }
908 -
909 -
    #[actix_rt::test]
910 -
    async fn test_identity_legacy_cookie_is_set() {
911 -
        let srv = create_identity_server(|c| c).await;
912 -
        let mut resp =
913 -
            test::call_service(&srv, TestRequest::with_uri("/").to_request()).await;
914 -
        assert_legacy_login_cookie(&mut resp, COOKIE_LOGIN);
915 -
        assert_logged_in(resp, None).await;
916 -
    }
917 -
918 -
    #[actix_rt::test]
919 -
    async fn test_identity_legacy_cookie_works() {
920 -
        let srv = create_identity_server(|c| c).await;
921 -
        let cookie = legacy_login_cookie(COOKIE_LOGIN);
922 -
        let mut resp = test::call_service(
923 -
            &srv,
924 -
            TestRequest::with_uri("/")
925 -
                .cookie(cookie.clone())
926 -
                .to_request(),
927 -
        )
928 -
        .await;
929 -
        assert_no_login_cookie(&mut resp);
930 -
        assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
931 -
    }
932 -
933 -
    #[actix_rt::test]
934 -
    async fn test_identity_legacy_cookie_rejected_if_visit_timestamp_needed() {
935 -
        let srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))).await;
936 -
        let cookie = legacy_login_cookie(COOKIE_LOGIN);
937 -
        let mut resp = test::call_service(
938 -
            &srv,
939 -
            TestRequest::with_uri("/")
940 -
                .cookie(cookie.clone())
941 -
                .to_request(),
942 -
        )
943 -
        .await;
944 -
        assert_login_cookie(
945 -
            &mut resp,
946 -
            COOKIE_LOGIN,
947 -
            LoginTimestampCheck::NoTimestamp,
948 -
            VisitTimeStampCheck::NewTimestamp,
949 -
        );
950 -
        assert_logged_in(resp, None).await;
951 -
    }
952 -
953 -
    #[actix_rt::test]
954 -
    async fn test_identity_legacy_cookie_rejected_if_login_timestamp_needed() {
955 -
        let srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
956 -
        let cookie = legacy_login_cookie(COOKIE_LOGIN);
957 -
        let mut resp = test::call_service(
958 -
            &srv,
959 -
            TestRequest::with_uri("/")
960 -
                .cookie(cookie.clone())
961 -
                .to_request(),
962 -
        )
963 -
        .await;
964 -
        assert_login_cookie(
965 -
            &mut resp,
966 -
            COOKIE_LOGIN,
967 -
            LoginTimestampCheck::NewTimestamp,
968 -
            VisitTimeStampCheck::NoTimestamp,
969 -
        );
970 -
        assert_logged_in(resp, None).await;
971 -
    }
972 -
973 -
    #[actix_rt::test]
974 -
    async fn test_identity_cookie_rejected_if_login_timestamp_needed() {
975 -
        let srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
976 -
        let cookie = login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now()));
977 -
        let mut resp = test::call_service(
978 -
            &srv,
979 -
            TestRequest::with_uri("/")
980 -
                .cookie(cookie.clone())
981 -
                .to_request(),
982 -
        )
983 -
        .await;
984 -
        assert_login_cookie(
985 -
            &mut resp,
986 -
            COOKIE_LOGIN,
987 -
            LoginTimestampCheck::NewTimestamp,
988 -
            VisitTimeStampCheck::NoTimestamp,
989 -
        );
990 -
        assert_logged_in(resp, None).await;
991 -
    }
992 -
993 -
    #[actix_rt::test]
994 -
    async fn test_identity_cookie_rejected_if_visit_timestamp_needed() {
995 -
        let srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))).await;
996 -
        let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None);
997 -
        let mut resp = test::call_service(
998 -
            &srv,
999 -
            TestRequest::with_uri("/")
1000 -
                .cookie(cookie.clone())
1001 -
                .to_request(),
1002 -
        )
1003 -
        .await;
1004 -
        assert_login_cookie(
1005 -
            &mut resp,
1006 -
            COOKIE_LOGIN,
1007 -
            LoginTimestampCheck::NoTimestamp,
1008 -
            VisitTimeStampCheck::NewTimestamp,
1009 -
        );
1010 -
        assert_logged_in(resp, None).await;
1011 -
    }
1012 -
1013 -
    #[actix_rt::test]
1014 -
    async fn test_identity_cookie_rejected_if_login_timestamp_too_old() {
1015 -
        let srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
1016 -
        let cookie = login_cookie(
1017 -
            COOKIE_LOGIN,
1018 -
            Some(SystemTime::now() - Duration::days(180)),
1019 -
            None,
1020 -
        );
1021 -
        let mut resp = test::call_service(
1022 -
            &srv,
1023 -
            TestRequest::with_uri("/")
1024 -
                .cookie(cookie.clone())
1025 -
                .to_request(),
1026 -
        )
1027 -
        .await;
1028 -
        assert_login_cookie(
1029 -
            &mut resp,
1030 -
            COOKIE_LOGIN,
1031 -
            LoginTimestampCheck::NewTimestamp,
1032 -
            VisitTimeStampCheck::NoTimestamp,
1033 -
        );
1034 -
        assert_logged_in(resp, None).await;
1035 -
    }
1036 -
1037 -
    #[actix_rt::test]
1038 -
    async fn test_identity_cookie_rejected_if_visit_timestamp_too_old() {
1039 -
        let srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))).await;
1040 -
        let cookie = login_cookie(
1041 -
            COOKIE_LOGIN,
1042 -
            None,
1043 -
            Some(SystemTime::now() - Duration::days(180)),
1044 -
        );
1045 -
        let mut resp = test::call_service(
1046 -
            &srv,
1047 -
            TestRequest::with_uri("/")
1048 -
                .cookie(cookie.clone())
1049 -
                .to_request(),
1050 -
        )
1051 -
        .await;
1052 -
        assert_login_cookie(
1053 -
            &mut resp,
1054 -
            COOKIE_LOGIN,
1055 -
            LoginTimestampCheck::NoTimestamp,
1056 -
            VisitTimeStampCheck::NewTimestamp,
1057 -
        );
1058 -
        assert_logged_in(resp, None).await;
1059 -
    }
1060 -
1061 -
    #[actix_rt::test]
1062 -
    async fn test_identity_cookie_not_updated_on_login_deadline() {
1063 -
        let srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
1064 -
        let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None);
1065 -
        let mut resp = test::call_service(
1066 -
            &srv,
1067 -
            TestRequest::with_uri("/")
1068 -
                .cookie(cookie.clone())
1069 -
                .to_request(),
1070 -
        )
1071 -
        .await;
1072 -
        assert_no_login_cookie(&mut resp);
1073 -
        assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
1074 -
    }
1075 -
1076 -
    // https://github.com/actix/actix-web/issues/1263
1077 -
    #[actix_rt::test]
1078 -
    async fn test_identity_cookie_updated_on_visit_deadline() {
1079 -
        let srv = create_identity_server(|c| {
1080 -
            c.visit_deadline(Duration::days(90))
1081 -
                .login_deadline(Duration::days(90))
1082 -
        })
1083 -
        .await;
1084 -
        let timestamp = SystemTime::now() - Duration::days(1);
1085 -
        let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp));
1086 -
        let mut resp = test::call_service(
1087 -
            &srv,
1088 -
            TestRequest::with_uri("/")
1089 -
                .cookie(cookie.clone())
1090 -
                .to_request(),
1091 -
        )
1092 -
        .await;
1093 -
        assert_login_cookie(
1094 -
            &mut resp,
1095 -
            COOKIE_LOGIN,
1096 -
            LoginTimestampCheck::OldTimestamp(timestamp),
1097 -
            VisitTimeStampCheck::NewTimestamp,
1098 -
        );
1099 -
        assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
1100 -
    }
1101 -
1102 -
    #[actix_rt::test]
1103 -
    async fn test_borrowed_mut_error() {
1104 -
        use futures_util::future::{lazy, ok, Ready};
1105 -
1106 -
        struct Ident;
1107 -
        impl IdentityPolicy for Ident {
1108 -
            type Future = Ready<Result<Option<String>, Error>>;
1109 -
            type ResponseFuture = Ready<Result<(), Error>>;
1110 -
1111 -
            fn from_request(&self, _: &mut ServiceRequest) -> Self::Future {
1112 -
                ok(Some("test".to_string()))
1113 -
            }
1114 -
1115 -
            fn to_response<B>(
1116 -
                &self,
1117 -
                _: Option<String>,
1118 -
                _: bool,
1119 -
                _: &mut ServiceResponse<B>,
1120 -
            ) -> Self::ResponseFuture {
1121 -
                ok(())
1122 -
            }
1123 -
        }
1124 -
1125 -
        let srv = IdentityServiceMiddleware {
1126 -
            backend: Rc::new(Ident),
1127 -
            service: Rc::new(into_service(|_: ServiceRequest| async move {
1128 -
                actix_rt::time::sleep(std::time::Duration::from_secs(100)).await;
1129 -
                Err::<ServiceResponse, _>(error::ErrorBadRequest("error"))
1130 -
            })),
1131 -
        };
1132 -
1133 -
        let srv2 = srv.clone();
1134 -
        let req = TestRequest::default().to_srv_request();
1135 -
        actix_rt::spawn(async move {
1136 -
            let _ = srv2.call(req).await;
1137 -
        });
1138 -
        actix_rt::time::sleep(std::time::Duration::from_millis(50)).await;
1139 -
1140 -
        let _ = lazy(|cx| srv.poll_ready(cx)).await;
1141 -
    }
1142 152
}

@@ -0,0 +1,101 @@
Loading
1 +
use actix_web::{
2 +
    dev::{Extensions, Payload},
3 +
    Error, FromRequest, HttpRequest,
4 +
};
5 +
use futures_util::future::{ready, Ready};
6 +
7 +
pub(crate) struct IdentityItem {
8 +
    pub(crate) id: Option<String>,
9 +
    pub(crate) changed: bool,
10 +
}
11 +
12 +
/// The extractor type to obtain your identity from a request.
13 +
///
14 +
/// ```
15 +
/// use actix_web::*;
16 +
/// use actix_identity::Identity;
17 +
///
18 +
/// #[get("/")]
19 +
/// async fn index(id: Identity) -> impl Responder {
20 +
///     // access request identity
21 +
///     if let Some(id) = id.identity() {
22 +
///         format!("Welcome! {}", id)
23 +
///     } else {
24 +
///         "Welcome Anonymous!".to_owned()
25 +
///     }
26 +
/// }
27 +
///
28 +
/// #[post("/login")]
29 +
/// async fn login(id: Identity) -> impl Responder {
30 +
///     // remember identity
31 +
///     id.remember("User1".to_owned());
32 +
///
33 +
///     HttpResponse::Ok()
34 +
/// }
35 +
///
36 +
/// #[post("/logout")]
37 +
/// async fn logout(id: Identity) -> impl Responder {
38 +
///     // remove identity
39 +
///     id.forget();
40 +
///
41 +
///     HttpResponse::Ok()
42 +
/// }
43 +
/// ```
44 +
#[derive(Clone)]
45 +
pub struct Identity(HttpRequest);
46 +
47 +
impl Identity {
48 +
    /// Return the claimed identity of the user associated request or `None` if no identity can be
49 +
    /// found associated with the request.
50 +
    pub fn identity(&self) -> Option<String> {
51 +
        Identity::get_identity(&self.0.extensions())
52 +
    }
53 +
54 +
    /// Remember identity.
55 +
    pub fn remember(&self, identity: String) {
56 +
        if let Some(id) = self.0.extensions_mut().get_mut::<IdentityItem>() {
57 +
            id.id = Some(identity);
58 +
            id.changed = true;
59 +
        }
60 +
    }
61 +
62 +
    /// This method is used to 'forget' the current identity on subsequent requests.
63 +
    pub fn forget(&self) {
64 +
        if let Some(id) = self.0.extensions_mut().get_mut::<IdentityItem>() {
65 +
            id.id = None;
66 +
            id.changed = true;
67 +
        }
68 +
    }
69 +
70 +
    pub(crate) fn get_identity(extensions: &Extensions) -> Option<String> {
71 +
        let id = extensions.get::<IdentityItem>()?;
72 +
        id.id.clone()
73 +
    }
74 +
}
75 +
76 +
/// Extractor implementation for Identity type.
77 +
///
78 +
/// ```
79 +
/// # use actix_web::*;
80 +
/// use actix_identity::Identity;
81 +
///
82 +
/// #[get("/")]
83 +
/// async fn index(id: Identity) -> impl Responder {
84 +
///     // access request identity
85 +
///     if let Some(id) = id.identity() {
86 +
///         format!("Welcome! {}", id)
87 +
///     } else {
88 +
///         "Welcome Anonymous!".to_owned()
89 +
///     }
90 +
/// }
91 +
/// ```
92 +
impl FromRequest for Identity {
93 +
    type Config = ();
94 +
    type Error = Error;
95 +
    type Future = Ready<Result<Identity, Error>>;
96 +
97 +
    #[inline]
98 +
    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
99 +
        ready(Ok(Identity(req.clone())))
100 +
    }
101 +
}

@@ -0,0 +1,827 @@
Loading
1 +
use std::{rc::Rc, time::SystemTime};
2 +
3 +
use futures_util::future::{ready, Ready};
4 +
use serde::{Deserialize, Serialize};
5 +
use time::Duration;
6 +
7 +
use actix_web::{
8 +
    cookie::{Cookie, CookieJar, Key, SameSite},
9 +
    dev::{ServiceRequest, ServiceResponse},
10 +
    error::{Error, Result},
11 +
    http::header::{self, HeaderValue},
12 +
    HttpMessage,
13 +
};
14 +
15 +
use crate::IdentityPolicy;
16 +
17 +
struct CookieIdentityInner {
18 +
    key: Key,
19 +
    key_v2: Key,
20 +
    name: String,
21 +
    path: String,
22 +
    domain: Option<String>,
23 +
    secure: bool,
24 +
    max_age: Option<Duration>,
25 +
    http_only: Option<bool>,
26 +
    same_site: Option<SameSite>,
27 +
    visit_deadline: Option<Duration>,
28 +
    login_deadline: Option<Duration>,
29 +
}
30 +
31 +
#[derive(Debug, Deserialize, Serialize)]
32 +
struct CookieValue {
33 +
    identity: String,
34 +
35 +
    #[serde(skip_serializing_if = "Option::is_none")]
36 +
    login_timestamp: Option<SystemTime>,
37 +
38 +
    #[serde(skip_serializing_if = "Option::is_none")]
39 +
    visit_timestamp: Option<SystemTime>,
40 +
}
41 +
42 +
#[derive(Debug)]
43 +
struct CookieIdentityExtension {
44 +
    login_timestamp: Option<SystemTime>,
45 +
}
46 +
47 +
impl CookieIdentityInner {
48 +
    fn new(key: &[u8]) -> CookieIdentityInner {
49 +
        let key_v2: Vec<u8> = [key, &[1, 0, 0, 0]].concat();
50 +
51 +
        CookieIdentityInner {
52 +
            key: Key::derive_from(key),
53 +
            key_v2: Key::derive_from(&key_v2),
54 +
            name: "actix-identity".to_owned(),
55 +
            path: "/".to_owned(),
56 +
            domain: None,
57 +
            secure: true,
58 +
            max_age: None,
59 +
            http_only: None,
60 +
            same_site: None,
61 +
            visit_deadline: None,
62 +
            login_deadline: None,
63 +
        }
64 +
    }
65 +
66 +
    fn set_cookie<B>(
67 +
        &self,
68 +
        resp: &mut ServiceResponse<B>,
69 +
        value: Option<CookieValue>,
70 +
    ) -> Result<()> {
71 +
        let add_cookie = value.is_some();
72 +
        let val = value.map(|val| {
73 +
            if !self.legacy_supported() {
74 +
                serde_json::to_string(&val)
75 +
            } else {
76 +
                Ok(val.identity)
77 +
            }
78 +
        });
79 +
80 +
        let mut cookie =
81 +
            Cookie::new(self.name.clone(), val.unwrap_or_else(|| Ok(String::new()))?);
82 +
        cookie.set_path(self.path.clone());
83 +
        cookie.set_secure(self.secure);
84 +
        cookie.set_http_only(true);
85 +
86 +
        if let Some(ref domain) = self.domain {
87 +
            cookie.set_domain(domain.clone());
88 +
        }
89 +
90 +
        if let Some(max_age) = self.max_age {
91 +
            cookie.set_max_age(max_age);
92 +
        }
93 +
94 +
        if let Some(http_only) = self.http_only {
95 +
            cookie.set_http_only(http_only);
96 +
        }
97 +
98 +
        if let Some(same_site) = self.same_site {
99 +
            cookie.set_same_site(same_site);
100 +
        }
101 +
102 +
        let mut jar = CookieJar::new();
103 +
104 +
        let key = if self.legacy_supported() {
105 +
            &self.key
106 +
        } else {
107 +
            &self.key_v2
108 +
        };
109 +
110 +
        if add_cookie {
111 +
            jar.private(&key).add(cookie);
112 +
        } else {
113 +
            jar.add_original(cookie.clone());
114 +
            jar.private(&key).remove(cookie);
115 +
        }
116 +
117 +
        for cookie in jar.delta() {
118 +
            let val = HeaderValue::from_str(&cookie.to_string())?;
119 +
            resp.headers_mut().append(header::SET_COOKIE, val);
120 +
        }
121 +
122 +
        Ok(())
123 +
    }
124 +
125 +
    fn load(&self, req: &ServiceRequest) -> Option<CookieValue> {
126 +
        let cookie = req.cookie(&self.name)?;
127 +
        let mut jar = CookieJar::new();
128 +
        jar.add_original(cookie.clone());
129 +
130 +
        let res = if self.legacy_supported() {
131 +
            jar.private(&self.key).get(&self.name).map(|n| CookieValue {
132 +
                identity: n.value().to_string(),
133 +
                login_timestamp: None,
134 +
                visit_timestamp: None,
135 +
            })
136 +
        } else {
137 +
            None
138 +
        };
139 +
140 +
        res.or_else(|| {
141 +
            jar.private(&self.key_v2)
142 +
                .get(&self.name)
143 +
                .and_then(|c| self.parse(c))
144 +
        })
145 +
    }
146 +
147 +
    fn parse(&self, cookie: Cookie<'_>) -> Option<CookieValue> {
148 +
        let value: CookieValue = serde_json::from_str(cookie.value()).ok()?;
149 +
        let now = SystemTime::now();
150 +
151 +
        if let Some(visit_deadline) = self.visit_deadline {
152 +
            let inactivity = now.duration_since(value.visit_timestamp?).ok()?;
153 +
154 +
            if inactivity > visit_deadline {
155 +
                return None;
156 +
            }
157 +
        }
158 +
159 +
        if let Some(login_deadline) = self.login_deadline {
160 +
            let logged_in_dur = now.duration_since(value.login_timestamp?).ok()?;
161 +
162 +
            if logged_in_dur > login_deadline {
163 +
                return None;
164 +
            }
165 +
        }
166 +
167 +
        Some(value)
168 +
    }
169 +
170 +
    fn legacy_supported(&self) -> bool {
171 +
        self.visit_deadline.is_none() && self.login_deadline.is_none()
172 +
    }
173 +
174 +
    fn always_update_cookie(&self) -> bool {
175 +
        self.visit_deadline.is_some()
176 +
    }
177 +
178 +
    fn requires_oob_data(&self) -> bool {
179 +
        self.login_deadline.is_some()
180 +
    }
181 +
}
182 +
183 +
/// Use cookies for request identity storage.
184 +
///
185 +
/// [See this page on MDN](mdn-cookies) for details on cookie attributes.
186 +
///
187 +
/// # Examples
188 +
/// ```
189 +
/// use actix_web::App;
190 +
/// use actix_identity::{CookieIdentityPolicy, IdentityService};
191 +
///
192 +
/// // create cookie identity backend
193 +
/// let policy = CookieIdentityPolicy::new(&[0; 32])
194 +
///            .domain("www.rust-lang.org")
195 +
///            .name("actix_auth")
196 +
///            .path("/")
197 +
///            .secure(true);
198 +
///
199 +
/// let app = App::new()
200 +
///     // wrap policy into identity middleware
201 +
///     .wrap(IdentityService::new(policy));
202 +
/// ```
203 +
///
204 +
/// [mdn-cookies]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies
205 +
pub struct CookieIdentityPolicy(Rc<CookieIdentityInner>);
206 +
207 +
impl CookieIdentityPolicy {
208 +
    /// Create new `CookieIdentityPolicy` instance.
209 +
    ///
210 +
    /// Key argument is the private key for issued cookies. If this value is changed, all issued
211 +
    /// cookie identities are invalidated.
212 +
    ///
213 +
    /// # Panics
214 +
    /// Panics if `key` is less than 32 bytes in length..
215 +
    pub fn new(key: &[u8]) -> CookieIdentityPolicy {
216 +
        CookieIdentityPolicy(Rc::new(CookieIdentityInner::new(key)))
217 +
    }
218 +
219 +
    /// Sets the name of issued cookies.
220 +
    pub fn name(mut self, value: impl Into<String>) -> CookieIdentityPolicy {
221 +
        self.inner_mut().name = value.into();
222 +
        self
223 +
    }
224 +
225 +
    /// Sets the `Path` attribute of issued cookies.
226 +
    pub fn path(mut self, value: impl Into<String>) -> CookieIdentityPolicy {
227 +
        self.inner_mut().path = value.into();
228 +
        self
229 +
    }
230 +
231 +
    /// Sets the `Domain` attribute of issued cookies.
232 +
    pub fn domain(mut self, value: impl Into<String>) -> CookieIdentityPolicy {
233 +
        self.inner_mut().domain = Some(value.into());
234 +
        self
235 +
    }
236 +
237 +
    /// Sets the `Secure` attribute of issued cookies.
238 +
    pub fn secure(mut self, value: bool) -> CookieIdentityPolicy {
239 +
        self.inner_mut().secure = value;
240 +
        self
241 +
    }
242 +
243 +
    /// Sets the `Max-Age` attribute of issued cookies.
244 +
    pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy {
245 +
        self.inner_mut().max_age = Some(value);
246 +
        self
247 +
    }
248 +
249 +
    /// Sets the `Max-Age` attribute of issued cookies with given number of seconds.
250 +
    pub fn max_age_secs(self, seconds: i64) -> CookieIdentityPolicy {
251 +
        self.max_age(Duration::seconds(seconds))
252 +
    }
253 +
254 +
    /// Sets the `HttpOnly` attribute of issued cookies.
255 +
    ///
256 +
    /// By default, the `HttpOnly` attribute is omitted from issued cookies.
257 +
    pub fn http_only(mut self, http_only: bool) -> Self {
258 +
        self.inner_mut().http_only = Some(http_only);
259 +
        self
260 +
    }
261 +
262 +
    /// Sets the `SameSite` attribute of issued cookies.
263 +
    ///
264 +
    /// By default, the `SameSite` attribute is omitted from issued cookies.
265 +
    pub fn same_site(mut self, same_site: SameSite) -> Self {
266 +
        self.inner_mut().same_site = Some(same_site);
267 +
        self
268 +
    }
269 +
270 +
    /// Accepts only users who have visited within given deadline.
271 +
    ///
272 +
    /// In other words, invalidate a login after some amount of inactivity. Using this feature
273 +
    /// causes updated cookies to be issued on each response in order to record the user's last
274 +
    /// visitation timestamp.
275 +
    ///
276 +
    /// By default, visit deadline is disabled.
277 +
    pub fn visit_deadline(mut self, deadline: Duration) -> CookieIdentityPolicy {
278 +
        self.inner_mut().visit_deadline = Some(deadline);
279 +
        self
280 +
    }
281 +
282 +
    /// Accepts only users who authenticated within the given deadline.
283 +
    ///
284 +
    /// In other words, invalidate a login after some amount of time, regardless of activity.
285 +
    /// While [`Max-Age`](CookieIdentityPolicy::max_age) is useful in constraining the cookie
286 +
    /// lifetime, it could be extended manually; using this feature encodes the deadline directly
287 +
    /// into the issued cookies, making it immutable to users.
288 +
    ///
289 +
    /// By default, login deadline is disabled.
290 +
    pub fn login_deadline(mut self, deadline: Duration) -> CookieIdentityPolicy {
291 +
        self.inner_mut().login_deadline = Some(deadline);
292 +
        self
293 +
    }
294 +
295 +
    fn inner_mut(&mut self) -> &mut CookieIdentityInner {
296 +
        Rc::get_mut(&mut self.0).unwrap()
297 +
    }
298 +
}
299 +
300 +
impl IdentityPolicy for CookieIdentityPolicy {
301 +
    type Future = Ready<Result<Option<String>, Error>>;
302 +
    type ResponseFuture = Ready<Result<(), Error>>;
303 +
304 +
    fn from_request(&self, req: &mut ServiceRequest) -> Self::Future {
305 +
        ready(Ok(self.0.load(req).map(|value| {
306 +
            let CookieValue {
307 +
                identity,
308 +
                login_timestamp,
309 +
                ..
310 +
            } = value;
311 +
312 +
            if self.0.requires_oob_data() {
313 +
                req.extensions_mut()
314 +
                    .insert(CookieIdentityExtension { login_timestamp });
315 +
            }
316 +
317 +
            identity
318 +
        })))
319 +
    }
320 +
321 +
    fn to_response<B>(
322 +
        &self,
323 +
        id: Option<String>,
324 +
        changed: bool,
325 +
        res: &mut ServiceResponse<B>,
326 +
    ) -> Self::ResponseFuture {
327 +
        let _ = if changed {
328 +
            let login_timestamp = SystemTime::now();
329 +
330 +
            self.0.set_cookie(
331 +
                res,
332 +
                id.map(|identity| CookieValue {
333 +
                    identity,
334 +
                    login_timestamp: self.0.login_deadline.map(|_| login_timestamp),
335 +
                    visit_timestamp: self.0.visit_deadline.map(|_| login_timestamp),
336 +
                }),
337 +
            )
338 +
        } else if self.0.always_update_cookie() && id.is_some() {
339 +
            let visit_timestamp = SystemTime::now();
340 +
341 +
            let login_timestamp = if self.0.requires_oob_data() {
342 +
                let CookieIdentityExtension { login_timestamp } =
343 +
                    res.request().extensions_mut().remove().unwrap();
344 +
345 +
                login_timestamp
346 +
            } else {
347 +
                None
348 +
            };
349 +
350 +
            self.0.set_cookie(
351 +
                res,
352 +
                Some(CookieValue {
353 +
                    identity: id.unwrap(),
354 +
                    login_timestamp,
355 +
                    visit_timestamp: self.0.visit_deadline.map(|_| visit_timestamp),
356 +
                }),
357 +
            )
358 +
        } else {
359 +
            Ok(())
360 +
        };
361 +
362 +
        ready(Ok(()))
363 +
    }
364 +
}
365 +
366 +
#[cfg(test)]
367 +
mod tests {
368 +
    use std::{borrow::Borrow, time::SystemTime};
369 +
370 +
    use actix_web::{
371 +
        cookie::{Cookie, CookieJar, Key, SameSite},
372 +
        dev::ServiceResponse,
373 +
        http::{header, StatusCode},
374 +
        test::{self, TestRequest},
375 +
        web, App, HttpResponse,
376 +
    };
377 +
    use time::Duration;
378 +
379 +
    use super::*;
380 +
    use crate::{tests::*, Identity, IdentityService};
381 +
382 +
    fn login_cookie(
383 +
        identity: &'static str,
384 +
        login_timestamp: Option<SystemTime>,
385 +
        visit_timestamp: Option<SystemTime>,
386 +
    ) -> Cookie<'static> {
387 +
        let mut jar = CookieJar::new();
388 +
        let key: Vec<u8> = COOKIE_KEY_MASTER
389 +
            .iter()
390 +
            .chain([1, 0, 0, 0].iter())
391 +
            .copied()
392 +
            .collect();
393 +
394 +
        jar.private(&Key::derive_from(&key)).add(Cookie::new(
395 +
            COOKIE_NAME,
396 +
            serde_json::to_string(&CookieValue {
397 +
                identity: identity.to_string(),
398 +
                login_timestamp,
399 +
                visit_timestamp,
400 +
            })
401 +
            .unwrap(),
402 +
        ));
403 +
404 +
        jar.get(COOKIE_NAME).unwrap().clone()
405 +
    }
406 +
407 +
    fn assert_login_cookie(
408 +
        response: &mut ServiceResponse,
409 +
        identity: &str,
410 +
        login_timestamp: LoginTimestampCheck,
411 +
        visit_timestamp: VisitTimeStampCheck,
412 +
    ) {
413 +
        let mut cookies = CookieJar::new();
414 +
415 +
        for cookie in response.headers().get_all(header::SET_COOKIE) {
416 +
            cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
417 +
        }
418 +
419 +
        let key: Vec<u8> = COOKIE_KEY_MASTER
420 +
            .iter()
421 +
            .chain([1, 0, 0, 0].iter())
422 +
            .copied()
423 +
            .collect();
424 +
425 +
        let cookie = cookies
426 +
            .private(&Key::derive_from(&key))
427 +
            .get(COOKIE_NAME)
428 +
            .unwrap();
429 +
430 +
        let cv: CookieValue = serde_json::from_str(cookie.value()).unwrap();
431 +
        assert_eq!(cv.identity, identity);
432 +
433 +
        let now = SystemTime::now();
434 +
        let t30sec_ago = now - Duration::seconds(30);
435 +
436 +
        match login_timestamp {
437 +
            LoginTimestampCheck::NoTimestamp => assert_eq!(cv.login_timestamp, None),
438 +
            LoginTimestampCheck::NewTimestamp => assert!(
439 +
                t30sec_ago <= cv.login_timestamp.unwrap()
440 +
                    && cv.login_timestamp.unwrap() <= now
441 +
            ),
442 +
            LoginTimestampCheck::OldTimestamp(old_timestamp) => {
443 +
                assert_eq!(cv.login_timestamp, Some(old_timestamp))
444 +
            }
445 +
        }
446 +
447 +
        match visit_timestamp {
448 +
            VisitTimeStampCheck::NoTimestamp => assert_eq!(cv.visit_timestamp, None),
449 +
            VisitTimeStampCheck::NewTimestamp => assert!(
450 +
                t30sec_ago <= cv.visit_timestamp.unwrap()
451 +
                    && cv.visit_timestamp.unwrap() <= now
452 +
            ),
453 +
        }
454 +
    }
455 +
456 +
    #[actix_rt::test]
457 +
    async fn test_identity_flow() {
458 +
        let srv = test::init_service(
459 +
            App::new()
460 +
                .wrap(IdentityService::new(
461 +
                    CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
462 +
                        .domain("www.rust-lang.org")
463 +
                        .name(COOKIE_NAME)
464 +
                        .path("/")
465 +
                        .secure(true),
466 +
                ))
467 +
                .service(web::resource("/index").to(|id: Identity| {
468 +
                    if id.identity().is_some() {
469 +
                        HttpResponse::Created()
470 +
                    } else {
471 +
                        HttpResponse::Ok()
472 +
                    }
473 +
                }))
474 +
                .service(web::resource("/login").to(|id: Identity| {
475 +
                    id.remember(COOKIE_LOGIN.to_string());
476 +
                    HttpResponse::Ok()
477 +
                }))
478 +
                .service(web::resource("/logout").to(|id: Identity| {
479 +
                    if id.identity().is_some() {
480 +
                        id.forget();
481 +
                        HttpResponse::Ok()
482 +
                    } else {
483 +
                        HttpResponse::BadRequest()
484 +
                    }
485 +
                })),
486 +
        )
487 +
        .await;
488 +
        let resp =
489 +
            test::call_service(&srv, TestRequest::with_uri("/index").to_request()).await;
490 +
        assert_eq!(resp.status(), StatusCode::OK);
491 +
492 +
        let resp =
493 +
            test::call_service(&srv, TestRequest::with_uri("/login").to_request()).await;
494 +
        assert_eq!(resp.status(), StatusCode::OK);
495 +
        let c = resp.response().cookies().next().unwrap().to_owned();
496 +
497 +
        let resp = test::call_service(
498 +
            &srv,
499 +
            TestRequest::with_uri("/index")
500 +
                .cookie(c.clone())
501 +
                .to_request(),
502 +
        )
503 +
        .await;
504 +
        assert_eq!(resp.status(), StatusCode::CREATED);
505 +
506 +
        let resp = test::call_service(
507 +
            &srv,
508 +
            TestRequest::with_uri("/logout")
509 +
                .cookie(c.clone())
510 +
                .to_request(),
511 +
        )
512 +
        .await;
513 +
        assert_eq!(resp.status(), StatusCode::OK);
514 +
        assert!(resp.headers().contains_key(header::SET_COOKIE))
515 +
    }
516 +
517 +
    #[actix_rt::test]
518 +
    async fn test_identity_max_age_time() {
519 +
        let duration = Duration::days(1);
520 +
521 +
        let srv = test::init_service(
522 +
            App::new()
523 +
                .wrap(IdentityService::new(
524 +
                    CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
525 +
                        .domain("www.rust-lang.org")
526 +
                        .name(COOKIE_NAME)
527 +
                        .path("/")
528 +
                        .max_age(duration)
529 +
                        .secure(true),
530 +
                ))
531 +
                .service(web::resource("/login").to(|id: Identity| {
532 +
                    id.remember("test".to_string());
533 +
                    HttpResponse::Ok()
534 +
                })),
535 +
        )
536 +
        .await;
537 +
538 +
        let resp =
539 +
            test::call_service(&srv, TestRequest::with_uri("/login").to_request()).await;
540 +
        assert_eq!(resp.status(), StatusCode::OK);
541 +
        assert!(resp.headers().contains_key(header::SET_COOKIE));
542 +
        let c = resp.response().cookies().next().unwrap().to_owned();
543 +
        assert_eq!(duration, c.max_age().unwrap());
544 +
    }
545 +
546 +
    #[actix_rt::test]
547 +
    async fn test_http_only_same_site() {
548 +
        let srv = test::init_service(
549 +
            App::new()
550 +
                .wrap(IdentityService::new(
551 +
                    CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
552 +
                        .domain("www.rust-lang.org")
553 +
                        .name(COOKIE_NAME)
554 +
                        .path("/")
555 +
                        .http_only(true)
556 +
                        .same_site(SameSite::None),
557 +
                ))
558 +
                .service(web::resource("/login").to(|id: Identity| {
559 +
                    id.remember("test".to_string());
560 +
                    HttpResponse::Ok()
561 +
                })),
562 +
        )
563 +
        .await;
564 +
565 +
        let resp =
566 +
            test::call_service(&srv, TestRequest::with_uri("/login").to_request()).await;
567 +
568 +
        assert_eq!(resp.status(), StatusCode::OK);
569 +
        assert!(resp.headers().contains_key(header::SET_COOKIE));
570 +
571 +
        let c = resp.response().cookies().next().unwrap().to_owned();
572 +
        assert!(c.http_only().unwrap());
573 +
        assert_eq!(SameSite::None, c.same_site().unwrap());
574 +
    }
575 +
576 +
    fn legacy_login_cookie(identity: &'static str) -> Cookie<'static> {
577 +
        let mut jar = CookieJar::new();
578 +
        jar.private(&Key::derive_from(&COOKIE_KEY_MASTER))
579 +
            .add(Cookie::new(COOKIE_NAME, identity));
580 +
        jar.get(COOKIE_NAME).unwrap().clone()
581 +
    }
582 +
583 +
    async fn assert_logged_in(response: ServiceResponse, identity: Option<&str>) {
584 +
        let bytes = test::read_body(response).await;
585 +
        let resp: Option<String> = serde_json::from_slice(&bytes[..]).unwrap();
586 +
        assert_eq!(resp.as_ref().map(|s| s.borrow()), identity);
587 +
    }
588 +
589 +
    fn assert_legacy_login_cookie(response: &mut ServiceResponse, identity: &str) {
590 +
        let mut cookies = CookieJar::new();
591 +
        for cookie in response.headers().get_all(header::SET_COOKIE) {
592 +
            cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
593 +
        }
594 +
        let cookie = cookies
595 +
            .private(&Key::derive_from(&COOKIE_KEY_MASTER))
596 +
            .get(COOKIE_NAME)
597 +
            .unwrap();
598 +
        assert_eq!(cookie.value(), identity);
599 +
    }
600 +
601 +
    fn assert_no_login_cookie(response: &mut ServiceResponse) {
602 +
        let mut cookies = CookieJar::new();
603 +
        for cookie in response.headers().get_all(header::SET_COOKIE) {
604 +
            cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
605 +
        }
606 +
        assert!(cookies.get(COOKIE_NAME).is_none());
607 +
    }
608 +
609 +
    #[actix_rt::test]
610 +
    async fn test_identity_max_age() {
611 +
        let seconds = 60;
612 +
        let srv = test::init_service(
613 +
            App::new()
614 +
                .wrap(IdentityService::new(
615 +
                    CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
616 +
                        .domain("www.rust-lang.org")
617 +
                        .name(COOKIE_NAME)
618 +
                        .path("/")
619 +
                        .max_age_secs(seconds)
620 +
                        .secure(true),
621 +
                ))
622 +
                .service(web::resource("/login").to(|id: Identity| {
623 +
                    id.remember("test".to_string());
624 +
                    HttpResponse::Ok()
625 +
                })),
626 +
        )
627 +
        .await;
628 +
        let resp =
629 +
            test::call_service(&srv, TestRequest::with_uri("/login").to_request()).await;
630 +
        assert_eq!(resp.status(), StatusCode::OK);
631 +
        assert!(resp.headers().contains_key(header::SET_COOKIE));
632 +
        let c = resp.response().cookies().next().unwrap().to_owned();
633 +
        assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap());
634 +
    }
635 +
636 +
    #[actix_rt::test]
637 +
    async fn test_identity_legacy_cookie_is_set() {
638 +
        let srv = create_identity_server(|c| c).await;
639 +
        let mut resp =
640 +
            test::call_service(&srv, TestRequest::with_uri("/").to_request()).await;
641 +
        assert_legacy_login_cookie(&mut resp, COOKIE_LOGIN);
642 +
        assert_logged_in(resp, None).await;
643 +
    }
644 +
645 +
    #[actix_rt::test]
646 +
    async fn test_identity_legacy_cookie_works() {
647 +
        let srv = create_identity_server(|c| c).await;
648 +
        let cookie = legacy_login_cookie(COOKIE_LOGIN);
649 +
        let mut resp = test::call_service(
650 +
            &srv,
651 +
            TestRequest::with_uri("/")
652 +
                .cookie(cookie.clone())
653 +
                .to_request(),
654 +
        )
655 +
        .await;
656 +
        assert_no_login_cookie(&mut resp);
657 +
        assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
658 +
    }
659 +
660 +
    #[actix_rt::test]
661 +
    async fn test_identity_legacy_cookie_rejected_if_visit_timestamp_needed() {
662 +
        let srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))).await;
663 +
        let cookie = legacy_login_cookie(COOKIE_LOGIN);
664 +
        let mut resp = test::call_service(
665 +
            &srv,
666 +
            TestRequest::with_uri("/")
667 +
                .cookie(cookie.clone())
668 +
                .to_request(),
669 +
        )
670 +
        .await;
671 +
        assert_login_cookie(
672 +
            &mut resp,
673 +
            COOKIE_LOGIN,
674 +
            LoginTimestampCheck::NoTimestamp,
675 +
            VisitTimeStampCheck::NewTimestamp,
676 +
        );
677 +
        assert_logged_in(resp, None).await;
678 +
    }
679 +
680 +
    #[actix_rt::test]
681 +
    async fn test_identity_legacy_cookie_rejected_if_login_timestamp_needed() {
682 +
        let srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
683 +
        let cookie = legacy_login_cookie(COOKIE_LOGIN);
684 +
        let mut resp = test::call_service(
685 +
            &srv,
686 +
            TestRequest::with_uri("/")
687 +
                .cookie(cookie.clone())
688 +
                .to_request(),
689 +
        )
690 +
        .await;
691 +
        assert_login_cookie(
692 +
            &mut resp,
693 +
            COOKIE_LOGIN,
694 +
            LoginTimestampCheck::NewTimestamp,
695 +
            VisitTimeStampCheck::NoTimestamp,
696 +
        );
697 +
        assert_logged_in(resp, None).await;
698 +
    }
699 +
700 +
    #[actix_rt::test]
701 +
    async fn test_identity_cookie_rejected_if_login_timestamp_needed() {
702 +
        let srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
703 +
        let cookie = login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now()));
704 +
        let mut resp = test::call_service(
705 +
            &srv,
706 +
            TestRequest::with_uri("/")
707 +
                .cookie(cookie.clone())
708 +
                .to_request(),
709 +
        )
710 +
        .await;
711 +
        assert_login_cookie(
712 +
            &mut resp,
713 +
            COOKIE_LOGIN,
714 +
            LoginTimestampCheck::NewTimestamp,
715 +
            VisitTimeStampCheck::NoTimestamp,
716 +
        );
717 +
        assert_logged_in(resp, None).await;
718 +
    }
719 +
720 +
    #[actix_rt::test]
721 +
    async fn test_identity_cookie_rejected_if_visit_timestamp_needed() {
722 +
        let srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))).await;
723 +
        let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None);
724 +
        let mut resp = test::call_service(
725 +
            &srv,
726 +
            TestRequest::with_uri("/")
727 +
                .cookie(cookie.clone())
728 +
                .to_request(),
729 +
        )
730 +
        .await;
731 +
        assert_login_cookie(
732 +
            &mut resp,
733 +
            COOKIE_LOGIN,
734 +
            LoginTimestampCheck::NoTimestamp,
735 +
            VisitTimeStampCheck::NewTimestamp,
736 +
        );
737 +
        assert_logged_in(resp, None).await;
738 +
    }
739 +
740 +
    #[actix_rt::test]
741 +
    async fn test_identity_cookie_rejected_if_login_timestamp_too_old() {
742 +
        let srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
743 +
        let cookie = login_cookie(
744 +
            COOKIE_LOGIN,
745 +
            Some(SystemTime::now() - Duration::days(180)),
746 +
            None,
747 +
        );
748 +
        let mut resp = test::call_service(
749 +
            &srv,
750 +
            TestRequest::with_uri("/")
751 +
                .cookie(cookie.clone())
752 +
                .to_request(),
753 +
        )
754 +
        .await;
755 +
        assert_login_cookie(
756 +
            &mut resp,
757 +
            COOKIE_LOGIN,
758 +
            LoginTimestampCheck::NewTimestamp,
759 +
            VisitTimeStampCheck::NoTimestamp,
760 +
        );
761 +
        assert_logged_in(resp, None).await;
762 +
    }
763 +
764 +
    #[actix_rt::test]
765 +
    async fn test_identity_cookie_rejected_if_visit_timestamp_too_old() {
766 +
        let srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))).await;
767 +
        let cookie = login_cookie(
768 +
            COOKIE_LOGIN,
769 +
            None,
770 +
            Some(SystemTime::now() - Duration::days(180)),
771 +
        );
772 +
        let mut resp = test::call_service(
773 +
            &srv,
774 +
            TestRequest::with_uri("/")
775 +
                .cookie(cookie.clone())
776 +
                .to_request(),
777 +
        )
778 +
        .await;
779 +
        assert_login_cookie(
780 +
            &mut resp,
781 +
            COOKIE_LOGIN,
782 +
            LoginTimestampCheck::NoTimestamp,
783 +
            VisitTimeStampCheck::NewTimestamp,
784 +
        );
785 +
        assert_logged_in(resp, None).await;
786 +
    }
787 +
788 +
    #[actix_rt::test]
789 +
    async fn test_identity_cookie_not_updated_on_login_deadline() {
790 +
        let srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
791 +
        let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None);
792 +
        let mut resp = test::call_service(
793 +
            &srv,
794 +
            TestRequest::with_uri("/")
795 +
                .cookie(cookie.clone())
796 +
                .to_request(),
797 +
        )
798 +
        .await;
799 +
        assert_no_login_cookie(&mut resp);
800 +
        assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
801 +
    }
802 +
803 +
    #[actix_rt::test]
804 +
    async fn test_identity_cookie_updated_on_visit_deadline() {
805 +
        let srv = create_identity_server(|c| {
806 +
            c.visit_deadline(Duration::days(90))
807 +
                .login_deadline(Duration::days(90))
808 +
        })
809 +
        .await;
810 +
        let timestamp = SystemTime::now() - Duration::days(1);
811 +
        let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp));
812 +
        let mut resp = test::call_service(
813 +
            &srv,
814 +
            TestRequest::with_uri("/")
815 +
                .cookie(cookie.clone())
816 +
                .to_request(),
817 +
        )
818 +
        .await;
819 +
        assert_login_cookie(
820 +
            &mut resp,
821 +
            COOKIE_LOGIN,
822 +
            LoginTimestampCheck::OldTimestamp(timestamp),
823 +
            VisitTimeStampCheck::NewTimestamp,
824 +
        );
825 +
        assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
826 +
    }
827 +
}
Files Coverage
actix-cors/src 87.59%
actix-identity/src 95.96%
actix-redis/src 82.26%
actix-session/src 89.67%
actix-web-httpauth/src 60.59%
actix-protobuf/src/lib.rs 50.00%
Project Totals (29 files) 80.65%
1
comment: false
2

3
coverage:
4
  status:
5
    project:
6
      default:
7
        threshold: 100% # make CI green
8
    patch:
9
      default:
10
        threshold: 100% # make CI green
11

12
# ignore code coverage on following paths
13
ignore:
14
  - "**/tests"
15
  - "**/benches"
16
  - "**/examples"
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading