mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 01:12:24 +00:00
Breaking changes to auth provider, principal store outsourced to new config file
This commit is contained in:
@@ -10,7 +10,7 @@ use calendar_set::CalendarSetResourceService;
|
||||
use principal::{PrincipalResource, PrincipalResourceService};
|
||||
use rustical_dav::resource::{NamedRoute, ResourceService, ResourceServiceRoute};
|
||||
use rustical_dav::resources::RootResourceService;
|
||||
use rustical_store::auth::{AuthenticationMiddleware, AuthenticationProvider, UserStore};
|
||||
use rustical_store::auth::{AuthenticationMiddleware, AuthenticationProvider};
|
||||
use rustical_store::{AddressbookStore, CalendarStore, ContactBirthdayStore, SubscriptionStore};
|
||||
use std::sync::Arc;
|
||||
use subscription::subscription_resource;
|
||||
@@ -25,13 +25,11 @@ mod subscription;
|
||||
pub use error::Error;
|
||||
|
||||
pub fn caldav_service<
|
||||
US: UserStore,
|
||||
AP: AuthenticationProvider,
|
||||
AS: AddressbookStore,
|
||||
C: CalendarStore,
|
||||
S: SubscriptionStore,
|
||||
>(
|
||||
user_store: Arc<US>,
|
||||
auth_provider: Arc<AP>,
|
||||
store: Arc<C>,
|
||||
addr_store: Arc<AS>,
|
||||
@@ -40,7 +38,7 @@ pub fn caldav_service<
|
||||
let birthday_store = Arc::new(ContactBirthdayStore::new(addr_store));
|
||||
|
||||
web::scope("")
|
||||
.wrap(AuthenticationMiddleware::new(auth_provider))
|
||||
.wrap(AuthenticationMiddleware::new(auth_provider.clone()))
|
||||
.wrap(
|
||||
ErrorHandlers::new().handler(StatusCode::METHOD_NOT_ALLOWED, |res| {
|
||||
Ok(ErrorHandlerResponse::Response(
|
||||
@@ -68,7 +66,7 @@ pub fn caldav_service<
|
||||
.service(
|
||||
web::scope("/principal").service(
|
||||
web::scope("/{principal}")
|
||||
.service(PrincipalResourceService{store: user_store, home_set: &[
|
||||
.service(PrincipalResourceService{auth_provider, home_set: &[
|
||||
("calendar", false), ("birthdays", true)
|
||||
]}.actix_resource().name(PrincipalResource::route_name()))
|
||||
.service(web::scope("/calendar")
|
||||
|
||||
@@ -9,7 +9,7 @@ use rustical_dav::privileges::UserPrivilegeSet;
|
||||
use rustical_dav::resource::{NamedRoute, Resource, ResourceService};
|
||||
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
|
||||
use rustical_store::auth::user::PrincipalType;
|
||||
use rustical_store::auth::{User, UserStore};
|
||||
use rustical_store::auth::{AuthenticationProvider, User};
|
||||
use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize};
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -131,13 +131,13 @@ impl Resource for PrincipalResource {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PrincipalResourceService<US: UserStore> {
|
||||
pub store: Arc<US>,
|
||||
pub struct PrincipalResourceService<AP: AuthenticationProvider> {
|
||||
pub auth_provider: Arc<AP>,
|
||||
pub home_set: &'static [(&'static str, bool)],
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl<US: UserStore> ResourceService for PrincipalResourceService<US> {
|
||||
impl<AP: AuthenticationProvider> ResourceService for PrincipalResourceService<AP> {
|
||||
type PathComponents = (String,);
|
||||
type MemberType = CalendarSetResource;
|
||||
type Resource = PrincipalResource;
|
||||
@@ -148,8 +148,8 @@ impl<US: UserStore> ResourceService for PrincipalResourceService<US> {
|
||||
(principal,): &Self::PathComponents,
|
||||
) -> Result<Self::Resource, Self::Error> {
|
||||
let user = self
|
||||
.store
|
||||
.get_user(principal)
|
||||
.auth_provider
|
||||
.get_principal(principal)
|
||||
.await?
|
||||
.ok_or(crate::Error::NotFound)?;
|
||||
Ok(PrincipalResource {
|
||||
|
||||
@@ -15,7 +15,7 @@ use principal::{PrincipalResource, PrincipalResourceService};
|
||||
use rustical_dav::resource::{NamedRoute, ResourceService};
|
||||
use rustical_dav::resources::RootResourceService;
|
||||
use rustical_store::{
|
||||
auth::{AuthenticationMiddleware, AuthenticationProvider, UserStore},
|
||||
auth::{AuthenticationMiddleware, AuthenticationProvider},
|
||||
AddressbookStore, SubscriptionStore,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
@@ -27,12 +27,11 @@ pub mod principal;
|
||||
|
||||
pub fn carddav_service<AP: AuthenticationProvider, A: AddressbookStore, S: SubscriptionStore>(
|
||||
auth_provider: Arc<AP>,
|
||||
user_store: Arc<impl UserStore>,
|
||||
store: Arc<A>,
|
||||
subscription_store: Arc<S>,
|
||||
) -> impl HttpServiceFactory {
|
||||
web::scope("")
|
||||
.wrap(AuthenticationMiddleware::new(auth_provider))
|
||||
.wrap(AuthenticationMiddleware::new(auth_provider.clone()))
|
||||
.wrap(
|
||||
ErrorHandlers::new().handler(StatusCode::METHOD_NOT_ALLOWED, |res| {
|
||||
Ok(ErrorHandlerResponse::Response(
|
||||
@@ -60,7 +59,7 @@ pub fn carddav_service<AP: AuthenticationProvider, A: AddressbookStore, S: Subsc
|
||||
web::scope("/principal").service(
|
||||
web::scope("/{principal}")
|
||||
.service(
|
||||
PrincipalResourceService::new(store.clone(), user_store)
|
||||
PrincipalResourceService::new(store.clone(), auth_provider)
|
||||
.actix_resource()
|
||||
.name(PrincipalResource::route_name()),
|
||||
)
|
||||
|
||||
@@ -6,21 +6,21 @@ use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp};
|
||||
use rustical_dav::privileges::UserPrivilegeSet;
|
||||
use rustical_dav::resource::{NamedRoute, Resource, ResourceService};
|
||||
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
|
||||
use rustical_store::auth::{User, UserStore};
|
||||
use rustical_store::auth::{AuthenticationProvider, User};
|
||||
use rustical_store::AddressbookStore;
|
||||
use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct PrincipalResourceService<A: AddressbookStore, US: UserStore> {
|
||||
pub struct PrincipalResourceService<A: AddressbookStore, AP: AuthenticationProvider> {
|
||||
addr_store: Arc<A>,
|
||||
user_store: Arc<US>,
|
||||
auth_provider: Arc<AP>,
|
||||
}
|
||||
|
||||
impl<A: AddressbookStore, US: UserStore> PrincipalResourceService<A, US> {
|
||||
pub fn new(addr_store: Arc<A>, user_store: Arc<US>) -> Self {
|
||||
impl<A: AddressbookStore, AP: AuthenticationProvider> PrincipalResourceService<A, AP> {
|
||||
pub fn new(addr_store: Arc<A>, auth_provider: Arc<AP>) -> Self {
|
||||
Self {
|
||||
addr_store,
|
||||
user_store,
|
||||
auth_provider,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,9 @@ impl Resource for PrincipalResource {
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl<A: AddressbookStore, US: UserStore> ResourceService for PrincipalResourceService<A, US> {
|
||||
impl<A: AddressbookStore, AP: AuthenticationProvider> ResourceService
|
||||
for PrincipalResourceService<A, AP>
|
||||
{
|
||||
type PathComponents = (String,);
|
||||
type MemberType = AddressbookResource;
|
||||
type Resource = PrincipalResource;
|
||||
@@ -144,8 +146,8 @@ impl<A: AddressbookStore, US: UserStore> ResourceService for PrincipalResourceSe
|
||||
(principal,): &Self::PathComponents,
|
||||
) -> Result<Self::Resource, Self::Error> {
|
||||
let user = self
|
||||
.user_store
|
||||
.get_user(principal)
|
||||
.auth_provider
|
||||
.get_principal(principal)
|
||||
.await?
|
||||
.ok_or(crate::Error::NotFound)?;
|
||||
Ok(PrincipalResource { principal: user })
|
||||
|
||||
@@ -25,6 +25,8 @@ pbkdf2 = { workspace = true }
|
||||
chrono-tz = { workspace = true }
|
||||
derive_more = { workspace = true }
|
||||
rustical_xml.workspace = true
|
||||
toml.workspace = true
|
||||
tokio.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
rstest = { workspace = true }
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
pub mod middleware;
|
||||
pub mod static_user_store;
|
||||
pub mod toml_user_store;
|
||||
pub mod user;
|
||||
mod user_store;
|
||||
use crate::error::Error;
|
||||
use async_trait::async_trait;
|
||||
|
||||
#[async_trait]
|
||||
pub trait AuthenticationProvider: 'static {
|
||||
async fn get_principal(&self, id: &str) -> Result<Option<User>, crate::Error>;
|
||||
async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error>;
|
||||
}
|
||||
|
||||
pub use middleware::AuthenticationMiddleware;
|
||||
pub use static_user_store::{StaticUserStore, StaticUserStoreConfig};
|
||||
pub use toml_user_store::{TomlPrincipalStore, TomlUserStoreConfig};
|
||||
pub use user::User;
|
||||
pub use user_store::UserStore;
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
use crate::{auth::User, error::Error};
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::{AuthenticationProvider, UserStore};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
pub struct StaticUserStoreConfig {
|
||||
pub users: Vec<User>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct StaticUserStore {
|
||||
pub users: HashMap<String, User>,
|
||||
}
|
||||
|
||||
impl StaticUserStore {
|
||||
pub fn new(config: StaticUserStoreConfig) -> Self {
|
||||
Self {
|
||||
users: HashMap::from_iter(config.users.into_iter().map(|user| (user.id.clone(), user))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UserStore for StaticUserStore {
|
||||
async fn get_user(&self, id: &str) -> Result<Option<User>, crate::Error> {
|
||||
Ok(self.users.get(id).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AuthenticationProvider for StaticUserStore {
|
||||
async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error> {
|
||||
let user: User = match self.get_user(user_id).await? {
|
||||
Some(user) => user,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
// Try app tokens first since they are cheaper to calculate
|
||||
// They can afford less iterations since they can be generated with high entropy
|
||||
for app_token in &user.app_tokens {
|
||||
if password_auth::verify_password(token, app_token).is_ok() {
|
||||
return Ok(Some(user));
|
||||
}
|
||||
}
|
||||
|
||||
let password = match &user.password {
|
||||
Some(password) => password,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
if password_auth::verify_password(token, password).is_ok() {
|
||||
return Ok(Some(user));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
73
crates/store/src/auth/toml_user_store.rs
Normal file
73
crates/store/src/auth/toml_user_store.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use super::AuthenticationProvider;
|
||||
use crate::{auth::User, error::Error};
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, fs, io};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
struct TomlDataModel {
|
||||
principals: Vec<User>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
pub struct TomlUserStoreConfig {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TomlPrincipalStore {
|
||||
pub principals: RwLock<HashMap<String, User>>,
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum TomlStoreError {
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("Error parsing users toml: {0}")]
|
||||
Toml(#[from] toml::de::Error),
|
||||
}
|
||||
|
||||
impl TomlPrincipalStore {
|
||||
pub fn new(config: TomlUserStoreConfig) -> Result<Self, TomlStoreError> {
|
||||
let TomlDataModel { principals } = toml::from_str(&fs::read_to_string(&config.path)?)?;
|
||||
Ok(Self {
|
||||
principals: RwLock::new(HashMap::from_iter(
|
||||
principals.into_iter().map(|user| (user.id.clone(), user)),
|
||||
)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AuthenticationProvider for TomlPrincipalStore {
|
||||
async fn get_principal(&self, id: &str) -> Result<Option<User>, crate::Error> {
|
||||
Ok(self.principals.read().await.get(id).cloned())
|
||||
}
|
||||
|
||||
async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error> {
|
||||
let user: User = match self.get_principal(user_id).await? {
|
||||
Some(user) => user,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
// Try app tokens first since they are cheaper to calculate
|
||||
// They can afford less iterations since they can be generated with high entropy
|
||||
for app_token in &user.app_tokens {
|
||||
if password_auth::verify_password(token, app_token).is_ok() {
|
||||
return Ok(Some(user));
|
||||
}
|
||||
}
|
||||
|
||||
let password = match &user.password {
|
||||
Some(password) => password,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
if password_auth::verify_password(token, password).is_ok() {
|
||||
return Ok(Some(user));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
use super::User;
|
||||
use async_trait::async_trait;
|
||||
|
||||
#[async_trait]
|
||||
pub trait UserStore: 'static {
|
||||
async fn get_user(&self, id: &str) -> Result<Option<User>, crate::Error>;
|
||||
}
|
||||
Reference in New Issue
Block a user