Compare commits
	
		
			2 Commits
		
	
	
		
			70b417865f
			...
			bd4144a5ba
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| bd4144a5ba | |||
| 1647ca3838 | 
| @ -26,3 +26,4 @@ async-trait = "0.1" | ||||
| serde = { version = "1", features = [ "derive" ] } | ||||
| file-owner = { version = "0.1" } | ||||
| 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, | ||||
| }; | ||||
| use axum_typed_multipart::{TryFromMultipart, TypedMultipart}; | ||||
| use snafu::prelude::*; | ||||
| use tower_cookies::{Cookies, Cookie}; | ||||
| use tower_cookies::Cookies; | ||||
| use yunohost_api::{Username, Password}; | ||||
| 
 | ||||
| use crate::{ | ||||
|     error::*, | ||||
|     state::{COOKIE_NAME, RoutableAppState, sessions::LoggedInUser}, | ||||
|     state::{COOKIE_NAME, RoutableAppState, LoggedInUser}, | ||||
| }; | ||||
| 
 | ||||
| #[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); | ||||
|         let session = state.sessions.make_session(COOKIE_NAME, &form.username).await; | ||||
|         cookies.add(session.cookie()); | ||||
|         Ok(format!("Welcome {}", &form.username)) | ||||
|         Ok(format!("Welcome {}", session.username())) | ||||
|     } else { | ||||
|         debug!("Login failed for user {}", &form.username); | ||||
|         Ok(format!("Invalid login for {}", &form.username)) | ||||
|  | ||||
| @ -1,15 +1,10 @@ | ||||
| use axum::extract::State; | ||||
| use tower_cookies::{Cookies, Cookie}; | ||||
| 
 | ||||
| use std::borrow::Cow; | ||||
| 
 | ||||
| use crate::state::{ | ||||
|     COOKIE_NAME, | ||||
|     RoutableAppState, | ||||
|     sessions::LoggedOutUser, | ||||
| }; | ||||
| use crate::state::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 { | ||||
|         let cookie = user.cookie(); | ||||
|         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; | ||||
| 
 | ||||
| mod authorize; | ||||
| mod index; | ||||
| mod login; | ||||
| mod logout; | ||||
| @ -16,6 +17,7 @@ pub fn router(subpath: Option<String>, state: RoutableAppState) -> Router { | ||||
|         .route("/", get(index::route)) | ||||
|         .route("/login/", post(login::route)) | ||||
|         .route("/logout/", get(logout::route)) | ||||
|         .route("/authorize/", get(authorize::route)) | ||||
|         .layer(CookieManagerLayer::new()) | ||||
|         .with_state(state); | ||||
|     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 yunohost_api::{YunohostUsers, Username, Password}; | ||||
| use yunohost_api::{YunohostUsers, Username, Password, YunohostPermissions}; | ||||
| 
 | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use crate::error::*; | ||||
| 
 | ||||
| pub mod login; | ||||
| pub use login::LoggedInUser; | ||||
| pub mod logout; | ||||
| pub use logout::LoggedOutUser; | ||||
| pub mod sessions; | ||||
| use sessions::SessionManager; | ||||
| pub use sessions::{Session, SessionManager}; | ||||
| 
 | ||||
| pub type RoutableAppState = Arc<AppState>; | ||||
| 
 | ||||
| @ -15,6 +19,7 @@ pub const COOKIE_NAME: &'static str = "yunohost.ssowat"; | ||||
| pub struct AppState { | ||||
|     pub sessions: SessionManager, | ||||
|     pub users: YunohostUsers, | ||||
|     pub permissions: YunohostPermissions, | ||||
| } | ||||
| 
 | ||||
| impl AppState { | ||||
| @ -23,6 +28,8 @@ impl AppState { | ||||
|             sessions: SessionManager::new().context(SessionSnafu)?, | ||||
|             // Timeout in ms
 | ||||
|             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 snafu::prelude::*; | ||||
| use std::borrow::Cow; | ||||
| use tokio::sync::RwLock; | ||||
| use tower_cookies::{Cookies, Cookie}; | ||||
| use tower_cookies::Cookie; | ||||
| use yunohost_api::Username; | ||||
| 
 | ||||
| use crate::{ | ||||
|     error::*, | ||||
|     state::{COOKIE_NAME, RoutableAppState}, | ||||
|     utils::time::now, | ||||
| }; | ||||
| use crate::utils::time::now; | ||||
| 
 | ||||
| /// An error related to session management
 | ||||
| #[derive(Debug, Snafu)] | ||||
| @ -217,151 +208,3 @@ impl Session { | ||||
|         ).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 url::Url; | ||||
| 
 | ||||
| @ -23,12 +24,58 @@ impl SSOWatConfig { | ||||
|                 // Domain not managed
 | ||||
|                 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 { | ||||
|             // No domain (eg. http://8.8.8.8/)
 | ||||
|             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