1use std::task::{Context, Poll};
48use std::time::SystemTime;
49use std::{convert::Infallible, future::Future, rc::Rc};
50
51use cookie::{Cookie, CookieJar, Key, SameSite};
52use derive_more::{Display, From};
53use futures::future::{ok, FutureExt, LocalBoxFuture, Ready};
54use serde::{Deserialize, Serialize};
55use time::Duration;
56
57use ntex::http::header::{self, HeaderValue};
58use ntex::http::{error::HttpError, HttpMessage, Payload};
59use ntex::service::{Middleware, Service, ServiceCtx};
60use ntex::util::Extensions;
61use ntex::web::{
62 DefaultError, ErrorRenderer, FromRequest, HttpRequest, WebRequest, WebResponse,
63 WebResponseError,
64};
65
66#[derive(Clone)]
93pub struct Identity(HttpRequest);
94
95impl Identity {
96 pub fn identity(&self) -> Option<String> {
99 Identity::get_identity(&self.0.extensions())
100 }
101
102 pub fn remember(&self, identity: String) {
104 if let Some(id) = self.0.extensions_mut().get_mut::<IdentityItem>() {
105 id.id = Some(identity);
106 id.changed = true;
107 }
108 }
109
110 pub fn forget(&self) {
113 if let Some(id) = self.0.extensions_mut().get_mut::<IdentityItem>() {
114 id.id = None;
115 id.changed = true;
116 }
117 }
118
119 fn get_identity(extensions: &Extensions) -> Option<String> {
120 if let Some(id) = extensions.get::<IdentityItem>() {
121 id.id.clone()
122 } else {
123 None
124 }
125 }
126}
127
128struct IdentityItem {
129 id: Option<String>,
130 changed: bool,
131}
132
133pub trait RequestIdentity {
138 fn get_identity(&self) -> Option<String>;
139}
140
141impl<T> RequestIdentity for T
142where
143 T: HttpMessage,
144{
145 fn get_identity(&self) -> Option<String> {
146 Identity::get_identity(&self.message_extensions())
147 }
148}
149
150impl<Err: ErrorRenderer> FromRequest<Err> for Identity {
166 type Error = Infallible;
167 type Future = Ready<Result<Identity, Infallible>>;
168
169 #[inline]
170 fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
171 ok(Identity(req.clone()))
172 }
173}
174
175pub trait IdentityPolicy<Err>: Sized + 'static {
177 type Future: Future<Output = Result<Option<String>, Self::Error>>;
179
180 type ResponseFuture: Future<Output = Result<(), Self::Error>>;
182
183 type Error;
185
186 fn from_request(&self, request: &mut WebRequest<Err>) -> Self::Future;
188
189 fn to_response(
191 &self,
192 identity: Option<String>,
193 changed: bool,
194 response: &mut WebResponse,
195 ) -> Self::ResponseFuture;
196}
197
198pub struct IdentityService<T> {
212 backend: Rc<T>,
213}
214
215impl<T> IdentityService<T> {
216 pub fn new(backend: T) -> Self {
218 IdentityService { backend: Rc::new(backend) }
219 }
220}
221
222impl<S, T> Middleware<S> for IdentityService<T> {
223 type Service = IdentityServiceMiddleware<S, T>;
224
225 fn create(&self, service: S) -> Self::Service {
226 IdentityServiceMiddleware { service, backend: self.backend.clone() }
227 }
228}
229
230#[doc(hidden)]
231pub struct IdentityServiceMiddleware<S, T> {
232 backend: Rc<T>,
233 service: S,
234}
235
236impl<S: Clone, T> Clone for IdentityServiceMiddleware<S, T> {
237 fn clone(&self) -> Self {
238 Self { backend: self.backend.clone(), service: self.service.clone() }
239 }
240}
241
242impl<S, T, Err> Service<WebRequest<Err>> for IdentityServiceMiddleware<S, T>
243where
244 S: Service<WebRequest<Err>, Response = WebResponse> + 'static,
245 T: IdentityPolicy<Err>,
246 Err: ErrorRenderer,
247 Err::Container: From<S::Error>,
248 Err::Container: From<T::Error>,
249{
250 type Response = WebResponse;
251 type Error = S::Error;
252 type Future<'f> = LocalBoxFuture<'f, Result<Self::Response, Self::Error>>;
253
254 fn poll_ready(&self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
255 self.service.poll_ready(cx)
256 }
257
258 fn poll_shutdown(&self, cx: &mut Context) -> Poll<()> {
259 self.service.poll_shutdown(cx)
260 }
261
262 fn call<'a>(
263 &'a self,
264 mut req: WebRequest<Err>,
265 ctx: ServiceCtx<'a, Self>,
266 ) -> Self::Future<'a> {
267 async move {
268 match self.backend.from_request(&mut req).await {
269 Ok(id) => {
270 req.extensions_mut().insert(IdentityItem { id, changed: false });
271
272 let mut res = ctx.call(&self.service, req).await?;
274 let id = res.request().extensions_mut().remove::<IdentityItem>();
275
276 if let Some(id) = id {
277 match self.backend.to_response(id.id, id.changed, &mut res).await {
278 Ok(_) => Ok(res),
279 Err(e) => Ok(WebResponse::error_response::<Err, _>(res, e)),
280 }
281 } else {
282 Ok(res)
283 }
284 }
285 Err(err) => Ok(req.error_response(err)),
286 }
287 }
288 .boxed_local()
289 }
290}
291
292struct CookieIdentityInner {
293 key: Key,
294 key_v2: Key,
295 name: String,
296 path: String,
297 domain: Option<String>,
298 secure: bool,
299 max_age: Option<Duration>,
300 same_site: Option<SameSite>,
301 visit_deadline: Option<Duration>,
302 login_deadline: Option<Duration>,
303}
304
305#[derive(Deserialize, Serialize, Debug)]
306struct CookieValue {
307 identity: String,
308 #[serde(skip_serializing_if = "Option::is_none")]
309 login_timestamp: Option<SystemTime>,
310 #[serde(skip_serializing_if = "Option::is_none")]
311 visit_timestamp: Option<SystemTime>,
312}
313
314#[derive(Debug)]
315struct CookieIdentityExtention {
316 login_timestamp: Option<SystemTime>,
317}
318
319impl CookieIdentityInner {
320 fn new(key: &[u8]) -> CookieIdentityInner {
321 let key_v2: Vec<u8> = key.iter().chain([1, 0, 0, 0].iter()).cloned().collect();
322 CookieIdentityInner {
323 key: Key::derive_from(key),
324 key_v2: Key::derive_from(&key_v2),
325 name: "ntex-identity".to_owned(),
326 path: "/".to_owned(),
327 domain: None,
328 secure: true,
329 max_age: None,
330 same_site: None,
331 visit_deadline: None,
332 login_deadline: None,
333 }
334 }
335
336 fn set_cookie(
337 &self,
338 resp: &mut WebResponse,
339 value: Option<CookieValue>,
340 ) -> Result<(), CookieIdentityPolicyError> {
341 let add_cookie = value.is_some();
342 let val = value.map(|val| {
343 if !self.legacy_supported() {
344 serde_json::to_string(&val)
345 } else {
346 Ok(val.identity)
347 }
348 });
349 let mut cookie =
350 Cookie::new(self.name.clone(), val.unwrap_or_else(|| Ok(String::new()))?);
351 cookie.set_path(self.path.clone());
352 cookie.set_secure(self.secure);
353 cookie.set_http_only(true);
354 cookie.set_max_age(self.max_age);
355
356 if let Some(ref domain) = self.domain {
357 cookie.set_domain(domain.clone());
358 }
359
360 if let Some(same_site) = self.same_site {
361 cookie.set_same_site(same_site);
362 }
363
364 let mut jar = CookieJar::new();
365 let key = if self.legacy_supported() { &self.key } else { &self.key_v2 };
366 if add_cookie {
367 jar.private_mut(key).add(cookie);
368 } else {
369 jar.add_original(cookie.clone());
370 jar.private_mut(key).remove(cookie);
371 }
372 for cookie in jar.delta() {
373 let val = HeaderValue::from_str(&cookie.to_string()).map_err(HttpError::from)?;
374 resp.headers_mut().append(header::SET_COOKIE, val);
375 }
376 Ok(())
377 }
378
379 fn load<Err>(&self, req: &WebRequest<Err>) -> Option<CookieValue> {
380 let cookie = req.cookie(&self.name)?;
381 let mut jar = CookieJar::new();
382 jar.add_original(cookie.clone());
383 let res = if self.legacy_supported() {
384 jar.private(&self.key).get(&self.name).map(|n| CookieValue {
385 identity: n.value().to_string(),
386 login_timestamp: None,
387 visit_timestamp: None,
388 })
389 } else {
390 None
391 };
392 res.or_else(|| jar.private(&self.key_v2).get(&self.name).and_then(|c| self.parse(c)))
393 }
394
395 fn parse(&self, cookie: Cookie) -> Option<CookieValue> {
396 let value: CookieValue = serde_json::from_str(cookie.value()).ok()?;
397 let now = SystemTime::now();
398 if let Some(visit_deadline) = self.visit_deadline {
399 if now.duration_since(value.visit_timestamp?).ok()? > visit_deadline {
400 return None;
401 }
402 }
403 if let Some(login_deadline) = self.login_deadline {
404 if now.duration_since(value.login_timestamp?).ok()? > login_deadline {
405 return None;
406 }
407 }
408 Some(value)
409 }
410
411 fn legacy_supported(&self) -> bool {
412 self.visit_deadline.is_none() && self.login_deadline.is_none()
413 }
414
415 fn always_update_cookie(&self) -> bool {
416 self.visit_deadline.is_some()
417 }
418
419 fn requires_oob_data(&self) -> bool {
420 self.login_deadline.is_some()
421 }
422}
423
424pub struct CookieIdentityPolicy(Rc<CookieIdentityInner>);
447
448#[derive(Debug, Display, From)]
449pub enum CookieIdentityPolicyError {
450 Http(HttpError),
451 Json(serde_json::error::Error),
452}
453
454impl WebResponseError<DefaultError> for CookieIdentityPolicyError {}
455
456impl CookieIdentityPolicy {
457 pub fn new(key: &[u8]) -> Self {
461 CookieIdentityPolicy(Rc::new(CookieIdentityInner::new(key)))
462 }
463
464 pub fn path<S: Into<String>>(mut self, value: S) -> Self {
466 Rc::get_mut(&mut self.0).unwrap().path = value.into();
467 self
468 }
469
470 pub fn name<S: Into<String>>(mut self, value: S) -> Self {
472 Rc::get_mut(&mut self.0).unwrap().name = value.into();
473 self
474 }
475
476 pub fn domain<S: Into<String>>(mut self, value: S) -> Self {
478 Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into());
479 self
480 }
481
482 pub fn secure(mut self, value: bool) -> Self {
487 Rc::get_mut(&mut self.0).unwrap().secure = value;
488 self
489 }
490
491 pub fn max_age(self, seconds: i64) -> Self {
493 self.max_age_time(Duration::seconds(seconds))
494 }
495
496 pub fn max_age_time(mut self, value: Duration) -> Self {
498 Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
499 self
500 }
501
502 pub fn same_site(mut self, same_site: SameSite) -> Self {
504 Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site);
505 self
506 }
507
508 pub fn visit_deadline(mut self, value: Duration) -> Self {
512 Rc::get_mut(&mut self.0).unwrap().visit_deadline = Some(value);
513 self
514 }
515
516 pub fn login_deadline(mut self, value: Duration) -> Self {
520 Rc::get_mut(&mut self.0).unwrap().login_deadline = Some(value);
521 self
522 }
523}
524
525impl<Err: ErrorRenderer> IdentityPolicy<Err> for CookieIdentityPolicy {
526 type Error = CookieIdentityPolicyError;
527 type Future = Ready<Result<Option<String>, CookieIdentityPolicyError>>;
528 type ResponseFuture = Ready<Result<(), CookieIdentityPolicyError>>;
529
530 fn from_request(&self, req: &mut WebRequest<Err>) -> Self::Future {
531 ok(self.0.load(req).map(|CookieValue { identity, login_timestamp, .. }| {
532 if self.0.requires_oob_data() {
533 req.extensions_mut().insert(CookieIdentityExtention { login_timestamp });
534 }
535 identity
536 }))
537 }
538
539 fn to_response(
540 &self,
541 id: Option<String>,
542 changed: bool,
543 res: &mut WebResponse,
544 ) -> Self::ResponseFuture {
545 let _ = if changed {
546 let login_timestamp = SystemTime::now();
547 self.0.set_cookie(
548 res,
549 id.map(|identity| CookieValue {
550 identity,
551 login_timestamp: self.0.login_deadline.map(|_| login_timestamp),
552 visit_timestamp: self.0.visit_deadline.map(|_| login_timestamp),
553 }),
554 )
555 } else if self.0.always_update_cookie() && id.is_some() {
556 let visit_timestamp = SystemTime::now();
557 let login_timestamp = if self.0.requires_oob_data() {
558 let CookieIdentityExtention { login_timestamp: lt } =
559 res.request().extensions_mut().remove().unwrap();
560 lt
561 } else {
562 None
563 };
564 self.0.set_cookie(
565 res,
566 Some(CookieValue {
567 identity: id.unwrap(),
568 login_timestamp,
569 visit_timestamp: self.0.visit_deadline.map(|_| visit_timestamp),
570 }),
571 )
572 } else {
573 Ok(())
574 };
575 ok(())
576 }
577}
578
579#[cfg(test)]
580mod tests {
581 use std::borrow::Borrow;
582
583 use super::*;
584 use ntex::web::test::{self, TestRequest};
585 use ntex::web::{self, error, App, Error, HttpResponse};
586 use ntex::{http::StatusCode, service::into_service, service::Pipeline, time};
587
588 const COOKIE_KEY_MASTER: [u8; 32] = [0; 32];
589 const COOKIE_NAME: &str = "ntex_auth";
590 const COOKIE_LOGIN: &str = "test";
591
592 #[ntex::test]
593 async fn test_identity() {
594 let srv = test::init_service(
595 App::new()
596 .wrap(IdentityService::new(
597 CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
598 .domain("www.rust-lang.org")
599 .name(COOKIE_NAME)
600 .path("/")
601 .secure(true),
602 ))
603 .service(web::resource("/index").to(|id: Identity| async move {
604 if id.identity().is_some() {
605 HttpResponse::Created()
606 } else {
607 HttpResponse::Ok()
608 }
609 }))
610 .service(web::resource("/login").to(|id: Identity| async move {
611 id.remember(COOKIE_LOGIN.to_string());
612 HttpResponse::Ok()
613 }))
614 .service(web::resource("/logout").to(|id: Identity| async move {
615 if id.identity().is_some() {
616 id.forget();
617 HttpResponse::Ok()
618 } else {
619 HttpResponse::BadRequest()
620 }
621 })),
622 )
623 .await;
624 let resp = test::call_service(&srv, TestRequest::with_uri("/index").to_request()).await;
625 assert_eq!(resp.status(), StatusCode::OK);
626
627 let resp = test::call_service(&srv, TestRequest::with_uri("/login").to_request()).await;
628 assert_eq!(resp.status(), StatusCode::OK);
629 let c = resp.response().cookies().next().unwrap().to_owned();
630
631 let resp = test::call_service(
632 &srv,
633 TestRequest::with_uri("/index").cookie(c.clone()).to_request(),
634 )
635 .await;
636 assert_eq!(resp.status(), StatusCode::CREATED);
637
638 let resp = test::call_service(
639 &srv,
640 TestRequest::with_uri("/logout").cookie(c.clone()).to_request(),
641 )
642 .await;
643 assert_eq!(resp.status(), StatusCode::OK);
644 assert!(resp.headers().contains_key(header::SET_COOKIE))
645 }
646
647 #[ntex::test]
648 async fn test_identity_max_age_time() {
649 let duration = Duration::days(1);
650 let srv = test::init_service(
651 App::new()
652 .wrap(IdentityService::new(
653 CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
654 .domain("www.rust-lang.org")
655 .name(COOKIE_NAME)
656 .path("/")
657 .max_age_time(duration)
658 .secure(true),
659 ))
660 .service(web::resource("/login").to(|id: Identity| async move {
661 id.remember("test".to_string());
662 HttpResponse::Ok()
663 })),
664 )
665 .await;
666 let resp = test::call_service(&srv, TestRequest::with_uri("/login").to_request()).await;
667 assert_eq!(resp.status(), StatusCode::OK);
668 assert!(resp.headers().contains_key(header::SET_COOKIE));
669 let c = resp.response().cookies().next().unwrap().to_owned();
670 assert_eq!(duration, c.max_age().unwrap());
671 }
672
673 #[ntex::test]
674 async fn test_identity_max_age() {
675 let seconds = 60;
676 let srv = test::init_service(
677 App::new()
678 .wrap(IdentityService::new(
679 CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
680 .domain("www.rust-lang.org")
681 .name(COOKIE_NAME)
682 .path("/")
683 .max_age(seconds)
684 .secure(true),
685 ))
686 .service(web::resource("/login").to(|id: Identity| async move {
687 id.remember("test".to_string());
688 HttpResponse::Ok()
689 })),
690 )
691 .await;
692 let resp = test::call_service(&srv, TestRequest::with_uri("/login").to_request()).await;
693 assert_eq!(resp.status(), StatusCode::OK);
694 assert!(resp.headers().contains_key(header::SET_COOKIE));
695 let c = resp.response().cookies().next().unwrap().to_owned();
696 assert_eq!(Duration::seconds(seconds), c.max_age().unwrap());
697 }
698
699 async fn create_identity_server<
700 F: Fn(CookieIdentityPolicy) -> CookieIdentityPolicy + Sync + Send + Clone + 'static,
701 >(
702 f: F,
703 ) -> Pipeline<
704 impl ntex::service::Service<ntex::http::Request, Response = WebResponse, Error = Error>,
705 > {
706 test::init_service(
707 App::new()
708 .wrap(IdentityService::new(f(CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
709 .secure(false)
710 .name(COOKIE_NAME))))
711 .service(web::resource("/").to(|id: Identity| async move {
712 let identity = id.identity();
713 if identity.is_none() {
714 id.remember(COOKIE_LOGIN.to_string())
715 }
716 web::types::Json(identity)
717 })),
718 )
719 .await
720 }
721
722 fn legacy_login_cookie(identity: &'static str) -> Cookie<'static> {
723 let mut jar = CookieJar::new();
724 jar.private_mut(&Key::derive_from(&COOKIE_KEY_MASTER))
725 .add(Cookie::new(COOKIE_NAME, identity));
726 jar.get(COOKIE_NAME).unwrap().clone()
727 }
728
729 fn login_cookie(
730 identity: &'static str,
731 login_timestamp: Option<SystemTime>,
732 visit_timestamp: Option<SystemTime>,
733 ) -> Cookie<'static> {
734 let mut jar = CookieJar::new();
735 let key: Vec<u8> =
736 COOKIE_KEY_MASTER.iter().chain([1, 0, 0, 0].iter()).copied().collect();
737 jar.private_mut(&Key::derive_from(&key)).add(Cookie::new(
738 COOKIE_NAME,
739 serde_json::to_string(&CookieValue {
740 identity: identity.to_string(),
741 login_timestamp,
742 visit_timestamp,
743 })
744 .unwrap(),
745 ));
746 jar.get(COOKIE_NAME).unwrap().clone()
747 }
748
749 async fn assert_logged_in(response: WebResponse, identity: Option<&str>) {
750 let bytes = test::read_body(response).await;
751 let resp: Option<String> = serde_json::from_slice(&bytes[..]).unwrap();
752 assert_eq!(resp.as_ref().map(|s| s.borrow()), identity);
753 }
754
755 fn assert_legacy_login_cookie(response: &mut WebResponse, identity: &str) {
756 let mut cookies = CookieJar::new();
757 for cookie in response.headers().get_all(header::SET_COOKIE) {
758 cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
759 }
760 let cookie =
761 cookies.private(&Key::derive_from(&COOKIE_KEY_MASTER)).get(COOKIE_NAME).unwrap();
762 assert_eq!(cookie.value(), identity);
763 }
764
765 enum LoginTimestampCheck {
766 Incorrect,
767 New,
768 Old(SystemTime),
769 }
770
771 enum VisitTimeStampCheck {
772 NoTimestamp,
773 NewTimestamp,
774 }
775
776 fn assert_login_cookie(
777 response: &mut WebResponse,
778 identity: &str,
779 login_timestamp: LoginTimestampCheck,
780 visit_timestamp: VisitTimeStampCheck,
781 ) {
782 let mut cookies = CookieJar::new();
783 for cookie in response.headers().get_all(header::SET_COOKIE) {
784 cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
785 }
786 let key: Vec<u8> =
787 COOKIE_KEY_MASTER.iter().chain([1, 0, 0, 0].iter()).copied().collect();
788 let cookie = cookies.private(&Key::derive_from(&key)).get(COOKIE_NAME).unwrap();
789 let cv: CookieValue = serde_json::from_str(cookie.value()).unwrap();
790 assert_eq!(cv.identity, identity);
791 let now = SystemTime::now();
792 let t30sec_ago = now - Duration::seconds(30);
793 match login_timestamp {
794 LoginTimestampCheck::Incorrect => assert_eq!(cv.login_timestamp, None),
795 LoginTimestampCheck::New => assert!(
796 t30sec_ago <= cv.login_timestamp.unwrap() && cv.login_timestamp.unwrap() <= now
797 ),
798 LoginTimestampCheck::Old(old_timestamp) => {
799 assert_eq!(cv.login_timestamp, Some(old_timestamp))
800 }
801 }
802 match visit_timestamp {
803 VisitTimeStampCheck::NoTimestamp => assert_eq!(cv.visit_timestamp, None),
804 VisitTimeStampCheck::NewTimestamp => assert!(
805 t30sec_ago <= cv.visit_timestamp.unwrap() && cv.visit_timestamp.unwrap() <= now
806 ),
807 }
808 }
809
810 fn assert_no_login_cookie(response: &mut WebResponse) {
811 let mut cookies = CookieJar::new();
812 for cookie in response.headers().get_all(header::SET_COOKIE) {
813 cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
814 }
815 assert!(cookies.get(COOKIE_NAME).is_none());
816 }
817
818 #[ntex::test]
819 async fn test_identity_legacy_cookie_is_set() {
820 let srv = create_identity_server(|c| c).await;
821 let mut resp = test::call_service(&srv, TestRequest::with_uri("/").to_request()).await;
822 assert_legacy_login_cookie(&mut resp, COOKIE_LOGIN);
823 assert_logged_in(resp, None).await;
824 }
825
826 #[ntex::test]
827 async fn test_identity_legacy_cookie_works() {
828 let srv = create_identity_server(|c| c).await;
829 let cookie = legacy_login_cookie(COOKIE_LOGIN);
830 let mut resp = test::call_service(
831 &srv,
832 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
833 )
834 .await;
835 assert_no_login_cookie(&mut resp);
836 assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
837 }
838
839 #[ntex::test]
840 async fn test_identity_legacy_cookie_rejected_if_visit_timestamp_needed() {
841 let srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))).await;
842 let cookie = legacy_login_cookie(COOKIE_LOGIN);
843 let mut resp = test::call_service(
844 &srv,
845 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
846 )
847 .await;
848 assert_login_cookie(
849 &mut resp,
850 COOKIE_LOGIN,
851 LoginTimestampCheck::Incorrect,
852 VisitTimeStampCheck::NewTimestamp,
853 );
854 assert_logged_in(resp, None).await;
855 }
856
857 #[ntex::test]
858 async fn test_identity_legacy_cookie_rejected_if_login_timestamp_needed() {
859 let srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
860 let cookie = legacy_login_cookie(COOKIE_LOGIN);
861 let mut resp = test::call_service(
862 &srv,
863 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
864 )
865 .await;
866 assert_login_cookie(
867 &mut resp,
868 COOKIE_LOGIN,
869 LoginTimestampCheck::New,
870 VisitTimeStampCheck::NoTimestamp,
871 );
872 assert_logged_in(resp, None).await;
873 }
874
875 #[ntex::test]
876 async fn test_identity_cookie_rejected_if_login_timestamp_needed() {
877 let srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
878 let cookie = login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now()));
879 let mut resp = test::call_service(
880 &srv,
881 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
882 )
883 .await;
884 assert_login_cookie(
885 &mut resp,
886 COOKIE_LOGIN,
887 LoginTimestampCheck::New,
888 VisitTimeStampCheck::NoTimestamp,
889 );
890 assert_logged_in(resp, None).await;
891 }
892
893 #[ntex::test]
894 async fn test_identity_cookie_rejected_if_visit_timestamp_needed() {
895 let srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))).await;
896 let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None);
897 let mut resp = test::call_service(
898 &srv,
899 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
900 )
901 .await;
902 assert_login_cookie(
903 &mut resp,
904 COOKIE_LOGIN,
905 LoginTimestampCheck::Incorrect,
906 VisitTimeStampCheck::NewTimestamp,
907 );
908 assert_logged_in(resp, None).await;
909 }
910
911 #[ntex::test]
912 async fn test_identity_cookie_rejected_if_login_timestamp_too_old() {
913 let srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
914 let cookie =
915 login_cookie(COOKIE_LOGIN, Some(SystemTime::now() - Duration::days(180)), None);
916 let mut resp = test::call_service(
917 &srv,
918 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
919 )
920 .await;
921 assert_login_cookie(
922 &mut resp,
923 COOKIE_LOGIN,
924 LoginTimestampCheck::New,
925 VisitTimeStampCheck::NoTimestamp,
926 );
927 assert_logged_in(resp, None).await;
928 }
929
930 #[ntex::test]
931 async fn test_identity_cookie_rejected_if_visit_timestamp_too_old() {
932 let srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))).await;
933 let cookie =
934 login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now() - Duration::days(180)));
935 let mut resp = test::call_service(
936 &srv,
937 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
938 )
939 .await;
940 assert_login_cookie(
941 &mut resp,
942 COOKIE_LOGIN,
943 LoginTimestampCheck::Incorrect,
944 VisitTimeStampCheck::NewTimestamp,
945 );
946 assert_logged_in(resp, None).await;
947 }
948
949 #[ntex::test]
950 async fn test_identity_cookie_not_updated_on_login_deadline() {
951 let srv = create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
952 let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None);
953 let mut resp = test::call_service(
954 &srv,
955 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
956 )
957 .await;
958 assert_no_login_cookie(&mut resp);
959 assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
960 }
961
962 #[ntex::test]
964 async fn test_identity_cookie_updated_on_visit_deadline() {
965 let srv = create_identity_server(|c| {
966 c.visit_deadline(Duration::days(90)).login_deadline(Duration::days(90))
967 })
968 .await;
969 let timestamp = SystemTime::now() - Duration::days(1);
970 let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp));
971 let mut resp = test::call_service(
972 &srv,
973 TestRequest::with_uri("/").cookie(cookie.clone()).to_request(),
974 )
975 .await;
976 assert_login_cookie(
977 &mut resp,
978 COOKIE_LOGIN,
979 LoginTimestampCheck::Old(timestamp),
980 VisitTimeStampCheck::NewTimestamp,
981 );
982 assert_logged_in(resp, Some(COOKIE_LOGIN)).await;
983 }
984
985 #[ntex::test]
986 async fn test_borrowed_mut_error() {
987 use futures::future::{lazy, ok, Ready};
988 use ntex::web::{DefaultError, Error};
989
990 struct Ident;
991 impl<Err: ErrorRenderer> IdentityPolicy<Err> for Ident {
992 type Error = Error;
993 type Future = Ready<Result<Option<String>, Error>>;
994 type ResponseFuture = Ready<Result<(), Error>>;
995
996 fn from_request(&self, _: &mut WebRequest<Err>) -> Self::Future {
997 ok(Some("test".to_string()))
998 }
999
1000 fn to_response(
1001 &self,
1002 _: Option<String>,
1003 _: bool,
1004 _: &mut WebResponse,
1005 ) -> Self::ResponseFuture {
1006 ok(())
1007 }
1008 }
1009
1010 let srv: Pipeline<_> = IdentityServiceMiddleware {
1011 backend: Rc::new(Ident),
1012 service: into_service(|_: WebRequest<DefaultError>| async move {
1013 time::sleep(time::Seconds(100)).await;
1014 Err::<WebResponse, _>(error::ErrorBadRequest("error"))
1015 }),
1016 }
1017 .into();
1018
1019 let srv2 = srv.clone();
1020 let req = TestRequest::default().to_srv_request();
1021 ntex::rt::spawn(async move {
1022 let _ = srv2.call(req).await;
1023 });
1024 time::sleep(time::Millis(50)).await;
1025
1026 let _ = lazy(|cx| srv.poll_ready(cx)).await;
1027 }
1028}