Add more debugging information in authorize route, and cargo fmt
This commit is contained in:
parent
9481c0ff7e
commit
907f22bfd4
|
@ -8,4 +8,4 @@ use std::path::PathBuf;
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
/// Where to place the UNIX socket for SSOWat
|
/// Where to place the UNIX socket for SSOWat
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
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,36 +1,40 @@
|
||||||
use axum::{
|
use axum::{
|
||||||
RequestPartsExt,
|
extract::{FromRequestParts, State},
|
||||||
extract::{FromRequestParts, State},
|
http::{header::HeaderMap, request::Parts, StatusCode},
|
||||||
http::{StatusCode, header::HeaderMap, request::Parts},
|
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(
|
||||||
let headers: HeaderMap = parts.extract().await.unwrap();
|
parts: &mut Parts,
|
||||||
if let Some(uri) = headers.get("X-Original-URI") {
|
state: &RoutableAppState,
|
||||||
// TODO: error
|
) -> Result<Self, Self::Rejection> {
|
||||||
Ok(
|
let headers: HeaderMap = parts.extract().await.unwrap();
|
||||||
OriginalURI(Url::parse(uri.to_str().unwrap()).unwrap())
|
if let Some(uri) = headers.get("X-Original-URI") {
|
||||||
)
|
trace!("Received original URI: {}", uri.to_str().unwrap());
|
||||||
} else {
|
// TODO: error
|
||||||
// TODO: error
|
Ok(OriginalURI(Url::parse(uri.to_str().unwrap()).unwrap()))
|
||||||
panic!()
|
} else {
|
||||||
}
|
// TODO: error
|
||||||
}
|
panic!()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<Url> for OriginalURI {
|
impl AsRef<Url> for OriginalURI {
|
||||||
|
@ -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);
|
||||||
|
if let Some(username) = username {
|
||||||
|
headers.insert("Remote-User", username.as_str().parse().unwrap());
|
||||||
|
headers.insert("user", username.as_str().parse().unwrap());
|
||||||
|
}
|
||||||
|
let res = (StatusCode::OK, headers).into_response();
|
||||||
|
debug!("{:?}", res);
|
||||||
|
res
|
||||||
} else {
|
} else {
|
||||||
StatusCode::FORBIDDEN
|
debug!("User {:?} is not authorized.", username);
|
||||||
|
(StatusCode::FORBIDDEN, headers).into_response()
|
||||||
}
|
}
|
||||||
}, Err(e) => {
|
}
|
||||||
|
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,
|
||||||
// First compare the timestamp (cheapest operation to invalidate the match)
|
jar: &RwLock<Vec<Session>>,
|
||||||
cookie.timestamp == timestamp
|
timestamp: i64,
|
||||||
&& cookie.content == content
|
content: &str,
|
||||||
}).cloned()
|
) -> Option<Session> {
|
||||||
|
jar.read()
|
||||||
|
.await
|
||||||
|
.iter()
|
||||||
|
.find(|cookie| {
|
||||||
|
// First compare the timestamp (cheapest operation to invalidate the match)
|
||||||
|
cookie.timestamp == timestamp && cookie.content == content
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,13 +194,16 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verifies whether a given cookie string has expired, before parsing it entirely. Returns:
|
/// Verifies whether a given cookie string has expired, before parsing it entirely. Returns:
|
||||||
/// - Err(SessionError) when the cookie is malformed
|
/// - Err(SessionError) when the cookie is malformed
|
||||||
/// - Ok(Some(timestamp)) when the cookie is still valid
|
/// - Ok(Some(timestamp)) when the cookie is still valid
|
||||||
|
@ -193,7 +223,7 @@ impl Session {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The typed [`yunohost_api::Username`] for which this cookie is deemed valid.
|
/// The typed [`yunohost_api::Username`] for which this cookie is deemed valid.
|
||||||
pub fn username(&self) -> &Username {
|
pub fn username(&self) -> &Username {
|
||||||
&self.username
|
&self.username
|
||||||
|
@ -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,34 +38,41 @@ 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 socket;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod socket;
|
|
|
@ -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,16 +110,16 @@ 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
|
||||||
FSPermissions::new().chmod(0o777).apply_to(&path).await?;
|
FSPermissions::new().chmod(0o777).apply_to(&path).await?;
|
||||||
|
|
||||||
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,5 +1,5 @@
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
|
||||||
pub fn now() -> i64 {
|
pub fn now() -> i64 {
|
||||||
Utc::now().timestamp()
|
Utc::now().timestamp()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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…
Reference in New Issue
Block a user