Add more debugging information in authorize route, and cargo fmt
This commit is contained in:
		
							parent
							
								
									9481c0ff7e
								
							
						
					
					
						commit
						907f22bfd4
					
				
							
								
								
									
										24
									
								
								src/error.rs
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								src/error.rs
									
									
									
									
									
								
							@ -7,7 +7,10 @@ use std::path::PathBuf;
 | 
				
			|||||||
#[snafu(visibility(pub))]
 | 
					#[snafu(visibility(pub))]
 | 
				
			||||||
pub enum Error {
 | 
					pub enum Error {
 | 
				
			||||||
    #[snafu(display("Failed to spawn unix socket at {}", path.display()))]
 | 
					    #[snafu(display("Failed to spawn unix socket at {}", path.display()))]
 | 
				
			||||||
    SocketCreate { path: PathBuf, source: std::io::Error },
 | 
					    SocketCreate {
 | 
				
			||||||
 | 
					        path: PathBuf,
 | 
				
			||||||
 | 
					        source: std::io::Error,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[snafu(display("Failed to spawn a web server"))]
 | 
					    #[snafu(display("Failed to spawn a web server"))]
 | 
				
			||||||
    Server { source: hyper::Error },
 | 
					    Server { source: hyper::Error },
 | 
				
			||||||
@ -16,19 +19,30 @@ pub enum Error {
 | 
				
			|||||||
    Yunohost { source: yunohost_api::Error },
 | 
					    Yunohost { source: yunohost_api::Error },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[snafu(display("{}", source))]
 | 
					    #[snafu(display("{}", source))]
 | 
				
			||||||
    Session { source: crate::state::sessions::SessionError },
 | 
					    Session {
 | 
				
			||||||
 | 
					        source: crate::state::sessions::SessionError,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[snafu(display("Failed to executed tokio task"))]
 | 
					    #[snafu(display("Failed to executed tokio task"))]
 | 
				
			||||||
    TokioTask { source: tokio::task::JoinError },
 | 
					    TokioTask { source: tokio::task::JoinError },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[snafu(display("Failed to set permissions on file {}", path.display()))]
 | 
					    #[snafu(display("Failed to set permissions on file {}", path.display()))]
 | 
				
			||||||
    Permissions { path: PathBuf, source: std::io::Error },
 | 
					    Permissions {
 | 
				
			||||||
 | 
					        path: PathBuf,
 | 
				
			||||||
 | 
					        source: std::io::Error,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[snafu(display("Failed to set owner on file {}", path.display()))]
 | 
					    #[snafu(display("Failed to set owner on file {}", path.display()))]
 | 
				
			||||||
    PermissionsChown { path: PathBuf, source: file_owner::FileOwnerError },
 | 
					    PermissionsChown {
 | 
				
			||||||
 | 
					        path: PathBuf,
 | 
				
			||||||
 | 
					        source: file_owner::FileOwnerError,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[snafu(display("Failed to set group on file {}", path.display()))]
 | 
					    #[snafu(display("Failed to set group on file {}", path.display()))]
 | 
				
			||||||
    PermissionsChgrp { path: PathBuf, source: file_owner::FileOwnerError },
 | 
					    PermissionsChgrp {
 | 
				
			||||||
 | 
					        path: PathBuf,
 | 
				
			||||||
 | 
					        source: file_owner::FileOwnerError,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[snafu(display("No cookie jar"))]
 | 
					    #[snafu(display("No cookie jar"))]
 | 
				
			||||||
    Cookie,
 | 
					    Cookie,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										16
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								src/main.rs
									
									
									
									
									
								
							@ -1,7 +1,11 @@
 | 
				
			|||||||
#[macro_use] extern crate async_trait;
 | 
					#[macro_use]
 | 
				
			||||||
#[macro_use] extern crate axum;
 | 
					extern crate async_trait;
 | 
				
			||||||
#[macro_use] extern crate log;
 | 
					#[macro_use]
 | 
				
			||||||
#[macro_use] extern crate serde;
 | 
					extern crate axum;
 | 
				
			||||||
 | 
					#[macro_use]
 | 
				
			||||||
 | 
					extern crate log;
 | 
				
			||||||
 | 
					#[macro_use]
 | 
				
			||||||
 | 
					extern crate serde;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use clap::Parser;
 | 
					use clap::Parser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -25,9 +29,7 @@ async fn main() -> Result<(), error::Error> {
 | 
				
			|||||||
        .await
 | 
					        .await
 | 
				
			||||||
        .unwrap();
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let state = Arc::new(
 | 
					    let state = Arc::new(state::AppState::new().await?);
 | 
				
			||||||
        state::AppState::new().await?
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let app = routes::router(Some("/ssowat/".to_string()), state);
 | 
					    let app = routes::router(Some("/ssowat/".to_string()), state);
 | 
				
			||||||
    utils::socket::serve(&path, app).await?;
 | 
					    utils::socket::serve(&path, app).await?;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,31 +1,35 @@
 | 
				
			|||||||
use axum::{
 | 
					use axum::{
 | 
				
			||||||
	RequestPartsExt,
 | 
					 | 
				
			||||||
    extract::{FromRequestParts, State},
 | 
					    extract::{FromRequestParts, State},
 | 
				
			||||||
	http::{StatusCode, header::HeaderMap, request::Parts},
 | 
					    http::{header::HeaderMap, request::Parts, StatusCode},
 | 
				
			||||||
 | 
					    response::IntoResponse,
 | 
				
			||||||
 | 
					    RequestPartsExt,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use snafu::prelude::*;
 | 
					use snafu::prelude::*;
 | 
				
			||||||
use url::Url;
 | 
					use url::Url;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    error::*,
 | 
					    error::*,
 | 
				
			||||||
    state::{RoutableAppState, LoggedInUser},
 | 
					 | 
				
			||||||
    state::sessions::*,
 | 
					    state::sessions::*,
 | 
				
			||||||
 | 
					    state::{LoggedInUser, RoutableAppState},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: Implement as a typed header
 | 
					// TODO: Implement as a typed header
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub struct OriginalURI(Url);
 | 
					pub struct OriginalURI(Url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[async_trait]
 | 
					#[async_trait]
 | 
				
			||||||
impl FromRequestParts<RoutableAppState> for OriginalURI {
 | 
					impl FromRequestParts<RoutableAppState> for OriginalURI {
 | 
				
			||||||
    type Rejection = Error;
 | 
					    type Rejection = Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn from_request_parts(parts: &mut Parts, state: &RoutableAppState) -> Result<Self, Self::Rejection> {
 | 
					    async fn from_request_parts(
 | 
				
			||||||
 | 
					        parts: &mut Parts,
 | 
				
			||||||
 | 
					        state: &RoutableAppState,
 | 
				
			||||||
 | 
					    ) -> Result<Self, Self::Rejection> {
 | 
				
			||||||
        let headers: HeaderMap = parts.extract().await.unwrap();
 | 
					        let headers: HeaderMap = parts.extract().await.unwrap();
 | 
				
			||||||
        if let Some(uri) = headers.get("X-Original-URI") {
 | 
					        if let Some(uri) = headers.get("X-Original-URI") {
 | 
				
			||||||
 | 
					            trace!("Received original URI: {}", uri.to_str().unwrap());
 | 
				
			||||||
            // TODO: error
 | 
					            // TODO: error
 | 
				
			||||||
			Ok(
 | 
					            Ok(OriginalURI(Url::parse(uri.to_str().unwrap()).unwrap()))
 | 
				
			||||||
				OriginalURI(Url::parse(uri.to_str().unwrap()).unwrap())
 | 
					 | 
				
			||||||
			)
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            // TODO: error
 | 
					            // TODO: error
 | 
				
			||||||
            panic!()
 | 
					            panic!()
 | 
				
			||||||
@ -39,24 +43,43 @@ impl AsRef<Url> for OriginalURI {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[debug_handler]
 | 
					#[debug_handler]
 | 
				
			||||||
pub async fn route(user: Option<LoggedInUser>, uri: Option<OriginalURI>, state: State<RoutableAppState>) -> StatusCode {
 | 
					pub async fn route(
 | 
				
			||||||
 | 
					    user: Option<LoggedInUser>,
 | 
				
			||||||
 | 
					    uri: Option<OriginalURI>,
 | 
				
			||||||
 | 
					    state: State<RoutableAppState>,
 | 
				
			||||||
 | 
					) -> impl IntoResponse {
 | 
				
			||||||
 | 
					    debug!("Starting authorization request");
 | 
				
			||||||
    if uri.is_none() {
 | 
					    if uri.is_none() {
 | 
				
			||||||
        panic!();
 | 
					        panic!();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let uri = uri.unwrap();
 | 
					    let uri = uri.unwrap();
 | 
				
			||||||
    let username = user.map(|u| u.username().clone());
 | 
					    let username = user.map(|u| u.username().clone());
 | 
				
			||||||
 | 
					    debug!(
 | 
				
			||||||
 | 
					        "Requesting authorization for user {:?} URI {}",
 | 
				
			||||||
 | 
					        username,
 | 
				
			||||||
 | 
					        uri.0.as_str()
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    match state.permissions.ssowat_config() {
 | 
					    match state.permissions.ssowat_config() {
 | 
				
			||||||
        Ok(conf) => {
 | 
					        Ok(conf) => {
 | 
				
			||||||
 | 
					            let mut headers = HeaderMap::new();
 | 
				
			||||||
            if conf.user_has_permission_for_uri(username.as_ref(), uri.as_ref()) {
 | 
					            if conf.user_has_permission_for_uri(username.as_ref(), uri.as_ref()) {
 | 
				
			||||||
                StatusCode::OK
 | 
					                debug!("User {:?} is authorized.", username);
 | 
				
			||||||
            } else {
 | 
					                if let Some(username) = username {
 | 
				
			||||||
                StatusCode::FORBIDDEN
 | 
					                    headers.insert("Remote-User", username.as_str().parse().unwrap());
 | 
				
			||||||
 | 
					                    headers.insert("user", username.as_str().parse().unwrap());
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
        }, Err(e) => {
 | 
					                let res = (StatusCode::OK, headers).into_response();
 | 
				
			||||||
 | 
					                debug!("{:?}", res);
 | 
				
			||||||
 | 
					                res
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                debug!("User {:?} is not authorized.", username);
 | 
				
			||||||
 | 
					                (StatusCode::FORBIDDEN, headers).into_response()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Err(e) => {
 | 
				
			||||||
            panic!()
 | 
					            panic!()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,16 +1,16 @@
 | 
				
			|||||||
use axum::{
 | 
					use axum::{
 | 
				
			||||||
    extract::{FromRequest, Form, Json, State},
 | 
					    extract::{Form, FromRequest, Json, State},
 | 
				
			||||||
    http::{self, Request, StatusCode},
 | 
					    http::{self, Request, StatusCode},
 | 
				
			||||||
    response::{IntoResponse, Response},
 | 
					    response::{IntoResponse, Response},
 | 
				
			||||||
    RequestExt,
 | 
					    RequestExt,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use axum_typed_multipart::{TryFromMultipart, TypedMultipart};
 | 
					use axum_typed_multipart::{TryFromMultipart, TypedMultipart};
 | 
				
			||||||
use tower_cookies::Cookies;
 | 
					use tower_cookies::Cookies;
 | 
				
			||||||
use yunohost_api::{Username, Password};
 | 
					use yunohost_api::{Password, Username};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    error::*,
 | 
					    error::*,
 | 
				
			||||||
    state::{COOKIE_NAME, RoutableAppState, LoggedInUser},
 | 
					    state::{LoggedInUser, RoutableAppState, COOKIE_NAME},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, TryFromMultipart, Deserialize)]
 | 
					#[derive(Debug, TryFromMultipart, Deserialize)]
 | 
				
			||||||
@ -29,16 +29,20 @@ where
 | 
				
			|||||||
    B::Data: Into<axum::body::Bytes>,
 | 
					    B::Data: Into<axum::body::Bytes>,
 | 
				
			||||||
    B::Error: Into<axum::BoxError> + Send + std::error::Error,
 | 
					    B::Error: Into<axum::BoxError> + Send + std::error::Error,
 | 
				
			||||||
    B: Send + 'static + axum::body::HttpBody,
 | 
					    B: Send + 'static + axum::body::HttpBody,
 | 
				
			||||||
    S: Send
 | 
					    S: Send,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    type Rejection = Response;
 | 
					    type Rejection = Response;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn from_request(req: Request<B>, _state: &S) -> Result<Self, Self::Rejection> {
 | 
					    async fn from_request(req: Request<B>, _state: &S) -> Result<Self, Self::Rejection> {
 | 
				
			||||||
        let headers = req.headers();
 | 
					        let headers = req.headers();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Some(mime) = headers.get(http::header::CONTENT_TYPE).and_then(|v| v.to_str().ok()) {
 | 
					        if let Some(mime) = headers
 | 
				
			||||||
 | 
					            .get(http::header::CONTENT_TYPE)
 | 
				
			||||||
 | 
					            .and_then(|v| v.to_str().ok())
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
            if mime.starts_with("application/json") {
 | 
					            if mime.starts_with("application/json") {
 | 
				
			||||||
                let Json(login_form): Json<LoginForm> = req.extract().await.map_err(IntoResponse::into_response)?;
 | 
					                let Json(login_form): Json<LoginForm> =
 | 
				
			||||||
 | 
					                    req.extract().await.map_err(IntoResponse::into_response)?;
 | 
				
			||||||
                return Ok(login_form);
 | 
					                return Ok(login_form);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -48,7 +52,8 @@ where
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if mime.starts_with("multipart/form-data") {
 | 
					            if mime.starts_with("multipart/form-data") {
 | 
				
			||||||
                let TypedMultipart(login_form): TypedMultipart<LoginForm> = req.extract().await.map_err(IntoResponse::into_response)?;
 | 
					                let TypedMultipart(login_form): TypedMultipart<LoginForm> =
 | 
				
			||||||
 | 
					                    req.extract().await.map_err(IntoResponse::into_response)?;
 | 
				
			||||||
                return Ok(login_form);
 | 
					                return Ok(login_form);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Err(StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response())
 | 
					            Err(StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response())
 | 
				
			||||||
@ -59,19 +64,37 @@ where
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[debug_handler]
 | 
					#[debug_handler]
 | 
				
			||||||
pub async fn route(user: Option<LoggedInUser>, cookies: Cookies, state: State<RoutableAppState>, form: LoginForm) -> Result<String, Error> {
 | 
					pub async fn route(
 | 
				
			||||||
 | 
					    user: Option<LoggedInUser>,
 | 
				
			||||||
 | 
					    cookies: Cookies,
 | 
				
			||||||
 | 
					    state: State<RoutableAppState>,
 | 
				
			||||||
 | 
					    form: LoginForm,
 | 
				
			||||||
 | 
					) -> Result<String, Error> {
 | 
				
			||||||
    trace!("ROUTE: /login/");
 | 
					    trace!("ROUTE: /login/");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if let Some(username) = user {
 | 
					    if let Some(username) = user {
 | 
				
			||||||
        return Ok(format!("Welcome back, {}! You were already logged in.", username));
 | 
					        return Ok(format!(
 | 
				
			||||||
 | 
					            "Welcome back, {}! You were already logged in.",
 | 
				
			||||||
 | 
					            username
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    debug!("Performing login attempt for user {}", &form.username);
 | 
					    debug!("Performing login attempt for user {}", &form.username);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // No cookie, or cookie is invalid. Perform login.
 | 
					    // No cookie, or cookie is invalid. Perform login.
 | 
				
			||||||
    if state.check_login(&form.username, &form.password).await.unwrap() {
 | 
					    if state
 | 
				
			||||||
        debug!("Login was successful for user {}. Saving cookie now.", &form.username);
 | 
					        .check_login(&form.username, &form.password)
 | 
				
			||||||
        let session = state.sessions.make_session(COOKIE_NAME, &form.username).await;
 | 
					        .await
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        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());
 | 
					        cookies.add(session.cookie());
 | 
				
			||||||
        Ok(format!("Welcome {}", session.username()))
 | 
					        Ok(format!("Welcome {}", session.username()))
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
use tower_cookies::{Cookies, Cookie};
 | 
					use tower_cookies::{Cookie, Cookies};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::borrow::Cow;
 | 
					use std::borrow::Cow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -7,7 +7,10 @@ use crate::state::LoggedOutUser;
 | 
				
			|||||||
pub async fn route(user: Option<LoggedOutUser>, cookies: Cookies) -> 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()),
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
        return format!("Goodbye, {}. You are now logged out.", user.username());
 | 
					        return format!("Goodbye, {}. You are now logged out.", user.username());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -21,8 +21,7 @@ pub fn router(subpath: Option<String>, state: RoutableAppState) -> Router {
 | 
				
			|||||||
        .layer(CookieManagerLayer::new())
 | 
					        .layer(CookieManagerLayer::new())
 | 
				
			||||||
        .with_state(state);
 | 
					        .with_state(state);
 | 
				
			||||||
    if let Some(p) = subpath {
 | 
					    if let Some(p) = subpath {
 | 
				
			||||||
        Router::new()
 | 
					        Router::new().nest(&p, app)
 | 
				
			||||||
            .nest(&p, app)
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        app
 | 
					        app
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,15 +1,11 @@
 | 
				
			|||||||
use axum::{
 | 
					use axum::{extract::FromRequestParts, http::request::Parts, RequestPartsExt};
 | 
				
			||||||
    extract::FromRequestParts,
 | 
					 | 
				
			||||||
    http::request::Parts,
 | 
					 | 
				
			||||||
    RequestPartsExt,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use snafu::prelude::*;
 | 
					use snafu::prelude::*;
 | 
				
			||||||
use tower_cookies::Cookies;
 | 
					use tower_cookies::Cookies;
 | 
				
			||||||
use yunohost_api::Username;
 | 
					use yunohost_api::Username;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    error::*,
 | 
					    error::*,
 | 
				
			||||||
    state::{COOKIE_NAME, RoutableAppState, sessions::*},
 | 
					    state::{sessions::*, RoutableAppState, COOKIE_NAME},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone, Debug)]
 | 
					#[derive(Clone, Debug)]
 | 
				
			||||||
@ -33,8 +29,6 @@ impl LoggedInUser {
 | 
				
			|||||||
    pub fn timestamp(&self) -> i64 {
 | 
					    pub fn timestamp(&self) -> i64 {
 | 
				
			||||||
        self.timestamp
 | 
					        self.timestamp
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<Session> for LoggedInUser {
 | 
					impl From<Session> for LoggedInUser {
 | 
				
			||||||
@ -59,30 +53,51 @@ impl AsRef<Username> for LoggedInUser {
 | 
				
			|||||||
impl FromRequestParts<RoutableAppState> for LoggedInUser {
 | 
					impl FromRequestParts<RoutableAppState> for LoggedInUser {
 | 
				
			||||||
    type Rejection = Error;
 | 
					    type Rejection = Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn from_request_parts(parts: &mut Parts, state: &RoutableAppState) -> Result<Self, Self::Rejection> {
 | 
					    async fn from_request_parts(
 | 
				
			||||||
 | 
					        parts: &mut Parts,
 | 
				
			||||||
 | 
					        state: &RoutableAppState,
 | 
				
			||||||
 | 
					    ) -> Result<Self, Self::Rejection> {
 | 
				
			||||||
        let cookies: Cookies = parts.extract().await.map_err(|_| Error::Cookie)?;
 | 
					        let cookies: Cookies = parts.extract().await.map_err(|_| Error::Cookie)?;
 | 
				
			||||||
        trace!("[SESSION:{}/login] Checking if the user has a valid session", COOKIE_NAME);
 | 
					        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) {
 | 
					        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);
 | 
					            trace!(
 | 
				
			||||||
 | 
					                "[SESSION:{}/login] User claims to have a session with cookie: {}",
 | 
				
			||||||
 | 
					                COOKIE_NAME,
 | 
				
			||||||
 | 
					                &session_cookie
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            session_cookie
 | 
					            session_cookie
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            trace!("[SESSION:{}/login] No current session cookie", COOKIE_NAME);
 | 
					            trace!("[SESSION:{}/login] No current session cookie", COOKIE_NAME);
 | 
				
			||||||
            return Err(Error::Session {
 | 
					            return Err(Error::Session {
 | 
				
			||||||
                source: SessionError::NoSession {
 | 
					                source: SessionError::NoSession {
 | 
				
			||||||
                    cookie_name: COOKIE_NAME.to_string(),
 | 
					                    cookie_name: COOKIE_NAME.to_string(),
 | 
				
			||||||
                }
 | 
					                },
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Some(session) = state.sessions.verify_cookie_session(session_cookie.value()).await.context(SessionSnafu)? {
 | 
					        if let Some(session) = state
 | 
				
			||||||
            debug!("[SESSION:{}/login] User {} resumed session.", COOKIE_NAME, &session.username);
 | 
					            .sessions
 | 
				
			||||||
 | 
					            .verify_cookie_session(session_cookie.value())
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .context(SessionSnafu)?
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            debug!(
 | 
				
			||||||
 | 
					                "[SESSION:{}/login] User {} resumed session.",
 | 
				
			||||||
 | 
					                COOKIE_NAME, &session.username
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            return Ok(session.into());
 | 
					            return Ok(session.into());
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            trace!("[SESSION:{}/login] User session is invalid or has expired.", COOKIE_NAME);
 | 
					            trace!(
 | 
				
			||||||
 | 
					                "[SESSION:{}/login] User session is invalid or has expired.",
 | 
				
			||||||
 | 
					                COOKIE_NAME
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            return Err(Error::Session {
 | 
					            return Err(Error::Session {
 | 
				
			||||||
                source: SessionError::InvalidOrExpired {
 | 
					                source: SessionError::InvalidOrExpired {
 | 
				
			||||||
                    cookie_name: COOKIE_NAME.to_string(),
 | 
					                    cookie_name: COOKIE_NAME.to_string(),
 | 
				
			||||||
                }
 | 
					                },
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,16 +1,12 @@
 | 
				
			|||||||
use axum::{
 | 
					use axum::{extract::FromRequestParts, http::request::Parts, RequestPartsExt};
 | 
				
			||||||
    extract::FromRequestParts,
 | 
					 | 
				
			||||||
    http::request::Parts,
 | 
					 | 
				
			||||||
    RequestPartsExt,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use snafu::prelude::*;
 | 
					use snafu::prelude::*;
 | 
				
			||||||
use std::borrow::Cow;
 | 
					use std::borrow::Cow;
 | 
				
			||||||
use tower_cookies::{Cookies, Cookie};
 | 
					use tower_cookies::{Cookie, Cookies};
 | 
				
			||||||
use yunohost_api::Username;
 | 
					use yunohost_api::Username;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    error::*,
 | 
					    error::*,
 | 
				
			||||||
    state::{COOKIE_NAME, RoutableAppState, sessions::*},
 | 
					    state::{sessions::*, RoutableAppState, COOKIE_NAME},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct LoggedOutUser(Session);
 | 
					pub struct LoggedOutUser(Session);
 | 
				
			||||||
@ -28,9 +24,9 @@ impl LoggedOutUser {
 | 
				
			|||||||
        Cookie::build(
 | 
					        Cookie::build(
 | 
				
			||||||
            Cow::Owned(self.0.cookie_name.to_string()),
 | 
					            Cow::Owned(self.0.cookie_name.to_string()),
 | 
				
			||||||
            Cow::Owned(self.0.content.to_string()),
 | 
					            Cow::Owned(self.0.content.to_string()),
 | 
				
			||||||
        ).path(
 | 
					        )
 | 
				
			||||||
            Cow::Owned("/".to_string())
 | 
					        .path(Cow::Owned("/".to_string()))
 | 
				
			||||||
        ).finish()
 | 
					        .finish()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -44,25 +40,47 @@ impl From<Session> for LoggedOutUser {
 | 
				
			|||||||
impl FromRequestParts<RoutableAppState> for LoggedOutUser {
 | 
					impl FromRequestParts<RoutableAppState> for LoggedOutUser {
 | 
				
			||||||
    type Rejection = Error;
 | 
					    type Rejection = Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn from_request_parts(parts: &mut Parts, state: &RoutableAppState) -> Result<Self, Self::Rejection> {
 | 
					    async fn from_request_parts(
 | 
				
			||||||
 | 
					        parts: &mut Parts,
 | 
				
			||||||
 | 
					        state: &RoutableAppState,
 | 
				
			||||||
 | 
					    ) -> Result<Self, Self::Rejection> {
 | 
				
			||||||
        let cookies: Cookies = parts.extract().await.map_err(|_| Error::Cookie)?;
 | 
					        let cookies: Cookies = parts.extract().await.map_err(|_| Error::Cookie)?;
 | 
				
			||||||
        trace!("[SESSION:{}/logout] Checking if the user has a valid session", COOKIE_NAME);
 | 
					        trace!(
 | 
				
			||||||
 | 
					            "[SESSION:{}/logout] Checking if the user has a valid session",
 | 
				
			||||||
 | 
					            COOKIE_NAME
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        trace!("{:#?}", parts.headers);
 | 
					        trace!("{:#?}", parts.headers);
 | 
				
			||||||
        let session_cookie = if let Some(session_cookie) = cookies.get(COOKIE_NAME) {
 | 
					        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);
 | 
					            trace!(
 | 
				
			||||||
 | 
					                "[SESSION:{}/logout] User claims to have a session with cookie: {}",
 | 
				
			||||||
 | 
					                COOKIE_NAME,
 | 
				
			||||||
 | 
					                &session_cookie
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            session_cookie
 | 
					            session_cookie
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            trace!("[SESSION:{}/logout] No current session cookie", COOKIE_NAME);
 | 
					            trace!("[SESSION:{}/logout] No current session cookie", COOKIE_NAME);
 | 
				
			||||||
            return Err(Error::Session {
 | 
					            return Err(Error::Session {
 | 
				
			||||||
                source: SessionError::NoSession {
 | 
					                source: SessionError::NoSession {
 | 
				
			||||||
                    cookie_name: COOKIE_NAME.to_string(),
 | 
					                    cookie_name: COOKIE_NAME.to_string(),
 | 
				
			||||||
                }
 | 
					                },
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Some(session) = state.sessions.verify_cookie_session(session_cookie.value()).await.context(SessionSnafu)? {
 | 
					        if let Some(session) = state
 | 
				
			||||||
            debug!("[SESSION:{}/logout] User {} resumed session, requesting to log out.", COOKIE_NAME, &session.username);
 | 
					            .sessions
 | 
				
			||||||
            if state.sessions.invalidate_session(session.timestamp, &session.content).await {
 | 
					            .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());
 | 
					                return Ok(session.into());
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                warn!("[SESSION:{}/logout] User {} requested logout but was already logged out. Here's the session:\n{:#?}", COOKIE_NAME, &session.username, &session);
 | 
					                warn!("[SESSION:{}/logout] User {} requested logout but was already logged out. Here's the session:\n{:#?}", COOKIE_NAME, &session.username, &session);
 | 
				
			||||||
@ -70,15 +88,18 @@ impl FromRequestParts<RoutableAppState> for LoggedOutUser {
 | 
				
			|||||||
                return Err(Error::Session {
 | 
					                return Err(Error::Session {
 | 
				
			||||||
                    source: SessionError::InvalidOrExpired {
 | 
					                    source: SessionError::InvalidOrExpired {
 | 
				
			||||||
                        cookie_name: COOKIE_NAME.to_string(),
 | 
					                        cookie_name: COOKIE_NAME.to_string(),
 | 
				
			||||||
                    }
 | 
					                    },
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            trace!("[SESSION:{}/logout] User session is invalid or has expired.", COOKIE_NAME);
 | 
					            trace!(
 | 
				
			||||||
 | 
					                "[SESSION:{}/logout] User session is invalid or has expired.",
 | 
				
			||||||
 | 
					                COOKIE_NAME
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            return Err(Error::Session {
 | 
					            return Err(Error::Session {
 | 
				
			||||||
                source: SessionError::InvalidOrExpired {
 | 
					                source: SessionError::InvalidOrExpired {
 | 
				
			||||||
                    cookie_name: COOKIE_NAME.to_string(),
 | 
					                    cookie_name: COOKIE_NAME.to_string(),
 | 
				
			||||||
                }
 | 
					                },
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
use snafu::prelude::*;
 | 
					use snafu::prelude::*;
 | 
				
			||||||
use yunohost_api::{YunohostUsers, Username, Password, YunohostPermissions};
 | 
					use yunohost_api::{Password, Username, YunohostPermissions, YunohostUsers};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::sync::Arc;
 | 
					use std::sync::Arc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -33,7 +33,14 @@ impl AppState {
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn check_login(&self, username: &Username, password: &Password) -> Result<bool, Error> {
 | 
					    pub async fn check_login(
 | 
				
			||||||
        self.users.check_credentials(username, password).await.context(YunohostSnafu)
 | 
					        &self,
 | 
				
			||||||
 | 
					        username: &Username,
 | 
				
			||||||
 | 
					        password: &Password,
 | 
				
			||||||
 | 
					    ) -> Result<bool, Error> {
 | 
				
			||||||
 | 
					        self.users
 | 
				
			||||||
 | 
					            .check_credentials(username, password)
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .context(YunohostSnafu)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
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;
 | 
				
			||||||
@ -16,9 +16,15 @@ pub enum SessionError {
 | 
				
			|||||||
    #[snafu(display("Malformed cookie: {}", content))]
 | 
					    #[snafu(display("Malformed cookie: {}", content))]
 | 
				
			||||||
    MalformedCookie { content: String },
 | 
					    MalformedCookie { content: String },
 | 
				
			||||||
    #[snafu(display("Malformed hex cookie signature: {}", sig))]
 | 
					    #[snafu(display("Malformed hex cookie signature: {}", sig))]
 | 
				
			||||||
    MalformedCookieSig { sig: String, source: hex::FromHexError },
 | 
					    MalformedCookieSig {
 | 
				
			||||||
 | 
					        sig: String,
 | 
				
			||||||
 | 
					        source: hex::FromHexError,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    #[snafu(display("Malformed cookie UNIX timestamp: {}", timestamp))]
 | 
					    #[snafu(display("Malformed cookie UNIX timestamp: {}", timestamp))]
 | 
				
			||||||
    MalformedCookieTimestamp { timestamp: String, source: std::num::ParseIntError },
 | 
					    MalformedCookieTimestamp {
 | 
				
			||||||
 | 
					        timestamp: String,
 | 
				
			||||||
 | 
					        source: std::num::ParseIntError,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    #[snafu(display("Malformed cookie username (empty)"))]
 | 
					    #[snafu(display("Malformed cookie username (empty)"))]
 | 
				
			||||||
    MalformedCookieUsername { source: yunohost_api::Error },
 | 
					    MalformedCookieUsername { source: yunohost_api::Error },
 | 
				
			||||||
    #[snafu(display("Invalid or expired session {}", cookie_name))]
 | 
					    #[snafu(display("Invalid or expired session {}", cookie_name))]
 | 
				
			||||||
@ -44,7 +50,6 @@ pub struct SessionManager {
 | 
				
			|||||||
    pub invalidated_cookies: RwLock<Vec<Session>>,
 | 
					    pub invalidated_cookies: RwLock<Vec<Session>>,
 | 
				
			||||||
    /// Expiration duration for set cookies, in seconds
 | 
					    /// Expiration duration for set cookies, in seconds
 | 
				
			||||||
    pub expiration_secs: u64,
 | 
					    pub expiration_secs: u64,
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl SessionManager {
 | 
					impl SessionManager {
 | 
				
			||||||
@ -59,7 +64,7 @@ impl SessionManager {
 | 
				
			|||||||
            cookies: RwLock::new(Vec::new()),
 | 
					            cookies: RwLock::new(Vec::new()),
 | 
				
			||||||
            invalidated_cookies: RwLock::new(Vec::new()),
 | 
					            invalidated_cookies: RwLock::new(Vec::new()),
 | 
				
			||||||
            // TODO: make expiration configurable
 | 
					            // TODO: make expiration configurable
 | 
				
			||||||
            expiration_secs: 3600 * 24 * 7
 | 
					            expiration_secs: 3600 * 24 * 7,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -67,15 +72,24 @@ impl SessionManager {
 | 
				
			|||||||
    /// - Err(_) if the cookie format is really wrong
 | 
					    /// - Err(_) if the cookie format is really wrong
 | 
				
			||||||
    /// - Ok(Some(Session)) if the user is still logged in
 | 
					    /// - Ok(Some(Session)) if the user is still logged in
 | 
				
			||||||
    /// - Ok(None) if the user is no longer lgoged in (invalid/expired cookie)
 | 
					    /// - Ok(None) if the user is no longer lgoged in (invalid/expired cookie)
 | 
				
			||||||
    pub async fn verify_cookie_session(&self, cookie_claim: &str) -> Result<Option<Session>, SessionError> {
 | 
					    pub async fn verify_cookie_session(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        cookie_claim: &str,
 | 
				
			||||||
 | 
					    ) -> Result<Option<Session>, SessionError> {
 | 
				
			||||||
        // First check the expiration of the claimed cookie.
 | 
					        // First check the expiration of the claimed cookie.
 | 
				
			||||||
        // If the timestamp was messed with, it will fail further verification.
 | 
					        // If the timestamp was messed with, it will fail further verification.
 | 
				
			||||||
        if let Some(claimed_timestamp) = Session::has_expired(cookie_claim, self.expiration_secs)? {
 | 
					        if let Some(claimed_timestamp) = Session::has_expired(cookie_claim, self.expiration_secs)? {
 | 
				
			||||||
            // The claimed timestamp is still valid. Check if we ever had that cookie in memory.
 | 
					            // The claimed timestamp is still valid. Check if we ever had that cookie in memory.
 | 
				
			||||||
            // If the server was restarted, user will have to login again.
 | 
					            // If the server was restarted, user will have to login again.
 | 
				
			||||||
            if let Some(valid_cookie) = self.find_cookie(&self.cookies, claimed_timestamp, &cookie_claim).await {
 | 
					            if let Some(valid_cookie) = self
 | 
				
			||||||
 | 
					                .find_cookie(&self.cookies, claimed_timestamp, &cookie_claim)
 | 
				
			||||||
 | 
					                .await
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
                // Make sure the session hasn't been invalidated
 | 
					                // Make sure the session hasn't been invalidated
 | 
				
			||||||
                if let Some(_invalidated_cookie) = self.find_cookie(&self.invalidated_cookies, claimed_timestamp, &cookie_claim).await {
 | 
					                if let Some(_invalidated_cookie) = self
 | 
				
			||||||
 | 
					                    .find_cookie(&self.invalidated_cookies, claimed_timestamp, &cookie_claim)
 | 
				
			||||||
 | 
					                    .await
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
                    // User has logged out or been removed from the system
 | 
					                    // User has logged out or been removed from the system
 | 
				
			||||||
                    Ok(None)
 | 
					                    Ok(None)
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
@ -98,12 +112,10 @@ impl SessionManager {
 | 
				
			|||||||
    pub async fn make_session(&self, cookie_name: &str, username: &Username) -> Session {
 | 
					    pub async fn make_session(&self, cookie_name: &str, username: &Username) -> Session {
 | 
				
			||||||
        let now = now();
 | 
					        let now = now();
 | 
				
			||||||
        let signable_payload = format!("{now}:{cookie_name}:{username}");
 | 
					        let signable_payload = format!("{now}:{cookie_name}:{username}");
 | 
				
			||||||
        let signed_payload = hmac::sign(&self.secret, signable_payload.as_bytes()).as_ref().to_vec();
 | 
					        let signed_payload = hmac::sign(&self.secret, signable_payload.as_bytes())
 | 
				
			||||||
        let cookie_payload = format!(
 | 
					            .as_ref()
 | 
				
			||||||
            "{}:{}",
 | 
					            .to_vec();
 | 
				
			||||||
            signable_payload,
 | 
					        let cookie_payload = format!("{}:{}", signable_payload, hex::encode(&signed_payload),);
 | 
				
			||||||
            hex::encode(&signed_payload),
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let cookie = Session {
 | 
					        let cookie = Session {
 | 
				
			||||||
            cookie_name: cookie_name.to_string(),
 | 
					            cookie_name: cookie_name.to_string(),
 | 
				
			||||||
@ -126,9 +138,16 @@ impl SessionManager {
 | 
				
			|||||||
    /// Invalidated a previously-set session cookie
 | 
					    /// Invalidated a previously-set session cookie
 | 
				
			||||||
    pub async fn invalidate_session(&self, timestamp: i64, content: &str) -> bool {
 | 
					    pub async fn invalidate_session(&self, timestamp: i64, content: &str) -> bool {
 | 
				
			||||||
        if let Some(cookie) = self.find_cookie(&self.cookies, timestamp, content).await {
 | 
					        if let Some(cookie) = self.find_cookie(&self.cookies, timestamp, content).await {
 | 
				
			||||||
            debug!("[SESSION:{}] User {} is logging out", &cookie.cookie_name, &cookie.username);
 | 
					            debug!(
 | 
				
			||||||
 | 
					                "[SESSION:{}] User {} is logging out",
 | 
				
			||||||
 | 
					                &cookie.cookie_name, &cookie.username
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            // Make sure the cookie was not already invalidated to avoid DOSing our invalidated cookie storage
 | 
					            // Make sure the cookie was not already invalidated to avoid DOSing our invalidated cookie storage
 | 
				
			||||||
            if self.find_cookie(&self.invalidated_cookies, timestamp, content).await.is_none() {
 | 
					            if self
 | 
				
			||||||
 | 
					                .find_cookie(&self.invalidated_cookies, timestamp, content)
 | 
				
			||||||
 | 
					                .await
 | 
				
			||||||
 | 
					                .is_none()
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
                let mut jar = self.invalidated_cookies.write().await;
 | 
					                let mut jar = self.invalidated_cookies.write().await;
 | 
				
			||||||
                jar.push(cookie);
 | 
					                jar.push(cookie);
 | 
				
			||||||
                return true;
 | 
					                return true;
 | 
				
			||||||
@ -140,12 +159,20 @@ impl SessionManager {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Helper method to find a cookie with a specific timestamp, name and username in a cookie jar
 | 
					    /// Helper method to find a cookie with a specific timestamp, name and username in a cookie jar
 | 
				
			||||||
    async fn find_cookie(&self, jar: &RwLock<Vec<Session>>, timestamp: i64, content: &str) -> Option<Session> {
 | 
					    async fn find_cookie(
 | 
				
			||||||
        jar.read().await.iter().find(|cookie| {
 | 
					        &self,
 | 
				
			||||||
 | 
					        jar: &RwLock<Vec<Session>>,
 | 
				
			||||||
 | 
					        timestamp: i64,
 | 
				
			||||||
 | 
					        content: &str,
 | 
				
			||||||
 | 
					    ) -> Option<Session> {
 | 
				
			||||||
 | 
					        jar.read()
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .iter()
 | 
				
			||||||
 | 
					            .find(|cookie| {
 | 
				
			||||||
                // First compare the timestamp (cheapest operation to invalidate the match)
 | 
					                // First compare the timestamp (cheapest operation to invalidate the match)
 | 
				
			||||||
            cookie.timestamp == timestamp
 | 
					                cookie.timestamp == timestamp && cookie.content == content
 | 
				
			||||||
            && cookie.content == content
 | 
					            })
 | 
				
			||||||
        }).cloned()
 | 
					            .cloned()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -167,9 +194,12 @@ pub struct Session {
 | 
				
			|||||||
impl Session {
 | 
					impl Session {
 | 
				
			||||||
    /// Extrats the timestamp from a stringy cookie
 | 
					    /// Extrats the timestamp from a stringy cookie
 | 
				
			||||||
    pub fn timestamp(cookie: &str) -> Result<i64, SessionError> {
 | 
					    pub fn timestamp(cookie: &str) -> Result<i64, SessionError> {
 | 
				
			||||||
        let (timestamp, _rest) = cookie.split_once(':')
 | 
					        let (timestamp, _rest) = cookie.split_once(':').context(MalformedCookieSnafu {
 | 
				
			||||||
            .context(MalformedCookieSnafu { content: cookie.to_string() })?;
 | 
					            content: cookie.to_string(),
 | 
				
			||||||
        let timestamp: i64 = timestamp.parse().context(MalformedCookieTimestampSnafu { timestamp })?;
 | 
					        })?;
 | 
				
			||||||
 | 
					        let timestamp: i64 = timestamp
 | 
				
			||||||
 | 
					            .parse()
 | 
				
			||||||
 | 
					            .context(MalformedCookieTimestampSnafu { timestamp })?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(timestamp)
 | 
					        Ok(timestamp)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -203,8 +233,8 @@ impl Session {
 | 
				
			|||||||
        Cookie::build(
 | 
					        Cookie::build(
 | 
				
			||||||
            Cow::Owned(self.cookie_name.to_string()),
 | 
					            Cow::Owned(self.cookie_name.to_string()),
 | 
				
			||||||
            Cow::Owned(self.content.to_string()),
 | 
					            Cow::Owned(self.content.to_string()),
 | 
				
			||||||
        ).path(
 | 
					        )
 | 
				
			||||||
            Cow::Owned("/".to_string())
 | 
					        .path(Cow::Owned("/".to_string()))
 | 
				
			||||||
        ).finish()
 | 
					        .finish()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,15 +1,8 @@
 | 
				
			|||||||
use file_owner::PathExt;
 | 
					use file_owner::PathExt;
 | 
				
			||||||
use snafu::prelude::*;
 | 
					use snafu::prelude::*;
 | 
				
			||||||
use tokio::{
 | 
					use tokio::{fs::set_permissions, task::spawn_blocking};
 | 
				
			||||||
    fs::set_permissions,
 | 
					 | 
				
			||||||
    task::spawn_blocking,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::{
 | 
					use std::{fs::Permissions, os::unix::fs::PermissionsExt, path::Path};
 | 
				
			||||||
    fs::Permissions,
 | 
					 | 
				
			||||||
    os::unix::fs::PermissionsExt,
 | 
					 | 
				
			||||||
    path::Path,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::error::*;
 | 
					use crate::error::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -45,32 +38,39 @@ impl FSPermissions {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    pub async fn apply_to(&self, path: &Path) -> Result<(), Error> {
 | 
					    pub async fn apply_to(&self, path: &Path) -> Result<(), Error> {
 | 
				
			||||||
        if let Some(mode) = self.mode {
 | 
					        if let Some(mode) = self.mode {
 | 
				
			||||||
            set_permissions(
 | 
					            set_permissions(path, Permissions::from_mode(mode))
 | 
				
			||||||
                path,
 | 
					                .await
 | 
				
			||||||
                Permissions::from_mode(mode)
 | 
					                .context(PermissionsSnafu {
 | 
				
			||||||
            ).await.context(PermissionsSnafu { path: path.to_path_buf()})?;
 | 
					                    path: path.to_path_buf(),
 | 
				
			||||||
 | 
					                })?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Some(owner) = &self.owner {
 | 
					        if let Some(owner) = &self.owner {
 | 
				
			||||||
            let owner = owner.to_string();
 | 
					            let owner = owner.to_string();
 | 
				
			||||||
            let path = path.to_path_buf();
 | 
					            let path = path.to_path_buf();
 | 
				
			||||||
            let _ = spawn_blocking(move || -> Result<(), Error> {
 | 
					            let _ = spawn_blocking(move || -> Result<(), Error> {
 | 
				
			||||||
                Ok(
 | 
					                Ok(path
 | 
				
			||||||
                    path.set_owner(owner.as_str())
 | 
					                    .set_owner(owner.as_str())
 | 
				
			||||||
                        .context(PermissionsChownSnafu { path: path.to_path_buf() })?
 | 
					                    .context(PermissionsChownSnafu {
 | 
				
			||||||
                )
 | 
					                        path: path.to_path_buf(),
 | 
				
			||||||
            }).await.context(TokioTaskSnafu)?;
 | 
					                    })?)
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .context(TokioTaskSnafu)?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Some(group) = &self.group {
 | 
					        if let Some(group) = &self.group {
 | 
				
			||||||
            let group = group.to_string();
 | 
					            let group = group.to_string();
 | 
				
			||||||
            let path = path.to_path_buf();
 | 
					            let path = path.to_path_buf();
 | 
				
			||||||
            let _ = spawn_blocking(move || -> Result<(), Error> {
 | 
					            let _ = spawn_blocking(move || -> Result<(), Error> {
 | 
				
			||||||
                Ok(
 | 
					                Ok(path
 | 
				
			||||||
                    path.set_group(group.as_str())
 | 
					                    .set_group(group.as_str())
 | 
				
			||||||
                        .context(PermissionsChgrpSnafu { path: path.to_path_buf() })?
 | 
					                    .context(PermissionsChgrpSnafu {
 | 
				
			||||||
                )
 | 
					                        path: path.to_path_buf(),
 | 
				
			||||||
            }).await.context(TokioTaskSnafu)?;
 | 
					                    })?)
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .context(TokioTaskSnafu)?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,3 @@
 | 
				
			|||||||
pub mod fs;
 | 
					pub mod fs;
 | 
				
			||||||
pub mod time;
 | 
					 | 
				
			||||||
pub mod socket;
 | 
					pub mod socket;
 | 
				
			||||||
 | 
					pub mod time;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,4 @@
 | 
				
			|||||||
use axum::{
 | 
					use axum::{extract::connect_info, Router};
 | 
				
			||||||
    Router,
 | 
					 | 
				
			||||||
    extract::connect_info,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use futures::ready;
 | 
					use futures::ready;
 | 
				
			||||||
use hyper::{
 | 
					use hyper::{
 | 
				
			||||||
    client::connect::{Connected, Connection},
 | 
					    client::connect::{Connected, Connection},
 | 
				
			||||||
@ -21,10 +18,7 @@ use tokio::{
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
use tower::BoxError;
 | 
					use tower::BoxError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{error::*, utils::fs::FSPermissions};
 | 
				
			||||||
    error::*,
 | 
					 | 
				
			||||||
    utils::fs::FSPermissions,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct ServerAccept {
 | 
					pub struct ServerAccept {
 | 
				
			||||||
    uds: UnixListener,
 | 
					    uds: UnixListener,
 | 
				
			||||||
@ -32,9 +26,7 @@ pub struct ServerAccept {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
impl ServerAccept {
 | 
					impl ServerAccept {
 | 
				
			||||||
    pub fn new(uds: UnixListener) -> ServerAccept {
 | 
					    pub fn new(uds: UnixListener) -> ServerAccept {
 | 
				
			||||||
        ServerAccept {
 | 
					        ServerAccept { uds }
 | 
				
			||||||
            uds,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -64,10 +56,7 @@ impl AsyncWrite for ClientConnection {
 | 
				
			|||||||
        Pin::new(&mut self.stream).poll_write(cx, buf)
 | 
					        Pin::new(&mut self.stream).poll_write(cx, buf)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn poll_flush(
 | 
					    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
 | 
				
			||||||
        mut self: Pin<&mut Self>,
 | 
					 | 
				
			||||||
        cx: &mut Context<'_>,
 | 
					 | 
				
			||||||
    ) -> Poll<Result<(), io::Error>> {
 | 
					 | 
				
			||||||
        Pin::new(&mut self.stream).poll_flush(cx)
 | 
					        Pin::new(&mut self.stream).poll_flush(cx)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -121,8 +110,7 @@ pub async fn serve(path: &Path, app: Router) -> Result<(), Error> {
 | 
				
			|||||||
        .await
 | 
					        .await
 | 
				
			||||||
        .unwrap();
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let uds = UnixListener::bind(path.clone())
 | 
					    let uds = UnixListener::bind(path.clone()).context(SocketCreateSnafu { path: path.clone() })?;
 | 
				
			||||||
        .context(SocketCreateSnafu { path: path.clone() })?;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: make proper permissions
 | 
					    // TODO: make proper permissions
 | 
				
			||||||
    // Apply 777 permissions
 | 
					    // Apply 777 permissions
 | 
				
			||||||
@ -130,7 +118,8 @@ pub async fn serve(path: &Path, app: Router) -> Result<(), Error> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    hyper::Server::builder(ServerAccept::new(uds))
 | 
					    hyper::Server::builder(ServerAccept::new(uds))
 | 
				
			||||||
        .serve(app.into_make_service_with_connect_info::<UdsConnectInfo>())
 | 
					        .serve(app.into_make_service_with_connect_info::<UdsConnectInfo>())
 | 
				
			||||||
        .await.context(ServerSnafu)?;
 | 
					        .await
 | 
				
			||||||
 | 
					        .context(ServerSnafu)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					#[macro_use] extern crate log;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mod cache;
 | 
					mod cache;
 | 
				
			||||||
pub use cache::JsonCache;
 | 
					pub use cache::JsonCache;
 | 
				
			||||||
mod credentials;
 | 
					mod credentials;
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,7 @@ impl SSOWatConfig {
 | 
				
			|||||||
        if let Some(domain) = uri.domain() {
 | 
					        if let Some(domain) = uri.domain() {
 | 
				
			||||||
            if ! self.domains.contains(&domain.to_string()) {
 | 
					            if ! self.domains.contains(&domain.to_string()) {
 | 
				
			||||||
                // Domain not managed
 | 
					                // Domain not managed
 | 
				
			||||||
 | 
					                trace!("Domain {} not managed by Yunohost", &domain);
 | 
				
			||||||
                return None;
 | 
					                return None;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -31,19 +32,25 @@ impl SSOWatConfig {
 | 
				
			|||||||
                .trim_start_matches("s")
 | 
					                .trim_start_matches("s")
 | 
				
			||||||
                .trim_start_matches("://");
 | 
					                .trim_start_matches("://");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            trace!("Checking permissions for {}", stripped_uri);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Check which app matches this URI, to find corresponding permission
 | 
					            // Check which app matches this URI, to find corresponding permission
 | 
				
			||||||
            for (key, val) in &self.permissions {
 | 
					            for (key, val) in &self.permissions {
 | 
				
			||||||
                for uri_format in &val.uris {
 | 
					                for uri_format in &val.uris {
 | 
				
			||||||
                    if uri_format.starts_with("re:") {
 | 
					                    if uri_format.starts_with("re:") {
 | 
				
			||||||
 | 
					                        trace!("Checking if URI matches regex: {}", uri_format);
 | 
				
			||||||
                        let uri_format = uri_format.trim_start_matches("re:");
 | 
					                        let uri_format = uri_format.trim_start_matches("re:");
 | 
				
			||||||
                        // TODO: generate regex in advance
 | 
					                        // TODO: generate regex in advance
 | 
				
			||||||
                        // TODO: error
 | 
					                        // TODO: error
 | 
				
			||||||
                        let re = Regex::new(uri_format).unwrap();
 | 
					                        let re = Regex::new(uri_format).unwrap();
 | 
				
			||||||
                        if re.is_match(stripped_uri) {
 | 
					                        if re.is_match(stripped_uri) {
 | 
				
			||||||
 | 
					                            trace!("Found URI matches regex app: {}", key);
 | 
				
			||||||
                            return Some(key.clone());
 | 
					                            return Some(key.clone());
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        trace!("Checking if URI starts with: {}", uri_format);
 | 
				
			||||||
                        if stripped_uri.starts_with(uri_format) {
 | 
					                        if stripped_uri.starts_with(uri_format) {
 | 
				
			||||||
 | 
					                            trace!("Found URI matches app: {}", key);
 | 
				
			||||||
                            return Some(key.clone());
 | 
					                            return Some(key.clone());
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@ -51,9 +58,11 @@ impl SSOWatConfig {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // No app URI matched
 | 
					            // No app URI matched
 | 
				
			||||||
 | 
					            trace!("No application matched for URI {}", stripped_uri);
 | 
				
			||||||
            return None;
 | 
					            return None;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            // No domain (eg. http://8.8.8.8/)
 | 
					            // No domain (eg. http://8.8.8.8/)
 | 
				
			||||||
 | 
					            trace!("No domain requested for permission request");
 | 
				
			||||||
            return None;
 | 
					            return None;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -96,3 +105,9 @@ pub struct Permission {
 | 
				
			|||||||
pub struct PermissionName {
 | 
					pub struct PermissionName {
 | 
				
			||||||
    name: String,
 | 
					    name: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl std::fmt::Display for PermissionName {
 | 
				
			||||||
 | 
					    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
				
			||||||
 | 
					        self.name.fmt(fmt)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user