Compare commits
	
		
			No commits in common. "bd4144a5ba7cd7c5c184d547b800a765c84c41ef" and "70b417865f835ce385caab19ce5cb19945f1ae20" have entirely different histories.
		
	
	
		
			bd4144a5ba
			...
			70b417865f
		
	
		
| @ -25,5 +25,4 @@ axum_typed_multipart = "0.8" | |||||||
| async-trait = "0.1" | async-trait = "0.1" | ||||||
| serde = { version = "1", features = [ "derive" ] } | serde = { version = "1", features = [ "derive" ] } | ||||||
| file-owner = { version = "0.1" } | file-owner = { version = "0.1" } | ||||||
| tower-cookies = "0.9" | tower-cookies = "0.9" | ||||||
| url = "2.4" |  | ||||||
| @ -1,63 +0,0 @@ | |||||||
| use axum::{ |  | ||||||
| 	RequestPartsExt, |  | ||||||
| 	extract::{FromRequestParts, State}, |  | ||||||
| 	http::{StatusCode, header::HeaderMap, request::Parts}, |  | ||||||
| }; |  | ||||||
| use snafu::prelude::*; |  | ||||||
| use url::Url; |  | ||||||
| 
 |  | ||||||
| use crate::{ |  | ||||||
|     error::*, |  | ||||||
|     state::{RoutableAppState, LoggedInUser}, |  | ||||||
|     state::sessions::*, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // TODO: Implement as a typed header
 |  | ||||||
| pub struct OriginalURI(Url); |  | ||||||
| 
 |  | ||||||
| #[async_trait] |  | ||||||
| impl FromRequestParts<RoutableAppState> for OriginalURI { |  | ||||||
|     type Rejection = Error; |  | ||||||
| 
 |  | ||||||
|     async fn from_request_parts(parts: &mut Parts, state: &RoutableAppState) -> Result<Self, Self::Rejection> { |  | ||||||
| 		let headers: HeaderMap = parts.extract().await.unwrap(); |  | ||||||
| 		if let Some(uri) = headers.get("X-Original-URI") { |  | ||||||
| 			// TODO: error
 |  | ||||||
| 			Ok( |  | ||||||
| 				OriginalURI(Url::parse(uri.to_str().unwrap()).unwrap()) |  | ||||||
| 			) |  | ||||||
| 	    } else { |  | ||||||
| 			// TODO: error
 |  | ||||||
| 			panic!() |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl AsRef<Url> for OriginalURI { |  | ||||||
|     fn as_ref(&self) -> &Url { |  | ||||||
|         &self.0 |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #[debug_handler] |  | ||||||
| pub async fn route(user: Option<LoggedInUser>, uri: Option<OriginalURI>, state: State<RoutableAppState>) -> StatusCode { |  | ||||||
|     if uri.is_none() { |  | ||||||
|         panic!(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let uri = uri.unwrap(); |  | ||||||
|     let username = user.map(|u| u.username().clone()); |  | ||||||
| 
 |  | ||||||
|     match state.permissions.ssowat_config() { |  | ||||||
|         Ok(conf) => { |  | ||||||
|             if conf.user_has_permission_for_uri(username.as_ref(), uri.as_ref()) { |  | ||||||
|                 StatusCode::OK |  | ||||||
|             } else { |  | ||||||
|                 StatusCode::FORBIDDEN |  | ||||||
|             } |  | ||||||
|         }, Err(e) => { |  | ||||||
|             panic!() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -5,12 +5,13 @@ use axum::{ | |||||||
|     RequestExt, |     RequestExt, | ||||||
| }; | }; | ||||||
| use axum_typed_multipart::{TryFromMultipart, TypedMultipart}; | use axum_typed_multipart::{TryFromMultipart, TypedMultipart}; | ||||||
| use tower_cookies::Cookies; | use snafu::prelude::*; | ||||||
|  | use tower_cookies::{Cookies, Cookie}; | ||||||
| use yunohost_api::{Username, Password}; | use yunohost_api::{Username, Password}; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     error::*, |     error::*, | ||||||
|     state::{COOKIE_NAME, RoutableAppState, LoggedInUser}, |     state::{COOKIE_NAME, RoutableAppState, sessions::LoggedInUser}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, TryFromMultipart, Deserialize)] | #[derive(Debug, TryFromMultipart, Deserialize)] | ||||||
| @ -73,7 +74,7 @@ pub async fn route(user: Option<LoggedInUser>, cookies: Cookies, state: State<Ro | |||||||
|         debug!("Login was successful for user {}. Saving cookie now.", &form.username); |         debug!("Login was successful for user {}. Saving cookie now.", &form.username); | ||||||
|         let session = state.sessions.make_session(COOKIE_NAME, &form.username).await; |         let session = state.sessions.make_session(COOKIE_NAME, &form.username).await; | ||||||
|         cookies.add(session.cookie()); |         cookies.add(session.cookie()); | ||||||
|         Ok(format!("Welcome {}", session.username())) |         Ok(format!("Welcome {}", &form.username)) | ||||||
|     } else { |     } else { | ||||||
|         debug!("Login failed for user {}", &form.username); |         debug!("Login failed for user {}", &form.username); | ||||||
|         Ok(format!("Invalid login for {}", &form.username)) |         Ok(format!("Invalid login for {}", &form.username)) | ||||||
|  | |||||||
| @ -1,10 +1,15 @@ | |||||||
|  | use axum::extract::State; | ||||||
| use tower_cookies::{Cookies, Cookie}; | use tower_cookies::{Cookies, Cookie}; | ||||||
| 
 | 
 | ||||||
| use std::borrow::Cow; | use std::borrow::Cow; | ||||||
| 
 | 
 | ||||||
| use crate::state::LoggedOutUser; | use crate::state::{ | ||||||
|  |     COOKIE_NAME, | ||||||
|  |     RoutableAppState, | ||||||
|  |     sessions::LoggedOutUser, | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| pub async fn route(user: Option<LoggedOutUser>, cookies: Cookies) -> String { | pub async fn route(user: Option<LoggedOutUser>, cookies: Cookies, state: State<RoutableAppState>) -> String { | ||||||
|     if let Some(user) = user { |     if let Some(user) = user { | ||||||
|         let cookie = user.cookie(); |         let cookie = user.cookie(); | ||||||
|         cookies.remove(Cookie::new(Cow::Owned(cookie.name().to_string()), Cow::Owned(cookie.value().to_string()))); |         cookies.remove(Cookie::new(Cow::Owned(cookie.name().to_string()), Cow::Owned(cookie.value().to_string()))); | ||||||
|  | |||||||
| @ -6,7 +6,6 @@ use tower_cookies::CookieManagerLayer; | |||||||
| 
 | 
 | ||||||
| use crate::state::RoutableAppState; | use crate::state::RoutableAppState; | ||||||
| 
 | 
 | ||||||
| mod authorize; |  | ||||||
| mod index; | mod index; | ||||||
| mod login; | mod login; | ||||||
| mod logout; | mod logout; | ||||||
| @ -17,7 +16,6 @@ pub fn router(subpath: Option<String>, state: RoutableAppState) -> Router { | |||||||
|         .route("/", get(index::route)) |         .route("/", get(index::route)) | ||||||
|         .route("/login/", post(login::route)) |         .route("/login/", post(login::route)) | ||||||
|         .route("/logout/", get(logout::route)) |         .route("/logout/", get(logout::route)) | ||||||
|         .route("/authorize/", get(authorize::route)) |  | ||||||
|         .layer(CookieManagerLayer::new()) |         .layer(CookieManagerLayer::new()) | ||||||
|         .with_state(state); |         .with_state(state); | ||||||
|     if let Some(p) = subpath { |     if let Some(p) = subpath { | ||||||
|  | |||||||
| @ -1,89 +0,0 @@ | |||||||
| use axum::{ |  | ||||||
|     extract::FromRequestParts, |  | ||||||
|     http::request::Parts, |  | ||||||
|     RequestPartsExt, |  | ||||||
| }; |  | ||||||
| use snafu::prelude::*; |  | ||||||
| use tower_cookies::Cookies; |  | ||||||
| use yunohost_api::Username; |  | ||||||
| 
 |  | ||||||
| use crate::{ |  | ||||||
|     error::*, |  | ||||||
|     state::{COOKIE_NAME, RoutableAppState, sessions::*}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| #[derive(Clone, Debug)] |  | ||||||
| pub struct LoggedInUser { |  | ||||||
|     username: Username, |  | ||||||
|     timestamp: i64, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl LoggedInUser { |  | ||||||
|     pub fn new(username: Username, timestamp: i64) -> LoggedInUser { |  | ||||||
|         LoggedInUser { |  | ||||||
|             username, |  | ||||||
|             timestamp, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn username(&self) -> &Username { |  | ||||||
|         &self.username |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn timestamp(&self) -> i64 { |  | ||||||
|         self.timestamp |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl From<Session> for LoggedInUser { |  | ||||||
|     fn from(s: Session) -> LoggedInUser { |  | ||||||
|         LoggedInUser::new(s.username, s.timestamp) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl std::fmt::Display for LoggedInUser { |  | ||||||
|     fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |  | ||||||
|         self.username.fmt(fmt) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl AsRef<Username> for LoggedInUser { |  | ||||||
|     fn as_ref(&self) -> &Username { |  | ||||||
|         &self.username() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[async_trait] |  | ||||||
| impl FromRequestParts<RoutableAppState> for LoggedInUser { |  | ||||||
|     type Rejection = Error; |  | ||||||
| 
 |  | ||||||
|     async fn from_request_parts(parts: &mut Parts, state: &RoutableAppState) -> Result<Self, Self::Rejection> { |  | ||||||
|         let cookies: Cookies = parts.extract().await.map_err(|_| Error::Cookie)?; |  | ||||||
|         trace!("[SESSION:{}/login] Checking if the user has a valid session", COOKIE_NAME); |  | ||||||
|         let session_cookie = if let Some(session_cookie) = cookies.get(COOKIE_NAME) { |  | ||||||
|             trace!("[SESSION:{}/login] User claims to have a session with cookie: {}", COOKIE_NAME, &session_cookie); |  | ||||||
|             session_cookie |  | ||||||
|         } else { |  | ||||||
|             trace!("[SESSION:{}/login] No current session cookie", COOKIE_NAME); |  | ||||||
|             return Err(Error::Session { |  | ||||||
|                 source: SessionError::NoSession { |  | ||||||
|                     cookie_name: COOKIE_NAME.to_string(), |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         if let Some(session) = state.sessions.verify_cookie_session(session_cookie.value()).await.context(SessionSnafu)? { |  | ||||||
|             debug!("[SESSION:{}/login] User {} resumed session.", COOKIE_NAME, &session.username); |  | ||||||
|             return Ok(session.into()); |  | ||||||
|         } else { |  | ||||||
|             trace!("[SESSION:{}/login] User session is invalid or has expired.", COOKIE_NAME); |  | ||||||
|             return Err(Error::Session { |  | ||||||
|                 source: SessionError::InvalidOrExpired { |  | ||||||
|                     cookie_name: COOKIE_NAME.to_string(), |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,85 +0,0 @@ | |||||||
| use axum::{ |  | ||||||
|     extract::FromRequestParts, |  | ||||||
|     http::request::Parts, |  | ||||||
|     RequestPartsExt, |  | ||||||
| }; |  | ||||||
| use snafu::prelude::*; |  | ||||||
| use std::borrow::Cow; |  | ||||||
| use tower_cookies::{Cookies, Cookie}; |  | ||||||
| use yunohost_api::Username; |  | ||||||
| 
 |  | ||||||
| use crate::{ |  | ||||||
|     error::*, |  | ||||||
|     state::{COOKIE_NAME, RoutableAppState, sessions::*}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| pub struct LoggedOutUser(Session); |  | ||||||
| 
 |  | ||||||
| impl LoggedOutUser { |  | ||||||
|     pub fn new(session: Session) -> Self { |  | ||||||
|         Self(session) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn username(&self) -> &Username { |  | ||||||
|         self.0.username() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn cookie(&self) -> Cookie { |  | ||||||
|         Cookie::build( |  | ||||||
|             Cow::Owned(self.0.cookie_name.to_string()), |  | ||||||
|             Cow::Owned(self.0.content.to_string()), |  | ||||||
|         ).path( |  | ||||||
|             Cow::Owned("/".to_string()) |  | ||||||
|         ).finish() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl From<Session> for LoggedOutUser { |  | ||||||
|     fn from(s: Session) -> LoggedOutUser { |  | ||||||
|         LoggedOutUser::new(s) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[async_trait] |  | ||||||
| impl FromRequestParts<RoutableAppState> for LoggedOutUser { |  | ||||||
|     type Rejection = Error; |  | ||||||
| 
 |  | ||||||
|     async fn from_request_parts(parts: &mut Parts, state: &RoutableAppState) -> Result<Self, Self::Rejection> { |  | ||||||
|         let cookies: Cookies = parts.extract().await.map_err(|_| Error::Cookie)?; |  | ||||||
|         trace!("[SESSION:{}/logout] Checking if the user has a valid session", COOKIE_NAME); |  | ||||||
|         trace!("{:#?}", parts.headers); |  | ||||||
|         let session_cookie = if let Some(session_cookie) = cookies.get(COOKIE_NAME) { |  | ||||||
|             trace!("[SESSION:{}/logout] User claims to have a session with cookie: {}", COOKIE_NAME, &session_cookie); |  | ||||||
|             session_cookie |  | ||||||
|         } else { |  | ||||||
|             trace!("[SESSION:{}/logout] No current session cookie", COOKIE_NAME); |  | ||||||
|             return Err(Error::Session { |  | ||||||
|                 source: SessionError::NoSession { |  | ||||||
|                     cookie_name: COOKIE_NAME.to_string(), |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         if let Some(session) = state.sessions.verify_cookie_session(session_cookie.value()).await.context(SessionSnafu)? { |  | ||||||
|             debug!("[SESSION:{}/logout] User {} resumed session, requesting to log out.", COOKIE_NAME, &session.username); |  | ||||||
|             if state.sessions.invalidate_session(session.timestamp, &session.content).await { |  | ||||||
|                 return Ok(session.into()); |  | ||||||
|             } else { |  | ||||||
|                 warn!("[SESSION:{}/logout] User {} requested logout but was already logged out. Here's the session:\n{:#?}", COOKIE_NAME, &session.username, &session); |  | ||||||
|                 warn!("This is probably a race condition but is completely harmless."); |  | ||||||
|                 return Err(Error::Session { |  | ||||||
|                     source: SessionError::InvalidOrExpired { |  | ||||||
|                         cookie_name: COOKIE_NAME.to_string(), |  | ||||||
|                     } |  | ||||||
|                 }); |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             trace!("[SESSION:{}/logout] User session is invalid or has expired.", COOKIE_NAME); |  | ||||||
|             return Err(Error::Session { |  | ||||||
|                 source: SessionError::InvalidOrExpired { |  | ||||||
|                     cookie_name: COOKIE_NAME.to_string(), |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,16 +1,12 @@ | |||||||
| use snafu::prelude::*; | use snafu::prelude::*; | ||||||
| use yunohost_api::{YunohostUsers, Username, Password, YunohostPermissions}; | use yunohost_api::{YunohostUsers, Username, Password}; | ||||||
| 
 | 
 | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| 
 | 
 | ||||||
| use crate::error::*; | use crate::error::*; | ||||||
| 
 | 
 | ||||||
| pub mod login; |  | ||||||
| pub use login::LoggedInUser; |  | ||||||
| pub mod logout; |  | ||||||
| pub use logout::LoggedOutUser; |  | ||||||
| pub mod sessions; | pub mod sessions; | ||||||
| pub use sessions::{Session, SessionManager}; | use sessions::SessionManager; | ||||||
| 
 | 
 | ||||||
| pub type RoutableAppState = Arc<AppState>; | pub type RoutableAppState = Arc<AppState>; | ||||||
| 
 | 
 | ||||||
| @ -19,7 +15,6 @@ pub const COOKIE_NAME: &'static str = "yunohost.ssowat"; | |||||||
| pub struct AppState { | pub struct AppState { | ||||||
|     pub sessions: SessionManager, |     pub sessions: SessionManager, | ||||||
|     pub users: YunohostUsers, |     pub users: YunohostUsers, | ||||||
|     pub permissions: YunohostPermissions, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl AppState { | impl AppState { | ||||||
| @ -28,8 +23,6 @@ impl AppState { | |||||||
|             sessions: SessionManager::new().context(SessionSnafu)?, |             sessions: SessionManager::new().context(SessionSnafu)?, | ||||||
|             // Timeout in ms
 |             // Timeout in ms
 | ||||||
|             users: YunohostUsers::new(500).await.context(YunohostSnafu)?, |             users: YunohostUsers::new(500).await.context(YunohostSnafu)?, | ||||||
|             // TODO: make async
 |  | ||||||
|             permissions: YunohostPermissions::new().context(YunohostSnafu)?, |  | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,11 +1,20 @@ | |||||||
|  | use axum::{ | ||||||
|  |     extract::FromRequestParts, | ||||||
|  |     http::request::Parts, | ||||||
|  |     RequestPartsExt, | ||||||
|  | }; | ||||||
| use ring::{hmac,rand}; | use ring::{hmac,rand}; | ||||||
| use snafu::prelude::*; | use snafu::prelude::*; | ||||||
| use std::borrow::Cow; | use std::borrow::Cow; | ||||||
| use tokio::sync::RwLock; | use tokio::sync::RwLock; | ||||||
| use tower_cookies::Cookie; | use tower_cookies::{Cookies, Cookie}; | ||||||
| use yunohost_api::Username; | use yunohost_api::Username; | ||||||
| 
 | 
 | ||||||
| use crate::utils::time::now; | use crate::{ | ||||||
|  |     error::*, | ||||||
|  |     state::{COOKIE_NAME, RoutableAppState}, | ||||||
|  |     utils::time::now, | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| /// An error related to session management
 | /// An error related to session management
 | ||||||
| #[derive(Debug, Snafu)] | #[derive(Debug, Snafu)] | ||||||
| @ -208,3 +217,151 @@ impl Session { | |||||||
|         ).finish() |         ).finish() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #[derive(Clone, Debug)] | ||||||
|  | pub struct LoggedInUser { | ||||||
|  |     username: Username, | ||||||
|  |     timestamp: i64, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl LoggedInUser { | ||||||
|  |     pub fn new(username: Username, timestamp: i64) -> LoggedInUser { | ||||||
|  |         LoggedInUser { | ||||||
|  |             username, | ||||||
|  |             timestamp, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn as_str(&self) -> &str { | ||||||
|  |         self.username.as_str() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn username(&self) -> &Username { | ||||||
|  |         &self.username | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn timestamp(&self) -> i64 { | ||||||
|  |         self.timestamp | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<Session> for LoggedInUser { | ||||||
|  |     fn from(s: Session) -> LoggedInUser { | ||||||
|  |         LoggedInUser { | ||||||
|  |             username: s.username, | ||||||
|  |             timestamp: s.timestamp, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl std::fmt::Display for LoggedInUser { | ||||||
|  |     fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||||
|  |         self.username.fmt(fmt) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[async_trait] | ||||||
|  | impl FromRequestParts<RoutableAppState> for LoggedInUser { | ||||||
|  |     type Rejection = Error; | ||||||
|  | 
 | ||||||
|  |     async fn from_request_parts(parts: &mut Parts, state: &RoutableAppState) -> Result<Self, Self::Rejection> { | ||||||
|  |         let cookies: Cookies = parts.extract().await.map_err(|_| Error::Cookie)?; | ||||||
|  |         trace!("[SESSION:{}/login] Checking if the user has a valid session", COOKIE_NAME); | ||||||
|  |         let session_cookie = if let Some(session_cookie) = cookies.get(COOKIE_NAME) { | ||||||
|  |             trace!("[SESSION:{}/login] User claims to have a session with cookie: {}", COOKIE_NAME, &session_cookie); | ||||||
|  |             session_cookie | ||||||
|  |         } else { | ||||||
|  |             trace!("[SESSION:{}/login] No current session cookie", COOKIE_NAME); | ||||||
|  |             return Err(Error::Session { | ||||||
|  |                 source: SessionError::NoSession { | ||||||
|  |                     cookie_name: COOKIE_NAME.to_string(), | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         if let Some(session) = state.sessions.verify_cookie_session(session_cookie.value()).await.context(SessionSnafu)? { | ||||||
|  |             debug!("[SESSION:{}/login] User {} resumed session.", COOKIE_NAME, &session.username); | ||||||
|  |             return Ok(session.into()); | ||||||
|  |         } else { | ||||||
|  |             trace!("[SESSION:{}/login] User session is invalid or has expired.", COOKIE_NAME); | ||||||
|  |             return Err(Error::Session { | ||||||
|  |                 source: SessionError::InvalidOrExpired { | ||||||
|  |                     cookie_name: COOKIE_NAME.to_string(), | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub struct LoggedOutUser(Session); | ||||||
|  | 
 | ||||||
|  | impl LoggedOutUser { | ||||||
|  |     pub fn new(session: Session) -> Self { | ||||||
|  |         Self(session) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn username(&self) -> &Username { | ||||||
|  |         self.0.username() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn cookie(&self) -> Cookie { | ||||||
|  |         Cookie::build( | ||||||
|  |             Cow::Owned(self.0.cookie_name.to_string()), | ||||||
|  |             Cow::Owned(self.0.content.to_string()), | ||||||
|  |         ).path( | ||||||
|  |             Cow::Owned("/".to_string()) | ||||||
|  |         ).finish() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<Session> for LoggedOutUser { | ||||||
|  |     fn from(s: Session) -> LoggedOutUser { | ||||||
|  |         LoggedOutUser(s) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[async_trait] | ||||||
|  | impl FromRequestParts<RoutableAppState> for LoggedOutUser { | ||||||
|  |     type Rejection = Error; | ||||||
|  | 
 | ||||||
|  |     async fn from_request_parts(parts: &mut Parts, state: &RoutableAppState) -> Result<Self, Self::Rejection> { | ||||||
|  |         let cookies: Cookies = parts.extract().await.map_err(|_| Error::Cookie)?; | ||||||
|  |         trace!("[SESSION:{}/logout] Checking if the user has a valid session", COOKIE_NAME); | ||||||
|  |         trace!("{:#?}", parts.headers); | ||||||
|  |         let session_cookie = if let Some(session_cookie) = cookies.get(COOKIE_NAME) { | ||||||
|  |             trace!("[SESSION:{}/logout] User claims to have a session with cookie: {}", COOKIE_NAME, &session_cookie); | ||||||
|  |             session_cookie | ||||||
|  |         } else { | ||||||
|  |             trace!("[SESSION:{}/logout] No current session cookie", COOKIE_NAME); | ||||||
|  |             return Err(Error::Session { | ||||||
|  |                 source: SessionError::NoSession { | ||||||
|  |                     cookie_name: COOKIE_NAME.to_string(), | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         if let Some(session) = state.sessions.verify_cookie_session(session_cookie.value()).await.context(SessionSnafu)? { | ||||||
|  |             debug!("[SESSION:{}/logout] User {} resumed session, requesting to log out.", COOKIE_NAME, &session.username); | ||||||
|  |             if state.sessions.invalidate_session(session.timestamp, &session.content).await { | ||||||
|  |                 return Ok(session.into()); | ||||||
|  |             } else { | ||||||
|  |                 warn!("[SESSION:{}/logout] User {} requested logout but was already logged out. Here's the session:\n{:#?}", COOKIE_NAME, &session.username, &session); | ||||||
|  |                 warn!("This is probably a race condition but is completely harmless."); | ||||||
|  |                 return Err(Error::Session { | ||||||
|  |                     source: SessionError::InvalidOrExpired { | ||||||
|  |                         cookie_name: COOKIE_NAME.to_string(), | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             trace!("[SESSION:{}/logout] User session is invalid or has expired.", COOKIE_NAME); | ||||||
|  |             return Err(Error::Session { | ||||||
|  |                 source: SessionError::InvalidOrExpired { | ||||||
|  |                     cookie_name: COOKIE_NAME.to_string(), | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,3 +0,0 @@ | |||||||
| socket = /tmp/ssowat.socket |  | ||||||
| socket_group = www-data |  | ||||||
| socket_mode = "0o770" |  | ||||||
| @ -1,4 +1,3 @@ | |||||||
| use regex::Regex; |  | ||||||
| use serde::{Serialize, Deserialize}; | use serde::{Serialize, Deserialize}; | ||||||
| use url::Url; | use url::Url; | ||||||
| 
 | 
 | ||||||
| @ -24,58 +23,12 @@ impl SSOWatConfig { | |||||||
|                 // Domain not managed
 |                 // Domain not managed
 | ||||||
|                 return None; |                 return None; | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|             // Strip protocol but keep full URL
 |  | ||||||
|             let stripped_uri = AsRef::<str>::as_ref(uri) |  | ||||||
|                 .trim_start_matches("http") |  | ||||||
|                 .trim_start_matches("s") |  | ||||||
|                 .trim_start_matches("://"); |  | ||||||
| 
 |  | ||||||
|             // Check which app matches this URI, to find corresponding permission
 |  | ||||||
|             for (key, val) in &self.permissions { |  | ||||||
|                 for uri_format in &val.uris { |  | ||||||
|                     if uri_format.starts_with("re:") { |  | ||||||
|                         let uri_format = uri_format.trim_start_matches("re:"); |  | ||||||
|                         // TODO: generate regex in advance
 |  | ||||||
|                         // TODO: error
 |  | ||||||
|                         let re = Regex::new(uri_format).unwrap(); |  | ||||||
|                         if re.is_match(stripped_uri) { |  | ||||||
|                             return Some(key.clone()); |  | ||||||
|                         } |  | ||||||
|                     } else { |  | ||||||
|                         if stripped_uri.starts_with(uri_format) { |  | ||||||
|                             return Some(key.clone()); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             // No app URI matched
 |  | ||||||
|             return None; |  | ||||||
|         } else { |         } else { | ||||||
|             // No domain (eg. http://8.8.8.8/)
 |             // No domain (eg. http://8.8.8.8/)
 | ||||||
|             return None; |             return None; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     } |         todo!(); | ||||||
| 
 |  | ||||||
|     pub fn user_has_permission_for_uri(&self, username: Option<&Username>, uri: &Url) -> bool { |  | ||||||
|         if let Some(permission_name) = self.permission_for_uri(uri) { |  | ||||||
|             let permission = self.permissions.get(&permission_name).unwrap(); |  | ||||||
|             if permission.public { |  | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if let Some(username) = username { |  | ||||||
|                 permission.users.contains(username) |  | ||||||
|             } else { |  | ||||||
|                 // User is not logged-in. Non-public URIs are not authorized
 |  | ||||||
|                 false |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             // No permission matching this URI
 |  | ||||||
|             false |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user