mirror of
https://github.com/lldap/lldap.git
synced 2026-07-25 17:58:44 +00:00
server: make attributes names, group names and emails case insensitive
In addition, group names and emails keep their casing
This commit is contained in:
committed by
nitnelave
parent
71d37b9e5e
commit
272c84c574
@@ -1,8 +1,8 @@
|
|||||||
use crate::domain::{
|
use crate::domain::{
|
||||||
error::Result,
|
error::Result,
|
||||||
types::{
|
types::{
|
||||||
AttributeType, AttributeValue, Group, GroupDetails, GroupId, JpegPhoto, User,
|
AttributeName, AttributeType, AttributeValue, Email, Group, GroupDetails, GroupId,
|
||||||
UserAndGroups, UserColumn, UserId, Uuid,
|
GroupName, JpegPhoto, User, UserAndGroups, UserColumn, UserId, Uuid,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@@ -54,10 +54,10 @@ pub enum UserRequestFilter {
|
|||||||
UserId(UserId),
|
UserId(UserId),
|
||||||
UserIdSubString(SubStringFilter),
|
UserIdSubString(SubStringFilter),
|
||||||
Equality(UserColumn, String),
|
Equality(UserColumn, String),
|
||||||
AttributeEquality(String, String),
|
AttributeEquality(AttributeName, String),
|
||||||
SubString(UserColumn, SubStringFilter),
|
SubString(UserColumn, SubStringFilter),
|
||||||
// Check if a user belongs to a group identified by name.
|
// Check if a user belongs to a group identified by name.
|
||||||
MemberOf(String),
|
MemberOf(GroupName),
|
||||||
// Same, by id.
|
// Same, by id.
|
||||||
MemberOfId(GroupId),
|
MemberOfId(GroupId),
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ pub enum GroupRequestFilter {
|
|||||||
And(Vec<GroupRequestFilter>),
|
And(Vec<GroupRequestFilter>),
|
||||||
Or(Vec<GroupRequestFilter>),
|
Or(Vec<GroupRequestFilter>),
|
||||||
Not(Box<GroupRequestFilter>),
|
Not(Box<GroupRequestFilter>),
|
||||||
DisplayName(String),
|
DisplayName(GroupName),
|
||||||
DisplayNameSubString(SubStringFilter),
|
DisplayNameSubString(SubStringFilter),
|
||||||
Uuid(Uuid),
|
Uuid(Uuid),
|
||||||
GroupId(GroupId),
|
GroupId(GroupId),
|
||||||
@@ -99,7 +99,7 @@ impl From<bool> for GroupRequestFilter {
|
|||||||
pub struct CreateUserRequest {
|
pub struct CreateUserRequest {
|
||||||
// Same fields as User, but no creation_date, and with password.
|
// Same fields as User, but no creation_date, and with password.
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
pub email: String,
|
pub email: Email,
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
pub first_name: Option<String>,
|
pub first_name: Option<String>,
|
||||||
pub last_name: Option<String>,
|
pub last_name: Option<String>,
|
||||||
@@ -111,32 +111,32 @@ pub struct CreateUserRequest {
|
|||||||
pub struct UpdateUserRequest {
|
pub struct UpdateUserRequest {
|
||||||
// Same fields as CreateUserRequest, but no with an extra layer of Option.
|
// Same fields as CreateUserRequest, but no with an extra layer of Option.
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
pub email: Option<String>,
|
pub email: Option<Email>,
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
pub first_name: Option<String>,
|
pub first_name: Option<String>,
|
||||||
pub last_name: Option<String>,
|
pub last_name: Option<String>,
|
||||||
pub avatar: Option<JpegPhoto>,
|
pub avatar: Option<JpegPhoto>,
|
||||||
pub delete_attributes: Vec<String>,
|
pub delete_attributes: Vec<AttributeName>,
|
||||||
pub insert_attributes: Vec<AttributeValue>,
|
pub insert_attributes: Vec<AttributeValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct CreateGroupRequest {
|
pub struct CreateGroupRequest {
|
||||||
pub display_name: String,
|
pub display_name: GroupName,
|
||||||
pub attributes: Vec<AttributeValue>,
|
pub attributes: Vec<AttributeValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
|
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct UpdateGroupRequest {
|
pub struct UpdateGroupRequest {
|
||||||
pub group_id: GroupId,
|
pub group_id: GroupId,
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<GroupName>,
|
||||||
pub delete_attributes: Vec<String>,
|
pub delete_attributes: Vec<AttributeName>,
|
||||||
pub insert_attributes: Vec<AttributeValue>,
|
pub insert_attributes: Vec<AttributeValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
|
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct AttributeSchema {
|
pub struct AttributeSchema {
|
||||||
pub name: String,
|
pub name: AttributeName,
|
||||||
//TODO: pub aliases: Vec<String>,
|
//TODO: pub aliases: Vec<String>,
|
||||||
pub attribute_type: AttributeType,
|
pub attribute_type: AttributeType,
|
||||||
pub is_list: bool,
|
pub is_list: bool,
|
||||||
@@ -147,7 +147,7 @@ pub struct AttributeSchema {
|
|||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
|
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct CreateAttributeRequest {
|
pub struct CreateAttributeRequest {
|
||||||
pub name: String,
|
pub name: AttributeName,
|
||||||
pub attribute_type: AttributeType,
|
pub attribute_type: AttributeType,
|
||||||
pub is_list: bool,
|
pub is_list: bool,
|
||||||
pub is_visible: bool,
|
pub is_visible: bool,
|
||||||
@@ -160,11 +160,11 @@ pub struct AttributeList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AttributeList {
|
impl AttributeList {
|
||||||
pub fn get_attribute_schema(&self, name: &str) -> Option<&AttributeSchema> {
|
pub fn get_attribute_schema(&self, name: &AttributeName) -> Option<&AttributeSchema> {
|
||||||
self.attributes.iter().find(|a| a.name == name)
|
self.attributes.iter().find(|a| a.name == *name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_attribute_type(&self, name: &str) -> Option<(AttributeType, bool)> {
|
pub fn get_attribute_type(&self, name: &AttributeName) -> Option<(AttributeType, bool)> {
|
||||||
self.get_attribute_schema(name)
|
self.get_attribute_schema(name)
|
||||||
.map(|a| (a.attribute_type, a.is_list))
|
.map(|a| (a.attribute_type, a.is_list))
|
||||||
}
|
}
|
||||||
@@ -224,8 +224,8 @@ pub trait SchemaBackendHandler: ReadSchemaBackendHandler {
|
|||||||
async fn add_user_attribute(&self, request: CreateAttributeRequest) -> Result<()>;
|
async fn add_user_attribute(&self, request: CreateAttributeRequest) -> Result<()>;
|
||||||
async fn add_group_attribute(&self, request: CreateAttributeRequest) -> Result<()>;
|
async fn add_group_attribute(&self, request: CreateAttributeRequest) -> Result<()>;
|
||||||
// Note: It's up to the caller to make sure that the attribute is not hardcoded.
|
// Note: It's up to the caller to make sure that the attribute is not hardcoded.
|
||||||
async fn delete_user_attribute(&self, name: &str) -> Result<()>;
|
async fn delete_user_attribute(&self, name: &AttributeName) -> Result<()>;
|
||||||
async fn delete_group_attribute(&self, name: &str) -> Result<()>;
|
async fn delete_group_attribute(&self, name: &AttributeName) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::domain::{
|
|||||||
handler::{GroupListerBackendHandler, GroupRequestFilter},
|
handler::{GroupListerBackendHandler, GroupRequestFilter},
|
||||||
ldap::error::LdapError,
|
ldap::error::LdapError,
|
||||||
schema::{PublicSchema, SchemaGroupAttributeExtractor},
|
schema::{PublicSchema, SchemaGroupAttributeExtractor},
|
||||||
types::{Group, UserId, Uuid},
|
types::{AttributeName, Group, UserId, Uuid},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@@ -23,15 +23,15 @@ pub fn get_group_attribute(
|
|||||||
base_dn_str: &str,
|
base_dn_str: &str,
|
||||||
attribute: &str,
|
attribute: &str,
|
||||||
user_filter: &Option<UserId>,
|
user_filter: &Option<UserId>,
|
||||||
ignored_group_attributes: &[String],
|
ignored_group_attributes: &[AttributeName],
|
||||||
schema: &PublicSchema,
|
schema: &PublicSchema,
|
||||||
) -> Option<Vec<Vec<u8>>> {
|
) -> Option<Vec<Vec<u8>>> {
|
||||||
let attribute = attribute.to_ascii_lowercase();
|
let attribute = AttributeName::from(attribute);
|
||||||
let attribute_values = match attribute.as_str() {
|
let attribute_values = match attribute.as_str() {
|
||||||
"objectclass" => vec![b"groupOfUniqueNames".to_vec()],
|
"objectclass" => vec![b"groupOfUniqueNames".to_vec()],
|
||||||
// Always returned as part of the base response.
|
// Always returned as part of the base response.
|
||||||
"dn" | "distinguishedname" => return None,
|
"dn" | "distinguishedname" => return None,
|
||||||
"cn" | "uid" | "id" => vec![group.display_name.clone().into_bytes()],
|
"cn" | "uid" | "id" => vec![group.display_name.to_string().into_bytes()],
|
||||||
"entryuuid" | "uuid" => vec![group.uuid.to_string().into_bytes()],
|
"entryuuid" | "uuid" => vec![group.uuid.to_string().into_bytes()],
|
||||||
"member" | "uniquemember" => group
|
"member" | "uniquemember" => group
|
||||||
.users
|
.users
|
||||||
@@ -48,11 +48,11 @@ pub fn get_group_attribute(
|
|||||||
attribute
|
attribute
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
attr => {
|
_ => {
|
||||||
if !ignored_group_attributes.contains(&attribute) {
|
if !ignored_group_attributes.contains(&attribute) {
|
||||||
match get_custom_attribute::<SchemaGroupAttributeExtractor>(
|
match get_custom_attribute::<SchemaGroupAttributeExtractor>(
|
||||||
&group.attributes,
|
&group.attributes,
|
||||||
attr,
|
&attribute,
|
||||||
schema,
|
schema,
|
||||||
) {
|
) {
|
||||||
Some(v) => return Some(v),
|
Some(v) => return Some(v),
|
||||||
@@ -91,7 +91,7 @@ fn make_ldap_search_group_result_entry(
|
|||||||
base_dn_str: &str,
|
base_dn_str: &str,
|
||||||
attributes: &[String],
|
attributes: &[String],
|
||||||
user_filter: &Option<UserId>,
|
user_filter: &Option<UserId>,
|
||||||
ignored_group_attributes: &[String],
|
ignored_group_attributes: &[AttributeName],
|
||||||
schema: &PublicSchema,
|
schema: &PublicSchema,
|
||||||
) -> LdapSearchResultEntry {
|
) -> LdapSearchResultEntry {
|
||||||
let expanded_attributes = expand_group_attribute_wildcards(attributes);
|
let expanded_attributes = expand_group_attribute_wildcards(attributes);
|
||||||
@@ -125,12 +125,12 @@ fn convert_group_filter(
|
|||||||
let rec = |f| convert_group_filter(ldap_info, f);
|
let rec = |f| convert_group_filter(ldap_info, f);
|
||||||
match filter {
|
match filter {
|
||||||
LdapFilter::Equality(field, value) => {
|
LdapFilter::Equality(field, value) => {
|
||||||
let field = &field.to_ascii_lowercase();
|
let field = AttributeName::from(field.as_str());
|
||||||
let value = &value.to_ascii_lowercase();
|
let value = value.to_ascii_lowercase();
|
||||||
match field.as_str() {
|
match field.as_str() {
|
||||||
"member" | "uniquemember" => {
|
"member" | "uniquemember" => {
|
||||||
let user_name = get_user_id_from_distinguished_name(
|
let user_name = get_user_id_from_distinguished_name(
|
||||||
value,
|
&value,
|
||||||
&ldap_info.base_dn,
|
&ldap_info.base_dn,
|
||||||
&ldap_info.base_dn_str,
|
&ldap_info.base_dn_str,
|
||||||
)?;
|
)?;
|
||||||
@@ -150,8 +150,8 @@ fn convert_group_filter(
|
|||||||
warn!("Invalid dn filter on group: {}", value);
|
warn!("Invalid dn filter on group: {}", value);
|
||||||
GroupRequestFilter::from(false)
|
GroupRequestFilter::from(false)
|
||||||
})),
|
})),
|
||||||
_ => match map_group_field(field) {
|
_ => match map_group_field(&field) {
|
||||||
Some("display_name") => Ok(GroupRequestFilter::DisplayName(value.to_string())),
|
Some("display_name") => Ok(GroupRequestFilter::DisplayName(value.into())),
|
||||||
Some("uuid") => Ok(GroupRequestFilter::Uuid(
|
Some("uuid") => Ok(GroupRequestFilter::Uuid(
|
||||||
Uuid::try_from(value.as_str()).map_err(|e| LdapError {
|
Uuid::try_from(value.as_str()).map_err(|e| LdapError {
|
||||||
code: LdapResultCode::InappropriateMatching,
|
code: LdapResultCode::InappropriateMatching,
|
||||||
@@ -159,9 +159,9 @@ fn convert_group_filter(
|
|||||||
})?,
|
})?,
|
||||||
)),
|
)),
|
||||||
_ => {
|
_ => {
|
||||||
if !ldap_info.ignored_group_attributes.contains(field) {
|
if !ldap_info.ignored_group_attributes.contains(&field) {
|
||||||
warn!(
|
warn!(
|
||||||
r#"Ignoring unknown group attribute "{:?}" in filter.\n\
|
r#"Ignoring unknown group attribute "{}" in filter.\n\
|
||||||
To disable this warning, add it to "ignored_group_attributes" in the config."#,
|
To disable this warning, add it to "ignored_group_attributes" in the config."#,
|
||||||
field
|
field
|
||||||
);
|
);
|
||||||
@@ -179,24 +179,24 @@ fn convert_group_filter(
|
|||||||
)),
|
)),
|
||||||
LdapFilter::Not(filter) => Ok(GroupRequestFilter::Not(Box::new(rec(filter)?))),
|
LdapFilter::Not(filter) => Ok(GroupRequestFilter::Not(Box::new(rec(filter)?))),
|
||||||
LdapFilter::Present(field) => {
|
LdapFilter::Present(field) => {
|
||||||
let field = &field.to_ascii_lowercase();
|
let field = AttributeName::from(field.as_str());
|
||||||
Ok(GroupRequestFilter::from(
|
Ok(GroupRequestFilter::from(
|
||||||
field == "objectclass"
|
field.as_str() == "objectclass"
|
||||||
|| field == "dn"
|
|| field.as_str() == "dn"
|
||||||
|| field == "distinguishedname"
|
|| field.as_str() == "distinguishedname"
|
||||||
|| map_group_field(field).is_some(),
|
|| map_group_field(&field).is_some(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
LdapFilter::Substring(field, substring_filter) => {
|
LdapFilter::Substring(field, substring_filter) => {
|
||||||
let field = &field.to_ascii_lowercase();
|
let field = AttributeName::from(field.as_str());
|
||||||
match map_group_field(field.as_str()) {
|
match map_group_field(&field) {
|
||||||
Some("display_name") => Ok(GroupRequestFilter::DisplayNameSubString(
|
Some("display_name") => Ok(GroupRequestFilter::DisplayNameSubString(
|
||||||
substring_filter.clone().into(),
|
substring_filter.clone().into(),
|
||||||
)),
|
)),
|
||||||
_ => Err(LdapError {
|
_ => Err(LdapError {
|
||||||
code: LdapResultCode::UnwillingToPerform,
|
code: LdapResultCode::UnwillingToPerform,
|
||||||
message: format!(
|
message: format!(
|
||||||
"Unsupported group attribute for substring filter: {:?}",
|
"Unsupported group attribute for substring filter: \"{}\"",
|
||||||
field
|
field
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use crate::domain::{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
schema::{PublicSchema, SchemaUserAttributeExtractor},
|
schema::{PublicSchema, SchemaUserAttributeExtractor},
|
||||||
types::{GroupDetails, User, UserAndGroups, UserColumn, UserId},
|
types::{AttributeName, GroupDetails, User, UserAndGroups, UserColumn, UserId},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn get_user_attribute(
|
pub fn get_user_attribute(
|
||||||
@@ -22,10 +22,10 @@ pub fn get_user_attribute(
|
|||||||
attribute: &str,
|
attribute: &str,
|
||||||
base_dn_str: &str,
|
base_dn_str: &str,
|
||||||
groups: Option<&[GroupDetails]>,
|
groups: Option<&[GroupDetails]>,
|
||||||
ignored_user_attributes: &[String],
|
ignored_user_attributes: &[AttributeName],
|
||||||
schema: &PublicSchema,
|
schema: &PublicSchema,
|
||||||
) -> Option<Vec<Vec<u8>>> {
|
) -> Option<Vec<Vec<u8>>> {
|
||||||
let attribute = attribute.to_ascii_lowercase();
|
let attribute = AttributeName::from(attribute);
|
||||||
let attribute_values = match attribute.as_str() {
|
let attribute_values = match attribute.as_str() {
|
||||||
"objectclass" => vec![
|
"objectclass" => vec![
|
||||||
b"inetOrgPerson".to_vec(),
|
b"inetOrgPerson".to_vec(),
|
||||||
@@ -37,20 +37,22 @@ pub fn get_user_attribute(
|
|||||||
"dn" | "distinguishedname" => return None,
|
"dn" | "distinguishedname" => return None,
|
||||||
"uid" | "user_id" | "id" => vec![user.user_id.to_string().into_bytes()],
|
"uid" | "user_id" | "id" => vec![user.user_id.to_string().into_bytes()],
|
||||||
"entryuuid" | "uuid" => vec![user.uuid.to_string().into_bytes()],
|
"entryuuid" | "uuid" => vec![user.uuid.to_string().into_bytes()],
|
||||||
"mail" | "email" => vec![user.email.clone().into_bytes()],
|
"mail" | "email" => vec![user.email.to_string().into_bytes()],
|
||||||
"givenname" | "first_name" | "firstname" => get_custom_attribute::<
|
"givenname" | "first_name" | "firstname" => {
|
||||||
SchemaUserAttributeExtractor,
|
get_custom_attribute::<SchemaUserAttributeExtractor>(
|
||||||
>(
|
&user.attributes,
|
||||||
&user.attributes, "first_name", schema
|
&"first_name".into(),
|
||||||
)?,
|
schema,
|
||||||
|
)?
|
||||||
|
}
|
||||||
"sn" | "last_name" | "lastname" => get_custom_attribute::<SchemaUserAttributeExtractor>(
|
"sn" | "last_name" | "lastname" => get_custom_attribute::<SchemaUserAttributeExtractor>(
|
||||||
&user.attributes,
|
&user.attributes,
|
||||||
"last_name",
|
&"last_name".into(),
|
||||||
schema,
|
schema,
|
||||||
)?,
|
)?,
|
||||||
"jpegphoto" | "avatar" => get_custom_attribute::<SchemaUserAttributeExtractor>(
|
"jpegphoto" | "avatar" => get_custom_attribute::<SchemaUserAttributeExtractor>(
|
||||||
&user.attributes,
|
&user.attributes,
|
||||||
"avatar",
|
&"avatar".into(),
|
||||||
schema,
|
schema,
|
||||||
)?,
|
)?,
|
||||||
"memberof" => groups
|
"memberof" => groups
|
||||||
@@ -80,7 +82,7 @@ pub fn get_user_attribute(
|
|||||||
if !ignored_user_attributes.contains(&attribute) {
|
if !ignored_user_attributes.contains(&attribute) {
|
||||||
match get_custom_attribute::<SchemaUserAttributeExtractor>(
|
match get_custom_attribute::<SchemaUserAttributeExtractor>(
|
||||||
&user.attributes,
|
&user.attributes,
|
||||||
attr,
|
&attribute,
|
||||||
schema,
|
schema,
|
||||||
) {
|
) {
|
||||||
Some(v) => return Some(v),
|
Some(v) => return Some(v),
|
||||||
@@ -118,7 +120,7 @@ fn make_ldap_search_user_result_entry(
|
|||||||
base_dn_str: &str,
|
base_dn_str: &str,
|
||||||
attributes: &[String],
|
attributes: &[String],
|
||||||
groups: Option<&[GroupDetails]>,
|
groups: Option<&[GroupDetails]>,
|
||||||
ignored_user_attributes: &[String],
|
ignored_user_attributes: &[AttributeName],
|
||||||
schema: &PublicSchema,
|
schema: &PublicSchema,
|
||||||
) -> LdapSearchResultEntry {
|
) -> LdapSearchResultEntry {
|
||||||
let expanded_attributes = expand_user_attribute_wildcards(attributes);
|
let expanded_attributes = expand_user_attribute_wildcards(attributes);
|
||||||
@@ -156,7 +158,7 @@ fn convert_user_filter(ldap_info: &LdapInfo, filter: &LdapFilter) -> LdapResult<
|
|||||||
)),
|
)),
|
||||||
LdapFilter::Not(filter) => Ok(UserRequestFilter::Not(Box::new(rec(filter)?))),
|
LdapFilter::Not(filter) => Ok(UserRequestFilter::Not(Box::new(rec(filter)?))),
|
||||||
LdapFilter::Equality(field, value) => {
|
LdapFilter::Equality(field, value) => {
|
||||||
let field = &field.to_ascii_lowercase();
|
let field = AttributeName::from(field.as_str());
|
||||||
match field.as_str() {
|
match field.as_str() {
|
||||||
"memberof" => Ok(UserRequestFilter::MemberOf(
|
"memberof" => Ok(UserRequestFilter::MemberOf(
|
||||||
get_group_id_from_distinguished_name(
|
get_group_id_from_distinguished_name(
|
||||||
@@ -179,7 +181,7 @@ fn convert_user_filter(ldap_info: &LdapInfo, filter: &LdapFilter) -> LdapResult<
|
|||||||
warn!("Invalid dn filter on user: {}", value);
|
warn!("Invalid dn filter on user: {}", value);
|
||||||
UserRequestFilter::from(false)
|
UserRequestFilter::from(false)
|
||||||
})),
|
})),
|
||||||
_ => match map_user_field(field) {
|
_ => match map_user_field(&field) {
|
||||||
UserFieldType::PrimaryField(UserColumn::UserId) => {
|
UserFieldType::PrimaryField(UserColumn::UserId) => {
|
||||||
Ok(UserRequestFilter::UserId(UserId::new(value)))
|
Ok(UserRequestFilter::UserId(UserId::new(value)))
|
||||||
}
|
}
|
||||||
@@ -187,11 +189,11 @@ fn convert_user_filter(ldap_info: &LdapInfo, filter: &LdapFilter) -> LdapResult<
|
|||||||
Ok(UserRequestFilter::Equality(field, value.clone()))
|
Ok(UserRequestFilter::Equality(field, value.clone()))
|
||||||
}
|
}
|
||||||
UserFieldType::Attribute(field) => Ok(UserRequestFilter::AttributeEquality(
|
UserFieldType::Attribute(field) => Ok(UserRequestFilter::AttributeEquality(
|
||||||
field.to_owned(),
|
AttributeName::from(field),
|
||||||
value.clone(),
|
value.clone(),
|
||||||
)),
|
)),
|
||||||
UserFieldType::NoMatch => {
|
UserFieldType::NoMatch => {
|
||||||
if !ldap_info.ignored_user_attributes.contains(field) {
|
if !ldap_info.ignored_user_attributes.contains(&field) {
|
||||||
warn!(
|
warn!(
|
||||||
r#"Ignoring unknown user attribute "{}" in filter.\n\
|
r#"Ignoring unknown user attribute "{}" in filter.\n\
|
||||||
To disable this warning, add it to "ignored_user_attributes" in the config"#,
|
To disable this warning, add it to "ignored_user_attributes" in the config"#,
|
||||||
@@ -204,18 +206,18 @@ fn convert_user_filter(ldap_info: &LdapInfo, filter: &LdapFilter) -> LdapResult<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
LdapFilter::Present(field) => {
|
LdapFilter::Present(field) => {
|
||||||
let field = &field.to_ascii_lowercase();
|
let field = AttributeName::from(field.as_str());
|
||||||
// Check that it's a field we support.
|
// Check that it's a field we support.
|
||||||
Ok(UserRequestFilter::from(
|
Ok(UserRequestFilter::from(
|
||||||
field == "objectclass"
|
field.as_str() == "objectclass"
|
||||||
|| field == "dn"
|
|| field.as_str() == "dn"
|
||||||
|| field == "distinguishedname"
|
|| field.as_str() == "distinguishedname"
|
||||||
|| !matches!(map_user_field(field), UserFieldType::NoMatch),
|
|| !matches!(map_user_field(&field), UserFieldType::NoMatch),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
LdapFilter::Substring(field, substring_filter) => {
|
LdapFilter::Substring(field, substring_filter) => {
|
||||||
let field = &field.to_ascii_lowercase();
|
let field = AttributeName::from(field.as_str());
|
||||||
match map_user_field(field.as_str()) {
|
match map_user_field(&field) {
|
||||||
UserFieldType::PrimaryField(UserColumn::UserId) => Ok(
|
UserFieldType::PrimaryField(UserColumn::UserId) => Ok(
|
||||||
UserRequestFilter::UserIdSubString(substring_filter.clone().into()),
|
UserRequestFilter::UserIdSubString(substring_filter.clone().into()),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ use crate::domain::{
|
|||||||
handler::SubStringFilter,
|
handler::SubStringFilter,
|
||||||
ldap::error::{LdapError, LdapResult},
|
ldap::error::{LdapError, LdapResult},
|
||||||
schema::{PublicSchema, SchemaAttributeExtractor},
|
schema::{PublicSchema, SchemaAttributeExtractor},
|
||||||
types::{AttributeType, AttributeValue, JpegPhoto, UserColumn, UserId},
|
types::{
|
||||||
|
AttributeName, AttributeType, AttributeValue, GroupName, JpegPhoto, UserColumn, UserId,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
impl From<LdapSubstringFilter> for SubStringFilter {
|
impl From<LdapSubstringFilter> for SubStringFilter {
|
||||||
@@ -103,8 +105,8 @@ pub fn get_group_id_from_distinguished_name(
|
|||||||
dn: &str,
|
dn: &str,
|
||||||
base_tree: &[(String, String)],
|
base_tree: &[(String, String)],
|
||||||
base_dn_str: &str,
|
base_dn_str: &str,
|
||||||
) -> LdapResult<String> {
|
) -> LdapResult<GroupName> {
|
||||||
get_id_from_distinguished_name(dn, base_tree, base_dn_str, true)
|
get_id_from_distinguished_name(dn, base_tree, base_dn_str, true).map(GroupName::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(all_attribute_keys), level = "debug")]
|
#[instrument(skip(all_attribute_keys), level = "debug")]
|
||||||
@@ -160,9 +162,8 @@ pub enum UserFieldType {
|
|||||||
Attribute(&'static str),
|
Attribute(&'static str),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_user_field(field: &str) -> UserFieldType {
|
pub fn map_user_field(field: &AttributeName) -> UserFieldType {
|
||||||
assert!(field == field.to_ascii_lowercase());
|
match field.as_str() {
|
||||||
match field {
|
|
||||||
"uid" | "user_id" | "id" => UserFieldType::PrimaryField(UserColumn::UserId),
|
"uid" | "user_id" | "id" => UserFieldType::PrimaryField(UserColumn::UserId),
|
||||||
"mail" | "email" => UserFieldType::PrimaryField(UserColumn::Email),
|
"mail" | "email" => UserFieldType::PrimaryField(UserColumn::Email),
|
||||||
"cn" | "displayname" | "display_name" => {
|
"cn" | "displayname" | "display_name" => {
|
||||||
@@ -179,9 +180,8 @@ pub fn map_user_field(field: &str) -> UserFieldType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_group_field(field: &str) -> Option<&'static str> {
|
pub fn map_group_field(field: &AttributeName) -> Option<&'static str> {
|
||||||
assert!(field == field.to_ascii_lowercase());
|
Some(match field.as_str() {
|
||||||
Some(match field {
|
|
||||||
"cn" | "displayname" | "uid" | "display_name" => "display_name",
|
"cn" | "displayname" | "uid" | "display_name" => "display_name",
|
||||||
"creationdate" | "createtimestamp" | "modifytimestamp" | "creation_date" => "creation_date",
|
"creationdate" | "createtimestamp" | "modifytimestamp" | "creation_date" => "creation_date",
|
||||||
"entryuuid" | "uuid" => "uuid",
|
"entryuuid" | "uuid" => "uuid",
|
||||||
@@ -192,13 +192,13 @@ pub fn map_group_field(field: &str) -> Option<&'static str> {
|
|||||||
pub struct LdapInfo {
|
pub struct LdapInfo {
|
||||||
pub base_dn: Vec<(String, String)>,
|
pub base_dn: Vec<(String, String)>,
|
||||||
pub base_dn_str: String,
|
pub base_dn_str: String,
|
||||||
pub ignored_user_attributes: Vec<String>,
|
pub ignored_user_attributes: Vec<AttributeName>,
|
||||||
pub ignored_group_attributes: Vec<String>,
|
pub ignored_group_attributes: Vec<AttributeName>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_custom_attribute<Extractor: SchemaAttributeExtractor>(
|
pub fn get_custom_attribute<Extractor: SchemaAttributeExtractor>(
|
||||||
attributes: &[AttributeValue],
|
attributes: &[AttributeValue],
|
||||||
attribute_name: &str,
|
attribute_name: &AttributeName,
|
||||||
schema: &PublicSchema,
|
schema: &PublicSchema,
|
||||||
) -> Option<Vec<Vec<u8>>> {
|
) -> Option<Vec<Vec<u8>>> {
|
||||||
let convert_date = |date| {
|
let convert_date = |date| {
|
||||||
@@ -212,7 +212,7 @@ pub fn get_custom_attribute<Extractor: SchemaAttributeExtractor>(
|
|||||||
.and_then(|attribute_type| {
|
.and_then(|attribute_type| {
|
||||||
attributes
|
attributes
|
||||||
.iter()
|
.iter()
|
||||||
.find(|a| a.name == attribute_name)
|
.find(|a| &a.name == attribute_name)
|
||||||
.map(|attribute| match attribute_type {
|
.map(|attribute| match attribute_type {
|
||||||
(AttributeType::String, false) => {
|
(AttributeType::String, false) => {
|
||||||
vec![attribute.value.unwrap::<String>().into_bytes()]
|
vec![attribute.value.unwrap::<String>().into_bytes()]
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::domain::{handler::AttributeSchema, types::AttributeType};
|
use crate::domain::{
|
||||||
|
handler::AttributeSchema,
|
||||||
|
types::{AttributeName, AttributeType},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||||
#[sea_orm(table_name = "group_attribute_schema")]
|
#[sea_orm(table_name = "group_attribute_schema")]
|
||||||
@@ -11,7 +14,7 @@ pub struct Model {
|
|||||||
auto_increment = false,
|
auto_increment = false,
|
||||||
column_name = "group_attribute_schema_name"
|
column_name = "group_attribute_schema_name"
|
||||||
)]
|
)]
|
||||||
pub attribute_name: String,
|
pub attribute_name: AttributeName,
|
||||||
#[sea_orm(column_name = "group_attribute_schema_type")]
|
#[sea_orm(column_name = "group_attribute_schema_type")]
|
||||||
pub attribute_type: AttributeType,
|
pub attribute_type: AttributeType,
|
||||||
#[sea_orm(column_name = "group_attribute_schema_is_list")]
|
#[sea_orm(column_name = "group_attribute_schema_is_list")]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::domain::types::{AttributeValue, GroupId, Serialized};
|
use crate::domain::types::{AttributeName, AttributeValue, GroupId, Serialized};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||||
#[sea_orm(table_name = "group_attributes")]
|
#[sea_orm(table_name = "group_attributes")]
|
||||||
@@ -17,7 +17,7 @@ pub struct Model {
|
|||||||
auto_increment = false,
|
auto_increment = false,
|
||||||
column_name = "group_attribute_name"
|
column_name = "group_attribute_name"
|
||||||
)]
|
)]
|
||||||
pub attribute_name: String,
|
pub attribute_name: AttributeName,
|
||||||
#[sea_orm(column_name = "group_attribute_value")]
|
#[sea_orm(column_name = "group_attribute_value")]
|
||||||
pub value: Serialized,
|
pub value: Serialized,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,15 @@
|
|||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::domain::types::{GroupId, Uuid};
|
use crate::domain::types::{GroupId, GroupName, 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")]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
pub group_id: GroupId,
|
pub group_id: GroupId,
|
||||||
pub display_name: String,
|
pub display_name: GroupName,
|
||||||
|
pub lowercase_display_name: String,
|
||||||
pub creation_date: chrono::NaiveDateTime,
|
pub creation_date: chrono::NaiveDateTime,
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::domain::{handler::AttributeSchema, types::AttributeType};
|
use crate::domain::{
|
||||||
|
handler::AttributeSchema,
|
||||||
|
types::{AttributeName, AttributeType},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||||
#[sea_orm(table_name = "user_attribute_schema")]
|
#[sea_orm(table_name = "user_attribute_schema")]
|
||||||
@@ -11,7 +14,7 @@ pub struct Model {
|
|||||||
auto_increment = false,
|
auto_increment = false,
|
||||||
column_name = "user_attribute_schema_name"
|
column_name = "user_attribute_schema_name"
|
||||||
)]
|
)]
|
||||||
pub attribute_name: String,
|
pub attribute_name: AttributeName,
|
||||||
#[sea_orm(column_name = "user_attribute_schema_type")]
|
#[sea_orm(column_name = "user_attribute_schema_type")]
|
||||||
pub attribute_type: AttributeType,
|
pub attribute_type: AttributeType,
|
||||||
#[sea_orm(column_name = "user_attribute_schema_is_list")]
|
#[sea_orm(column_name = "user_attribute_schema_is_list")]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::domain::types::{AttributeValue, Serialized, UserId};
|
use crate::domain::types::{AttributeName, AttributeValue, Serialized, UserId};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||||
#[sea_orm(table_name = "user_attributes")]
|
#[sea_orm(table_name = "user_attributes")]
|
||||||
@@ -17,7 +17,7 @@ pub struct Model {
|
|||||||
auto_increment = false,
|
auto_increment = false,
|
||||||
column_name = "user_attribute_name"
|
column_name = "user_attribute_name"
|
||||||
)]
|
)]
|
||||||
pub attribute_name: String,
|
pub attribute_name: AttributeName,
|
||||||
#[sea_orm(column_name = "user_attribute_value")]
|
#[sea_orm(column_name = "user_attribute_value")]
|
||||||
pub value: Serialized,
|
pub value: Serialized,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
use sea_orm::{entity::prelude::*, sea_query::BlobSize};
|
use sea_orm::{entity::prelude::*, sea_query::BlobSize};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::domain::types::{UserId, Uuid};
|
use crate::domain::types::{Email, UserId, Uuid};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
|
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
|
||||||
pub struct Entity;
|
pub struct Entity;
|
||||||
@@ -13,7 +13,8 @@ pub struct Entity;
|
|||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
pub email: String,
|
pub email: Email,
|
||||||
|
pub lowercase_email: String,
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
pub creation_date: chrono::NaiveDateTime,
|
pub creation_date: chrono::NaiveDateTime,
|
||||||
pub password_hash: Option<Vec<u8>>,
|
pub password_hash: Option<Vec<u8>>,
|
||||||
@@ -32,6 +33,7 @@ impl EntityName for Entity {
|
|||||||
pub enum Column {
|
pub enum Column {
|
||||||
UserId,
|
UserId,
|
||||||
Email,
|
Email,
|
||||||
|
LowercaseEmail,
|
||||||
DisplayName,
|
DisplayName,
|
||||||
CreationDate,
|
CreationDate,
|
||||||
PasswordHash,
|
PasswordHash,
|
||||||
@@ -47,6 +49,7 @@ impl ColumnTrait for Column {
|
|||||||
match self {
|
match self {
|
||||||
Column::UserId => ColumnType::String(Some(255)),
|
Column::UserId => ColumnType::String(Some(255)),
|
||||||
Column::Email => ColumnType::String(Some(255)),
|
Column::Email => ColumnType::String(Some(255)),
|
||||||
|
Column::LowercaseEmail => ColumnType::String(Some(255)),
|
||||||
Column::DisplayName => ColumnType::String(Some(255)),
|
Column::DisplayName => ColumnType::String(Some(255)),
|
||||||
Column::CreationDate => ColumnType::DateTime,
|
Column::CreationDate => ColumnType::DateTime,
|
||||||
Column::PasswordHash => ColumnType::Binary(BlobSize::Medium),
|
Column::PasswordHash => ColumnType::Binary(BlobSize::Medium),
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ impl From<Schema> for PublicSchema {
|
|||||||
fn from(mut schema: Schema) -> Self {
|
fn from(mut schema: Schema) -> Self {
|
||||||
schema.user_attributes.attributes.extend_from_slice(&[
|
schema.user_attributes.attributes.extend_from_slice(&[
|
||||||
AttributeSchema {
|
AttributeSchema {
|
||||||
name: "user_id".to_owned(),
|
name: "user_id".into(),
|
||||||
attribute_type: AttributeType::String,
|
attribute_type: AttributeType::String,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -45,7 +45,7 @@ impl From<Schema> for PublicSchema {
|
|||||||
is_hardcoded: true,
|
is_hardcoded: true,
|
||||||
},
|
},
|
||||||
AttributeSchema {
|
AttributeSchema {
|
||||||
name: "creation_date".to_owned(),
|
name: "creation_date".into(),
|
||||||
attribute_type: AttributeType::DateTime,
|
attribute_type: AttributeType::DateTime,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -53,7 +53,7 @@ impl From<Schema> for PublicSchema {
|
|||||||
is_hardcoded: true,
|
is_hardcoded: true,
|
||||||
},
|
},
|
||||||
AttributeSchema {
|
AttributeSchema {
|
||||||
name: "mail".to_owned(),
|
name: "mail".into(),
|
||||||
attribute_type: AttributeType::String,
|
attribute_type: AttributeType::String,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -61,7 +61,7 @@ impl From<Schema> for PublicSchema {
|
|||||||
is_hardcoded: true,
|
is_hardcoded: true,
|
||||||
},
|
},
|
||||||
AttributeSchema {
|
AttributeSchema {
|
||||||
name: "uuid".to_owned(),
|
name: "uuid".into(),
|
||||||
attribute_type: AttributeType::String,
|
attribute_type: AttributeType::String,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -69,7 +69,7 @@ impl From<Schema> for PublicSchema {
|
|||||||
is_hardcoded: true,
|
is_hardcoded: true,
|
||||||
},
|
},
|
||||||
AttributeSchema {
|
AttributeSchema {
|
||||||
name: "display_name".to_owned(),
|
name: "display_name".into(),
|
||||||
attribute_type: AttributeType::String,
|
attribute_type: AttributeType::String,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -83,7 +83,7 @@ impl From<Schema> for PublicSchema {
|
|||||||
.sort_by(|a, b| a.name.cmp(&b.name));
|
.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
schema.group_attributes.attributes.extend_from_slice(&[
|
schema.group_attributes.attributes.extend_from_slice(&[
|
||||||
AttributeSchema {
|
AttributeSchema {
|
||||||
name: "group_id".to_owned(),
|
name: "group_id".into(),
|
||||||
attribute_type: AttributeType::Integer,
|
attribute_type: AttributeType::Integer,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -91,7 +91,7 @@ impl From<Schema> for PublicSchema {
|
|||||||
is_hardcoded: true,
|
is_hardcoded: true,
|
||||||
},
|
},
|
||||||
AttributeSchema {
|
AttributeSchema {
|
||||||
name: "creation_date".to_owned(),
|
name: "creation_date".into(),
|
||||||
attribute_type: AttributeType::DateTime,
|
attribute_type: AttributeType::DateTime,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -99,7 +99,7 @@ impl From<Schema> for PublicSchema {
|
|||||||
is_hardcoded: true,
|
is_hardcoded: true,
|
||||||
},
|
},
|
||||||
AttributeSchema {
|
AttributeSchema {
|
||||||
name: "uuid".to_owned(),
|
name: "uuid".into(),
|
||||||
attribute_type: AttributeType::String,
|
attribute_type: AttributeType::String,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -107,7 +107,7 @@ impl From<Schema> for PublicSchema {
|
|||||||
is_hardcoded: true,
|
is_hardcoded: true,
|
||||||
},
|
},
|
||||||
AttributeSchema {
|
AttributeSchema {
|
||||||
name: "display_name".to_owned(),
|
name: "display_name".into(),
|
||||||
attribute_type: AttributeType::String,
|
attribute_type: AttributeType::String,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ pub mod tests {
|
|||||||
handler
|
handler
|
||||||
.create_user(CreateUserRequest {
|
.create_user(CreateUserRequest {
|
||||||
user_id: UserId::new(name),
|
user_id: UserId::new(name),
|
||||||
email: format!("{}@bob.bob", name),
|
email: format!("{}@bob.bob", name).into(),
|
||||||
display_name: Some("display ".to_string() + name),
|
display_name: Some("display ".to_string() + name),
|
||||||
first_name: Some("first ".to_string() + name),
|
first_name: Some("first ".to_string() + name),
|
||||||
last_name: Some("last ".to_string() + name),
|
last_name: Some("last ".to_string() + name),
|
||||||
@@ -100,7 +100,7 @@ pub mod tests {
|
|||||||
pub async fn insert_group(handler: &SqlBackendHandler, name: &str) -> GroupId {
|
pub async fn insert_group(handler: &SqlBackendHandler, name: &str) -> GroupId {
|
||||||
handler
|
handler
|
||||||
.create_group(CreateGroupRequest {
|
.create_group(CreateGroupRequest {
|
||||||
display_name: name.to_owned(),
|
display_name: name.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ fn get_group_filter_expr(filter: GroupRequestFilter) -> Cond {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Not(f) => get_group_filter_expr(*f).not(),
|
Not(f) => get_group_filter_expr(*f).not(),
|
||||||
DisplayName(name) => GroupColumn::DisplayName.eq(name).into_condition(),
|
DisplayName(name) => GroupColumn::LowercaseDisplayName
|
||||||
|
.eq(name.as_str().to_lowercase())
|
||||||
|
.into_condition(),
|
||||||
GroupId(id) => GroupColumn::GroupId.eq(id.0).into_condition(),
|
GroupId(id) => GroupColumn::GroupId.eq(id.0).into_condition(),
|
||||||
Uuid(uuid) => GroupColumn::Uuid.eq(uuid.to_string()).into_condition(),
|
Uuid(uuid) => GroupColumn::Uuid.eq(uuid.to_string()).into_condition(),
|
||||||
// WHERE (group_id in (SELECT group_id FROM memberships WHERE user_id = user))
|
// WHERE (group_id in (SELECT group_id FROM memberships WHERE user_id = user))
|
||||||
@@ -153,9 +155,11 @@ impl GroupBackendHandler for SqlBackendHandler {
|
|||||||
#[instrument(skip(self), level = "debug", ret, err)]
|
#[instrument(skip(self), level = "debug", ret, err)]
|
||||||
async fn create_group(&self, request: CreateGroupRequest) -> Result<GroupId> {
|
async fn create_group(&self, request: CreateGroupRequest) -> Result<GroupId> {
|
||||||
let now = chrono::Utc::now().naive_utc();
|
let now = chrono::Utc::now().naive_utc();
|
||||||
let uuid = Uuid::from_name_and_date(&request.display_name, &now);
|
let uuid = Uuid::from_name_and_date(request.display_name.as_str(), &now);
|
||||||
|
let lower_display_name = request.display_name.as_str().to_lowercase();
|
||||||
let new_group = model::groups::ActiveModel {
|
let new_group = model::groups::ActiveModel {
|
||||||
display_name: Set(request.display_name),
|
display_name: Set(request.display_name),
|
||||||
|
lowercase_display_name: Set(lower_display_name),
|
||||||
creation_date: Set(now),
|
creation_date: Set(now),
|
||||||
uuid: Set(uuid),
|
uuid: Set(uuid),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -217,9 +221,14 @@ impl SqlBackendHandler {
|
|||||||
request: UpdateGroupRequest,
|
request: UpdateGroupRequest,
|
||||||
transaction: &DatabaseTransaction,
|
transaction: &DatabaseTransaction,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let lower_display_name = request
|
||||||
|
.display_name
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.as_str().to_lowercase());
|
||||||
let update_group = model::groups::ActiveModel {
|
let update_group = model::groups::ActiveModel {
|
||||||
group_id: Set(request.group_id),
|
group_id: Set(request.group_id),
|
||||||
display_name: request.display_name.map(Set).unwrap_or_default(),
|
display_name: request.display_name.map(Set).unwrap_or_default(),
|
||||||
|
lowercase_display_name: lower_display_name.map(Set).unwrap_or_default(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
update_group.update(transaction).await?;
|
update_group.update(transaction).await?;
|
||||||
@@ -288,7 +297,7 @@ mod tests {
|
|||||||
use crate::domain::{
|
use crate::domain::{
|
||||||
handler::{CreateAttributeRequest, SchemaBackendHandler, SubStringFilter},
|
handler::{CreateAttributeRequest, SchemaBackendHandler, SubStringFilter},
|
||||||
sql_backend_handler::tests::*,
|
sql_backend_handler::tests::*,
|
||||||
types::{AttributeType, Serialized, UserId},
|
types::{AttributeType, GroupName, Serialized, UserId},
|
||||||
};
|
};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
@@ -308,7 +317,7 @@ mod tests {
|
|||||||
async fn get_group_names(
|
async fn get_group_names(
|
||||||
handler: &SqlBackendHandler,
|
handler: &SqlBackendHandler,
|
||||||
filters: Option<GroupRequestFilter>,
|
filters: Option<GroupRequestFilter>,
|
||||||
) -> Vec<String> {
|
) -> Vec<GroupName> {
|
||||||
handler
|
handler
|
||||||
.list_groups(filters)
|
.list_groups(filters)
|
||||||
.await
|
.await
|
||||||
@@ -324,9 +333,9 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_group_names(&fixture.handler, None).await,
|
get_group_names(&fixture.handler, None).await,
|
||||||
vec![
|
vec![
|
||||||
"Best Group".to_owned(),
|
"Best Group".into(),
|
||||||
"Empty Group".to_owned(),
|
"Empty Group".into(),
|
||||||
"Worst Group".to_owned()
|
"Worst Group".into()
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -338,12 +347,25 @@ mod tests {
|
|||||||
get_group_names(
|
get_group_names(
|
||||||
&fixture.handler,
|
&fixture.handler,
|
||||||
Some(GroupRequestFilter::Or(vec![
|
Some(GroupRequestFilter::Or(vec![
|
||||||
GroupRequestFilter::DisplayName("Empty Group".to_owned()),
|
GroupRequestFilter::DisplayName("Empty Group".into()),
|
||||||
GroupRequestFilter::Member(UserId::new("bob")),
|
GroupRequestFilter::Member(UserId::new("bob")),
|
||||||
]))
|
]))
|
||||||
)
|
)
|
||||||
.await,
|
.await,
|
||||||
vec!["Best Group".to_owned(), "Empty Group".to_owned()]
|
vec!["Best Group".into(), "Empty Group".into()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_list_groups_case_insensitive_filter() {
|
||||||
|
let fixture = TestFixture::new().await;
|
||||||
|
assert_eq!(
|
||||||
|
get_group_names(
|
||||||
|
&fixture.handler,
|
||||||
|
Some(GroupRequestFilter::DisplayName("eMpTy gRoup".into()),)
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
vec!["Empty Group".into()]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,7 +377,7 @@ mod tests {
|
|||||||
&fixture.handler,
|
&fixture.handler,
|
||||||
Some(GroupRequestFilter::And(vec![
|
Some(GroupRequestFilter::And(vec![
|
||||||
GroupRequestFilter::Not(Box::new(GroupRequestFilter::DisplayName(
|
GroupRequestFilter::Not(Box::new(GroupRequestFilter::DisplayName(
|
||||||
"value".to_owned()
|
"value".into()
|
||||||
))),
|
))),
|
||||||
GroupRequestFilter::GroupId(fixture.groups[0]),
|
GroupRequestFilter::GroupId(fixture.groups[0]),
|
||||||
]))
|
]))
|
||||||
@@ -392,7 +414,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(details.group_id, fixture.groups[0]);
|
assert_eq!(details.group_id, fixture.groups[0]);
|
||||||
assert_eq!(details.display_name, "Best Group");
|
assert_eq!(details.display_name, "Best Group".into());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_group_ids(
|
get_group_ids(
|
||||||
&fixture.handler,
|
&fixture.handler,
|
||||||
@@ -410,7 +432,7 @@ mod tests {
|
|||||||
.handler
|
.handler
|
||||||
.update_group(UpdateGroupRequest {
|
.update_group(UpdateGroupRequest {
|
||||||
group_id: fixture.groups[0],
|
group_id: fixture.groups[0],
|
||||||
display_name: Some("Awesomest Group".to_owned()),
|
display_name: Some("Awesomest Group".into()),
|
||||||
delete_attributes: Vec::new(),
|
delete_attributes: Vec::new(),
|
||||||
insert_attributes: Vec::new(),
|
insert_attributes: Vec::new(),
|
||||||
})
|
})
|
||||||
@@ -421,7 +443,7 @@ mod tests {
|
|||||||
.get_group_details(fixture.groups[0])
|
.get_group_details(fixture.groups[0])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(details.display_name, "Awesomest Group");
|
assert_eq!(details.display_name, "Awesomest Group".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -452,7 +474,7 @@ mod tests {
|
|||||||
fixture
|
fixture
|
||||||
.handler
|
.handler
|
||||||
.add_group_attribute(CreateAttributeRequest {
|
.add_group_attribute(CreateAttributeRequest {
|
||||||
name: "new_attribute".to_owned(),
|
name: "new_attribute".into(),
|
||||||
attribute_type: AttributeType::String,
|
attribute_type: AttributeType::String,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -463,9 +485,9 @@ mod tests {
|
|||||||
let new_group_id = fixture
|
let new_group_id = fixture
|
||||||
.handler
|
.handler
|
||||||
.create_group(CreateGroupRequest {
|
.create_group(CreateGroupRequest {
|
||||||
display_name: "New Group".to_owned(),
|
display_name: "New Group".into(),
|
||||||
attributes: vec![AttributeValue {
|
attributes: vec![AttributeValue {
|
||||||
name: "new_attribute".to_owned(),
|
name: "new_attribute".into(),
|
||||||
value: Serialized::from("value"),
|
value: Serialized::from("value"),
|
||||||
}],
|
}],
|
||||||
})
|
})
|
||||||
@@ -476,11 +498,11 @@ mod tests {
|
|||||||
.get_group_details(new_group_id)
|
.get_group_details(new_group_id)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(&group_details.display_name, "New Group");
|
assert_eq!(group_details.display_name, "New Group".into());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
group_details.attributes,
|
group_details.attributes,
|
||||||
vec![AttributeValue {
|
vec![AttributeValue {
|
||||||
name: "new_attribute".to_owned(),
|
name: "new_attribute".into(),
|
||||||
value: Serialized::from("value"),
|
value: Serialized::from("value"),
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
@@ -492,7 +514,7 @@ mod tests {
|
|||||||
fixture
|
fixture
|
||||||
.handler
|
.handler
|
||||||
.add_group_attribute(CreateAttributeRequest {
|
.add_group_attribute(CreateAttributeRequest {
|
||||||
name: "new_attribute".to_owned(),
|
name: "new_attribute".into(),
|
||||||
attribute_type: AttributeType::Integer,
|
attribute_type: AttributeType::Integer,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -502,7 +524,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let group_id = fixture.groups[0];
|
let group_id = fixture.groups[0];
|
||||||
let attributes = vec![AttributeValue {
|
let attributes = vec![AttributeValue {
|
||||||
name: "new_attribute".to_owned(),
|
name: "new_attribute".into(),
|
||||||
value: Serialized::from(&42i64),
|
value: Serialized::from(&42i64),
|
||||||
}];
|
}];
|
||||||
fixture
|
fixture
|
||||||
@@ -522,7 +544,7 @@ mod tests {
|
|||||||
.update_group(UpdateGroupRequest {
|
.update_group(UpdateGroupRequest {
|
||||||
group_id,
|
group_id,
|
||||||
display_name: None,
|
display_name: None,
|
||||||
delete_attributes: vec!["new_attribute".to_owned()],
|
delete_attributes: vec!["new_attribute".into()],
|
||||||
insert_attributes: Vec::new(),
|
insert_attributes: Vec::new(),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub enum Users {
|
|||||||
Table,
|
Table,
|
||||||
UserId,
|
UserId,
|
||||||
Email,
|
Email,
|
||||||
|
LowercaseEmail,
|
||||||
DisplayName,
|
DisplayName,
|
||||||
FirstName,
|
FirstName,
|
||||||
LastName,
|
LastName,
|
||||||
@@ -34,6 +35,7 @@ pub enum Groups {
|
|||||||
Table,
|
Table,
|
||||||
GroupId,
|
GroupId,
|
||||||
DisplayName,
|
DisplayName,
|
||||||
|
LowercaseDisplayName,
|
||||||
CreationDate,
|
CreationDate,
|
||||||
Uuid,
|
Uuid,
|
||||||
}
|
}
|
||||||
@@ -875,6 +877,53 @@ async fn migrate_to_v5(transaction: DatabaseTransaction) -> Result<DatabaseTrans
|
|||||||
Ok(transaction)
|
Ok(transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn migrate_to_v6(transaction: DatabaseTransaction) -> Result<DatabaseTransaction, DbErr> {
|
||||||
|
let builder = transaction.get_database_backend();
|
||||||
|
transaction
|
||||||
|
.execute(
|
||||||
|
builder.build(
|
||||||
|
Table::alter().table(Groups::Table).add_column(
|
||||||
|
ColumnDef::new(Groups::LowercaseDisplayName)
|
||||||
|
.string_len(255)
|
||||||
|
.not_null()
|
||||||
|
.default("UNSET"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
transaction
|
||||||
|
.execute(
|
||||||
|
builder.build(
|
||||||
|
Table::alter().table(Users::Table).add_column(
|
||||||
|
ColumnDef::new(Users::LowercaseEmail)
|
||||||
|
.string_len(255)
|
||||||
|
.not_null()
|
||||||
|
.default("UNSET"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
transaction
|
||||||
|
.execute(builder.build(Query::update().table(Groups::Table).value(
|
||||||
|
Groups::LowercaseDisplayName,
|
||||||
|
Func::lower(Expr::col(Groups::DisplayName)),
|
||||||
|
)))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
transaction
|
||||||
|
.execute(
|
||||||
|
builder.build(
|
||||||
|
Query::update()
|
||||||
|
.table(Users::Table)
|
||||||
|
.value(Users::LowercaseEmail, Func::lower(Expr::col(Users::Email))),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(transaction)
|
||||||
|
}
|
||||||
|
|
||||||
// This is needed to make an array of async functions.
|
// This is needed to make an array of async functions.
|
||||||
macro_rules! to_sync {
|
macro_rules! to_sync {
|
||||||
($l:ident) => {
|
($l:ident) => {
|
||||||
@@ -900,6 +949,7 @@ pub async fn migrate_from_version(
|
|||||||
to_sync!(migrate_to_v3),
|
to_sync!(migrate_to_v3),
|
||||||
to_sync!(migrate_to_v4),
|
to_sync!(migrate_to_v4),
|
||||||
to_sync!(migrate_to_v5),
|
to_sync!(migrate_to_v5),
|
||||||
|
to_sync!(migrate_to_v6),
|
||||||
];
|
];
|
||||||
assert_eq!(migrations.len(), (LAST_SCHEMA_VERSION.0 - 1) as usize);
|
assert_eq!(migrations.len(), (LAST_SCHEMA_VERSION.0 - 1) as usize);
|
||||||
for migration in 2..=last_version.0 {
|
for migration in 2..=last_version.0 {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::domain::{
|
|||||||
},
|
},
|
||||||
model,
|
model,
|
||||||
sql_backend_handler::SqlBackendHandler,
|
sql_backend_handler::SqlBackendHandler,
|
||||||
|
types::AttributeName,
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use sea_orm::{
|
use sea_orm::{
|
||||||
@@ -52,15 +53,15 @@ impl SchemaBackendHandler for SqlBackendHandler {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_user_attribute(&self, name: &str) -> Result<()> {
|
async fn delete_user_attribute(&self, name: &AttributeName) -> Result<()> {
|
||||||
model::UserAttributeSchema::delete_by_id(name)
|
model::UserAttributeSchema::delete_by_id(name.clone())
|
||||||
.exec(&self.sql_pool)
|
.exec(&self.sql_pool)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_group_attribute(&self, name: &str) -> Result<()> {
|
async fn delete_group_attribute(&self, name: &AttributeName) -> Result<()> {
|
||||||
model::GroupAttributeSchema::delete_by_id(name)
|
model::GroupAttributeSchema::delete_by_id(name.clone())
|
||||||
.exec(&self.sql_pool)
|
.exec(&self.sql_pool)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -123,7 +124,7 @@ mod tests {
|
|||||||
user_attributes: AttributeList {
|
user_attributes: AttributeList {
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
AttributeSchema {
|
AttributeSchema {
|
||||||
name: "avatar".to_owned(),
|
name: "avatar".into(),
|
||||||
attribute_type: AttributeType::JpegPhoto,
|
attribute_type: AttributeType::JpegPhoto,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -131,7 +132,7 @@ mod tests {
|
|||||||
is_hardcoded: true,
|
is_hardcoded: true,
|
||||||
},
|
},
|
||||||
AttributeSchema {
|
AttributeSchema {
|
||||||
name: "first_name".to_owned(),
|
name: "first_name".into(),
|
||||||
attribute_type: AttributeType::String,
|
attribute_type: AttributeType::String,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -139,7 +140,7 @@ mod tests {
|
|||||||
is_hardcoded: true,
|
is_hardcoded: true,
|
||||||
},
|
},
|
||||||
AttributeSchema {
|
AttributeSchema {
|
||||||
name: "last_name".to_owned(),
|
name: "last_name".into(),
|
||||||
attribute_type: AttributeType::String,
|
attribute_type: AttributeType::String,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -159,7 +160,7 @@ mod tests {
|
|||||||
async fn test_user_attribute_add_and_delete() {
|
async fn test_user_attribute_add_and_delete() {
|
||||||
let fixture = TestFixture::new().await;
|
let fixture = TestFixture::new().await;
|
||||||
let new_attribute = CreateAttributeRequest {
|
let new_attribute = CreateAttributeRequest {
|
||||||
name: "new_attribute".to_owned(),
|
name: "new_attribute".into(),
|
||||||
attribute_type: AttributeType::Integer,
|
attribute_type: AttributeType::Integer,
|
||||||
is_list: true,
|
is_list: true,
|
||||||
is_visible: false,
|
is_visible: false,
|
||||||
@@ -171,7 +172,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let expected_value = AttributeSchema {
|
let expected_value = AttributeSchema {
|
||||||
name: "new_attribute".to_owned(),
|
name: "new_attribute".into(),
|
||||||
attribute_type: AttributeType::Integer,
|
attribute_type: AttributeType::Integer,
|
||||||
is_list: true,
|
is_list: true,
|
||||||
is_visible: false,
|
is_visible: false,
|
||||||
@@ -188,7 +189,7 @@ mod tests {
|
|||||||
.contains(&expected_value));
|
.contains(&expected_value));
|
||||||
fixture
|
fixture
|
||||||
.handler
|
.handler
|
||||||
.delete_user_attribute("new_attribute")
|
.delete_user_attribute(&"new_attribute".into())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!fixture
|
assert!(!fixture
|
||||||
@@ -205,7 +206,7 @@ mod tests {
|
|||||||
async fn test_group_attribute_add_and_delete() {
|
async fn test_group_attribute_add_and_delete() {
|
||||||
let fixture = TestFixture::new().await;
|
let fixture = TestFixture::new().await;
|
||||||
let new_attribute = CreateAttributeRequest {
|
let new_attribute = CreateAttributeRequest {
|
||||||
name: "new_attribute".to_owned(),
|
name: "NeW_aTTribute".into(),
|
||||||
attribute_type: AttributeType::JpegPhoto,
|
attribute_type: AttributeType::JpegPhoto,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -217,7 +218,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let expected_value = AttributeSchema {
|
let expected_value = AttributeSchema {
|
||||||
name: "new_attribute".to_owned(),
|
name: "new_attribute".into(),
|
||||||
attribute_type: AttributeType::JpegPhoto,
|
attribute_type: AttributeType::JpegPhoto,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -234,7 +235,7 @@ mod tests {
|
|||||||
.contains(&expected_value));
|
.contains(&expected_value));
|
||||||
fixture
|
fixture
|
||||||
.handler
|
.handler
|
||||||
.delete_group_attribute("new_attribute")
|
.delete_group_attribute(&"new_attriBUte".into())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!fixture
|
assert!(!fixture
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ pub type DbConnection = sea_orm::DatabaseConnection;
|
|||||||
#[derive(Copy, PartialEq, Eq, Debug, Clone, PartialOrd, Ord, DeriveValueType)]
|
#[derive(Copy, PartialEq, Eq, Debug, Clone, PartialOrd, Ord, DeriveValueType)]
|
||||||
pub struct SchemaVersion(pub i16);
|
pub struct SchemaVersion(pub i16);
|
||||||
|
|
||||||
pub const LAST_SCHEMA_VERSION: SchemaVersion = SchemaVersion(5);
|
pub const LAST_SCHEMA_VERSION: SchemaVersion = SchemaVersion(6);
|
||||||
|
|
||||||
pub async fn init_table(pool: &DbConnection) -> anyhow::Result<()> {
|
pub async fn init_table(pool: &DbConnection) -> anyhow::Result<()> {
|
||||||
let version = {
|
let version = {
|
||||||
@@ -51,8 +51,8 @@ mod tests {
|
|||||||
sql_pool
|
sql_pool
|
||||||
.execute(raw_statement(
|
.execute(raw_statement(
|
||||||
r#"INSERT INTO users
|
r#"INSERT INTO users
|
||||||
(user_id, email, display_name, creation_date, password_hash, uuid)
|
(user_id, email, lowercase_email, display_name, creation_date, password_hash, uuid)
|
||||||
VALUES ("bôb", "böb@bob.bob", "Bob Bobbersön", "1970-01-01 00:00:00", "bob00", "abc")"#,
|
VALUES ("bôb", "böb@bob.bob", "böb@bob.bob", "Bob Bobbersön", "1970-01-01 00:00:00", "bob00", "abc")"#,
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -373,6 +373,83 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_migration_to_v6() {
|
||||||
|
crate::infra::logging::init_for_tests();
|
||||||
|
let sql_pool = get_in_memory_db().await;
|
||||||
|
upgrade_to_v1(&sql_pool).await.unwrap();
|
||||||
|
migrate_from_version(&sql_pool, SchemaVersion(1), SchemaVersion(5))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
sql_pool
|
||||||
|
.execute(raw_statement(
|
||||||
|
r#"INSERT INTO users (user_id, email, display_name, creation_date, uuid)
|
||||||
|
VALUES ("bob", "BOb@bob.com", "", "1970-01-01 00:00:00", "a02eaf13-48a7-30f6-a3d4-040ff7c52b04")"#,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
sql_pool
|
||||||
|
.execute(raw_statement(
|
||||||
|
r#"INSERT INTO groups (display_name, creation_date, uuid)
|
||||||
|
VALUES ("BestGroup", "1970-01-01 00:00:00", "986765a5-3f03-389e-b47b-536b2d6e1bec")"#,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
migrate_from_version(&sql_pool, SchemaVersion(5), SchemaVersion(6))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
sql_migrations::JustSchemaVersion::find_by_statement(raw_statement(
|
||||||
|
r#"SELECT version FROM metadata"#
|
||||||
|
))
|
||||||
|
.one(&sql_pool)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap(),
|
||||||
|
sql_migrations::JustSchemaVersion {
|
||||||
|
version: SchemaVersion(6)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
#[derive(FromQueryResult, PartialEq, Eq, Debug)]
|
||||||
|
struct ShortUserDetails {
|
||||||
|
email: String,
|
||||||
|
lowercase_email: String,
|
||||||
|
}
|
||||||
|
let result = ShortUserDetails::find_by_statement(raw_statement(
|
||||||
|
r#"SELECT email, lowercase_email FROM users WHERE user_id = "bob""#,
|
||||||
|
))
|
||||||
|
.one(&sql_pool)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
ShortUserDetails {
|
||||||
|
email: "BOb@bob.com".to_owned(),
|
||||||
|
lowercase_email: "bob@bob.com".to_owned(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
#[derive(FromQueryResult, PartialEq, Eq, Debug)]
|
||||||
|
struct ShortGroupDetails {
|
||||||
|
display_name: String,
|
||||||
|
lowercase_display_name: String,
|
||||||
|
}
|
||||||
|
let result = ShortGroupDetails::find_by_statement(raw_statement(
|
||||||
|
r#"SELECT display_name, lowercase_display_name FROM groups"#,
|
||||||
|
))
|
||||||
|
.one(&sql_pool)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
ShortGroupDetails {
|
||||||
|
display_name: "BestGroup".to_owned(),
|
||||||
|
lowercase_display_name: "bestgroup".to_owned(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_too_high_version() {
|
async fn test_too_high_version() {
|
||||||
let sql_pool = get_in_memory_db().await;
|
let sql_pool = get_in_memory_db().await;
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ use crate::domain::{
|
|||||||
},
|
},
|
||||||
model::{self, GroupColumn, UserColumn},
|
model::{self, GroupColumn, UserColumn},
|
||||||
sql_backend_handler::SqlBackendHandler,
|
sql_backend_handler::SqlBackendHandler,
|
||||||
types::{AttributeValue, GroupDetails, GroupId, Serialized, User, UserAndGroups, UserId, Uuid},
|
types::{
|
||||||
|
AttributeName, AttributeValue, GroupDetails, GroupId, Serialized, User, UserAndGroups,
|
||||||
|
UserId, Uuid,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use sea_orm::{
|
use sea_orm::{
|
||||||
@@ -19,7 +22,7 @@ use sea_orm::{
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
fn attribute_condition(name: String, value: String) -> Cond {
|
fn attribute_condition(name: AttributeName, value: String) -> Cond {
|
||||||
Expr::in_subquery(
|
Expr::in_subquery(
|
||||||
Expr::col(UserColumn::UserId.as_column_ref()),
|
Expr::col(UserColumn::UserId.as_column_ref()),
|
||||||
model::UserAttributes::find()
|
model::UserAttributes::find()
|
||||||
@@ -53,14 +56,17 @@ fn get_user_filter_expr(filter: UserRequestFilter) -> Cond {
|
|||||||
Or(fs) => get_repeated_filter(fs, Cond::any(), false),
|
Or(fs) => get_repeated_filter(fs, Cond::any(), false),
|
||||||
Not(f) => get_user_filter_expr(*f).not(),
|
Not(f) => get_user_filter_expr(*f).not(),
|
||||||
UserId(user_id) => ColumnTrait::eq(&UserColumn::UserId, user_id).into_condition(),
|
UserId(user_id) => ColumnTrait::eq(&UserColumn::UserId, user_id).into_condition(),
|
||||||
Equality(s1, s2) => {
|
Equality(column, value) => {
|
||||||
if s1 == UserColumn::UserId {
|
if column == UserColumn::UserId {
|
||||||
panic!("User id should be wrapped")
|
panic!("User id should be wrapped")
|
||||||
|
} else if column == UserColumn::Email {
|
||||||
|
ColumnTrait::eq(&UserColumn::LowercaseEmail, value.as_str().to_lowercase())
|
||||||
|
.into_condition()
|
||||||
} else {
|
} else {
|
||||||
ColumnTrait::eq(&s1, s2).into_condition()
|
ColumnTrait::eq(&column, value).into_condition()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AttributeEquality(s1, s2) => attribute_condition(s1, s2),
|
AttributeEquality(column, value) => attribute_condition(column, value),
|
||||||
MemberOf(group) => Expr::col((group_table, GroupColumn::DisplayName))
|
MemberOf(group) => Expr::col((group_table, GroupColumn::DisplayName))
|
||||||
.eq(group)
|
.eq(group)
|
||||||
.into_condition(),
|
.into_condition(),
|
||||||
@@ -159,9 +165,11 @@ impl SqlBackendHandler {
|
|||||||
transaction: &DatabaseTransaction,
|
transaction: &DatabaseTransaction,
|
||||||
request: UpdateUserRequest,
|
request: UpdateUserRequest,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let lower_email = request.email.as_ref().map(|s| s.as_str().to_lowercase());
|
||||||
let update_user = model::users::ActiveModel {
|
let update_user = model::users::ActiveModel {
|
||||||
user_id: ActiveValue::Set(request.user_id.clone()),
|
user_id: ActiveValue::Set(request.user_id.clone()),
|
||||||
email: request.email.map(ActiveValue::Set).unwrap_or_default(),
|
email: request.email.map(ActiveValue::Set).unwrap_or_default(),
|
||||||
|
lowercase_email: lower_email.map(ActiveValue::Set).unwrap_or_default(),
|
||||||
display_name: to_value(&request.display_name),
|
display_name: to_value(&request.display_name),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
@@ -173,27 +181,27 @@ impl SqlBackendHandler {
|
|||||||
let mut update_user_attributes = Vec::new();
|
let mut update_user_attributes = Vec::new();
|
||||||
let mut remove_user_attributes = Vec::new();
|
let mut remove_user_attributes = Vec::new();
|
||||||
let mut process_serialized =
|
let mut process_serialized =
|
||||||
|value: ActiveValue<Serialized>, attribute_name: &str| match &value {
|
|value: ActiveValue<Serialized>, attribute_name: AttributeName| match &value {
|
||||||
ActiveValue::NotSet => {
|
ActiveValue::NotSet => {
|
||||||
remove_user_attributes.push(attribute_name.to_owned());
|
remove_user_attributes.push(attribute_name);
|
||||||
}
|
}
|
||||||
ActiveValue::Set(_) => {
|
ActiveValue::Set(_) => {
|
||||||
update_user_attributes.push(model::user_attributes::ActiveModel {
|
update_user_attributes.push(model::user_attributes::ActiveModel {
|
||||||
user_id: Set(request.user_id.clone()),
|
user_id: Set(request.user_id.clone()),
|
||||||
attribute_name: Set(attribute_name.to_owned()),
|
attribute_name: Set(attribute_name),
|
||||||
value,
|
value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
if let Some(value) = to_serialized_value(&request.first_name) {
|
if let Some(value) = to_serialized_value(&request.first_name) {
|
||||||
process_serialized(value, "first_name");
|
process_serialized(value, "first_name".into());
|
||||||
}
|
}
|
||||||
if let Some(value) = to_serialized_value(&request.last_name) {
|
if let Some(value) = to_serialized_value(&request.last_name) {
|
||||||
process_serialized(value, "last_name");
|
process_serialized(value, "last_name".into());
|
||||||
}
|
}
|
||||||
if let Some(avatar) = request.avatar {
|
if let Some(avatar) = request.avatar {
|
||||||
process_serialized(avatar.into_active_value(), "avatar");
|
process_serialized(avatar.into_active_value(), "avatar".into());
|
||||||
}
|
}
|
||||||
let schema = Self::get_schema_with_transaction(transaction).await?;
|
let schema = Self::get_schema_with_transaction(transaction).await?;
|
||||||
for attribute in request.insert_attributes {
|
for attribute in request.insert_attributes {
|
||||||
@@ -202,7 +210,7 @@ impl SqlBackendHandler {
|
|||||||
.get_attribute_type(&attribute.name)
|
.get_attribute_type(&attribute.name)
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
process_serialized(ActiveValue::Set(attribute.value), &attribute.name);
|
process_serialized(ActiveValue::Set(attribute.value), attribute.name.clone());
|
||||||
} else {
|
} else {
|
||||||
return Err(DomainError::InternalError(format!(
|
return Err(DomainError::InternalError(format!(
|
||||||
"User attribute name {} doesn't exist in the schema, yet was attempted to be inserted in the database",
|
"User attribute name {} doesn't exist in the schema, yet was attempted to be inserted in the database",
|
||||||
@@ -287,9 +295,11 @@ impl UserBackendHandler for SqlBackendHandler {
|
|||||||
async fn create_user(&self, request: CreateUserRequest) -> Result<()> {
|
async fn create_user(&self, request: CreateUserRequest) -> Result<()> {
|
||||||
let now = chrono::Utc::now().naive_utc();
|
let now = chrono::Utc::now().naive_utc();
|
||||||
let uuid = Uuid::from_name_and_date(request.user_id.as_str(), &now);
|
let uuid = Uuid::from_name_and_date(request.user_id.as_str(), &now);
|
||||||
|
let lower_email = request.email.as_str().to_lowercase();
|
||||||
let new_user = model::users::ActiveModel {
|
let new_user = model::users::ActiveModel {
|
||||||
user_id: Set(request.user_id.clone()),
|
user_id: Set(request.user_id.clone()),
|
||||||
email: Set(request.email),
|
email: Set(request.email),
|
||||||
|
lowercase_email: Set(lower_email),
|
||||||
display_name: to_value(&request.display_name),
|
display_name: to_value(&request.display_name),
|
||||||
creation_date: ActiveValue::Set(now),
|
creation_date: ActiveValue::Set(now),
|
||||||
uuid: ActiveValue::Set(uuid),
|
uuid: ActiveValue::Set(uuid),
|
||||||
@@ -299,21 +309,21 @@ impl UserBackendHandler for SqlBackendHandler {
|
|||||||
if let Some(first_name) = request.first_name {
|
if let Some(first_name) = request.first_name {
|
||||||
new_user_attributes.push(model::user_attributes::ActiveModel {
|
new_user_attributes.push(model::user_attributes::ActiveModel {
|
||||||
user_id: Set(request.user_id.clone()),
|
user_id: Set(request.user_id.clone()),
|
||||||
attribute_name: Set("first_name".to_owned()),
|
attribute_name: Set("first_name".into()),
|
||||||
value: Set(Serialized::from(&first_name)),
|
value: Set(Serialized::from(&first_name)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if let Some(last_name) = request.last_name {
|
if let Some(last_name) = request.last_name {
|
||||||
new_user_attributes.push(model::user_attributes::ActiveModel {
|
new_user_attributes.push(model::user_attributes::ActiveModel {
|
||||||
user_id: Set(request.user_id.clone()),
|
user_id: Set(request.user_id.clone()),
|
||||||
attribute_name: Set("last_name".to_owned()),
|
attribute_name: Set("last_name".into()),
|
||||||
value: Set(Serialized::from(&last_name)),
|
value: Set(Serialized::from(&last_name)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if let Some(avatar) = request.avatar {
|
if let Some(avatar) = request.avatar {
|
||||||
new_user_attributes.push(model::user_attributes::ActiveModel {
|
new_user_attributes.push(model::user_attributes::ActiveModel {
|
||||||
user_id: Set(request.user_id.clone()),
|
user_id: Set(request.user_id.clone()),
|
||||||
attribute_name: Set("avatar".to_owned()),
|
attribute_name: Set("avatar".into()),
|
||||||
value: Set(Serialized::from(&avatar)),
|
value: Set(Serialized::from(&avatar)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -452,7 +462,7 @@ mod tests {
|
|||||||
let users = get_user_names(
|
let users = get_user_names(
|
||||||
&fixture.handler,
|
&fixture.handler,
|
||||||
Some(UserRequestFilter::AttributeEquality(
|
Some(UserRequestFilter::AttributeEquality(
|
||||||
"first_name".to_string(),
|
AttributeName::from("first_name"),
|
||||||
"first bob".to_string(),
|
"first bob".to_string(),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
@@ -460,6 +470,30 @@ mod tests {
|
|||||||
assert_eq!(users, vec!["bob"]);
|
assert_eq!(users, vec!["bob"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_list_users_email_filter_uppercase_email() {
|
||||||
|
let fixture = TestFixture::new().await;
|
||||||
|
insert_user_no_password(&fixture.handler, "UppEr").await;
|
||||||
|
let users_and_emails = fixture
|
||||||
|
.handler
|
||||||
|
.list_users(
|
||||||
|
Some(UserRequestFilter::Equality(
|
||||||
|
UserColumn::Email,
|
||||||
|
"uPPer@bob.bob".to_string(),
|
||||||
|
)),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.map(|u| (u.user.user_id.to_string(), u.user.email.to_string()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
assert_eq!(
|
||||||
|
users_and_emails,
|
||||||
|
vec![("upper".to_owned(), "UppEr@bob.bob".to_owned())]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_list_users_substring_filter() {
|
async fn test_list_users_substring_filter() {
|
||||||
let fixture = TestFixture::new().await;
|
let fixture = TestFixture::new().await;
|
||||||
@@ -503,7 +537,7 @@ mod tests {
|
|||||||
let fixture = TestFixture::new().await;
|
let fixture = TestFixture::new().await;
|
||||||
let users = get_user_names(
|
let users = get_user_names(
|
||||||
&fixture.handler,
|
&fixture.handler,
|
||||||
Some(UserRequestFilter::MemberOf("Best Group".to_string())),
|
Some(UserRequestFilter::MemberOf("Best Group".into())),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(users, vec!["bob", "patrick"]);
|
assert_eq!(users, vec!["bob", "patrick"]);
|
||||||
@@ -515,7 +549,7 @@ mod tests {
|
|||||||
let users = get_user_names(
|
let users = get_user_names(
|
||||||
&fixture.handler,
|
&fixture.handler,
|
||||||
Some(UserRequestFilter::Or(vec![
|
Some(UserRequestFilter::Or(vec![
|
||||||
UserRequestFilter::MemberOf("Best Group".to_string()),
|
UserRequestFilter::MemberOf("Best Group".into()),
|
||||||
UserRequestFilter::Equality(UserColumn::Uuid, "abc".to_string()),
|
UserRequestFilter::Equality(UserColumn::Uuid, "abc".to_string()),
|
||||||
])),
|
])),
|
||||||
)
|
)
|
||||||
@@ -764,7 +798,7 @@ mod tests {
|
|||||||
.handler
|
.handler
|
||||||
.update_user(UpdateUserRequest {
|
.update_user(UpdateUserRequest {
|
||||||
user_id: UserId::new("bob"),
|
user_id: UserId::new("bob"),
|
||||||
email: Some("email".to_string()),
|
email: Some("email".into()),
|
||||||
display_name: Some("display_name".to_string()),
|
display_name: Some("display_name".to_string()),
|
||||||
first_name: Some("first_name".to_string()),
|
first_name: Some("first_name".to_string()),
|
||||||
last_name: Some("last_name".to_string()),
|
last_name: Some("last_name".to_string()),
|
||||||
@@ -780,21 +814,21 @@ mod tests {
|
|||||||
.get_user_details(&UserId::new("bob"))
|
.get_user_details(&UserId::new("bob"))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(user.email, "email");
|
assert_eq!(user.email, "email".into());
|
||||||
assert_eq!(user.display_name.unwrap(), "display_name");
|
assert_eq!(user.display_name.unwrap(), "display_name");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
user.attributes,
|
user.attributes,
|
||||||
vec![
|
vec![
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "avatar".to_owned(),
|
name: "avatar".into(),
|
||||||
value: Serialized::from(&JpegPhoto::for_tests())
|
value: Serialized::from(&JpegPhoto::for_tests())
|
||||||
},
|
},
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "first_name".to_owned(),
|
name: "first_name".into(),
|
||||||
value: Serialized::from("first_name")
|
value: Serialized::from("first_name")
|
||||||
},
|
},
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "last_name".to_owned(),
|
name: "last_name".into(),
|
||||||
value: Serialized::from("last_name")
|
value: Serialized::from("last_name")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -827,11 +861,11 @@ mod tests {
|
|||||||
user.attributes,
|
user.attributes,
|
||||||
vec![
|
vec![
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "avatar".to_owned(),
|
name: "avatar".into(),
|
||||||
value: Serialized::from(&JpegPhoto::for_tests())
|
value: Serialized::from(&JpegPhoto::for_tests())
|
||||||
},
|
},
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "first_name".to_owned(),
|
name: "first_name".into(),
|
||||||
value: Serialized::from("first bob")
|
value: Serialized::from("first bob")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -850,7 +884,7 @@ mod tests {
|
|||||||
last_name: None,
|
last_name: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
insert_attributes: vec![AttributeValue {
|
insert_attributes: vec![AttributeValue {
|
||||||
name: "first_name".to_owned(),
|
name: "first_name".into(),
|
||||||
value: Serialized::from("new first"),
|
value: Serialized::from("new first"),
|
||||||
}],
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -867,11 +901,11 @@ mod tests {
|
|||||||
user.attributes,
|
user.attributes,
|
||||||
vec![
|
vec![
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "first_name".to_owned(),
|
name: "first_name".into(),
|
||||||
value: Serialized::from("new first")
|
value: Serialized::from("new first")
|
||||||
},
|
},
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "last_name".to_owned(),
|
name: "last_name".into(),
|
||||||
value: Serialized::from("last bob")
|
value: Serialized::from("last bob")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -889,7 +923,7 @@ mod tests {
|
|||||||
first_name: None,
|
first_name: None,
|
||||||
last_name: None,
|
last_name: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
delete_attributes: vec!["first_name".to_owned()],
|
delete_attributes: vec!["first_name".into()],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@@ -903,7 +937,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
user.attributes,
|
user.attributes,
|
||||||
vec![AttributeValue {
|
vec![AttributeValue {
|
||||||
name: "last_name".to_owned(),
|
name: "last_name".into(),
|
||||||
value: Serialized::from("last bob")
|
value: Serialized::from("last bob")
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
@@ -920,9 +954,9 @@ mod tests {
|
|||||||
first_name: None,
|
first_name: None,
|
||||||
last_name: None,
|
last_name: None,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
delete_attributes: vec!["first_name".to_owned()],
|
delete_attributes: vec!["first_name".into()],
|
||||||
insert_attributes: vec![AttributeValue {
|
insert_attributes: vec![AttributeValue {
|
||||||
name: "first_name".to_owned(),
|
name: "first_name".into(),
|
||||||
value: Serialized::from("new first"),
|
value: Serialized::from("new first"),
|
||||||
}],
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -939,11 +973,11 @@ mod tests {
|
|||||||
user.attributes,
|
user.attributes,
|
||||||
vec![
|
vec![
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "first_name".to_owned(),
|
name: "first_name".into(),
|
||||||
value: Serialized::from("new first")
|
value: Serialized::from("new first")
|
||||||
},
|
},
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "last_name".to_owned(),
|
name: "last_name".into(),
|
||||||
value: Serialized::from("last bob")
|
value: Serialized::from("last bob")
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -970,7 +1004,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let avatar = AttributeValue {
|
let avatar = AttributeValue {
|
||||||
name: "avatar".to_owned(),
|
name: "avatar".into(),
|
||||||
value: Serialized::from(&JpegPhoto::for_tests()),
|
value: Serialized::from(&JpegPhoto::for_tests()),
|
||||||
};
|
};
|
||||||
assert!(user.attributes.contains(&avatar));
|
assert!(user.attributes.contains(&avatar));
|
||||||
@@ -1000,13 +1034,13 @@ mod tests {
|
|||||||
.handler
|
.handler
|
||||||
.create_user(CreateUserRequest {
|
.create_user(CreateUserRequest {
|
||||||
user_id: UserId::new("james"),
|
user_id: UserId::new("james"),
|
||||||
email: "email".to_string(),
|
email: "email".into(),
|
||||||
display_name: Some("display_name".to_string()),
|
display_name: Some("display_name".to_string()),
|
||||||
first_name: None,
|
first_name: None,
|
||||||
last_name: Some("last_name".to_string()),
|
last_name: Some("last_name".to_string()),
|
||||||
avatar: Some(JpegPhoto::for_tests()),
|
avatar: Some(JpegPhoto::for_tests()),
|
||||||
attributes: vec![AttributeValue {
|
attributes: vec![AttributeValue {
|
||||||
name: "first_name".to_owned(),
|
name: "first_name".into(),
|
||||||
value: Serialized::from("First Name"),
|
value: Serialized::from("First Name"),
|
||||||
}],
|
}],
|
||||||
})
|
})
|
||||||
@@ -1018,21 +1052,21 @@ mod tests {
|
|||||||
.get_user_details(&UserId::new("james"))
|
.get_user_details(&UserId::new("james"))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(user.email, "email");
|
assert_eq!(user.email, "email".into());
|
||||||
assert_eq!(user.display_name.unwrap(), "display_name");
|
assert_eq!(user.display_name.unwrap(), "display_name");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
user.attributes,
|
user.attributes,
|
||||||
vec![
|
vec![
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "avatar".to_owned(),
|
name: "avatar".into(),
|
||||||
value: Serialized::from(&JpegPhoto::for_tests())
|
value: Serialized::from(&JpegPhoto::for_tests())
|
||||||
},
|
},
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "first_name".to_owned(),
|
name: "first_name".into(),
|
||||||
value: Serialized::from("First Name")
|
value: Serialized::from("First Name")
|
||||||
},
|
},
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "last_name".to_owned(),
|
name: "last_name".into(),
|
||||||
value: Serialized::from("last_name")
|
value: Serialized::from("last_name")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+191
-23
@@ -1,3 +1,5 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use chrono::{NaiveDateTime, TimeZone};
|
use chrono::{NaiveDateTime, TimeZone};
|
||||||
use sea_orm::{
|
use sea_orm::{
|
||||||
@@ -121,12 +123,22 @@ impl Serialized {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Default, Serialize, Deserialize, DeriveValueType,
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
PartialOrd,
|
||||||
|
Ord,
|
||||||
|
Clone,
|
||||||
|
Debug,
|
||||||
|
Default,
|
||||||
|
Hash,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
DeriveValueType,
|
||||||
)]
|
)]
|
||||||
#[serde(from = "String")]
|
#[serde(from = "String")]
|
||||||
pub struct UserId(String);
|
pub struct CaseInsensitiveString(String);
|
||||||
|
|
||||||
impl UserId {
|
impl CaseInsensitiveString {
|
||||||
pub fn new(user_id: &str) -> Self {
|
pub fn new(user_id: &str) -> Self {
|
||||||
Self(user_id.to_lowercase())
|
Self(user_id.to_lowercase())
|
||||||
}
|
}
|
||||||
@@ -140,29 +152,185 @@ impl UserId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for UserId {
|
impl From<String> for CaseInsensitiveString {
|
||||||
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 {
|
fn from(s: String) -> Self {
|
||||||
Self::new(&s)
|
Self::new(&s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&UserId> for Value {
|
macro_rules! make_case_insensitive_string {
|
||||||
fn from(user_id: &UserId) -> Self {
|
($c:ident) => {
|
||||||
user_id.as_str().into()
|
#[derive(
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
PartialOrd,
|
||||||
|
Ord,
|
||||||
|
Clone,
|
||||||
|
Debug,
|
||||||
|
Default,
|
||||||
|
Hash,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
DeriveValueType,
|
||||||
|
)]
|
||||||
|
#[serde(from = "CaseInsensitiveString")]
|
||||||
|
pub struct $c(CaseInsensitiveString);
|
||||||
|
|
||||||
|
impl $c {
|
||||||
|
pub fn new(raw: &str) -> Self {
|
||||||
|
Self(CaseInsensitiveString::new(raw))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
self.0.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_string(self) -> String {
|
||||||
|
self.0.into_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CaseInsensitiveString> for $c {
|
||||||
|
fn from(s: CaseInsensitiveString) -> Self {
|
||||||
|
Self(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for $c {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
Self(CaseInsensitiveString::from(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for $c {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
Self::new(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for $c {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&$c> for Value {
|
||||||
|
fn from(user_id: &$c) -> Self {
|
||||||
|
user_id.as_str().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFromU64 for $c {
|
||||||
|
fn try_from_u64(_n: u64) -> Result<Self, DbErr> {
|
||||||
|
Err(DbErr::ConvertFromU64("$c cannot be constructed from u64"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_str_case_insensitive(s1: &str, s2: &str) -> Ordering {
|
||||||
|
let mut it_1 = s1.chars().flat_map(|c| c.to_lowercase());
|
||||||
|
let mut it_2 = s2.chars().flat_map(|c| c.to_lowercase());
|
||||||
|
loop {
|
||||||
|
match (it_1.next(), it_2.next()) {
|
||||||
|
(Some(c1), Some(c2)) => {
|
||||||
|
let o = c1.cmp(&c2);
|
||||||
|
if o != Ordering::Equal {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(None, Some(_)) => return Ordering::Less,
|
||||||
|
(Some(_), None) => return Ordering::Greater,
|
||||||
|
(None, None) => return Ordering::Equal,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFromU64 for UserId {
|
macro_rules! make_case_insensitive_comparable_string {
|
||||||
fn try_from_u64(_n: u64) -> Result<Self, DbErr> {
|
($c:ident) => {
|
||||||
Err(DbErr::ConvertFromU64(
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, DeriveValueType)]
|
||||||
"UserId cannot be constructed from u64",
|
pub struct $c(String);
|
||||||
))
|
|
||||||
|
impl PartialEq for $c {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
compare_str_case_insensitive(&self.0, &other.0) == Ordering::Equal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for $c {}
|
||||||
|
|
||||||
|
impl PartialOrd for $c {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(compare_str_case_insensitive(&self.0, &other.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for $c {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
compare_str_case_insensitive(&self.0, &other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for $c {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.0.to_lowercase().hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $c {
|
||||||
|
pub fn new(raw: &str) -> Self {
|
||||||
|
Self(raw.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
self.0.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_string(self) -> String {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for $c {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
Self(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for $c {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
Self::new(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for $c {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&$c> for Value {
|
||||||
|
fn from(user_id: &$c) -> Self {
|
||||||
|
user_id.as_str().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFromU64 for $c {
|
||||||
|
fn try_from_u64(_n: u64) -> Result<Self, DbErr> {
|
||||||
|
Err(DbErr::ConvertFromU64("$c cannot be constructed from u64"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
make_case_insensitive_string!(UserId);
|
||||||
|
make_case_insensitive_string!(AttributeName);
|
||||||
|
make_case_insensitive_comparable_string!(Email);
|
||||||
|
make_case_insensitive_comparable_string!(GroupName);
|
||||||
|
|
||||||
|
impl AsRef<GroupName> for GroupName {
|
||||||
|
fn as_ref(&self) -> &GroupName {
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,14 +451,14 @@ impl IntoActiveValue<Serialized> for JpegPhoto {
|
|||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, Hash)]
|
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, Hash)]
|
||||||
pub struct AttributeValue {
|
pub struct AttributeValue {
|
||||||
pub name: String,
|
pub name: AttributeName,
|
||||||
pub value: Serialized,
|
pub value: Serialized,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
pub email: String,
|
pub email: Email,
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
pub creation_date: NaiveDateTime,
|
pub creation_date: NaiveDateTime,
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
@@ -303,7 +471,7 @@ impl Default for User {
|
|||||||
let epoch = chrono::Utc.timestamp_opt(0, 0).unwrap().naive_utc();
|
let epoch = chrono::Utc.timestamp_opt(0, 0).unwrap().naive_utc();
|
||||||
User {
|
User {
|
||||||
user_id: UserId::default(),
|
user_id: UserId::default(),
|
||||||
email: String::new(),
|
email: Email::default(),
|
||||||
display_name: None,
|
display_name: None,
|
||||||
creation_date: epoch,
|
creation_date: epoch,
|
||||||
uuid: Uuid::from_name_and_date("", &epoch),
|
uuid: Uuid::from_name_and_date("", &epoch),
|
||||||
@@ -397,7 +565,7 @@ impl ValueType for AttributeType {
|
|||||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
pub struct Group {
|
pub struct Group {
|
||||||
pub id: GroupId,
|
pub id: GroupId,
|
||||||
pub display_name: String,
|
pub display_name: GroupName,
|
||||||
pub creation_date: NaiveDateTime,
|
pub creation_date: NaiveDateTime,
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub users: Vec<UserId>,
|
pub users: Vec<UserId>,
|
||||||
@@ -407,7 +575,7 @@ pub struct Group {
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct GroupDetails {
|
pub struct GroupDetails {
|
||||||
pub group_id: GroupId,
|
pub group_id: GroupId,
|
||||||
pub display_name: String,
|
pub display_name: GroupName,
|
||||||
pub creation_date: NaiveDateTime,
|
pub creation_date: NaiveDateTime,
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub attributes: Vec<AttributeValue>,
|
pub attributes: Vec<AttributeValue>,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use crate::domain::{
|
|||||||
UpdateUserRequest, UserBackendHandler, UserListerBackendHandler, UserRequestFilter,
|
UpdateUserRequest, UserBackendHandler, UserListerBackendHandler, UserRequestFilter,
|
||||||
},
|
},
|
||||||
schema::PublicSchema,
|
schema::PublicSchema,
|
||||||
types::{Group, GroupDetails, GroupId, User, UserAndGroups, UserId},
|
types::{AttributeName, Group, GroupDetails, GroupId, GroupName, User, UserAndGroups, UserId},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
@@ -110,8 +110,8 @@ pub trait AdminBackendHandler:
|
|||||||
async fn delete_group(&self, group_id: GroupId) -> Result<()>;
|
async fn delete_group(&self, group_id: GroupId) -> Result<()>;
|
||||||
async fn add_user_attribute(&self, request: CreateAttributeRequest) -> Result<()>;
|
async fn add_user_attribute(&self, request: CreateAttributeRequest) -> Result<()>;
|
||||||
async fn add_group_attribute(&self, request: CreateAttributeRequest) -> Result<()>;
|
async fn add_group_attribute(&self, request: CreateAttributeRequest) -> Result<()>;
|
||||||
async fn delete_user_attribute(&self, name: &str) -> Result<()>;
|
async fn delete_user_attribute(&self, name: &AttributeName) -> Result<()>;
|
||||||
async fn delete_group_attribute(&self, name: &str) -> Result<()>;
|
async fn delete_group_attribute(&self, name: &AttributeName) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -181,10 +181,10 @@ impl<Handler: BackendHandler> AdminBackendHandler for Handler {
|
|||||||
async fn add_group_attribute(&self, request: CreateAttributeRequest) -> Result<()> {
|
async fn add_group_attribute(&self, request: CreateAttributeRequest) -> Result<()> {
|
||||||
<Handler as SchemaBackendHandler>::add_group_attribute(self, request).await
|
<Handler as SchemaBackendHandler>::add_group_attribute(self, request).await
|
||||||
}
|
}
|
||||||
async fn delete_user_attribute(&self, name: &str) -> Result<()> {
|
async fn delete_user_attribute(&self, name: &AttributeName) -> Result<()> {
|
||||||
<Handler as SchemaBackendHandler>::delete_user_attribute(self, name).await
|
<Handler as SchemaBackendHandler>::delete_user_attribute(self, name).await
|
||||||
}
|
}
|
||||||
async fn delete_group_attribute(&self, name: &str) -> Result<()> {
|
async fn delete_group_attribute(&self, name: &AttributeName) -> Result<()> {
|
||||||
<Handler as SchemaBackendHandler>::delete_group_attribute(self, name).await
|
<Handler as SchemaBackendHandler>::delete_group_attribute(self, name).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,19 +264,23 @@ impl<Handler: BackendHandler> AccessControlledBackendHandler<Handler> {
|
|||||||
Ok(self.get_permissions_from_groups(user_id, user_groups.iter().map(|g| &g.display_name)))
|
Ok(self.get_permissions_from_groups(user_id, user_groups.iter().map(|g| &g.display_name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_permissions_from_groups<'a, Groups: Iterator<Item = &'a String> + Clone + 'a>(
|
pub fn get_permissions_from_groups<Groups, T>(
|
||||||
&self,
|
&self,
|
||||||
user_id: UserId,
|
user_id: UserId,
|
||||||
groups: Groups,
|
groups: Groups,
|
||||||
) -> ValidationResults {
|
) -> ValidationResults
|
||||||
let is_in_group = |name| groups.clone().any(|g| g == name);
|
where
|
||||||
|
Groups: Iterator<Item = T> + Clone,
|
||||||
|
T: AsRef<GroupName>,
|
||||||
|
{
|
||||||
|
let is_in_group = |name: GroupName| groups.clone().any(|g| *g.as_ref() == name);
|
||||||
ValidationResults {
|
ValidationResults {
|
||||||
user: user_id,
|
user: user_id,
|
||||||
permission: if is_in_group("lldap_admin") {
|
permission: if is_in_group("lldap_admin".into()) {
|
||||||
Permission::Admin
|
Permission::Admin
|
||||||
} else if is_in_group("lldap_password_manager") {
|
} else if is_in_group("lldap_password_manager".into()) {
|
||||||
Permission::PasswordManager
|
Permission::PasswordManager
|
||||||
} else if is_in_group("lldap_strict_readonly") {
|
} else if is_in_group("lldap_strict_readonly".into()) {
|
||||||
Permission::Readonly
|
Permission::Readonly
|
||||||
} else {
|
} else {
|
||||||
Permission::Regular
|
Permission::Regular
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ use crate::{
|
|||||||
error::DomainError,
|
error::DomainError,
|
||||||
handler::{BackendHandler, BindRequest, LoginHandler, UserRequestFilter},
|
handler::{BackendHandler, BindRequest, LoginHandler, UserRequestFilter},
|
||||||
opaque_handler::OpaqueHandler,
|
opaque_handler::OpaqueHandler,
|
||||||
types::{GroupDetails, UserColumn, UserId},
|
types::{GroupDetails, GroupName, UserColumn, UserId},
|
||||||
},
|
},
|
||||||
infra::{
|
infra::{
|
||||||
access_control::{ReadonlyBackendHandler, UserReadableBackendHandler, ValidationResults},
|
access_control::{ReadonlyBackendHandler, UserReadableBackendHandler, ValidationResults},
|
||||||
@@ -58,7 +58,10 @@ async fn create_jwt<Handler: TcpBackendHandler>(
|
|||||||
exp: Utc::now() + chrono::Duration::days(1),
|
exp: Utc::now() + chrono::Duration::days(1),
|
||||||
iat: Utc::now(),
|
iat: Utc::now(),
|
||||||
user: user.to_string(),
|
user: user.to_string(),
|
||||||
groups: groups.into_iter().map(|g| g.display_name).collect(),
|
groups: groups
|
||||||
|
.into_iter()
|
||||||
|
.map(|g| g.display_name.into_string())
|
||||||
|
.collect(),
|
||||||
};
|
};
|
||||||
let expiry = claims.exp.naive_utc();
|
let expiry = claims.exp.naive_utc();
|
||||||
let header = jwt::Header {
|
let header = jwt::Header {
|
||||||
@@ -187,7 +190,7 @@ where
|
|||||||
user.display_name
|
user.display_name
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.unwrap_or_else(|| user.user_id.as_str()),
|
.unwrap_or_else(|| user.user_id.as_str()),
|
||||||
&user.email,
|
user.email.as_str(),
|
||||||
&token,
|
&token,
|
||||||
&data.server_url,
|
&data.server_url,
|
||||||
&data.mail_options,
|
&data.mail_options,
|
||||||
@@ -503,7 +506,7 @@ where
|
|||||||
.get_user_groups(&user_id)
|
.get_user_groups(&user_id)
|
||||||
.await?
|
.await?
|
||||||
.iter()
|
.iter()
|
||||||
.any(|g| g.display_name == "lldap_admin");
|
.any(|g| g.display_name == "lldap_admin".into());
|
||||||
if !validation_result.can_change_password(&user_id, user_is_admin) {
|
if !validation_result.can_change_password(&user_id, user_is_admin) {
|
||||||
return Err(TcpError::UnauthorizedError(
|
return Err(TcpError::UnauthorizedError(
|
||||||
"Not authorized to change the user's password".to_string(),
|
"Not authorized to change the user's password".to_string(),
|
||||||
@@ -633,7 +636,11 @@ pub(crate) fn check_if_token_is_valid<Backend: BackendHandler>(
|
|||||||
}
|
}
|
||||||
Ok(state.backend_handler.get_permissions_from_groups(
|
Ok(state.backend_handler.get_permissions_from_groups(
|
||||||
UserId::new(&token.claims().user),
|
UserId::new(&token.claims().user),
|
||||||
token.claims().groups.iter(),
|
token
|
||||||
|
.claims()
|
||||||
|
.groups
|
||||||
|
.iter()
|
||||||
|
.map(|s| GroupName::from(s.as_str())),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
domain::types::UserId,
|
domain::types::{AttributeName, 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};
|
||||||
@@ -86,9 +86,9 @@ pub struct Configuration {
|
|||||||
#[builder(default = r#"String::from("sqlite://users.db?mode=rwc")"#)]
|
#[builder(default = r#"String::from("sqlite://users.db?mode=rwc")"#)]
|
||||||
pub database_url: String,
|
pub database_url: String,
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
pub ignored_user_attributes: Vec<String>,
|
pub ignored_user_attributes: Vec<AttributeName>,
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
pub ignored_group_attributes: Vec<String>,
|
pub ignored_group_attributes: Vec<AttributeName>,
|
||||||
#[builder(default = "false")]
|
#[builder(default = "false")]
|
||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
#[builder(default = r#"String::from("server_key")"#)]
|
#[builder(default = r#"String::from("server_key")"#)]
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use crate::{
|
|||||||
CreateUserRequest, UpdateGroupRequest, UpdateUserRequest,
|
CreateUserRequest, UpdateGroupRequest, UpdateUserRequest,
|
||||||
},
|
},
|
||||||
types::{
|
types::{
|
||||||
AttributeType, AttributeValue as DomainAttributeValue, GroupId, JpegPhoto, Serialized,
|
AttributeName, AttributeType, AttributeValue as DomainAttributeValue, GroupId,
|
||||||
UserId,
|
JpegPhoto, Serialized, UserId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
infra::{
|
infra::{
|
||||||
@@ -149,7 +149,7 @@ impl<Handler: BackendHandler> Mutation<Handler> {
|
|||||||
handler
|
handler
|
||||||
.create_user(CreateUserRequest {
|
.create_user(CreateUserRequest {
|
||||||
user_id: user_id.clone(),
|
user_id: user_id.clone(),
|
||||||
email: user.email,
|
email: user.email.into(),
|
||||||
display_name: user.display_name,
|
display_name: user.display_name,
|
||||||
first_name: user.first_name,
|
first_name: user.first_name,
|
||||||
last_name: user.last_name,
|
last_name: user.last_name,
|
||||||
@@ -225,12 +225,17 @@ impl<Handler: BackendHandler> Mutation<Handler> {
|
|||||||
handler
|
handler
|
||||||
.update_user(UpdateUserRequest {
|
.update_user(UpdateUserRequest {
|
||||||
user_id,
|
user_id,
|
||||||
email: user.email,
|
email: user.email.map(Into::into),
|
||||||
display_name: user.display_name,
|
display_name: user.display_name,
|
||||||
first_name: user.first_name,
|
first_name: user.first_name,
|
||||||
last_name: user.last_name,
|
last_name: user.last_name,
|
||||||
avatar,
|
avatar,
|
||||||
delete_attributes: user.remove_attributes.unwrap_or_default(),
|
delete_attributes: user
|
||||||
|
.remove_attributes
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect(),
|
||||||
insert_attributes,
|
insert_attributes,
|
||||||
})
|
})
|
||||||
.instrument(span)
|
.instrument(span)
|
||||||
@@ -263,8 +268,13 @@ impl<Handler: BackendHandler> Mutation<Handler> {
|
|||||||
handler
|
handler
|
||||||
.update_group(UpdateGroupRequest {
|
.update_group(UpdateGroupRequest {
|
||||||
group_id: GroupId(group.id),
|
group_id: GroupId(group.id),
|
||||||
display_name: group.display_name,
|
display_name: group.display_name.map(Into::into),
|
||||||
delete_attributes: group.remove_attributes.unwrap_or_default(),
|
delete_attributes: group
|
||||||
|
.remove_attributes
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect(),
|
||||||
insert_attributes,
|
insert_attributes,
|
||||||
})
|
})
|
||||||
.instrument(span)
|
.instrument(span)
|
||||||
@@ -377,7 +387,7 @@ impl<Handler: BackendHandler> Mutation<Handler> {
|
|||||||
))?;
|
))?;
|
||||||
handler
|
handler
|
||||||
.add_user_attribute(CreateAttributeRequest {
|
.add_user_attribute(CreateAttributeRequest {
|
||||||
name,
|
name: name.into(),
|
||||||
attribute_type,
|
attribute_type,
|
||||||
is_list,
|
is_list,
|
||||||
is_visible,
|
is_visible,
|
||||||
@@ -408,7 +418,7 @@ impl<Handler: BackendHandler> Mutation<Handler> {
|
|||||||
))?;
|
))?;
|
||||||
handler
|
handler
|
||||||
.add_group_attribute(CreateAttributeRequest {
|
.add_group_attribute(CreateAttributeRequest {
|
||||||
name,
|
name: name.into(),
|
||||||
attribute_type,
|
attribute_type,
|
||||||
is_list,
|
is_list,
|
||||||
is_visible,
|
is_visible,
|
||||||
@@ -424,6 +434,7 @@ impl<Handler: BackendHandler> Mutation<Handler> {
|
|||||||
name: String,
|
name: String,
|
||||||
) -> FieldResult<Success> {
|
) -> FieldResult<Success> {
|
||||||
let span = debug_span!("[GraphQL mutation] delete_user_attribute");
|
let span = debug_span!("[GraphQL mutation] delete_user_attribute");
|
||||||
|
let name = AttributeName::from(name);
|
||||||
span.in_scope(|| {
|
span.in_scope(|| {
|
||||||
debug!(?name);
|
debug!(?name);
|
||||||
});
|
});
|
||||||
@@ -438,9 +449,9 @@ impl<Handler: BackendHandler> Mutation<Handler> {
|
|||||||
.get_schema()
|
.get_schema()
|
||||||
.user_attributes
|
.user_attributes
|
||||||
.get_attribute_schema(&name)
|
.get_attribute_schema(&name)
|
||||||
.ok_or_else(|| anyhow!("Attribute {} is not defined in the schema", name))?;
|
.ok_or_else(|| anyhow!("Attribute {} is not defined in the schema", &name))?;
|
||||||
if attribute_schema.is_hardcoded {
|
if attribute_schema.is_hardcoded {
|
||||||
return Err(anyhow!("Permission denied: Attribute {} cannot be deleted", name).into());
|
return Err(anyhow!("Permission denied: Attribute {} cannot be deleted", &name).into());
|
||||||
}
|
}
|
||||||
handler
|
handler
|
||||||
.delete_user_attribute(&name)
|
.delete_user_attribute(&name)
|
||||||
@@ -454,6 +465,7 @@ impl<Handler: BackendHandler> Mutation<Handler> {
|
|||||||
name: String,
|
name: String,
|
||||||
) -> FieldResult<Success> {
|
) -> FieldResult<Success> {
|
||||||
let span = debug_span!("[GraphQL mutation] delete_group_attribute");
|
let span = debug_span!("[GraphQL mutation] delete_group_attribute");
|
||||||
|
let name = AttributeName::from(name);
|
||||||
span.in_scope(|| {
|
span.in_scope(|| {
|
||||||
debug!(?name);
|
debug!(?name);
|
||||||
});
|
});
|
||||||
@@ -468,9 +480,9 @@ impl<Handler: BackendHandler> Mutation<Handler> {
|
|||||||
.get_schema()
|
.get_schema()
|
||||||
.group_attributes
|
.group_attributes
|
||||||
.get_attribute_schema(&name)
|
.get_attribute_schema(&name)
|
||||||
.ok_or_else(|| anyhow!("Attribute {} is not defined in the schema", name))?;
|
.ok_or_else(|| anyhow!("Attribute {} is not defined in the schema", &name))?;
|
||||||
if attribute_schema.is_hardcoded {
|
if attribute_schema.is_hardcoded {
|
||||||
return Err(anyhow!("Permission denied: Attribute {} cannot be deleted", name).into());
|
return Err(anyhow!("Permission denied: Attribute {} cannot be deleted", &name).into());
|
||||||
}
|
}
|
||||||
handler
|
handler
|
||||||
.delete_group_attribute(&name)
|
.delete_group_attribute(&name)
|
||||||
@@ -496,7 +508,7 @@ async fn create_group_with_details<Handler: BackendHandler>(
|
|||||||
.map(|attr| deserialize_attribute(&schema.get_schema().group_attributes, attr, true))
|
.map(|attr| deserialize_attribute(&schema.get_schema().group_attributes, attr, true))
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
let request = CreateGroupRequest {
|
let request = CreateGroupRequest {
|
||||||
display_name: request.display_name,
|
display_name: request.display_name.into(),
|
||||||
attributes,
|
attributes,
|
||||||
};
|
};
|
||||||
let group_id = handler.create_group(request).await?;
|
let group_id = handler.create_group(request).await?;
|
||||||
@@ -512,8 +524,9 @@ fn deserialize_attribute(
|
|||||||
attribute: AttributeValue,
|
attribute: AttributeValue,
|
||||||
is_admin: bool,
|
is_admin: bool,
|
||||||
) -> FieldResult<DomainAttributeValue> {
|
) -> FieldResult<DomainAttributeValue> {
|
||||||
|
let attribute_name = AttributeName::from(attribute.name.as_str());
|
||||||
let attribute_schema = attribute_schema
|
let attribute_schema = attribute_schema
|
||||||
.get_attribute_schema(&attribute.name)
|
.get_attribute_schema(&attribute_name)
|
||||||
.ok_or_else(|| anyhow!("Attribute {} is not defined in the schema", attribute.name))?;
|
.ok_or_else(|| anyhow!("Attribute {} is not defined in the schema", attribute.name))?;
|
||||||
if !is_admin && !attribute_schema.is_editable {
|
if !is_admin && !attribute_schema.is_editable {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
@@ -571,7 +584,7 @@ fn deserialize_attribute(
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
Ok(DomainAttributeValue {
|
Ok(DomainAttributeValue {
|
||||||
name: attribute.name,
|
name: attribute_name,
|
||||||
value: deserialized_values,
|
value: deserialized_values,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ use crate::{
|
|||||||
PublicSchema, SchemaAttributeExtractor, SchemaGroupAttributeExtractor,
|
PublicSchema, SchemaAttributeExtractor, SchemaGroupAttributeExtractor,
|
||||||
SchemaUserAttributeExtractor,
|
SchemaUserAttributeExtractor,
|
||||||
},
|
},
|
||||||
types::{AttributeType, GroupDetails, GroupId, JpegPhoto, UserColumn, UserId},
|
types::{
|
||||||
|
AttributeName, AttributeType, GroupDetails, GroupId, JpegPhoto, UserColumn, UserId,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
infra::{
|
infra::{
|
||||||
access_control::{ReadonlyBackendHandler, UserReadableBackendHandler},
|
access_control::{ReadonlyBackendHandler, UserReadableBackendHandler},
|
||||||
@@ -50,7 +52,7 @@ impl TryInto<DomainRequestFilter> for RequestFilter {
|
|||||||
self.member_of_id,
|
self.member_of_id,
|
||||||
) {
|
) {
|
||||||
(Some(eq), None, None, None, None, None) => {
|
(Some(eq), None, None, None, None, None) => {
|
||||||
match map_user_field(&eq.field.to_ascii_lowercase()) {
|
match map_user_field(&eq.field.as_str().into()) {
|
||||||
UserFieldType::NoMatch => Err(format!("Unknown request filter: {}", &eq.field)),
|
UserFieldType::NoMatch => Err(format!("Unknown request filter: {}", &eq.field)),
|
||||||
UserFieldType::PrimaryField(UserColumn::UserId) => {
|
UserFieldType::PrimaryField(UserColumn::UserId) => {
|
||||||
Ok(DomainRequestFilter::UserId(UserId::new(&eq.value)))
|
Ok(DomainRequestFilter::UserId(UserId::new(&eq.value)))
|
||||||
@@ -59,7 +61,7 @@ impl TryInto<DomainRequestFilter> for RequestFilter {
|
|||||||
Ok(DomainRequestFilter::Equality(column, eq.value))
|
Ok(DomainRequestFilter::Equality(column, eq.value))
|
||||||
}
|
}
|
||||||
UserFieldType::Attribute(column) => Ok(DomainRequestFilter::AttributeEquality(
|
UserFieldType::Attribute(column) => Ok(DomainRequestFilter::AttributeEquality(
|
||||||
column.to_owned(),
|
AttributeName::from(column),
|
||||||
eq.value,
|
eq.value,
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
@@ -77,7 +79,9 @@ impl TryInto<DomainRequestFilter> for RequestFilter {
|
|||||||
(None, None, None, Some(not), None, None) => {
|
(None, None, None, Some(not), None, None) => {
|
||||||
Ok(DomainRequestFilter::Not(Box::new((*not).try_into()?)))
|
Ok(DomainRequestFilter::Not(Box::new((*not).try_into()?)))
|
||||||
}
|
}
|
||||||
(None, None, None, None, Some(group), None) => Ok(DomainRequestFilter::MemberOf(group)),
|
(None, None, None, None, Some(group), None) => {
|
||||||
|
Ok(DomainRequestFilter::MemberOf(group.into()))
|
||||||
|
}
|
||||||
(None, None, None, None, None, Some(group_id)) => {
|
(None, None, None, None, None, Some(group_id)) => {
|
||||||
Ok(DomainRequestFilter::MemberOfId(GroupId(group_id)))
|
Ok(DomainRequestFilter::MemberOfId(GroupId(group_id)))
|
||||||
}
|
}
|
||||||
@@ -228,7 +232,7 @@ impl<Handler: BackendHandler> User<Handler> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn email(&self) -> &str {
|
fn email(&self) -> &str {
|
||||||
&self.user.email
|
self.user.email.as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_name(&self) -> &str {
|
fn display_name(&self) -> &str {
|
||||||
@@ -239,7 +243,7 @@ impl<Handler: BackendHandler> User<Handler> {
|
|||||||
self.user
|
self.user
|
||||||
.attributes
|
.attributes
|
||||||
.iter()
|
.iter()
|
||||||
.find(|a| a.name == "first_name")
|
.find(|a| a.name.as_str() == "first_name")
|
||||||
.map(|a| a.value.unwrap())
|
.map(|a| a.value.unwrap())
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
}
|
}
|
||||||
@@ -248,7 +252,7 @@ impl<Handler: BackendHandler> User<Handler> {
|
|||||||
self.user
|
self.user
|
||||||
.attributes
|
.attributes
|
||||||
.iter()
|
.iter()
|
||||||
.find(|a| a.name == "last_name")
|
.find(|a| a.name.as_str() == "last_name")
|
||||||
.map(|a| a.value.unwrap())
|
.map(|a| a.value.unwrap())
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
}
|
}
|
||||||
@@ -257,7 +261,7 @@ impl<Handler: BackendHandler> User<Handler> {
|
|||||||
self.user
|
self.user
|
||||||
.attributes
|
.attributes
|
||||||
.iter()
|
.iter()
|
||||||
.find(|a| a.name == "avatar")
|
.find(|a| a.name.as_str() == "avatar")
|
||||||
.map(|a| String::from(&a.value.unwrap::<JpegPhoto>()))
|
.map(|a| String::from(&a.value.unwrap::<JpegPhoto>()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,7 +388,7 @@ impl<Handler: BackendHandler> From<GroupDetails> for Group<Handler> {
|
|||||||
fn from(group_details: GroupDetails) -> Self {
|
fn from(group_details: GroupDetails) -> Self {
|
||||||
Self {
|
Self {
|
||||||
group_id: group_details.group_id.0,
|
group_id: group_details.group_id.0,
|
||||||
display_name: group_details.display_name,
|
display_name: group_details.display_name.to_string(),
|
||||||
creation_date: group_details.creation_date,
|
creation_date: group_details.creation_date,
|
||||||
uuid: group_details.uuid.into_string(),
|
uuid: group_details.uuid.into_string(),
|
||||||
attributes: group_details.attributes,
|
attributes: group_details.attributes,
|
||||||
@@ -398,7 +402,7 @@ impl<Handler: BackendHandler> From<DomainGroup> for Group<Handler> {
|
|||||||
fn from(group: DomainGroup) -> Self {
|
fn from(group: DomainGroup) -> Self {
|
||||||
Self {
|
Self {
|
||||||
group_id: group.id.0,
|
group_id: group.id.0,
|
||||||
display_name: group.display_name,
|
display_name: group.display_name.to_string(),
|
||||||
creation_date: group.creation_date,
|
creation_date: group.creation_date,
|
||||||
uuid: group.uuid.into_string(),
|
uuid: group.uuid.into_string(),
|
||||||
attributes: group.attributes,
|
attributes: group.attributes,
|
||||||
@@ -417,7 +421,7 @@ pub struct AttributeSchema<Handler: BackendHandler> {
|
|||||||
#[graphql_object(context = Context<Handler>)]
|
#[graphql_object(context = Context<Handler>)]
|
||||||
impl<Handler: BackendHandler> AttributeSchema<Handler> {
|
impl<Handler: BackendHandler> AttributeSchema<Handler> {
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
self.schema.name.clone()
|
self.schema.name.to_string()
|
||||||
}
|
}
|
||||||
fn attribute_type(&self) -> AttributeType {
|
fn attribute_type(&self) -> AttributeType {
|
||||||
self.schema.attribute_type
|
self.schema.attribute_type
|
||||||
@@ -509,7 +513,7 @@ impl<Handler: BackendHandler, Extractor: SchemaAttributeExtractor>
|
|||||||
AttributeValue<Handler, Extractor>
|
AttributeValue<Handler, Extractor>
|
||||||
{
|
{
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
&self.attribute.name
|
self.attribute.name.as_str()
|
||||||
}
|
}
|
||||||
async fn value(&self, context: &Context<Handler>) -> FieldResult<Vec<String>> {
|
async fn value(&self, context: &Context<Handler>) -> FieldResult<Vec<String>> {
|
||||||
let handler = context
|
let handler = context
|
||||||
@@ -648,7 +652,7 @@ mod tests {
|
|||||||
user_attributes: DomainAttributeList {
|
user_attributes: DomainAttributeList {
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
DomainAttributeSchema {
|
DomainAttributeSchema {
|
||||||
name: "first_name".to_owned(),
|
name: "first_name".into(),
|
||||||
attribute_type: AttributeType::String,
|
attribute_type: AttributeType::String,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -656,7 +660,7 @@ mod tests {
|
|||||||
is_hardcoded: true,
|
is_hardcoded: true,
|
||||||
},
|
},
|
||||||
DomainAttributeSchema {
|
DomainAttributeSchema {
|
||||||
name: "last_name".to_owned(),
|
name: "last_name".into(),
|
||||||
attribute_type: AttributeType::String,
|
attribute_type: AttributeType::String,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -667,7 +671,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
group_attributes: DomainAttributeList {
|
group_attributes: DomainAttributeList {
|
||||||
attributes: vec![DomainAttributeSchema {
|
attributes: vec![DomainAttributeSchema {
|
||||||
name: "club_name".to_owned(),
|
name: "club_name".into(),
|
||||||
attribute_type: AttributeType::String,
|
attribute_type: AttributeType::String,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -682,16 +686,16 @@ mod tests {
|
|||||||
.return_once(|_| {
|
.return_once(|_| {
|
||||||
Ok(DomainUser {
|
Ok(DomainUser {
|
||||||
user_id: UserId::new("bob"),
|
user_id: UserId::new("bob"),
|
||||||
email: "bob@bobbers.on".to_string(),
|
email: "bob@bobbers.on".into(),
|
||||||
creation_date: chrono::Utc.timestamp_millis_opt(42).unwrap().naive_utc(),
|
creation_date: chrono::Utc.timestamp_millis_opt(42).unwrap().naive_utc(),
|
||||||
uuid: crate::uuid!("b1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
uuid: crate::uuid!("b1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
DomainAttributeValue {
|
DomainAttributeValue {
|
||||||
name: "first_name".to_owned(),
|
name: "first_name".into(),
|
||||||
value: Serialized::from("Bob"),
|
value: Serialized::from("Bob"),
|
||||||
},
|
},
|
||||||
DomainAttributeValue {
|
DomainAttributeValue {
|
||||||
name: "last_name".to_owned(),
|
name: "last_name".into(),
|
||||||
value: Serialized::from("Bobberson"),
|
value: Serialized::from("Bobberson"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -701,17 +705,17 @@ mod tests {
|
|||||||
let mut groups = HashSet::new();
|
let mut groups = HashSet::new();
|
||||||
groups.insert(GroupDetails {
|
groups.insert(GroupDetails {
|
||||||
group_id: GroupId(3),
|
group_id: GroupId(3),
|
||||||
display_name: "Bobbersons".to_string(),
|
display_name: "Bobbersons".into(),
|
||||||
creation_date: chrono::Utc.timestamp_nanos(42).naive_utc(),
|
creation_date: chrono::Utc.timestamp_nanos(42).naive_utc(),
|
||||||
uuid: crate::uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
uuid: crate::uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
||||||
attributes: vec![DomainAttributeValue {
|
attributes: vec![DomainAttributeValue {
|
||||||
name: "club_name".to_owned(),
|
name: "club_name".into(),
|
||||||
value: Serialized::from("Gang of Four"),
|
value: Serialized::from("Gang of Four"),
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
groups.insert(GroupDetails {
|
groups.insert(GroupDetails {
|
||||||
group_id: GroupId(7),
|
group_id: GroupId(7),
|
||||||
display_name: "Jefferees".to_string(),
|
display_name: "Jefferees".into(),
|
||||||
creation_date: chrono::Utc.timestamp_nanos(12).naive_utc(),
|
creation_date: chrono::Utc.timestamp_nanos(12).naive_utc(),
|
||||||
uuid: crate::uuid!("b1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
uuid: crate::uuid!("b1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
||||||
attributes: Vec::new(),
|
attributes: Vec::new(),
|
||||||
@@ -800,7 +804,7 @@ mod tests {
|
|||||||
"robert@bobbers.on".to_owned(),
|
"robert@bobbers.on".to_owned(),
|
||||||
),
|
),
|
||||||
DomainRequestFilter::AttributeEquality(
|
DomainRequestFilter::AttributeEquality(
|
||||||
"first_name".to_owned(),
|
AttributeName::from("first_name"),
|
||||||
"robert".to_owned(),
|
"robert".to_owned(),
|
||||||
),
|
),
|
||||||
]))),
|
]))),
|
||||||
@@ -811,7 +815,7 @@ mod tests {
|
|||||||
DomainUserAndGroups {
|
DomainUserAndGroups {
|
||||||
user: DomainUser {
|
user: DomainUser {
|
||||||
user_id: UserId::new("bob"),
|
user_id: UserId::new("bob"),
|
||||||
email: "bob@bobbers.on".to_owned(),
|
email: "bob@bobbers.on".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
groups: None,
|
groups: None,
|
||||||
@@ -819,7 +823,7 @@ mod tests {
|
|||||||
DomainUserAndGroups {
|
DomainUserAndGroups {
|
||||||
user: DomainUser {
|
user: DomainUser {
|
||||||
user_id: UserId::new("robert"),
|
user_id: UserId::new("robert"),
|
||||||
email: "robert@bobbers.on".to_owned(),
|
email: "robert@bobbers.on".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
groups: None,
|
groups: None,
|
||||||
@@ -1022,7 +1026,7 @@ mod tests {
|
|||||||
Ok(crate::domain::handler::Schema {
|
Ok(crate::domain::handler::Schema {
|
||||||
user_attributes: AttributeList {
|
user_attributes: AttributeList {
|
||||||
attributes: vec![crate::domain::handler::AttributeSchema {
|
attributes: vec![crate::domain::handler::AttributeSchema {
|
||||||
name: "invisible".to_owned(),
|
name: "invisible".into(),
|
||||||
attribute_type: AttributeType::JpegPhoto,
|
attribute_type: AttributeType::JpegPhoto,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: false,
|
is_visible: false,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
opaque_handler::OpaqueHandler,
|
opaque_handler::OpaqueHandler,
|
||||||
schema::PublicSchema,
|
schema::PublicSchema,
|
||||||
types::{Group, JpegPhoto, UserAndGroups, UserId},
|
types::{AttributeName, Email, Group, JpegPhoto, UserAndGroups, UserId},
|
||||||
},
|
},
|
||||||
infra::access_control::{
|
infra::access_control::{
|
||||||
AccessControlledBackendHandler, AdminBackendHandler, UserAndGroupListerBackendHandler,
|
AccessControlledBackendHandler, AdminBackendHandler, UserAndGroupListerBackendHandler,
|
||||||
@@ -233,8 +233,8 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
backend_handler: AccessControlledBackendHandler<Backend>,
|
backend_handler: AccessControlledBackendHandler<Backend>,
|
||||||
mut ldap_base_dn: String,
|
mut ldap_base_dn: String,
|
||||||
ignored_user_attributes: Vec<String>,
|
ignored_user_attributes: Vec<AttributeName>,
|
||||||
ignored_group_attributes: Vec<String>,
|
ignored_group_attributes: Vec<AttributeName>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
ldap_base_dn.make_ascii_lowercase();
|
ldap_base_dn.make_ascii_lowercase();
|
||||||
Self {
|
Self {
|
||||||
@@ -354,7 +354,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
),
|
),
|
||||||
})?
|
})?
|
||||||
.iter()
|
.iter()
|
||||||
.any(|g| g.display_name == "lldap_admin");
|
.any(|g| g.display_name == "lldap_admin".into());
|
||||||
if !credentials.can_change_password(&uid, user_is_admin) {
|
if !credentials.can_change_password(&uid, user_is_admin) {
|
||||||
Err(LdapError {
|
Err(LdapError {
|
||||||
code: LdapResultCode::InsufficentAccessRights,
|
code: LdapResultCode::InsufficentAccessRights,
|
||||||
@@ -479,7 +479,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
message: format!("Internal error while requesting user's groups: {:#?}", e),
|
message: format!("Internal error while requesting user's groups: {:#?}", e),
|
||||||
})?
|
})?
|
||||||
.iter()
|
.iter()
|
||||||
.any(|g| g.display_name == "lldap_admin");
|
.any(|g| g.display_name == "lldap_admin".into());
|
||||||
for change in &request.changes {
|
for change in &request.changes {
|
||||||
self.handle_modify_change(&uid, &credentials, user_is_admin, change)
|
self.handle_modify_change(&uid, &credentials, user_is_admin, change)
|
||||||
.await?
|
.await?
|
||||||
@@ -695,10 +695,12 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
backend_handler
|
backend_handler
|
||||||
.create_user(CreateUserRequest {
|
.create_user(CreateUserRequest {
|
||||||
user_id,
|
user_id,
|
||||||
email: get_attribute("mail")
|
email: Email::from(
|
||||||
.or_else(|| get_attribute("email"))
|
get_attribute("mail")
|
||||||
.transpose()?
|
.or_else(|| get_attribute("email"))
|
||||||
.unwrap_or_default(),
|
.transpose()?
|
||||||
|
.unwrap_or_default(),
|
||||||
|
),
|
||||||
display_name: get_attribute("cn").transpose()?,
|
display_name: get_attribute("cn").transpose()?,
|
||||||
first_name: get_attribute("givenname").transpose()?,
|
first_name: get_attribute("givenname").transpose()?,
|
||||||
last_name: get_attribute("sn").transpose()?,
|
last_name: get_attribute("sn").transpose()?,
|
||||||
@@ -857,7 +859,7 @@ mod tests {
|
|||||||
let mut set = HashSet::new();
|
let mut set = HashSet::new();
|
||||||
set.insert(GroupDetails {
|
set.insert(GroupDetails {
|
||||||
group_id: GroupId(42),
|
group_id: GroupId(42),
|
||||||
display_name: group,
|
display_name: group.into(),
|
||||||
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
||||||
uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
||||||
attributes: Vec::new(),
|
attributes: Vec::new(),
|
||||||
@@ -944,7 +946,7 @@ mod tests {
|
|||||||
let mut set = HashSet::new();
|
let mut set = HashSet::new();
|
||||||
set.insert(GroupDetails {
|
set.insert(GroupDetails {
|
||||||
group_id: GroupId(42),
|
group_id: GroupId(42),
|
||||||
display_name: "lldap_admin".to_string(),
|
display_name: "lldap_admin".into(),
|
||||||
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
||||||
uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
||||||
attributes: Vec::new(),
|
attributes: Vec::new(),
|
||||||
@@ -1031,7 +1033,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
groups: Some(vec![GroupDetails {
|
groups: Some(vec![GroupDetails {
|
||||||
group_id: GroupId(42),
|
group_id: GroupId(42),
|
||||||
display_name: "rockstars".to_string(),
|
display_name: "rockstars".into(),
|
||||||
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
||||||
uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
||||||
attributes: Vec::new(),
|
attributes: Vec::new(),
|
||||||
@@ -1179,16 +1181,16 @@ mod tests {
|
|||||||
UserAndGroups {
|
UserAndGroups {
|
||||||
user: User {
|
user: User {
|
||||||
user_id: UserId::new("bob_1"),
|
user_id: UserId::new("bob_1"),
|
||||||
email: "bob@bobmail.bob".to_string(),
|
email: "bob@bobmail.bob".into(),
|
||||||
display_name: Some("Bôb Böbberson".to_string()),
|
display_name: Some("Bôb Böbberson".to_string()),
|
||||||
uuid: uuid!("698e1d5f-7a40-3151-8745-b9b8a37839da"),
|
uuid: uuid!("698e1d5f-7a40-3151-8745-b9b8a37839da"),
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "first_name".to_owned(),
|
name: "first_name".into(),
|
||||||
value: Serialized::from("Bôb"),
|
value: Serialized::from("Bôb"),
|
||||||
},
|
},
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "last_name".to_owned(),
|
name: "last_name".into(),
|
||||||
value: Serialized::from("Böbberson"),
|
value: Serialized::from("Böbberson"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1199,19 +1201,19 @@ mod tests {
|
|||||||
UserAndGroups {
|
UserAndGroups {
|
||||||
user: User {
|
user: User {
|
||||||
user_id: UserId::new("jim"),
|
user_id: UserId::new("jim"),
|
||||||
email: "jim@cricket.jim".to_string(),
|
email: "jim@cricket.jim".into(),
|
||||||
display_name: Some("Jimminy Cricket".to_string()),
|
display_name: Some("Jimminy Cricket".to_string()),
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "avatar".to_owned(),
|
name: "avatar".into(),
|
||||||
value: Serialized::from(&JpegPhoto::for_tests()),
|
value: Serialized::from(&JpegPhoto::for_tests()),
|
||||||
},
|
},
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "first_name".to_owned(),
|
name: "first_name".into(),
|
||||||
value: Serialized::from("Jim"),
|
value: Serialized::from("Jim"),
|
||||||
},
|
},
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "last_name".to_owned(),
|
name: "last_name".into(),
|
||||||
value: Serialized::from("Cricket"),
|
value: Serialized::from("Cricket"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1347,7 +1349,7 @@ mod tests {
|
|||||||
Ok(vec![
|
Ok(vec![
|
||||||
Group {
|
Group {
|
||||||
id: GroupId(1),
|
id: GroupId(1),
|
||||||
display_name: "group_1".to_string(),
|
display_name: "group_1".into(),
|
||||||
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
||||||
users: vec![UserId::new("bob"), UserId::new("john")],
|
users: vec![UserId::new("bob"), UserId::new("john")],
|
||||||
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
||||||
@@ -1355,7 +1357,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
Group {
|
Group {
|
||||||
id: GroupId(3),
|
id: GroupId(3),
|
||||||
display_name: "BestGroup".to_string(),
|
display_name: "BestGroup".into(),
|
||||||
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
||||||
users: vec![UserId::new("john")],
|
users: vec![UserId::new("john")],
|
||||||
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
||||||
@@ -1426,9 +1428,9 @@ mod tests {
|
|||||||
let mut mock = MockTestBackendHandler::new();
|
let mut mock = MockTestBackendHandler::new();
|
||||||
mock.expect_list_groups()
|
mock.expect_list_groups()
|
||||||
.with(eq(Some(GroupRequestFilter::And(vec![
|
.with(eq(Some(GroupRequestFilter::And(vec![
|
||||||
GroupRequestFilter::DisplayName("group_1".to_string()),
|
GroupRequestFilter::DisplayName("group_1".into()),
|
||||||
GroupRequestFilter::Member(UserId::new("bob")),
|
GroupRequestFilter::Member(UserId::new("bob")),
|
||||||
GroupRequestFilter::DisplayName("rockstars".to_string()),
|
GroupRequestFilter::DisplayName("rockstars".into()),
|
||||||
false.into(),
|
false.into(),
|
||||||
GroupRequestFilter::Uuid(uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc")),
|
GroupRequestFilter::Uuid(uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc")),
|
||||||
true.into(),
|
true.into(),
|
||||||
@@ -1446,7 +1448,7 @@ mod tests {
|
|||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_| {
|
.return_once(|_| {
|
||||||
Ok(vec![Group {
|
Ok(vec![Group {
|
||||||
display_name: "group_1".to_string(),
|
display_name: "group_1".into(),
|
||||||
id: GroupId(1),
|
id: GroupId(1),
|
||||||
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
||||||
users: vec![],
|
users: vec![],
|
||||||
@@ -1511,13 +1513,13 @@ mod tests {
|
|||||||
mock.expect_list_groups()
|
mock.expect_list_groups()
|
||||||
.with(eq(Some(GroupRequestFilter::Or(vec![
|
.with(eq(Some(GroupRequestFilter::Or(vec![
|
||||||
GroupRequestFilter::Not(Box::new(GroupRequestFilter::DisplayName(
|
GroupRequestFilter::Not(Box::new(GroupRequestFilter::DisplayName(
|
||||||
"group_2".to_string(),
|
"group_2".into(),
|
||||||
))),
|
))),
|
||||||
]))))
|
]))))
|
||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_| {
|
.return_once(|_| {
|
||||||
Ok(vec![Group {
|
Ok(vec![Group {
|
||||||
display_name: "group_1".to_string(),
|
display_name: "group_1".into(),
|
||||||
id: GroupId(1),
|
id: GroupId(1),
|
||||||
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
||||||
users: vec![],
|
users: vec![],
|
||||||
@@ -1554,7 +1556,7 @@ mod tests {
|
|||||||
mock.expect_list_groups()
|
mock.expect_list_groups()
|
||||||
.with(eq(Some(GroupRequestFilter::And(vec![
|
.with(eq(Some(GroupRequestFilter::And(vec![
|
||||||
true.into(),
|
true.into(),
|
||||||
GroupRequestFilter::DisplayName("rockstars".to_string()),
|
GroupRequestFilter::DisplayName("rockstars".into()),
|
||||||
]))))
|
]))))
|
||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_| Ok(vec![]));
|
.return_once(|_| Ok(vec![]));
|
||||||
@@ -1598,7 +1600,7 @@ mod tests {
|
|||||||
mock.expect_list_groups()
|
mock.expect_list_groups()
|
||||||
.with(eq(Some(GroupRequestFilter::Or(vec![
|
.with(eq(Some(GroupRequestFilter::Or(vec![
|
||||||
GroupRequestFilter::Not(Box::new(GroupRequestFilter::DisplayName(
|
GroupRequestFilter::Not(Box::new(GroupRequestFilter::DisplayName(
|
||||||
"group_2".to_string(),
|
"group_2".into(),
|
||||||
))),
|
))),
|
||||||
]))))
|
]))))
|
||||||
.times(1)
|
.times(1)
|
||||||
@@ -1661,7 +1663,7 @@ mod tests {
|
|||||||
true.into(),
|
true.into(),
|
||||||
false.into(),
|
false.into(),
|
||||||
UserRequestFilter::AttributeEquality(
|
UserRequestFilter::AttributeEquality(
|
||||||
"first_name".to_owned(),
|
AttributeName::from("first_name"),
|
||||||
"firstname".to_owned(),
|
"firstname".to_owned(),
|
||||||
),
|
),
|
||||||
false.into(),
|
false.into(),
|
||||||
@@ -1765,7 +1767,7 @@ 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::MemberOf("group_1".to_string()))),
|
eq(Some(UserRequestFilter::MemberOf("group_1".into()))),
|
||||||
eq(false),
|
eq(false),
|
||||||
)
|
)
|
||||||
.times(1)
|
.times(1)
|
||||||
@@ -1865,15 +1867,15 @@ mod tests {
|
|||||||
Ok(vec![UserAndGroups {
|
Ok(vec![UserAndGroups {
|
||||||
user: User {
|
user: User {
|
||||||
user_id: UserId::new("bob_1"),
|
user_id: UserId::new("bob_1"),
|
||||||
email: "bob@bobmail.bob".to_string(),
|
email: "bob@bobmail.bob".into(),
|
||||||
display_name: Some("Bôb Böbberson".to_string()),
|
display_name: Some("Bôb Böbberson".to_string()),
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "first_name".to_owned(),
|
name: "first_name".into(),
|
||||||
value: Serialized::from("Bôb"),
|
value: Serialized::from("Bôb"),
|
||||||
},
|
},
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "last_name".to_owned(),
|
name: "last_name".into(),
|
||||||
value: Serialized::from("Böbberson"),
|
value: Serialized::from("Böbberson"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1888,7 +1890,7 @@ mod tests {
|
|||||||
.return_once(|_| {
|
.return_once(|_| {
|
||||||
Ok(vec![Group {
|
Ok(vec![Group {
|
||||||
id: GroupId(1),
|
id: GroupId(1),
|
||||||
display_name: "group_1".to_string(),
|
display_name: "group_1".into(),
|
||||||
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
||||||
users: vec![UserId::new("bob"), UserId::new("john")],
|
users: vec![UserId::new("bob"), UserId::new("john")],
|
||||||
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
||||||
@@ -1948,15 +1950,15 @@ mod tests {
|
|||||||
Ok(vec![UserAndGroups {
|
Ok(vec![UserAndGroups {
|
||||||
user: User {
|
user: User {
|
||||||
user_id: UserId::new("bob_1"),
|
user_id: UserId::new("bob_1"),
|
||||||
email: "bob@bobmail.bob".to_string(),
|
email: "bob@bobmail.bob".into(),
|
||||||
display_name: Some("Bôb Böbberson".to_string()),
|
display_name: Some("Bôb Böbberson".to_string()),
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "avatar".to_owned(),
|
name: "avatar".into(),
|
||||||
value: Serialized::from(&JpegPhoto::for_tests()),
|
value: Serialized::from(&JpegPhoto::for_tests()),
|
||||||
},
|
},
|
||||||
AttributeValue {
|
AttributeValue {
|
||||||
name: "last_name".to_owned(),
|
name: "last_name".into(),
|
||||||
value: Serialized::from("Böbberson"),
|
value: Serialized::from("Böbberson"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1971,7 +1973,7 @@ mod tests {
|
|||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
Ok(vec![Group {
|
Ok(vec![Group {
|
||||||
id: GroupId(1),
|
id: GroupId(1),
|
||||||
display_name: "group_1".to_string(),
|
display_name: "group_1".into(),
|
||||||
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
||||||
users: vec![UserId::new("bob"), UserId::new("john")],
|
users: vec![UserId::new("bob"), UserId::new("john")],
|
||||||
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
||||||
@@ -2354,7 +2356,7 @@ mod tests {
|
|||||||
let mut groups = HashSet::new();
|
let mut groups = HashSet::new();
|
||||||
groups.insert(GroupDetails {
|
groups.insert(GroupDetails {
|
||||||
group_id: GroupId(0),
|
group_id: GroupId(0),
|
||||||
display_name: "lldap_admin".to_string(),
|
display_name: "lldap_admin".into(),
|
||||||
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
||||||
uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
||||||
attributes: Vec::new(),
|
attributes: Vec::new(),
|
||||||
@@ -2434,7 +2436,7 @@ mod tests {
|
|||||||
mock.expect_create_user()
|
mock.expect_create_user()
|
||||||
.with(eq(CreateUserRequest {
|
.with(eq(CreateUserRequest {
|
||||||
user_id: UserId::new("bob"),
|
user_id: UserId::new("bob"),
|
||||||
email: "".to_owned(),
|
email: "".into(),
|
||||||
display_name: Some("Bob".to_string()),
|
display_name: Some("Bob".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}))
|
}))
|
||||||
@@ -2476,7 +2478,7 @@ mod tests {
|
|||||||
mock.expect_create_user()
|
mock.expect_create_user()
|
||||||
.with(eq(CreateUserRequest {
|
.with(eq(CreateUserRequest {
|
||||||
user_id: UserId::new("bob"),
|
user_id: UserId::new("bob"),
|
||||||
email: "".to_owned(),
|
email: "".into(),
|
||||||
display_name: Some("Bob".to_string()),
|
display_name: Some("Bob".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}))
|
}))
|
||||||
@@ -2533,7 +2535,7 @@ mod tests {
|
|||||||
Ok(vec![UserAndGroups {
|
Ok(vec![UserAndGroups {
|
||||||
user: User {
|
user: User {
|
||||||
user_id: UserId::new("bob"),
|
user_id: UserId::new("bob"),
|
||||||
email: "bob@bobmail.bob".to_string(),
|
email: "bob@bobmail.bob".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
groups: None,
|
groups: None,
|
||||||
@@ -2578,10 +2580,10 @@ mod tests {
|
|||||||
let mut mock = MockTestBackendHandler::new();
|
let mut mock = MockTestBackendHandler::new();
|
||||||
mock.expect_list_users().returning(|_, _| Ok(vec![]));
|
mock.expect_list_users().returning(|_, _| Ok(vec![]));
|
||||||
mock.expect_list_groups().returning(|f| {
|
mock.expect_list_groups().returning(|f| {
|
||||||
assert_eq!(f, Some(GroupRequestFilter::DisplayName("group".to_owned())));
|
assert_eq!(f, Some(GroupRequestFilter::DisplayName("group".into())));
|
||||||
Ok(vec![Group {
|
Ok(vec![Group {
|
||||||
id: GroupId(1),
|
id: GroupId(1),
|
||||||
display_name: "group".to_string(),
|
display_name: "group".into(),
|
||||||
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
||||||
users: vec![UserId::new("bob")],
|
users: vec![UserId::new("bob")],
|
||||||
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
||||||
@@ -2642,7 +2644,7 @@ mod tests {
|
|||||||
Ok(vec![UserAndGroups {
|
Ok(vec![UserAndGroups {
|
||||||
user: User {
|
user: User {
|
||||||
user_id: UserId::new("bob"),
|
user_id: UserId::new("bob"),
|
||||||
email: "bob@bobmail.bob".to_string(),
|
email: "bob@bobmail.bob".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
groups: None,
|
groups: None,
|
||||||
@@ -2672,10 +2674,10 @@ mod tests {
|
|||||||
let mut mock = MockTestBackendHandler::new();
|
let mut mock = MockTestBackendHandler::new();
|
||||||
mock.expect_list_users().returning(|_, _| Ok(vec![]));
|
mock.expect_list_users().returning(|_, _| Ok(vec![]));
|
||||||
mock.expect_list_groups().returning(|f| {
|
mock.expect_list_groups().returning(|f| {
|
||||||
assert_eq!(f, Some(GroupRequestFilter::DisplayName("group".to_owned())));
|
assert_eq!(f, Some(GroupRequestFilter::DisplayName("group".into())));
|
||||||
Ok(vec![Group {
|
Ok(vec![Group {
|
||||||
id: GroupId(1),
|
id: GroupId(1),
|
||||||
display_name: "group".to_string(),
|
display_name: "group".into(),
|
||||||
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
||||||
users: vec![UserId::new("bob")],
|
users: vec![UserId::new("bob")],
|
||||||
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
||||||
@@ -2736,7 +2738,7 @@ mod tests {
|
|||||||
user: User {
|
user: User {
|
||||||
user_id: UserId::new("test"),
|
user_id: UserId::new("test"),
|
||||||
attributes: vec![AttributeValue {
|
attributes: vec![AttributeValue {
|
||||||
name: "nickname".to_owned(),
|
name: "nickname".into(),
|
||||||
value: Serialized::from("Bob the Builder"),
|
value: Serialized::from("Bob the Builder"),
|
||||||
}],
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -2747,12 +2749,12 @@ mod tests {
|
|||||||
mock.expect_list_groups().times(1).return_once(|_| {
|
mock.expect_list_groups().times(1).return_once(|_| {
|
||||||
Ok(vec![Group {
|
Ok(vec![Group {
|
||||||
id: GroupId(1),
|
id: GroupId(1),
|
||||||
display_name: "group".to_string(),
|
display_name: "group".into(),
|
||||||
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
||||||
users: vec![UserId::new("bob")],
|
users: vec![UserId::new("bob")],
|
||||||
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
||||||
attributes: vec![AttributeValue {
|
attributes: vec![AttributeValue {
|
||||||
name: "club_name".to_owned(),
|
name: "club_name".into(),
|
||||||
value: Serialized::from("Breakfast Club"),
|
value: Serialized::from("Breakfast Club"),
|
||||||
}],
|
}],
|
||||||
}])
|
}])
|
||||||
@@ -2761,7 +2763,7 @@ mod tests {
|
|||||||
Ok(crate::domain::handler::Schema {
|
Ok(crate::domain::handler::Schema {
|
||||||
user_attributes: AttributeList {
|
user_attributes: AttributeList {
|
||||||
attributes: vec![AttributeSchema {
|
attributes: vec![AttributeSchema {
|
||||||
name: "nickname".to_owned(),
|
name: "nickname".into(),
|
||||||
attribute_type: AttributeType::String,
|
attribute_type: AttributeType::String,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -2771,7 +2773,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
group_attributes: AttributeList {
|
group_attributes: AttributeList {
|
||||||
attributes: vec![AttributeSchema {
|
attributes: vec![AttributeSchema {
|
||||||
name: "club_name".to_owned(),
|
name: "club_name".into(),
|
||||||
attribute_type: AttributeType::String,
|
attribute_type: AttributeType::String,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::{
|
|||||||
domain::{
|
domain::{
|
||||||
handler::{BackendHandler, LoginHandler},
|
handler::{BackendHandler, LoginHandler},
|
||||||
opaque_handler::OpaqueHandler,
|
opaque_handler::OpaqueHandler,
|
||||||
|
types::AttributeName,
|
||||||
},
|
},
|
||||||
infra::{
|
infra::{
|
||||||
access_control::AccessControlledBackendHandler,
|
access_control::AccessControlledBackendHandler,
|
||||||
@@ -72,8 +73,8 @@ async fn handle_ldap_stream<Stream, Backend>(
|
|||||||
stream: Stream,
|
stream: Stream,
|
||||||
backend_handler: Backend,
|
backend_handler: Backend,
|
||||||
ldap_base_dn: String,
|
ldap_base_dn: String,
|
||||||
ignored_user_attributes: Vec<String>,
|
ignored_user_attributes: Vec<AttributeName>,
|
||||||
ignored_group_attributes: Vec<String>,
|
ignored_group_attributes: Vec<AttributeName>,
|
||||||
) -> Result<Stream>
|
) -> Result<Stream>
|
||||||
where
|
where
|
||||||
Backend: BackendHandler + LoginHandler + OpaqueHandler + 'static,
|
Backend: BackendHandler + LoginHandler + OpaqueHandler + 'static,
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ mockall::mock! {
|
|||||||
impl SchemaBackendHandler for TestBackendHandler {
|
impl SchemaBackendHandler for TestBackendHandler {
|
||||||
async fn add_user_attribute(&self, request: CreateAttributeRequest) -> Result<()>;
|
async fn add_user_attribute(&self, request: CreateAttributeRequest) -> Result<()>;
|
||||||
async fn add_group_attribute(&self, request: CreateAttributeRequest) -> Result<()>;
|
async fn add_group_attribute(&self, request: CreateAttributeRequest) -> Result<()>;
|
||||||
async fn delete_user_attribute(&self, name: &str) -> Result<()>;
|
async fn delete_user_attribute(&self, name: &AttributeName) -> Result<()>;
|
||||||
async fn delete_group_attribute(&self, name: &str) -> Result<()>;
|
async fn delete_group_attribute(&self, name: &AttributeName) -> Result<()>;
|
||||||
}
|
}
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl BackendHandler for TestBackendHandler {}
|
impl BackendHandler for TestBackendHandler {}
|
||||||
@@ -74,7 +74,7 @@ pub fn setup_default_schema(mock: &mut MockTestBackendHandler) {
|
|||||||
user_attributes: AttributeList {
|
user_attributes: AttributeList {
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
AttributeSchema {
|
AttributeSchema {
|
||||||
name: "avatar".to_owned(),
|
name: "avatar".into(),
|
||||||
attribute_type: AttributeType::JpegPhoto,
|
attribute_type: AttributeType::JpegPhoto,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -82,7 +82,7 @@ pub fn setup_default_schema(mock: &mut MockTestBackendHandler) {
|
|||||||
is_hardcoded: true,
|
is_hardcoded: true,
|
||||||
},
|
},
|
||||||
AttributeSchema {
|
AttributeSchema {
|
||||||
name: "first_name".to_owned(),
|
name: "first_name".into(),
|
||||||
attribute_type: AttributeType::String,
|
attribute_type: AttributeType::String,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
@@ -90,7 +90,7 @@ pub fn setup_default_schema(mock: &mut MockTestBackendHandler) {
|
|||||||
is_hardcoded: true,
|
is_hardcoded: true,
|
||||||
},
|
},
|
||||||
AttributeSchema {
|
AttributeSchema {
|
||||||
name: "last_name".to_owned(),
|
name: "last_name".into(),
|
||||||
attribute_type: AttributeType::String,
|
attribute_type: AttributeType::String,
|
||||||
is_list: false,
|
is_list: false,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
|
|||||||
+5
-7
@@ -36,7 +36,7 @@ async fn create_admin_user(handler: &SqlBackendHandler, config: &Configuration)
|
|||||||
handler
|
handler
|
||||||
.create_user(CreateUserRequest {
|
.create_user(CreateUserRequest {
|
||||||
user_id: config.ldap_user_dn.clone(),
|
user_id: config.ldap_user_dn.clone(),
|
||||||
email: config.ldap_user_email.clone(),
|
email: config.ldap_user_email.clone().into(),
|
||||||
display_name: Some("Administrator".to_string()),
|
display_name: Some("Administrator".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
@@ -44,9 +44,7 @@ async fn create_admin_user(handler: &SqlBackendHandler, config: &Configuration)
|
|||||||
.await
|
.await
|
||||||
.context("Error creating admin user")?;
|
.context("Error creating admin user")?;
|
||||||
let groups = handler
|
let groups = handler
|
||||||
.list_groups(Some(GroupRequestFilter::DisplayName(
|
.list_groups(Some(GroupRequestFilter::DisplayName("lldap_admin".into())))
|
||||||
"lldap_admin".to_owned(),
|
|
||||||
)))
|
|
||||||
.await?;
|
.await?;
|
||||||
assert_eq!(groups.len(), 1);
|
assert_eq!(groups.len(), 1);
|
||||||
handler
|
handler
|
||||||
@@ -57,14 +55,14 @@ async fn create_admin_user(handler: &SqlBackendHandler, config: &Configuration)
|
|||||||
|
|
||||||
async fn ensure_group_exists(handler: &SqlBackendHandler, group_name: &str) -> Result<()> {
|
async fn ensure_group_exists(handler: &SqlBackendHandler, group_name: &str) -> Result<()> {
|
||||||
if handler
|
if handler
|
||||||
.list_groups(Some(GroupRequestFilter::DisplayName(group_name.to_owned())))
|
.list_groups(Some(GroupRequestFilter::DisplayName(group_name.into())))
|
||||||
.await?
|
.await?
|
||||||
.is_empty()
|
.is_empty()
|
||||||
{
|
{
|
||||||
warn!("Could not find {} group, trying to create it", group_name);
|
warn!("Could not find {} group, trying to create it", group_name);
|
||||||
handler
|
handler
|
||||||
.create_group(CreateGroupRequest {
|
.create_group(CreateGroupRequest {
|
||||||
display_name: group_name.to_owned(),
|
display_name: group_name.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@@ -94,7 +92,7 @@ async fn set_up_server(config: Configuration) -> Result<ServerBuilder> {
|
|||||||
ensure_group_exists(&backend_handler, "lldap_strict_readonly").await?;
|
ensure_group_exists(&backend_handler, "lldap_strict_readonly").await?;
|
||||||
let admin_present = if let Ok(admins) = backend_handler
|
let admin_present = if let Ok(admins) = backend_handler
|
||||||
.list_users(
|
.list_users(
|
||||||
Some(UserRequestFilter::MemberOf("lldap_admin".to_owned())),
|
Some(UserRequestFilter::MemberOf("lldap_admin".into())),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
Reference in New Issue
Block a user