Compare commits
2 Commits
70b417865f
...
bd4144a5ba
Author | SHA1 | Date | |
---|---|---|---|
bd4144a5ba | |||
1647ca3838 |
|
@ -25,4 +25,5 @@ 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"
|
||||||
|
|
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…
Reference in New Issue
Block a user