mirror of
				https://github.com/nitnelave/lldap.git
				synced 2023-04-12 14:25:13 +00:00 
			
		
		
		
	server: move domain types to a separate file
This commit is contained in:
		
							parent
							
								
									e89b1538af
								
							
						
					
					
						commit
						09a0522e2d
					
				| @ -1,223 +1,13 @@ | |||||||
| use super::error::*; | use super::{ | ||||||
|  |     error::Result, | ||||||
|  |     types::{ | ||||||
|  |         Group, GroupDetails, GroupId, JpegPhoto, User, UserAndGroups, UserColumn, UserId, Uuid, | ||||||
|  |     }, | ||||||
|  | }; | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use std::collections::HashSet; | use std::collections::HashSet; | ||||||
| 
 | 
 | ||||||
| pub use super::model::{GroupColumn, UserColumn}; |  | ||||||
| 
 |  | ||||||
| #[derive(PartialEq, Hash, Eq, Clone, Debug, Default, Serialize, Deserialize)] |  | ||||||
| #[serde(try_from = "&str")] |  | ||||||
| pub struct Uuid(String); |  | ||||||
| 
 |  | ||||||
| impl Uuid { |  | ||||||
|     pub fn from_name_and_date(name: &str, creation_date: &chrono::DateTime<chrono::Utc>) -> Self { |  | ||||||
|         Uuid( |  | ||||||
|             uuid::Uuid::new_v3( |  | ||||||
|                 &uuid::Uuid::NAMESPACE_X500, |  | ||||||
|                 &[name.as_bytes(), creation_date.to_rfc3339().as_bytes()].concat(), |  | ||||||
|             ) |  | ||||||
|             .to_string(), |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn as_str(&self) -> &str { |  | ||||||
|         &self.0 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn into_string(self) -> String { |  | ||||||
|         self.0 |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl<'a> std::convert::TryFrom<&'a str> for Uuid { |  | ||||||
|     type Error = anyhow::Error; |  | ||||||
|     fn try_from(s: &'a str) -> anyhow::Result<Self> { |  | ||||||
|         Ok(Uuid(uuid::Uuid::parse_str(s)?.to_string())) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl std::string::ToString for Uuid { |  | ||||||
|     fn to_string(&self) -> String { |  | ||||||
|         self.0.clone() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl sea_orm::TryGetable for Uuid { |  | ||||||
|     fn try_get( |  | ||||||
|         res: &sea_orm::QueryResult, |  | ||||||
|         pre: &str, |  | ||||||
|         col: &str, |  | ||||||
|     ) -> std::result::Result<Self, sea_orm::TryGetError> { |  | ||||||
|         Ok(Uuid(String::try_get(res, pre, col)?)) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[cfg(test)] |  | ||||||
| #[macro_export] |  | ||||||
| macro_rules! uuid { |  | ||||||
|     ($s:literal) => { |  | ||||||
|         <$crate::domain::handler::Uuid as std::convert::TryFrom<_>>::try_from($s).unwrap() |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(PartialEq, Eq, Clone, Debug, Default, Serialize, Deserialize)] |  | ||||||
| #[serde(from = "String")] |  | ||||||
| pub struct UserId(String); |  | ||||||
| 
 |  | ||||||
| impl UserId { |  | ||||||
|     pub fn new(user_id: &str) -> Self { |  | ||||||
|         Self(user_id.to_lowercase()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn as_str(&self) -> &str { |  | ||||||
|         self.0.as_str() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn into_string(self) -> String { |  | ||||||
|         self.0 |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl std::fmt::Display for UserId { |  | ||||||
|     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |  | ||||||
|         write!(f, "{}", self.0) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl From<String> for UserId { |  | ||||||
|     fn from(s: String) -> Self { |  | ||||||
|         Self::new(&s) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] |  | ||||||
| pub struct JpegPhoto(#[serde(with = "serde_bytes")] Vec<u8>); |  | ||||||
| 
 |  | ||||||
| impl JpegPhoto { |  | ||||||
|     pub fn null() -> Self { |  | ||||||
|         Self(vec![]) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl From<JpegPhoto> for sea_orm::Value { |  | ||||||
|     fn from(photo: JpegPhoto) -> Self { |  | ||||||
|         photo.0.into() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl From<&JpegPhoto> for sea_orm::Value { |  | ||||||
|     fn from(photo: &JpegPhoto) -> Self { |  | ||||||
|         photo.0.as_slice().into() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl TryFrom<&[u8]> for JpegPhoto { |  | ||||||
|     type Error = anyhow::Error; |  | ||||||
|     fn try_from(bytes: &[u8]) -> anyhow::Result<Self> { |  | ||||||
|         if bytes.is_empty() { |  | ||||||
|             return Ok(JpegPhoto::null()); |  | ||||||
|         } |  | ||||||
|         // Confirm that it's a valid Jpeg, then store only the bytes.
 |  | ||||||
|         image::io::Reader::with_format(std::io::Cursor::new(bytes), image::ImageFormat::Jpeg) |  | ||||||
|             .decode()?; |  | ||||||
|         Ok(JpegPhoto(bytes.to_vec())) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl TryFrom<Vec<u8>> for JpegPhoto { |  | ||||||
|     type Error = anyhow::Error; |  | ||||||
|     fn try_from(bytes: Vec<u8>) -> anyhow::Result<Self> { |  | ||||||
|         if bytes.is_empty() { |  | ||||||
|             return Ok(JpegPhoto::null()); |  | ||||||
|         } |  | ||||||
|         // Confirm that it's a valid Jpeg, then store only the bytes.
 |  | ||||||
|         image::io::Reader::with_format( |  | ||||||
|             std::io::Cursor::new(bytes.as_slice()), |  | ||||||
|             image::ImageFormat::Jpeg, |  | ||||||
|         ) |  | ||||||
|         .decode()?; |  | ||||||
|         Ok(JpegPhoto(bytes)) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl TryFrom<String> for JpegPhoto { |  | ||||||
|     type Error = anyhow::Error; |  | ||||||
|     fn try_from(string: String) -> anyhow::Result<Self> { |  | ||||||
|         // The String format is in base64.
 |  | ||||||
|         Self::try_from(base64::decode(string.as_str())?) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl From<&JpegPhoto> for String { |  | ||||||
|     fn from(val: &JpegPhoto) -> Self { |  | ||||||
|         base64::encode(&val.0) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl JpegPhoto { |  | ||||||
|     pub fn into_bytes(self) -> Vec<u8> { |  | ||||||
|         self.0 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #[cfg(test)] |  | ||||||
|     pub fn for_tests() -> Self { |  | ||||||
|         use image::{ImageOutputFormat, Rgb, RgbImage}; |  | ||||||
|         let img = RgbImage::from_fn(32, 32, |x, y| { |  | ||||||
|             if (x + y) % 2 == 0 { |  | ||||||
|                 Rgb([0, 0, 0]) |  | ||||||
|             } else { |  | ||||||
|                 Rgb([255, 255, 255]) |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|         let mut bytes: Vec<u8> = Vec::new(); |  | ||||||
|         img.write_to( |  | ||||||
|             &mut std::io::Cursor::new(&mut bytes), |  | ||||||
|             ImageOutputFormat::Jpeg(0), |  | ||||||
|         ) |  | ||||||
|         .unwrap(); |  | ||||||
|         Self(bytes) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, sea_orm::FromQueryResult)] |  | ||||||
| pub struct User { |  | ||||||
|     pub user_id: UserId, |  | ||||||
|     pub email: String, |  | ||||||
|     pub display_name: Option<String>, |  | ||||||
|     pub first_name: Option<String>, |  | ||||||
|     pub last_name: Option<String>, |  | ||||||
|     pub avatar: Option<JpegPhoto>, |  | ||||||
|     pub creation_date: chrono::DateTime<chrono::Utc>, |  | ||||||
|     pub uuid: Uuid, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[cfg(test)] |  | ||||||
| impl Default for User { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         use chrono::TimeZone; |  | ||||||
|         let epoch = chrono::Utc.timestamp_opt(0, 0).unwrap(); |  | ||||||
|         User { |  | ||||||
|             user_id: UserId::default(), |  | ||||||
|             email: String::new(), |  | ||||||
|             display_name: None, |  | ||||||
|             first_name: None, |  | ||||||
|             last_name: None, |  | ||||||
|             avatar: None, |  | ||||||
|             creation_date: epoch, |  | ||||||
|             uuid: Uuid::from_name_and_date("", &epoch), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(PartialEq, Eq, Debug, Serialize, Deserialize)] |  | ||||||
| pub struct Group { |  | ||||||
|     pub id: GroupId, |  | ||||||
|     pub display_name: String, |  | ||||||
|     pub creation_date: chrono::DateTime<chrono::Utc>, |  | ||||||
|     pub uuid: Uuid, |  | ||||||
|     pub users: Vec<UserId>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)] | #[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)] | ||||||
| pub struct BindRequest { | pub struct BindRequest { | ||||||
|     pub name: UserId, |     pub name: UserId, | ||||||
| @ -282,23 +72,6 @@ pub trait LoginHandler: Clone + Send { | |||||||
|     async fn bind(&self, request: BindRequest) -> Result<()>; |     async fn bind(&self, request: BindRequest) -> Result<()>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] |  | ||||||
| pub struct GroupId(pub i32); |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sea_orm::FromQueryResult)] |  | ||||||
| pub struct GroupDetails { |  | ||||||
|     pub group_id: GroupId, |  | ||||||
|     pub display_name: String, |  | ||||||
|     pub creation_date: chrono::DateTime<chrono::Utc>, |  | ||||||
|     pub uuid: Uuid, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, Clone, PartialEq, Eq)] |  | ||||||
| pub struct UserAndGroups { |  | ||||||
|     pub user: User, |  | ||||||
|     pub groups: Option<Vec<GroupDetails>>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[async_trait] | #[async_trait] | ||||||
| pub trait GroupBackendHandler { | pub trait GroupBackendHandler { | ||||||
|     async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>; |     async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>; | ||||||
|  | |||||||
| @ -4,8 +4,9 @@ use ldap3_proto::{ | |||||||
| use tracing::{debug, info, instrument, warn}; | use tracing::{debug, info, instrument, warn}; | ||||||
| 
 | 
 | ||||||
| use crate::domain::{ | use crate::domain::{ | ||||||
|     handler::{BackendHandler, Group, GroupColumn, GroupRequestFilter, UserId, Uuid}, |     handler::{BackendHandler, GroupRequestFilter}, | ||||||
|     ldap::error::LdapError, |     ldap::error::LdapError, | ||||||
|  |     types::{Group, GroupColumn, UserId, Uuid}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use super::{ | use super::{ | ||||||
|  | |||||||
| @ -4,8 +4,9 @@ use ldap3_proto::{ | |||||||
| use tracing::{debug, info, instrument, warn}; | use tracing::{debug, info, instrument, warn}; | ||||||
| 
 | 
 | ||||||
| use crate::domain::{ | use crate::domain::{ | ||||||
|     handler::{BackendHandler, GroupDetails, User, UserColumn, UserId, UserRequestFilter}, |     handler::{BackendHandler, UserRequestFilter}, | ||||||
|     ldap::{error::LdapError, utils::expand_attribute_wildcards}, |     ldap::{error::LdapError, utils::expand_attribute_wildcards}, | ||||||
|  |     types::{GroupDetails, User, UserColumn, UserId}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use super::{ | use super::{ | ||||||
|  | |||||||
| @ -2,9 +2,10 @@ use itertools::Itertools; | |||||||
| use ldap3_proto::LdapResultCode; | use ldap3_proto::LdapResultCode; | ||||||
| use tracing::{debug, instrument, warn}; | use tracing::{debug, instrument, warn}; | ||||||
| 
 | 
 | ||||||
| use crate::domain::handler::{GroupColumn, UserColumn, UserId}; | use crate::domain::{ | ||||||
| 
 |     ldap::error::{LdapError, LdapResult}, | ||||||
| use super::error::{LdapError, LdapResult}; |     types::{GroupColumn, UserColumn, UserId}, | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| fn make_dn_pair<I>(mut iter: I) -> LdapResult<(String, String)> | fn make_dn_pair<I>(mut iter: I) -> LdapResult<(String, String)> | ||||||
| where | where | ||||||
|  | |||||||
| @ -9,3 +9,4 @@ pub mod sql_migrations; | |||||||
| pub mod sql_opaque_handler; | pub mod sql_opaque_handler; | ||||||
| pub mod sql_tables; | pub mod sql_tables; | ||||||
| pub mod sql_user_backend_handler; | pub mod sql_user_backend_handler; | ||||||
|  | pub mod types; | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| use sea_orm::entity::prelude::*; | use sea_orm::entity::prelude::*; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
| use crate::domain::handler::{GroupId, Uuid}; | use crate::domain::types::{GroupId, Uuid}; | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] | ||||||
| #[sea_orm(table_name = "groups")] | #[sea_orm(table_name = "groups")] | ||||||
| @ -15,29 +15,6 @@ pub struct Model { | |||||||
|     pub uuid: Uuid, |     pub uuid: Uuid, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl From<Model> for crate::domain::handler::Group { |  | ||||||
|     fn from(group: Model) -> Self { |  | ||||||
|         Self { |  | ||||||
|             id: group.group_id, |  | ||||||
|             display_name: group.display_name, |  | ||||||
|             creation_date: group.creation_date, |  | ||||||
|             uuid: group.uuid, |  | ||||||
|             users: vec![], |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl From<Model> for crate::domain::handler::GroupDetails { |  | ||||||
|     fn from(group: Model) -> Self { |  | ||||||
|         Self { |  | ||||||
|             group_id: group.group_id, |  | ||||||
|             display_name: group.display_name, |  | ||||||
|             creation_date: group.creation_date, |  | ||||||
|             uuid: group.uuid, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] | ||||||
| pub enum Relation { | pub enum Relation { | ||||||
|     #[sea_orm(has_many = "super::memberships::Entity")] |     #[sea_orm(has_many = "super::memberships::Entity")] | ||||||
| @ -51,3 +28,26 @@ impl Related<super::memberships::Entity> for Entity { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl ActiveModelBehavior for ActiveModel {} | impl ActiveModelBehavior for ActiveModel {} | ||||||
|  | 
 | ||||||
|  | impl From<Model> for crate::domain::types::Group { | ||||||
|  |     fn from(group: Model) -> Self { | ||||||
|  |         Self { | ||||||
|  |             id: group.group_id, | ||||||
|  |             display_name: group.display_name, | ||||||
|  |             creation_date: group.creation_date, | ||||||
|  |             uuid: group.uuid, | ||||||
|  |             users: vec![], | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<Model> for crate::domain::types::GroupDetails { | ||||||
|  |     fn from(group: Model) -> Self { | ||||||
|  |         Self { | ||||||
|  |             group_id: group.group_id, | ||||||
|  |             display_name: group.display_name, | ||||||
|  |             creation_date: group.creation_date, | ||||||
|  |             uuid: group.uuid, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| use sea_orm::entity::prelude::*; | use sea_orm::entity::prelude::*; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
| use crate::domain::handler::UserId; | use crate::domain::types::UserId; | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] | ||||||
| #[sea_orm(table_name = "jwt_refresh_storage")] | #[sea_orm(table_name = "jwt_refresh_storage")] | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| use sea_orm::entity::prelude::*; | use sea_orm::entity::prelude::*; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
| use crate::domain::handler::UserId; | use crate::domain::types::UserId; | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] | ||||||
| #[sea_orm(table_name = "jwt_storage")] | #[sea_orm(table_name = "jwt_storage")] | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| use sea_orm::entity::prelude::*; | use sea_orm::entity::prelude::*; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
| use crate::domain::handler::{GroupId, UserId}; | use crate::domain::types::{GroupId, UserId}; | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] | ||||||
| #[sea_orm(table_name = "memberships")] | #[sea_orm(table_name = "memberships")] | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| use sea_orm::entity::prelude::*; | use sea_orm::entity::prelude::*; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
| use crate::domain::handler::UserId; | use crate::domain::types::UserId; | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] | ||||||
| #[sea_orm(table_name = "password_reset_tokens")] | #[sea_orm(table_name = "password_reset_tokens")] | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| use sea_orm::entity::prelude::*; | use sea_orm::entity::prelude::*; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
| use crate::domain::handler::{JpegPhoto, UserId, Uuid}; | use crate::domain::types::{JpegPhoto, UserId, Uuid}; | ||||||
| 
 | 
 | ||||||
| #[derive(Copy, Clone, Default, Debug, DeriveEntity)] | #[derive(Copy, Clone, Default, Debug, DeriveEntity)] | ||||||
| pub struct Entity; | pub struct Entity; | ||||||
| @ -118,7 +118,7 @@ impl Related<super::password_reset_tokens::Entity> for Entity { | |||||||
| 
 | 
 | ||||||
| impl ActiveModelBehavior for ActiveModel {} | impl ActiveModelBehavior for ActiveModel {} | ||||||
| 
 | 
 | ||||||
| impl From<Model> for crate::domain::handler::User { | impl From<Model> for crate::domain::types::User { | ||||||
|     fn from(user: Model) -> Self { |     fn from(user: Model) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             user_id: user.user_id, |             user_id: user.user_id, | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| use crate::domain::{error::*, handler::UserId}; | use crate::domain::{error::Result, types::UserId}; | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| 
 | 
 | ||||||
| pub use lldap_auth::{login, registration}; | pub use lldap_auth::{login, registration}; | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| use super::{handler::*, sql_tables::*}; | use super::{handler::BackendHandler, sql_tables::DbConnection}; | ||||||
| use crate::infra::configuration::Configuration; | use crate::infra::configuration::Configuration; | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| 
 | 
 | ||||||
| @ -20,8 +20,16 @@ impl BackendHandler for SqlBackendHandler {} | |||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| pub mod tests { | pub mod tests { | ||||||
|     use super::*; |     use super::*; | ||||||
|     use crate::domain::sql_tables::init_table; |     use crate::{ | ||||||
|     use crate::infra::configuration::ConfigurationBuilder; |         domain::{ | ||||||
|  |             handler::{ | ||||||
|  |                 CreateUserRequest, GroupBackendHandler, UserBackendHandler, UserRequestFilter, | ||||||
|  |             }, | ||||||
|  |             sql_tables::init_table, | ||||||
|  |             types::{GroupId, UserId}, | ||||||
|  |         }, | ||||||
|  |         infra::configuration::ConfigurationBuilder, | ||||||
|  |     }; | ||||||
|     use lldap_auth::{opaque, registration}; |     use lldap_auth::{opaque, registration}; | ||||||
|     use sea_orm::Database; |     use sea_orm::Database; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,12 +1,9 @@ | |||||||
| use crate::domain::handler::Uuid; | use crate::domain::{ | ||||||
| 
 |  | ||||||
| use super::{ |  | ||||||
|     error::{DomainError, Result}, |     error::{DomainError, Result}, | ||||||
|     handler::{ |     handler::{GroupBackendHandler, GroupRequestFilter, UpdateGroupRequest}, | ||||||
|         Group, GroupBackendHandler, GroupDetails, GroupId, GroupRequestFilter, UpdateGroupRequest, |  | ||||||
|     }, |  | ||||||
|     model::{self, GroupColumn, MembershipColumn}, |     model::{self, GroupColumn, MembershipColumn}, | ||||||
|     sql_backend_handler::SqlBackendHandler, |     sql_backend_handler::SqlBackendHandler, | ||||||
|  |     types::{Group, GroupDetails, GroupId, Uuid}, | ||||||
| }; | }; | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use sea_orm::{ | use sea_orm::{ | ||||||
| @ -155,7 +152,7 @@ impl GroupBackendHandler for SqlBackendHandler { | |||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use super::*; |     use super::*; | ||||||
|     use crate::domain::{handler::UserId, sql_backend_handler::tests::*}; |     use crate::domain::{sql_backend_handler::tests::*, types::UserId}; | ||||||
| 
 | 
 | ||||||
|     async fn get_group_ids( |     async fn get_group_ids( | ||||||
|         handler: &SqlBackendHandler, |         handler: &SqlBackendHandler, | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| use super::{ | use crate::domain::{ | ||||||
|     handler::{GroupId, UserId, Uuid}, |  | ||||||
|     sql_tables::{DbConnection, SchemaVersion}, |     sql_tables::{DbConnection, SchemaVersion}, | ||||||
|  |     types::{GroupId, UserId, Uuid}, | ||||||
| }; | }; | ||||||
| use sea_orm::{ConnectionTrait, FromQueryResult, Statement}; | use sea_orm::{ConnectionTrait, FromQueryResult, Statement}; | ||||||
| use sea_query::{ColumnDef, Expr, ForeignKey, ForeignKeyAction, Iden, Query, Table, Value}; | use sea_query::{ColumnDef, Expr, ForeignKey, ForeignKeyAction, Iden, Query, Table, Value}; | ||||||
|  | |||||||
| @ -1,9 +1,10 @@ | |||||||
| use super::{ | use super::{ | ||||||
|     error::{DomainError, Result}, |     error::{DomainError, Result}, | ||||||
|     handler::{BindRequest, LoginHandler, UserId}, |     handler::{BindRequest, LoginHandler}, | ||||||
|     model::{self, UserColumn}, |     model::{self, UserColumn}, | ||||||
|     opaque_handler::{login, registration, OpaqueHandler}, |     opaque_handler::{login, registration, OpaqueHandler}, | ||||||
|     sql_backend_handler::SqlBackendHandler, |     sql_backend_handler::SqlBackendHandler, | ||||||
|  |     types::UserId, | ||||||
| }; | }; | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use lldap_auth::opaque; | use lldap_auth::opaque; | ||||||
|  | |||||||
| @ -1,8 +1,5 @@ | |||||||
| use super::{ | use super::sql_migrations::{get_schema_version, migrate_from_version, upgrade_to_v1}; | ||||||
|     handler::{GroupId, JpegPhoto, UserId, Uuid}, | use sea_orm::Value; | ||||||
|     sql_migrations::{get_schema_version, migrate_from_version, upgrade_to_v1}, |  | ||||||
| }; |  | ||||||
| use sea_orm::{DbErr, Value}; |  | ||||||
| 
 | 
 | ||||||
| pub type DbConnection = sea_orm::DatabaseConnection; | pub type DbConnection = sea_orm::DatabaseConnection; | ||||||
| 
 | 
 | ||||||
| @ -19,181 +16,6 @@ impl sea_orm::TryGetable for SchemaVersion { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl From<GroupId> for sea_orm::Value { |  | ||||||
|     fn from(group_id: GroupId) -> Self { |  | ||||||
|         group_id.0.into() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl sea_orm::TryGetable for GroupId { |  | ||||||
|     fn try_get( |  | ||||||
|         res: &sea_orm::QueryResult, |  | ||||||
|         pre: &str, |  | ||||||
|         col: &str, |  | ||||||
|     ) -> Result<Self, sea_orm::TryGetError> { |  | ||||||
|         Ok(GroupId(i32::try_get(res, pre, col)?)) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl sea_orm::sea_query::value::ValueType for GroupId { |  | ||||||
|     fn try_from(v: sea_orm::Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> { |  | ||||||
|         Ok(GroupId(<i32 as sea_orm::sea_query::ValueType>::try_from( |  | ||||||
|             v, |  | ||||||
|         )?)) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn type_name() -> String { |  | ||||||
|         "GroupId".to_owned() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn array_type() -> sea_orm::sea_query::ArrayType { |  | ||||||
|         sea_orm::sea_query::ArrayType::Int |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn column_type() -> sea_orm::sea_query::ColumnType { |  | ||||||
|         sea_orm::sea_query::ColumnType::Integer(None) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl sea_orm::TryFromU64 for GroupId { |  | ||||||
|     fn try_from_u64(n: u64) -> Result<Self, sea_orm::DbErr> { |  | ||||||
|         Ok(GroupId(i32::try_from_u64(n)?)) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl From<UserId> for sea_orm::Value { |  | ||||||
|     fn from(user_id: UserId) -> Self { |  | ||||||
|         user_id.into_string().into() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl From<&UserId> for sea_orm::Value { |  | ||||||
|     fn from(user_id: &UserId) -> Self { |  | ||||||
|         user_id.as_str().into() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl sea_orm::TryGetable for UserId { |  | ||||||
|     fn try_get( |  | ||||||
|         res: &sea_orm::QueryResult, |  | ||||||
|         pre: &str, |  | ||||||
|         col: &str, |  | ||||||
|     ) -> Result<Self, sea_orm::TryGetError> { |  | ||||||
|         Ok(UserId::new(&String::try_get(res, pre, col)?)) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl sea_orm::TryFromU64 for UserId { |  | ||||||
|     fn try_from_u64(_n: u64) -> Result<Self, sea_orm::DbErr> { |  | ||||||
|         Err(sea_orm::DbErr::ConvertFromU64( |  | ||||||
|             "UserId cannot be constructed from u64", |  | ||||||
|         )) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl sea_orm::sea_query::value::ValueType for UserId { |  | ||||||
|     fn try_from(v: sea_orm::Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> { |  | ||||||
|         Ok(UserId::new( |  | ||||||
|             <String as sea_orm::sea_query::ValueType>::try_from(v)?.as_str(), |  | ||||||
|         )) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn type_name() -> String { |  | ||||||
|         "UserId".to_owned() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn array_type() -> sea_orm::sea_query::ArrayType { |  | ||||||
|         sea_orm::sea_query::ArrayType::String |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn column_type() -> sea_orm::sea_query::ColumnType { |  | ||||||
|         sea_orm::sea_query::ColumnType::String(Some(255)) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl From<Uuid> for sea_query::Value { |  | ||||||
|     fn from(uuid: Uuid) -> Self { |  | ||||||
|         uuid.as_str().into() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl From<&Uuid> for sea_query::Value { |  | ||||||
|     fn from(uuid: &Uuid) -> Self { |  | ||||||
|         uuid.as_str().into() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl sea_orm::TryGetable for JpegPhoto { |  | ||||||
|     fn try_get( |  | ||||||
|         res: &sea_orm::QueryResult, |  | ||||||
|         pre: &str, |  | ||||||
|         col: &str, |  | ||||||
|     ) -> Result<Self, sea_orm::TryGetError> { |  | ||||||
|         <JpegPhoto as std::convert::TryFrom<Vec<_>>>::try_from(Vec::<u8>::try_get(res, pre, col)?) |  | ||||||
|             .map_err(|e| { |  | ||||||
|                 sea_orm::TryGetError::DbErr(DbErr::TryIntoErr { |  | ||||||
|                     from: "[u8]", |  | ||||||
|                     into: "JpegPhoto", |  | ||||||
|                     source: e.into(), |  | ||||||
|                 }) |  | ||||||
|             }) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl sea_orm::sea_query::value::ValueType for JpegPhoto { |  | ||||||
|     fn try_from(v: sea_orm::Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> { |  | ||||||
|         <JpegPhoto as std::convert::TryFrom<_>>::try_from( |  | ||||||
|             <Vec<u8> as sea_orm::sea_query::ValueType>::try_from(v)?.as_slice(), |  | ||||||
|         ) |  | ||||||
|         .map_err(|_| sea_orm::sea_query::ValueTypeErr {}) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn type_name() -> String { |  | ||||||
|         "JpegPhoto".to_owned() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn array_type() -> sea_orm::sea_query::ArrayType { |  | ||||||
|         sea_orm::sea_query::ArrayType::Bytes |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn column_type() -> sea_orm::sea_query::ColumnType { |  | ||||||
|         sea_orm::sea_query::ColumnType::Binary(sea_orm::sea_query::BlobSize::Long) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl sea_orm::sea_query::Nullable for JpegPhoto { |  | ||||||
|     fn null() -> sea_orm::Value { |  | ||||||
|         JpegPhoto::null().into() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl sea_orm::entity::IntoActiveValue<JpegPhoto> for JpegPhoto { |  | ||||||
|     fn into_active_value(self) -> sea_orm::ActiveValue<JpegPhoto> { |  | ||||||
|         sea_orm::ActiveValue::Set(self) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl sea_orm::sea_query::value::ValueType for Uuid { |  | ||||||
|     fn try_from(v: sea_orm::Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> { |  | ||||||
|         <super::handler::Uuid as std::convert::TryFrom<_>>::try_from( |  | ||||||
|             <std::string::String as sea_orm::sea_query::ValueType>::try_from(v)?.as_str(), |  | ||||||
|         ) |  | ||||||
|         .map_err(|_| sea_orm::sea_query::ValueTypeErr {}) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn type_name() -> String { |  | ||||||
|         "Uuid".to_owned() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn array_type() -> sea_orm::sea_query::ArrayType { |  | ||||||
|         sea_orm::sea_query::ArrayType::String |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn column_type() -> sea_orm::sea_query::ColumnType { |  | ||||||
|         sea_orm::sea_query::ColumnType::String(Some(36)) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl From<SchemaVersion> for Value { | impl From<SchemaVersion> for Value { | ||||||
|     fn from(version: SchemaVersion) -> Self { |     fn from(version: SchemaVersion) -> Self { | ||||||
|         version.0.into() |         version.0.into() | ||||||
| @ -215,7 +37,10 @@ pub async fn init_table(pool: &DbConnection) -> anyhow::Result<()> { | |||||||
| 
 | 
 | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use crate::domain::sql_migrations; |     use crate::domain::{ | ||||||
|  |         sql_migrations, | ||||||
|  |         types::{GroupId, Uuid}, | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|     use super::*; |     use super::*; | ||||||
|     use chrono::prelude::*; |     use chrono::prelude::*; | ||||||
|  | |||||||
| @ -1,11 +1,9 @@ | |||||||
| use super::{ | use super::{ | ||||||
|     error::{DomainError, Result}, |     error::{DomainError, Result}, | ||||||
|     handler::{ |     handler::{CreateUserRequest, UpdateUserRequest, UserBackendHandler, UserRequestFilter}, | ||||||
|         CreateUserRequest, GroupDetails, GroupId, UpdateUserRequest, User, UserAndGroups, |  | ||||||
|         UserBackendHandler, UserId, UserRequestFilter, Uuid, |  | ||||||
|     }, |  | ||||||
|     model::{self, GroupColumn, UserColumn}, |     model::{self, GroupColumn, UserColumn}, | ||||||
|     sql_backend_handler::SqlBackendHandler, |     sql_backend_handler::SqlBackendHandler, | ||||||
|  |     types::{GroupDetails, GroupId, User, UserAndGroups, UserId, Uuid}, | ||||||
| }; | }; | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use sea_orm::{ | use sea_orm::{ | ||||||
| @ -245,8 +243,8 @@ impl UserBackendHandler for SqlBackendHandler { | |||||||
| mod tests { | mod tests { | ||||||
|     use super::*; |     use super::*; | ||||||
|     use crate::domain::{ |     use crate::domain::{ | ||||||
|         handler::{JpegPhoto, UserColumn}, |  | ||||||
|         sql_backend_handler::tests::*, |         sql_backend_handler::tests::*, | ||||||
|  |         types::{JpegPhoto, UserColumn}, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     #[tokio::test] |     #[tokio::test] | ||||||
|  | |||||||
							
								
								
									
										393
									
								
								server/src/domain/types.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										393
									
								
								server/src/domain/types.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,393 @@ | |||||||
|  | use sea_orm::{ | ||||||
|  |     entity::IntoActiveValue, | ||||||
|  |     sea_query::{value::ValueType, ArrayType, ColumnType, Nullable, ValueTypeErr}, | ||||||
|  |     DbErr, FromQueryResult, QueryResult, TryFromU64, TryGetError, TryGetable, Value, | ||||||
|  | }; | ||||||
|  | use serde::{Deserialize, Serialize}; | ||||||
|  | 
 | ||||||
|  | pub use super::model::{GroupColumn, UserColumn}; | ||||||
|  | 
 | ||||||
|  | pub type DateTime = chrono::DateTime<chrono::Utc>; | ||||||
|  | 
 | ||||||
|  | #[derive(PartialEq, Hash, Eq, Clone, Debug, Default, Serialize, Deserialize)] | ||||||
|  | #[serde(try_from = "&str")] | ||||||
|  | pub struct Uuid(String); | ||||||
|  | 
 | ||||||
|  | impl Uuid { | ||||||
|  |     pub fn from_name_and_date(name: &str, creation_date: &DateTime) -> Self { | ||||||
|  |         Uuid( | ||||||
|  |             uuid::Uuid::new_v3( | ||||||
|  |                 &uuid::Uuid::NAMESPACE_X500, | ||||||
|  |                 &[name.as_bytes(), creation_date.to_rfc3339().as_bytes()].concat(), | ||||||
|  |             ) | ||||||
|  |             .to_string(), | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn as_str(&self) -> &str { | ||||||
|  |         &self.0 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn into_string(self) -> String { | ||||||
|  |         self.0 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<'a> std::convert::TryFrom<&'a str> for Uuid { | ||||||
|  |     type Error = anyhow::Error; | ||||||
|  |     fn try_from(s: &'a str) -> anyhow::Result<Self> { | ||||||
|  |         Ok(Uuid(uuid::Uuid::parse_str(s)?.to_string())) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl std::string::ToString for Uuid { | ||||||
|  |     fn to_string(&self) -> String { | ||||||
|  |         self.0.clone() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl TryGetable for Uuid { | ||||||
|  |     fn try_get(res: &QueryResult, pre: &str, col: &str) -> std::result::Result<Self, TryGetError> { | ||||||
|  |         Ok(Uuid(String::try_get(res, pre, col)?)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl ValueType for Uuid { | ||||||
|  |     fn try_from(v: Value) -> Result<Self, ValueTypeErr> { | ||||||
|  |         <Self as std::convert::TryFrom<_>>::try_from( | ||||||
|  |             <std::string::String as sea_orm::sea_query::ValueType>::try_from(v)?.as_str(), | ||||||
|  |         ) | ||||||
|  |         .map_err(|_| ValueTypeErr {}) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn type_name() -> String { | ||||||
|  |         "Uuid".to_owned() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn array_type() -> ArrayType { | ||||||
|  |         ArrayType::String | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn column_type() -> ColumnType { | ||||||
|  |         ColumnType::String(Some(36)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<Uuid> for Value { | ||||||
|  |     fn from(uuid: Uuid) -> Self { | ||||||
|  |         uuid.as_str().into() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<&Uuid> for Value { | ||||||
|  |     fn from(uuid: &Uuid) -> Self { | ||||||
|  |         uuid.as_str().into() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[cfg(test)] | ||||||
|  | #[macro_export] | ||||||
|  | macro_rules! uuid { | ||||||
|  |     ($s:literal) => { | ||||||
|  |         <$crate::domain::types::Uuid as std::convert::TryFrom<_>>::try_from($s).unwrap() | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(PartialEq, Eq, Clone, Debug, Default, Serialize, Deserialize)] | ||||||
|  | #[serde(from = "String")] | ||||||
|  | pub struct UserId(String); | ||||||
|  | 
 | ||||||
|  | impl UserId { | ||||||
|  |     pub fn new(user_id: &str) -> Self { | ||||||
|  |         Self(user_id.to_lowercase()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn as_str(&self) -> &str { | ||||||
|  |         self.0.as_str() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn into_string(self) -> String { | ||||||
|  |         self.0 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl std::fmt::Display for UserId { | ||||||
|  |     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | ||||||
|  |         write!(f, "{}", self.0) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<String> for UserId { | ||||||
|  |     fn from(s: String) -> Self { | ||||||
|  |         Self::new(&s) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<UserId> for Value { | ||||||
|  |     fn from(user_id: UserId) -> Self { | ||||||
|  |         user_id.into_string().into() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<&UserId> for Value { | ||||||
|  |     fn from(user_id: &UserId) -> Self { | ||||||
|  |         user_id.as_str().into() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl TryGetable for UserId { | ||||||
|  |     fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> { | ||||||
|  |         Ok(UserId::new(&String::try_get(res, pre, col)?)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl TryFromU64 for UserId { | ||||||
|  |     fn try_from_u64(_n: u64) -> Result<Self, DbErr> { | ||||||
|  |         Err(DbErr::ConvertFromU64( | ||||||
|  |             "UserId cannot be constructed from u64", | ||||||
|  |         )) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl ValueType for UserId { | ||||||
|  |     fn try_from(v: Value) -> Result<Self, ValueTypeErr> { | ||||||
|  |         Ok(UserId::new(<String as ValueType>::try_from(v)?.as_str())) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn type_name() -> String { | ||||||
|  |         "UserId".to_owned() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn array_type() -> ArrayType { | ||||||
|  |         ArrayType::String | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn column_type() -> ColumnType { | ||||||
|  |         ColumnType::String(Some(255)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] | ||||||
|  | pub struct JpegPhoto(#[serde(with = "serde_bytes")] Vec<u8>); | ||||||
|  | 
 | ||||||
|  | impl From<JpegPhoto> for Value { | ||||||
|  |     fn from(photo: JpegPhoto) -> Self { | ||||||
|  |         photo.0.into() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<&JpegPhoto> for Value { | ||||||
|  |     fn from(photo: &JpegPhoto) -> Self { | ||||||
|  |         photo.0.as_slice().into() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl TryFrom<&[u8]> for JpegPhoto { | ||||||
|  |     type Error = anyhow::Error; | ||||||
|  |     fn try_from(bytes: &[u8]) -> anyhow::Result<Self> { | ||||||
|  |         if bytes.is_empty() { | ||||||
|  |             return Ok(JpegPhoto::null()); | ||||||
|  |         } | ||||||
|  |         // Confirm that it's a valid Jpeg, then store only the bytes.
 | ||||||
|  |         image::io::Reader::with_format(std::io::Cursor::new(bytes), image::ImageFormat::Jpeg) | ||||||
|  |             .decode()?; | ||||||
|  |         Ok(JpegPhoto(bytes.to_vec())) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl TryFrom<Vec<u8>> for JpegPhoto { | ||||||
|  |     type Error = anyhow::Error; | ||||||
|  |     fn try_from(bytes: Vec<u8>) -> anyhow::Result<Self> { | ||||||
|  |         if bytes.is_empty() { | ||||||
|  |             return Ok(JpegPhoto::null()); | ||||||
|  |         } | ||||||
|  |         // Confirm that it's a valid Jpeg, then store only the bytes.
 | ||||||
|  |         image::io::Reader::with_format( | ||||||
|  |             std::io::Cursor::new(bytes.as_slice()), | ||||||
|  |             image::ImageFormat::Jpeg, | ||||||
|  |         ) | ||||||
|  |         .decode()?; | ||||||
|  |         Ok(JpegPhoto(bytes)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl TryFrom<String> for JpegPhoto { | ||||||
|  |     type Error = anyhow::Error; | ||||||
|  |     fn try_from(string: String) -> anyhow::Result<Self> { | ||||||
|  |         // The String format is in base64.
 | ||||||
|  |         <Self as TryFrom<_>>::try_from(base64::decode(string.as_str())?) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<&JpegPhoto> for String { | ||||||
|  |     fn from(val: &JpegPhoto) -> Self { | ||||||
|  |         base64::encode(&val.0) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl JpegPhoto { | ||||||
|  |     pub fn null() -> Self { | ||||||
|  |         Self(vec![]) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn into_bytes(self) -> Vec<u8> { | ||||||
|  |         self.0 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[cfg(test)] | ||||||
|  |     pub fn for_tests() -> Self { | ||||||
|  |         use image::{ImageOutputFormat, Rgb, RgbImage}; | ||||||
|  |         let img = RgbImage::from_fn(32, 32, |x, y| { | ||||||
|  |             if (x + y) % 2 == 0 { | ||||||
|  |                 Rgb([0, 0, 0]) | ||||||
|  |             } else { | ||||||
|  |                 Rgb([255, 255, 255]) | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         let mut bytes: Vec<u8> = Vec::new(); | ||||||
|  |         img.write_to( | ||||||
|  |             &mut std::io::Cursor::new(&mut bytes), | ||||||
|  |             ImageOutputFormat::Jpeg(0), | ||||||
|  |         ) | ||||||
|  |         .unwrap(); | ||||||
|  |         Self(bytes) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl TryGetable for JpegPhoto { | ||||||
|  |     fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> { | ||||||
|  |         <Self as std::convert::TryFrom<Vec<_>>>::try_from(Vec::<u8>::try_get(res, pre, col)?) | ||||||
|  |             .map_err(|e| { | ||||||
|  |                 TryGetError::DbErr(DbErr::TryIntoErr { | ||||||
|  |                     from: "[u8]", | ||||||
|  |                     into: "JpegPhoto", | ||||||
|  |                     source: e.into(), | ||||||
|  |                 }) | ||||||
|  |             }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl ValueType for JpegPhoto { | ||||||
|  |     fn try_from(v: Value) -> Result<Self, ValueTypeErr> { | ||||||
|  |         <Self as std::convert::TryFrom<_>>::try_from( | ||||||
|  |             <Vec<u8> as sea_orm::sea_query::ValueType>::try_from(v)?.as_slice(), | ||||||
|  |         ) | ||||||
|  |         .map_err(|_| ValueTypeErr {}) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn type_name() -> String { | ||||||
|  |         "JpegPhoto".to_owned() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn array_type() -> ArrayType { | ||||||
|  |         ArrayType::Bytes | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn column_type() -> ColumnType { | ||||||
|  |         ColumnType::Binary(sea_orm::sea_query::BlobSize::Long) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Nullable for JpegPhoto { | ||||||
|  |     fn null() -> Value { | ||||||
|  |         JpegPhoto::null().into() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl IntoActiveValue<JpegPhoto> for JpegPhoto { | ||||||
|  |     fn into_active_value(self) -> sea_orm::ActiveValue<JpegPhoto> { | ||||||
|  |         sea_orm::ActiveValue::Set(self) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, FromQueryResult)] | ||||||
|  | pub struct User { | ||||||
|  |     pub user_id: UserId, | ||||||
|  |     pub email: String, | ||||||
|  |     pub display_name: Option<String>, | ||||||
|  |     pub first_name: Option<String>, | ||||||
|  |     pub last_name: Option<String>, | ||||||
|  |     pub avatar: Option<JpegPhoto>, | ||||||
|  |     pub creation_date: DateTime, | ||||||
|  |     pub uuid: Uuid, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[cfg(test)] | ||||||
|  | impl Default for User { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         use chrono::TimeZone; | ||||||
|  |         let epoch = chrono::Utc.timestamp_opt(0, 0).unwrap(); | ||||||
|  |         User { | ||||||
|  |             user_id: UserId::default(), | ||||||
|  |             email: String::new(), | ||||||
|  |             display_name: None, | ||||||
|  |             first_name: None, | ||||||
|  |             last_name: None, | ||||||
|  |             avatar: None, | ||||||
|  |             creation_date: epoch, | ||||||
|  |             uuid: Uuid::from_name_and_date("", &epoch), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] | ||||||
|  | pub struct GroupId(pub i32); | ||||||
|  | 
 | ||||||
|  | impl From<GroupId> for Value { | ||||||
|  |     fn from(group_id: GroupId) -> Self { | ||||||
|  |         group_id.0.into() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl TryGetable for GroupId { | ||||||
|  |     fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> { | ||||||
|  |         Ok(GroupId(i32::try_get(res, pre, col)?)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl ValueType for GroupId { | ||||||
|  |     fn try_from(v: Value) -> Result<Self, ValueTypeErr> { | ||||||
|  |         Ok(GroupId(<i32 as ValueType>::try_from(v)?)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn type_name() -> String { | ||||||
|  |         "GroupId".to_owned() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn array_type() -> ArrayType { | ||||||
|  |         ArrayType::Int | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn column_type() -> ColumnType { | ||||||
|  |         ColumnType::Integer(None) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl TryFromU64 for GroupId { | ||||||
|  |     fn try_from_u64(n: u64) -> Result<Self, DbErr> { | ||||||
|  |         Ok(GroupId(i32::try_from_u64(n)?)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(PartialEq, Eq, Debug, Serialize, Deserialize)] | ||||||
|  | pub struct Group { | ||||||
|  |     pub id: GroupId, | ||||||
|  |     pub display_name: String, | ||||||
|  |     pub creation_date: DateTime, | ||||||
|  |     pub uuid: Uuid, | ||||||
|  |     pub users: Vec<UserId>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, FromQueryResult)] | ||||||
|  | pub struct GroupDetails { | ||||||
|  |     pub group_id: GroupId, | ||||||
|  |     pub display_name: String, | ||||||
|  |     pub creation_date: DateTime, | ||||||
|  |     pub uuid: Uuid, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Clone, PartialEq, Eq)] | ||||||
|  | pub struct UserAndGroups { | ||||||
|  |     pub user: User, | ||||||
|  |     pub groups: Option<Vec<GroupDetails>>, | ||||||
|  | } | ||||||
| @ -22,12 +22,12 @@ use tracing::{debug, instrument, warn}; | |||||||
| 
 | 
 | ||||||
| use lldap_auth::{login, password_reset, registration, JWTClaims}; | use lldap_auth::{login, password_reset, registration, JWTClaims}; | ||||||
| 
 | 
 | ||||||
| use crate::domain::handler::UserRequestFilter; |  | ||||||
| use crate::{ | use crate::{ | ||||||
|     domain::{ |     domain::{ | ||||||
|         error::DomainError, |         error::DomainError, | ||||||
|         handler::{BackendHandler, BindRequest, GroupDetails, LoginHandler, UserColumn, UserId}, |         handler::{BackendHandler, BindRequest, LoginHandler, UserRequestFilter}, | ||||||
|         opaque_handler::OpaqueHandler, |         opaque_handler::OpaqueHandler, | ||||||
|  |         types::{GroupDetails, UserColumn, UserId}, | ||||||
|     }, |     }, | ||||||
|     infra::{ |     infra::{ | ||||||
|         tcp_backend_handler::*, |         tcp_backend_handler::*, | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| use crate::{ | use crate::{ | ||||||
|     domain::handler::UserId, |     domain::types::UserId, | ||||||
|     infra::cli::{GeneralConfigOpts, LdapsOpts, RunOpts, SmtpEncryption, SmtpOpts, TestEmailOpts}, |     infra::cli::{GeneralConfigOpts, LdapsOpts, RunOpts, SmtpEncryption, SmtpOpts, TestEmailOpts}, | ||||||
| }; | }; | ||||||
| use anyhow::{Context, Result}; | use anyhow::{Context, Result}; | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| use crate::domain::handler::{ | use crate::domain::{ | ||||||
|     BackendHandler, CreateUserRequest, GroupId, JpegPhoto, UpdateGroupRequest, UpdateUserRequest, |     handler::{BackendHandler, CreateUserRequest, UpdateGroupRequest, UpdateUserRequest}, | ||||||
|     UserId, |     types::{GroupId, JpegPhoto, UserId}, | ||||||
| }; | }; | ||||||
| use anyhow::Context as AnyhowContext; | use anyhow::Context as AnyhowContext; | ||||||
| use juniper::{graphql_object, FieldResult, GraphQLInputObject, GraphQLObject}; | use juniper::{graphql_object, FieldResult, GraphQLInputObject, GraphQLObject}; | ||||||
|  | |||||||
| @ -1,15 +1,16 @@ | |||||||
| use crate::domain::{ | use crate::domain::{ | ||||||
|     handler::{BackendHandler, GroupDetails, GroupId, UserColumn, UserId}, |     handler::BackendHandler, | ||||||
|     ldap::utils::map_user_field, |     ldap::utils::map_user_field, | ||||||
|  |     types::{GroupDetails, GroupId, UserColumn, UserId}, | ||||||
| }; | }; | ||||||
| use juniper::{graphql_object, FieldResult, GraphQLInputObject}; | use juniper::{graphql_object, FieldResult, GraphQLInputObject}; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use tracing::{debug, debug_span, Instrument}; | use tracing::{debug, debug_span, Instrument}; | ||||||
| 
 | 
 | ||||||
| type DomainRequestFilter = crate::domain::handler::UserRequestFilter; | type DomainRequestFilter = crate::domain::handler::UserRequestFilter; | ||||||
| type DomainUser = crate::domain::handler::User; | type DomainUser = crate::domain::types::User; | ||||||
| type DomainGroup = crate::domain::handler::Group; | type DomainGroup = crate::domain::types::Group; | ||||||
| type DomainUserAndGroups = crate::domain::handler::UserAndGroups; | type DomainUserAndGroups = crate::domain::types::UserAndGroups; | ||||||
| use super::api::Context; | use super::api::Context; | ||||||
| 
 | 
 | ||||||
| #[derive(PartialEq, Eq, Debug, GraphQLInputObject)] | #[derive(PartialEq, Eq, Debug, GraphQLInputObject)] | ||||||
| @ -345,10 +346,7 @@ impl<Handler: BackendHandler> From<DomainGroup> for Group<Handler> { | |||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use super::*; |     use super::*; | ||||||
|     use crate::{ |     use crate::{domain::handler::MockTestBackendHandler, infra::auth_service::ValidationResults}; | ||||||
|         domain::handler::{MockTestBackendHandler, UserRequestFilter}, |  | ||||||
|         infra::auth_service::ValidationResults, |  | ||||||
|     }; |  | ||||||
|     use chrono::TimeZone; |     use chrono::TimeZone; | ||||||
|     use juniper::{ |     use juniper::{ | ||||||
|         execute, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLType, |         execute, graphql_value, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLType, | ||||||
| @ -457,9 +455,12 @@ mod tests { | |||||||
|         let mut mock = MockTestBackendHandler::new(); |         let mut mock = MockTestBackendHandler::new(); | ||||||
|         mock.expect_list_users() |         mock.expect_list_users() | ||||||
|             .with( |             .with( | ||||||
|                 eq(Some(UserRequestFilter::Or(vec![ |                 eq(Some(DomainRequestFilter::Or(vec![ | ||||||
|                     UserRequestFilter::UserId(UserId::new("bob")), |                     DomainRequestFilter::UserId(UserId::new("bob")), | ||||||
|                     UserRequestFilter::Equality(UserColumn::Email, "robert@bobbers.on".to_string()), |                     DomainRequestFilter::Equality( | ||||||
|  |                         UserColumn::Email, | ||||||
|  |                         "robert@bobbers.on".to_string(), | ||||||
|  |                     ), | ||||||
|                 ]))), |                 ]))), | ||||||
|                 eq(false), |                 eq(false), | ||||||
|             ) |             ) | ||||||
|  | |||||||
| @ -1,10 +1,6 @@ | |||||||
| use std::collections::HashMap; |  | ||||||
| 
 |  | ||||||
| use crate::{ | use crate::{ | ||||||
|     domain::{ |     domain::{ | ||||||
|         handler::{ |         handler::{BackendHandler, BindRequest, CreateUserRequest, LoginHandler}, | ||||||
|             BackendHandler, BindRequest, CreateUserRequest, JpegPhoto, LoginHandler, UserId, |  | ||||||
|         }, |  | ||||||
|         ldap::{ |         ldap::{ | ||||||
|             error::{LdapError, LdapResult}, |             error::{LdapError, LdapResult}, | ||||||
|             group::get_groups_list, |             group::get_groups_list, | ||||||
| @ -14,6 +10,7 @@ use crate::{ | |||||||
|             }, |             }, | ||||||
|         }, |         }, | ||||||
|         opaque_handler::OpaqueHandler, |         opaque_handler::OpaqueHandler, | ||||||
|  |         types::{JpegPhoto, UserId}, | ||||||
|     }, |     }, | ||||||
|     infra::auth_service::{Permission, ValidationResults}, |     infra::auth_service::{Permission, ValidationResults}, | ||||||
| }; | }; | ||||||
| @ -24,6 +21,7 @@ use ldap3_proto::proto::{ | |||||||
|     LdapResult as LdapResultOp, LdapResultCode, LdapSearchRequest, LdapSearchResultEntry, |     LdapResult as LdapResultOp, LdapResultCode, LdapSearchRequest, LdapSearchResultEntry, | ||||||
|     LdapSearchScope, |     LdapSearchScope, | ||||||
| }; | }; | ||||||
|  | use std::collections::HashMap; | ||||||
| use tracing::{debug, instrument, warn}; | use tracing::{debug, instrument, warn}; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, PartialEq, Eq, Clone)] | #[derive(Debug, PartialEq, Eq, Clone)] | ||||||
| @ -569,7 +567,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend | |||||||
| mod tests { | mod tests { | ||||||
|     use super::*; |     use super::*; | ||||||
|     use crate::{ |     use crate::{ | ||||||
|         domain::{error::Result, handler::*, opaque_handler::*}, |         domain::{error::Result, handler::*, opaque_handler::*, types::*}, | ||||||
|         uuid, |         uuid, | ||||||
|     }; |     }; | ||||||
|     use async_trait::async_trait; |     use async_trait::async_trait; | ||||||
|  | |||||||
| @ -1,9 +1,9 @@ | |||||||
| use super::tcp_backend_handler::TcpBackendHandler; | use super::tcp_backend_handler::TcpBackendHandler; | ||||||
| use crate::domain::{ | use crate::domain::{ | ||||||
|     error::*, |     error::*, | ||||||
|     handler::UserId, |  | ||||||
|     model::{self, JwtRefreshStorageColumn, JwtStorageColumn, PasswordResetTokensColumn}, |     model::{self, JwtRefreshStorageColumn, JwtStorageColumn, PasswordResetTokensColumn}, | ||||||
|     sql_backend_handler::SqlBackendHandler, |     sql_backend_handler::SqlBackendHandler, | ||||||
|  |     types::UserId, | ||||||
| }; | }; | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use sea_orm::{ | use sea_orm::{ | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use std::collections::HashSet; | use std::collections::HashSet; | ||||||
| 
 | 
 | ||||||
| use crate::domain::{error::Result, handler::UserId}; | use crate::domain::{error::Result, types::UserId}; | ||||||
| 
 | 
 | ||||||
| #[async_trait] | #[async_trait] | ||||||
| pub trait TcpBackendHandler { | pub trait TcpBackendHandler { | ||||||
| @ -22,7 +22,7 @@ pub trait TcpBackendHandler { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| use crate::domain::handler::*; | use crate::domain::{handler::*, types::*}; | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mockall::mock! { | mockall::mock! { | ||||||
|     pub TestTcpBackendHandler{} |     pub TestTcpBackendHandler{} | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Valentin Tolmer
						Valentin Tolmer