Compare commits
3 Commits
c5731665a3
...
1667c3295b
Author | SHA1 | Date | |
---|---|---|---|
1667c3295b | |||
4eb3ac6350 | |||
2d1f3a985f |
|
@ -10,7 +10,7 @@ futures = "0.3"
|
||||||
tower = "0.4"
|
tower = "0.4"
|
||||||
tokio = { version = "1", features = [ "full" ] }
|
tokio = { version = "1", features = [ "full" ] }
|
||||||
hyper = { version = "0.14", features = [ "full" ] }
|
hyper = { version = "0.14", features = [ "full" ] }
|
||||||
axum = { version = "0.6", features = [ "headers", "http2", "macros", "tracing" ] }
|
axum = { version = "0.6", features = [ "headers", "http2", "macros", "tracing", "json" ] }
|
||||||
clap = { version = "4.3", features = [ "derive" ] }
|
clap = { version = "4.3", features = [ "derive" ] }
|
||||||
snafu = "0.7"
|
snafu = "0.7"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
@ -20,7 +20,8 @@ env_logger = "0.10"
|
||||||
ring = "0.16"
|
ring = "0.16"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
chrono = { version = "0.4", features = [ "serde" ] }
|
chrono = { version = "0.4", features = [ "serde" ] }
|
||||||
yunohost-api = { path = "yunohost-api" }
|
yunohost-api = { path = "yunohost-api", features = [ "axum" ] }
|
||||||
axum_typed_multipart = "0.8"
|
axum_typed_multipart = "0.8"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
serde = { version = "1", features = [ "derive" ] }
|
serde = { version = "1", features = [ "derive" ] }
|
||||||
|
file-owner = { version = "0.1" }
|
12
src/error.rs
12
src/error.rs
|
@ -16,4 +16,16 @@ pub enum 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"))]
|
||||||
|
TokioTask { source: tokio::task::JoinError },
|
||||||
|
|
||||||
|
#[snafu(display("Failed to set permissions on file {}", path.display()))]
|
||||||
|
Permissions { path: PathBuf, source: std::io::Error },
|
||||||
|
|
||||||
|
#[snafu(display("Failed to set owner on file {}", path.display()))]
|
||||||
|
PermissionsChown { path: PathBuf, source: file_owner::FileOwnerError },
|
||||||
|
|
||||||
|
#[snafu(display("Failed to set group on file {}", path.display()))]
|
||||||
|
PermissionsChgrp { path: PathBuf, source: file_owner::FileOwnerError },
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#[macro_use] extern crate async_trait;
|
#[macro_use] extern crate async_trait;
|
||||||
#[macro_use] extern crate axum;
|
#[macro_use] extern crate axum;
|
||||||
|
#[macro_use] extern crate log;
|
||||||
#[macro_use] extern crate serde;
|
#[macro_use] extern crate serde;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{FromRequest, Form, Json, Query},
|
extract::{FromRequest, Form, Json, Query, 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 yunohost_api::{Username, Password};
|
||||||
|
|
||||||
|
use crate::state::RoutableAppState;
|
||||||
|
|
||||||
#[derive(Debug, TryFromMultipart, Deserialize)]
|
#[derive(Debug, TryFromMultipart, Deserialize)]
|
||||||
pub struct LoginForm {
|
pub struct LoginForm {
|
||||||
username: String,
|
username: Username,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
password: String,
|
password: Password,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -52,6 +55,10 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub async fn route(form: LoginForm) -> String {
|
pub async fn route(state: State<RoutableAppState>, form: LoginForm) -> String {
|
||||||
format!("Welcome {}", form.username)
|
if state.check_login(&form.username, &form.password).await.unwrap() {
|
||||||
|
format!("Welcome {}", &form.username)
|
||||||
|
} else {
|
||||||
|
format!("Invalid login for {}", &form.username)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ mod login;
|
||||||
pub fn router(subpath: Option<String>, state: RoutableAppState) -> Router {
|
pub fn router(subpath: Option<String>, state: RoutableAppState) -> Router {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(index::route))
|
.route("/", get(index::route))
|
||||||
.route("/login/", get(login::route))
|
.route("/login/", post(login::route))
|
||||||
.with_state(state);
|
.with_state(state);
|
||||||
if let Some(p) = subpath {
|
if let Some(p) = subpath {
|
||||||
Router::new()
|
Router::new()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use snafu::prelude::*;
|
use snafu::prelude::*;
|
||||||
use yunohost_api::YunohostUsers;
|
use yunohost_api::{YunohostUsers, Username, Password};
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -23,4 +23,8 @@ impl AppState {
|
||||||
users: YunohostUsers::new(500).await.context(YunohostSnafu)?,
|
users: YunohostUsers::new(500).await.context(YunohostSnafu)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn check_login(&self, username: &Username, password: &Password) -> Result<bool, Error> {
|
||||||
|
self.users.check_credentials(username, password).await.context(YunohostSnafu)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
78
src/utils/fs.rs
Normal file
78
src/utils/fs.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
use file_owner::PathExt;
|
||||||
|
use snafu::prelude::*;
|
||||||
|
use tokio::{
|
||||||
|
fs::set_permissions,
|
||||||
|
task::spawn_blocking,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fs::Permissions,
|
||||||
|
os::unix::fs::PermissionsExt,
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::error::*;
|
||||||
|
|
||||||
|
pub struct FSPermissions {
|
||||||
|
pub owner: Option<String>,
|
||||||
|
pub group: Option<String>,
|
||||||
|
pub mode: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FSPermissions {
|
||||||
|
pub fn new() -> FSPermissions {
|
||||||
|
FSPermissions {
|
||||||
|
owner: None,
|
||||||
|
group: None,
|
||||||
|
mode: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chown(mut self, owner: &str) -> Self {
|
||||||
|
self.owner = Some(owner.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chgrp(mut self, group: &str) -> Self {
|
||||||
|
self.group = Some(group.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chmod(mut self, mode: u32) -> Self {
|
||||||
|
self.mode = Some(mode);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn apply_to(&self, path: &Path) -> Result<(), Error> {
|
||||||
|
if let Some(mode) = self.mode {
|
||||||
|
set_permissions(
|
||||||
|
path,
|
||||||
|
Permissions::from_mode(mode)
|
||||||
|
).await.context(PermissionsSnafu { path: path.to_path_buf()})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(owner) = &self.owner {
|
||||||
|
let owner = owner.to_string();
|
||||||
|
let path = path.to_path_buf();
|
||||||
|
let _ = spawn_blocking(move || -> Result<(), Error> {
|
||||||
|
Ok(
|
||||||
|
path.set_owner(owner.as_str())
|
||||||
|
.context(PermissionsChownSnafu { path: path.to_path_buf() })?
|
||||||
|
)
|
||||||
|
}).await.context(TokioTaskSnafu)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(group) = &self.group {
|
||||||
|
let group = group.to_string();
|
||||||
|
let path = path.to_path_buf();
|
||||||
|
let _ = spawn_blocking(move || -> Result<(), Error> {
|
||||||
|
Ok(
|
||||||
|
path.set_group(group.as_str())
|
||||||
|
.context(PermissionsChgrpSnafu { path: path.to_path_buf() })?
|
||||||
|
)
|
||||||
|
}).await.context(TokioTaskSnafu)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
|
pub mod fs;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod socket;
|
pub mod socket;
|
|
@ -21,7 +21,10 @@ use tokio::{
|
||||||
};
|
};
|
||||||
use tower::BoxError;
|
use tower::BoxError;
|
||||||
|
|
||||||
use crate::error::*;
|
use crate::{
|
||||||
|
error::*,
|
||||||
|
utils::fs::FSPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct ServerAccept {
|
pub struct ServerAccept {
|
||||||
uds: UnixListener,
|
uds: UnixListener,
|
||||||
|
@ -118,7 +121,9 @@ pub async fn serve(path: &Path, app: Router) -> Result<(), Error> {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// TODO: set permissions
|
// TODO: make proper permissions
|
||||||
|
// Apply 777 permissions
|
||||||
|
FSPermissions::new().chmod(0o777).apply_to(&path).await?;
|
||||||
|
|
||||||
let uds = UnixListener::bind(path.clone())
|
let uds = UnixListener::bind(path.clone())
|
||||||
.context(SocketCreateSnafu { path: path.clone() })?;
|
.context(SocketCreateSnafu { path: path.clone() })?;
|
||||||
|
|
|
@ -15,7 +15,13 @@ serde = { version = "1", features = [ "derive" ] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
regex = "1.9"
|
regex = "1.9"
|
||||||
url = "2.4"
|
url = "2.4"
|
||||||
|
futures-util = { version = "0.3", optional = true }
|
||||||
|
axum = { version = "0.6", optional = true }
|
||||||
|
axum_typed_multipart = { version = "0.8", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
scan-rules = "0.2"
|
scan-rules = "0.2"
|
||||||
tokio = { version = "1", features = [ "sync", "rt" ] }
|
tokio = { version = "1", features = [ "sync", "rt" ] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
axum = [ "dep:futures-util", "dep:axum", "dep:axum_typed_multipart" ]
|
||||||
|
|
|
@ -1,6 +1,17 @@
|
||||||
use ldap3::dn_escape;
|
use ldap3::dn_escape;
|
||||||
use serde::{Serialize, Deserialize};
|
use snafu::prelude::*;
|
||||||
use snafu::OptionExt;
|
|
||||||
|
#[cfg(feature="axum")]
|
||||||
|
use axum::{
|
||||||
|
async_trait,
|
||||||
|
body::Bytes,
|
||||||
|
};
|
||||||
|
#[cfg(feature="axum")]
|
||||||
|
use axum_typed_multipart::{FieldMetadata, TryFromChunks, TypedMultipartError};
|
||||||
|
#[cfg(feature="axum")]
|
||||||
|
use futures_util::stream::Stream;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize, Deserializer};
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
@ -14,7 +25,7 @@ fn non_empty_string(s: &str) -> Option<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
||||||
pub struct Username(String);
|
pub struct Username(String);
|
||||||
|
|
||||||
impl Username {
|
impl Username {
|
||||||
|
@ -56,7 +67,29 @@ impl std::fmt::Display for Username {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
impl<'de> Deserialize<'de> for Username {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where D: Deserializer<'de>
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
FromStr::from_str(&s).map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="axum")]
|
||||||
|
#[async_trait]
|
||||||
|
impl TryFromChunks for Username {
|
||||||
|
async fn try_from_chunks(
|
||||||
|
chunks: impl Stream<Item = Result<Bytes, TypedMultipartError>> + Send + Sync + Unpin,
|
||||||
|
metadata: FieldMetadata,
|
||||||
|
) -> Result<Self, TypedMultipartError> {
|
||||||
|
let string = String::try_from_chunks(chunks, metadata).await?;
|
||||||
|
let data = Self::from_str(&string).map_err(|e| TypedMultipartError::Other { source: e.into() })?;
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
pub struct Password(String);
|
pub struct Password(String);
|
||||||
|
|
||||||
impl Password {
|
impl Password {
|
||||||
|
@ -81,3 +114,25 @@ impl FromStr for Password {
|
||||||
Password::new(s)
|
Password::new(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Password {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where D: Deserializer<'de>
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
FromStr::from_str(&s).map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="axum")]
|
||||||
|
#[async_trait]
|
||||||
|
impl TryFromChunks for Password {
|
||||||
|
async fn try_from_chunks(
|
||||||
|
chunks: impl Stream<Item = Result<Bytes, TypedMultipartError>> + Send + Sync + Unpin,
|
||||||
|
metadata: FieldMetadata,
|
||||||
|
) -> Result<Self, TypedMultipartError> {
|
||||||
|
let string = String::try_from_chunks(chunks, metadata).await?;
|
||||||
|
let data = Self::from_str(&string).map_err(|e| TypedMultipartError::Other { source: e.into() })?;
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::Username;
|
use crate::Username;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct SSOWatConfig {
|
pub struct SSOWatConfig {
|
||||||
domains: Vec<String>,
|
domains: Vec<String>,
|
||||||
permissions: HashMap<PermissionName, Permission>,
|
permissions: HashMap<PermissionName, Permission>,
|
||||||
|
@ -32,7 +32,7 @@ impl SSOWatConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct Permission {
|
pub struct Permission {
|
||||||
auth_header: bool,
|
auth_header: bool,
|
||||||
label: String,
|
label: String,
|
||||||
|
@ -43,7 +43,7 @@ pub struct Permission {
|
||||||
users: Vec<Username>,
|
users: Vec<Username>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
pub struct PermissionName {
|
pub struct PermissionName {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
name: String,
|
name: String,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user