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