Compare commits
	
		
			2 Commits
		
	
	
		
			70b417865f
			...
			bd4144a5ba
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| bd4144a5ba | |||
| 1647ca3838 | 
| @ -26,3 +26,4 @@ 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" | ||||||
|  | |||||||
							
								
								
									
										63
									
								
								src/routes/authorize.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/routes/authorize.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | |||||||
|  | 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,13 +5,12 @@ use axum::{ | |||||||
|     RequestExt, |     RequestExt, | ||||||
| }; | }; | ||||||
| use axum_typed_multipart::{TryFromMultipart, TypedMultipart}; | use axum_typed_multipart::{TryFromMultipart, TypedMultipart}; | ||||||
| use snafu::prelude::*; | use tower_cookies::Cookies; | ||||||
| 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, sessions::LoggedInUser}, |     state::{COOKIE_NAME, RoutableAppState, LoggedInUser}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, TryFromMultipart, Deserialize)] | #[derive(Debug, TryFromMultipart, Deserialize)] | ||||||
| @ -74,7 +73,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 {}", &form.username)) |         Ok(format!("Welcome {}", session.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,15 +1,10 @@ | |||||||
| 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::{ | use crate::state::LoggedOutUser; | ||||||
|     COOKIE_NAME, |  | ||||||
|     RoutableAppState, |  | ||||||
|     sessions::LoggedOutUser, |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| pub async fn route(user: Option<LoggedOutUser>, cookies: Cookies, state: State<RoutableAppState>) -> String { | pub async fn route(user: Option<LoggedOutUser>, cookies: Cookies) -> 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,6 +6,7 @@ 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; | ||||||
| @ -16,6 +17,7 @@ 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 { | ||||||
|  | |||||||
							
								
								
									
										89
									
								
								src/state/login.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/state/login.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | |||||||
|  | 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(), | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										85
									
								
								src/state/logout.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/state/logout.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | |||||||
|  | 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,12 +1,16 @@ | |||||||
| use snafu::prelude::*; | use snafu::prelude::*; | ||||||
| use yunohost_api::{YunohostUsers, Username, Password}; | use yunohost_api::{YunohostUsers, Username, Password, YunohostPermissions}; | ||||||
| 
 | 
 | ||||||
| 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; | ||||||
| use sessions::SessionManager; | pub use sessions::{Session, SessionManager}; | ||||||
| 
 | 
 | ||||||
| pub type RoutableAppState = Arc<AppState>; | pub type RoutableAppState = Arc<AppState>; | ||||||
| 
 | 
 | ||||||
| @ -15,6 +19,7 @@ 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 { | ||||||
| @ -23,6 +28,8 @@ 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,20 +1,11 @@ | |||||||
| 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::{Cookies, Cookie}; | use tower_cookies::Cookie; | ||||||
| use yunohost_api::Username; | use yunohost_api::Username; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::utils::time::now; | ||||||
|     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)] | ||||||
| @ -217,151 +208,3 @@ 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(), |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								ssowat.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								ssowat.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | socket = /tmp/ssowat.socket | ||||||
|  | socket_group = www-data | ||||||
|  | socket_mode = "0o770" | ||||||
| @ -1,3 +1,4 @@ | |||||||
|  | use regex::Regex; | ||||||
| use serde::{Serialize, Deserialize}; | use serde::{Serialize, Deserialize}; | ||||||
| use url::Url; | use url::Url; | ||||||
| 
 | 
 | ||||||
| @ -23,12 +24,58 @@ 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