mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-12-11 05:46:25 +08:00
Principal storage format improvements
This commit is contained in:
parent
107561297d
commit
4620a92fd8
22 changed files with 287 additions and 117 deletions
|
|
@ -73,7 +73,14 @@ impl Server {
|
|||
object_quota[typ as usize] = quota;
|
||||
}
|
||||
PrincipalData::Description(v) => description = Some(v),
|
||||
PrincipalData::Email(v) => {
|
||||
PrincipalData::PrimaryEmail(v) => {
|
||||
if emails.is_empty() {
|
||||
emails.push(v);
|
||||
} else {
|
||||
emails.insert(0, v);
|
||||
}
|
||||
}
|
||||
PrincipalData::EmailAlias(v) => {
|
||||
emails.push(v);
|
||||
}
|
||||
PrincipalData::Locale(v) => locale = Some(v),
|
||||
|
|
@ -129,13 +136,7 @@ impl Server {
|
|||
.caused_by(trc::location!())?
|
||||
&& group.typ == Type::Group
|
||||
{
|
||||
emails.extend(group.data.into_iter().filter_map(|data| {
|
||||
if let PrincipalData::Email(email) = data {
|
||||
Some(email)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}));
|
||||
emails.extend(group.into_email_addresses());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -131,7 +131,14 @@ impl PrincipalPropFind for Server {
|
|||
PrincipalData::Description(desc) => {
|
||||
description = Some(desc);
|
||||
}
|
||||
PrincipalData::Email(email) => {
|
||||
PrincipalData::PrimaryEmail(email) => {
|
||||
if emails.is_empty() {
|
||||
emails.push(email);
|
||||
} else {
|
||||
emails.insert(0, email);
|
||||
}
|
||||
}
|
||||
PrincipalData::EmailAlias(email) => {
|
||||
emails.push(email);
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
|||
|
|
@ -159,7 +159,9 @@ impl DirectoryStore for Store {
|
|||
for account_id in self.get_members(list_id).await? {
|
||||
if let Some(email) = self.get_principal(account_id).await?.and_then(|p| {
|
||||
p.data.into_iter().find_map(|data| {
|
||||
if let PrincipalData::Email(email) = data {
|
||||
if let PrincipalData::PrimaryEmail(email) | PrincipalData::EmailAlias(email) =
|
||||
data
|
||||
{
|
||||
Some(email)
|
||||
} else {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -206,6 +206,7 @@ impl ManageDirectory for Store {
|
|||
.assert_value(name_key.clone(), ())
|
||||
.create_document(principal_id);
|
||||
build_search_index(&mut batch, principal_id, None, Some(&principal));
|
||||
principal.sort();
|
||||
batch
|
||||
.set(
|
||||
name_key,
|
||||
|
|
@ -377,18 +378,19 @@ impl ManageDirectory for Store {
|
|||
|
||||
// Set fields
|
||||
principal_create.name = name;
|
||||
if let Some(description) = principal_set.take_str(PrincipalField::Description) {
|
||||
principal_create
|
||||
.data
|
||||
.push(PrincipalData::Description(description));
|
||||
}
|
||||
|
||||
for secret in principal_set
|
||||
.take_str_array(PrincipalField::Secrets)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
principal_create.data.push(PrincipalData::Secret(secret));
|
||||
}
|
||||
|
||||
if let Some(description) = principal_set.take_str(PrincipalField::Description) {
|
||||
principal_create
|
||||
.data
|
||||
.push(PrincipalData::Description(description));
|
||||
}
|
||||
|
||||
if let Some(picture) = principal_set.take_str(PrincipalField::Picture) {
|
||||
principal_create.data.push(PrincipalData::Picture(picture));
|
||||
}
|
||||
|
|
@ -518,9 +520,11 @@ impl ManageDirectory for Store {
|
|||
|
||||
// Make sure the e-mail is not taken and validate domain
|
||||
if principal_create.typ != Type::OauthClient {
|
||||
for email in principal_set
|
||||
for (idx, email) in principal_set
|
||||
.take_str_array(PrincipalField::Emails)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let email = email.to_lowercase();
|
||||
if self.rcpt(&email).await.caused_by(trc::location!())? != RcptType::Invalid {
|
||||
|
|
@ -535,7 +539,13 @@ impl ManageDirectory for Store {
|
|||
.filter(|v| v.typ == Type::Domain && v.has_tenant_access(tenant_id))
|
||||
.ok_or_else(|| not_found(domain.to_string()))?;
|
||||
}
|
||||
principal_create.data.push(PrincipalData::Email(email));
|
||||
if idx == 0 {
|
||||
principal_create
|
||||
.data
|
||||
.push(PrincipalData::PrimaryEmail(email));
|
||||
} else {
|
||||
principal_create.data.push(PrincipalData::EmailAlias(email));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -564,6 +574,7 @@ impl ManageDirectory for Store {
|
|||
}
|
||||
|
||||
// Serialize
|
||||
principal_create.sort();
|
||||
let archiver = Archiver::new(principal_create);
|
||||
let principal_bytes = archiver.serialize().caused_by(trc::location!())?;
|
||||
let principal_create = archiver.into_inner();
|
||||
|
|
@ -592,7 +603,7 @@ impl ManageDirectory for Store {
|
|||
);
|
||||
|
||||
// Write email to id mapping
|
||||
for email in principal_create.emails() {
|
||||
for email in principal_create.email_addresses() {
|
||||
batch.set(
|
||||
ValueClass::Directory(DirectoryClass::EmailToId(email.as_bytes().to_vec())),
|
||||
pinfo_email.serialize(),
|
||||
|
|
@ -830,7 +841,9 @@ impl ManageDirectory for Store {
|
|||
.clear(DirectoryClass::UsedQuota(principal_id));
|
||||
|
||||
for email in principal.data.iter() {
|
||||
if let ArchivedPrincipalData::Email(email) = email {
|
||||
if let ArchivedPrincipalData::PrimaryEmail(email)
|
||||
| ArchivedPrincipalData::EmailAlias(email) = email
|
||||
{
|
||||
batch.clear(DirectoryClass::EmailToId(email.as_bytes().to_vec()));
|
||||
}
|
||||
}
|
||||
|
|
@ -1160,25 +1173,9 @@ impl ManageDirectory for Store {
|
|||
PrincipalValue::String(secret),
|
||||
) => {
|
||||
if !principal.secrets().any(|v| *v == secret) {
|
||||
if secret.is_otp_auth() {
|
||||
// Add OTP Auth URLs to the beginning of the list
|
||||
principal.data.insert(0, PrincipalData::Secret(secret));
|
||||
|
||||
// Password changed, update changed principals
|
||||
changed_principals.add_change(
|
||||
principal_id,
|
||||
principal_type,
|
||||
change.field,
|
||||
);
|
||||
} else {
|
||||
principal.data.push(PrincipalData::Secret(secret));
|
||||
// Password changed, update changed principals
|
||||
changed_principals.add_change(
|
||||
principal_id,
|
||||
principal_type,
|
||||
change.field,
|
||||
);
|
||||
}
|
||||
principal.data.push(PrincipalData::Secret(secret));
|
||||
// Password changed, update changed principals
|
||||
changed_principals.add_change(principal_id, principal_type, change.field);
|
||||
}
|
||||
}
|
||||
(
|
||||
|
|
@ -1307,7 +1304,7 @@ impl ManageDirectory for Store {
|
|||
.map(|v| v.to_lowercase())
|
||||
.collect::<Vec<_>>();
|
||||
for email in &emails {
|
||||
if !principal.emails().any(|v| v == email) {
|
||||
if !principal.email_addresses().any(|v| v == email) {
|
||||
if validate_emails {
|
||||
self.validate_email(email, tenant_id, params.create_domains)
|
||||
.await?;
|
||||
|
|
@ -1321,7 +1318,7 @@ impl ManageDirectory for Store {
|
|||
}
|
||||
}
|
||||
|
||||
for email in principal.emails() {
|
||||
for email in principal.email_addresses() {
|
||||
if !emails.contains(email) {
|
||||
batch.clear(ValueClass::Directory(DirectoryClass::EmailToId(
|
||||
email.as_bytes().to_vec(),
|
||||
|
|
@ -1332,11 +1329,18 @@ impl ManageDirectory for Store {
|
|||
// Emails changed, update changed principals
|
||||
changed_principals.add_change(principal_id, principal_type, change.field);
|
||||
|
||||
principal
|
||||
.data
|
||||
.retain(|v| !matches!(v, PrincipalData::Email(_)));
|
||||
for email in emails {
|
||||
principal.data.push(PrincipalData::Email(email));
|
||||
principal.data.retain(|v| {
|
||||
!matches!(
|
||||
v,
|
||||
PrincipalData::PrimaryEmail(_) | PrincipalData::EmailAlias(_)
|
||||
)
|
||||
});
|
||||
for (idx, email) in emails.into_iter().enumerate() {
|
||||
if idx == 0 {
|
||||
principal.data.push(PrincipalData::PrimaryEmail(email));
|
||||
} else {
|
||||
principal.data.push(PrincipalData::EmailAlias(email));
|
||||
}
|
||||
}
|
||||
}
|
||||
(
|
||||
|
|
@ -1345,7 +1349,11 @@ impl ManageDirectory for Store {
|
|||
PrincipalValue::String(email),
|
||||
) => {
|
||||
let email = email.to_lowercase();
|
||||
if !principal.emails().any(|v| v == &email) {
|
||||
let mut emails_iter = principal.email_addresses().peekable();
|
||||
let has_emails = emails_iter.peek().is_some();
|
||||
let email_exists = emails_iter.any(|v| v == &email);
|
||||
drop(emails_iter);
|
||||
if !email_exists {
|
||||
if validate_emails {
|
||||
self.validate_email(&email, tenant_id, params.create_domains)
|
||||
.await?;
|
||||
|
|
@ -1356,7 +1364,11 @@ impl ManageDirectory for Store {
|
|||
)),
|
||||
pinfo_email.clone(),
|
||||
);
|
||||
principal.data.push(PrincipalData::Email(email));
|
||||
if has_emails {
|
||||
principal.data.push(PrincipalData::EmailAlias(email));
|
||||
} else {
|
||||
principal.data.push(PrincipalData::PrimaryEmail(email));
|
||||
}
|
||||
|
||||
// Emails changed, update changed principals
|
||||
changed_principals.add_change(principal_id, principal_type, change.field);
|
||||
|
|
@ -1368,15 +1380,33 @@ impl ManageDirectory for Store {
|
|||
PrincipalValue::String(email),
|
||||
) => {
|
||||
let email = email.to_lowercase();
|
||||
if principal.emails().any(|v| v == &email) {
|
||||
if principal.email_addresses().any(|v| v == &email) {
|
||||
let mut deleted_primary = false;
|
||||
principal.data.retain(|v| match v {
|
||||
PrincipalData::Email(v) => v != &email,
|
||||
PrincipalData::EmailAlias(v) => v != &email,
|
||||
PrincipalData::PrimaryEmail(v) => {
|
||||
if v == &email {
|
||||
deleted_primary = true;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
_ => true,
|
||||
});
|
||||
batch.clear(ValueClass::Directory(DirectoryClass::EmailToId(
|
||||
email.as_bytes().to_vec(),
|
||||
)));
|
||||
|
||||
if deleted_primary {
|
||||
for data in &mut principal.data {
|
||||
if let PrincipalData::EmailAlias(email) = data {
|
||||
*data = PrincipalData::PrimaryEmail(std::mem::take(email));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Emails changed, update changed principals
|
||||
changed_principals.add_change(principal_id, principal_type, change.field);
|
||||
}
|
||||
|
|
@ -2294,7 +2324,7 @@ impl ManageDirectory for Store {
|
|||
result.append_str(PrincipalField::Secrets, secret);
|
||||
}
|
||||
}
|
||||
PrincipalData::Email(email) => {
|
||||
PrincipalData::PrimaryEmail(email) | PrincipalData::EmailAlias(email) => {
|
||||
if fields.is_empty() || fields.contains(&PrincipalField::Emails) {
|
||||
result.append_str(PrincipalField::Emails, email);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -431,6 +431,7 @@ impl LdapMappings {
|
|||
let mut role = ROLE_USER;
|
||||
let mut member_of = vec![];
|
||||
let mut description = None;
|
||||
let mut has_primary_email = false;
|
||||
|
||||
for (attr, value) in entry.attrs {
|
||||
if self.attr_name.contains(&attr) {
|
||||
|
|
@ -438,9 +439,16 @@ impl LdapMappings {
|
|||
principal.name = value.into_iter().next().unwrap_or_default();
|
||||
} else {
|
||||
for (idx, item) in value.into_iter().enumerate() {
|
||||
principal
|
||||
.data
|
||||
.insert(0, PrincipalData::Email(item.to_lowercase()));
|
||||
if !has_primary_email {
|
||||
has_primary_email = true;
|
||||
principal
|
||||
.data
|
||||
.push(PrincipalData::PrimaryEmail(item.to_lowercase()));
|
||||
} else {
|
||||
principal
|
||||
.data
|
||||
.push(PrincipalData::EmailAlias(item.to_lowercase()));
|
||||
}
|
||||
if idx == 0 {
|
||||
principal.name = item;
|
||||
}
|
||||
|
|
@ -461,15 +469,22 @@ impl LdapMappings {
|
|||
}
|
||||
} else if self.attr_email_address.contains(&attr) {
|
||||
for item in value {
|
||||
principal
|
||||
.data
|
||||
.insert(0, PrincipalData::Email(item.to_lowercase()));
|
||||
if !has_primary_email {
|
||||
has_primary_email = true;
|
||||
principal
|
||||
.data
|
||||
.push(PrincipalData::PrimaryEmail(item.to_lowercase()));
|
||||
} else {
|
||||
principal
|
||||
.data
|
||||
.push(PrincipalData::EmailAlias(item.to_lowercase()));
|
||||
}
|
||||
}
|
||||
} else if self.attr_email_alias.contains(&attr) {
|
||||
for item in value {
|
||||
principal
|
||||
.data
|
||||
.push(PrincipalData::Email(item.to_lowercase()));
|
||||
.push(PrincipalData::EmailAlias(item.to_lowercase()));
|
||||
}
|
||||
} else if let Some(idx) = self.attr_description.iter().position(|a| a == &attr) {
|
||||
if (description.is_none() || idx == 0)
|
||||
|
|
|
|||
|
|
@ -108,9 +108,15 @@ impl MemoryDirectory {
|
|||
directory.domains.insert(domain.to_lowercase());
|
||||
}
|
||||
|
||||
principal
|
||||
.data
|
||||
.push(PrincipalData::Email(email.to_lowercase()));
|
||||
if pos == 0 {
|
||||
principal
|
||||
.data
|
||||
.push(PrincipalData::PrimaryEmail(email.to_lowercase()));
|
||||
} else {
|
||||
principal
|
||||
.data
|
||||
.push(PrincipalData::EmailAlias(email.to_lowercase()));
|
||||
}
|
||||
}
|
||||
|
||||
// Parse mailing lists
|
||||
|
|
|
|||
|
|
@ -80,8 +80,8 @@ impl MemoryDirectory {
|
|||
if let EmailType::List(uid) = item {
|
||||
for principal in &self.principals {
|
||||
if principal.id == *uid {
|
||||
if let Some(addr) = principal.emails().next() {
|
||||
result.push(addr.clone())
|
||||
if let Some(addr) = principal.primary_email() {
|
||||
result.push(addr.to_string())
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ impl BuildPrincipal for OpenIdResponse {
|
|||
|
||||
// Build principal
|
||||
let mut data = Vec::with_capacity(3);
|
||||
data.push(PrincipalData::Email(email));
|
||||
data.push(PrincipalData::PrimaryEmail(email));
|
||||
if let Some(name) = full_name {
|
||||
data.push(PrincipalData::Description(name));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,20 +158,29 @@ impl SqlDirectory {
|
|||
|
||||
// Obtain emails
|
||||
if !self.mappings.query_emails.is_empty() {
|
||||
let rows = self
|
||||
let mut rows = self
|
||||
.sql_store
|
||||
.sql_query::<Rows>(
|
||||
&self.mappings.query_emails,
|
||||
vec![external_principal.name().into()],
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
external_principal.data.extend(
|
||||
rows.rows
|
||||
.into_iter()
|
||||
.flat_map(|v| v.values.into_iter().map(|v| v.into_lower_string()))
|
||||
.map(PrincipalData::Email),
|
||||
);
|
||||
.caused_by(trc::location!())?
|
||||
.rows
|
||||
.into_iter()
|
||||
.flat_map(|v| v.values.into_iter().map(|v| v.into_lower_string()));
|
||||
|
||||
if external_principal.primary_email().is_none()
|
||||
&& let Some(email) = rows.next()
|
||||
{
|
||||
external_principal
|
||||
.data
|
||||
.push(PrincipalData::PrimaryEmail(email));
|
||||
}
|
||||
|
||||
external_principal
|
||||
.data
|
||||
.extend(rows.map(PrincipalData::EmailAlias));
|
||||
}
|
||||
|
||||
// Obtain account ID if not available
|
||||
|
|
@ -271,6 +280,7 @@ impl SqlMappings {
|
|||
|
||||
let mut principal = Principal::new(u32::MAX, Type::Individual);
|
||||
let mut role = ROLE_USER;
|
||||
let mut has_primary_email = false;
|
||||
|
||||
if let Some(row) = rows.rows.into_iter().next() {
|
||||
for (name, value) in rows.names.into_iter().zip(row.values) {
|
||||
|
|
@ -300,9 +310,16 @@ impl SqlMappings {
|
|||
}
|
||||
} else if name.eq_ignore_ascii_case(&self.column_email) {
|
||||
if let Value::Text(text) = value {
|
||||
principal
|
||||
.data
|
||||
.push(PrincipalData::Email(text.to_lowercase()));
|
||||
if !has_primary_email {
|
||||
has_primary_email = true;
|
||||
principal
|
||||
.data
|
||||
.push(PrincipalData::PrimaryEmail(text.to_lowercase()));
|
||||
} else {
|
||||
principal
|
||||
.data
|
||||
.push(PrincipalData::EmailAlias(text.to_lowercase()));
|
||||
}
|
||||
}
|
||||
} else if name.eq_ignore_ascii_case(&self.column_quota)
|
||||
&& let Value::Integer(quota) = value
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
use crate::{
|
||||
ArchivedPrincipal, ArchivedPrincipalData, FALLBACK_ADMIN_ID, Permission, PermissionGrant,
|
||||
Principal, PrincipalData, ROLE_ADMIN, Type,
|
||||
backend::internal::{PrincipalField, PrincipalSet, PrincipalUpdate, PrincipalValue},
|
||||
backend::internal::{
|
||||
PrincipalField, PrincipalSet, PrincipalUpdate, PrincipalValue, SpecialSecrets,
|
||||
},
|
||||
};
|
||||
use ahash::AHashSet;
|
||||
use nlp::tokenizers::word::WordTokenizer;
|
||||
|
|
@ -104,18 +106,64 @@ impl Principal {
|
|||
}
|
||||
|
||||
pub fn secrets(&self) -> impl Iterator<Item = &String> {
|
||||
self.data.iter().filter_map(|item| {
|
||||
if let PrincipalData::Secret(secret) = item {
|
||||
Some(secret)
|
||||
let mut found_secret = false;
|
||||
self.data
|
||||
.iter()
|
||||
.take_while(move |item| {
|
||||
if matches!(item, PrincipalData::Secret(_)) {
|
||||
found_secret = true;
|
||||
true
|
||||
} else {
|
||||
!found_secret
|
||||
}
|
||||
})
|
||||
.filter_map(|item| {
|
||||
if let PrincipalData::Secret(secret) = item {
|
||||
Some(secret)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn primary_email(&self) -> Option<&str> {
|
||||
self.data.iter().find_map(|item| {
|
||||
if let PrincipalData::PrimaryEmail(email) = item {
|
||||
Some(email.as_str())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn emails(&self) -> impl Iterator<Item = &String> {
|
||||
self.data.iter().filter_map(|item| {
|
||||
if let PrincipalData::Email(email) = item {
|
||||
pub fn email_addresses(&self) -> impl Iterator<Item = &String> {
|
||||
let mut found_email = false;
|
||||
self.data
|
||||
.iter()
|
||||
.take_while(move |item| {
|
||||
if matches!(
|
||||
item,
|
||||
PrincipalData::PrimaryEmail(_) | PrincipalData::EmailAlias(_)
|
||||
) {
|
||||
found_email = true;
|
||||
true
|
||||
} else {
|
||||
!found_email
|
||||
}
|
||||
})
|
||||
.filter_map(|item| {
|
||||
if let PrincipalData::PrimaryEmail(email) | PrincipalData::EmailAlias(email) = item
|
||||
{
|
||||
Some(email)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn into_primary_email(self) -> Option<String> {
|
||||
self.data.into_iter().find_map(|item| {
|
||||
if let PrincipalData::PrimaryEmail(email) = item {
|
||||
Some(email)
|
||||
} else {
|
||||
None
|
||||
|
|
@ -123,9 +171,9 @@ impl Principal {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn into_emails(self) -> impl Iterator<Item = String> {
|
||||
pub fn into_email_addresses(self) -> impl Iterator<Item = String> {
|
||||
self.data.into_iter().filter_map(|item| {
|
||||
if let PrincipalData::Email(email) = item {
|
||||
if let PrincipalData::PrimaryEmail(email) | PrincipalData::EmailAlias(email) = item {
|
||||
Some(email)
|
||||
} else {
|
||||
None
|
||||
|
|
@ -325,15 +373,27 @@ impl Principal {
|
|||
}
|
||||
|
||||
// Update emails
|
||||
if update_list(external.emails(), self.emails()) {
|
||||
if update_list(external.email_addresses(), self.email_addresses()) {
|
||||
if overwrite_emails {
|
||||
let mut new_emails = Vec::new();
|
||||
self.data.retain(|item| {
|
||||
!matches!(
|
||||
item,
|
||||
PrincipalData::PrimaryEmail(_) | PrincipalData::EmailAlias(_)
|
||||
)
|
||||
});
|
||||
self.data
|
||||
.retain(|item| !matches!(item, PrincipalData::Email(_)));
|
||||
self.data.extend(external.emails().map(|email| {
|
||||
new_emails.push(email.to_string());
|
||||
PrincipalData::Email(email.to_string())
|
||||
}));
|
||||
.extend(external.data.iter().filter_map(|v| match v {
|
||||
PrincipalData::PrimaryEmail(email) => {
|
||||
new_emails.push(email.clone());
|
||||
Some(PrincipalData::PrimaryEmail(email.clone()))
|
||||
}
|
||||
PrincipalData::EmailAlias(email) => {
|
||||
new_emails.push(email.clone());
|
||||
Some(PrincipalData::EmailAlias(email.clone()))
|
||||
}
|
||||
_ => None,
|
||||
}));
|
||||
updates.push(PrincipalUpdate::set(
|
||||
PrincipalField::Emails,
|
||||
PrincipalValue::StringList(new_emails),
|
||||
|
|
@ -341,16 +401,16 @@ impl Principal {
|
|||
} else {
|
||||
// Missing emails are appended to avoid overwriting locally defined aliases
|
||||
// This means that old email addresses need to be deleted either manually or using the API
|
||||
let current_emails = self.emails().collect::<AHashSet<_>>();
|
||||
let current_emails = self.email_addresses().collect::<AHashSet<_>>();
|
||||
let mut new_emails = Vec::new();
|
||||
for email in external.emails() {
|
||||
for email in external.email_addresses() {
|
||||
let email = email.to_lowercase();
|
||||
if !current_emails.contains(&email) {
|
||||
updates.push(PrincipalUpdate::add_item(
|
||||
PrincipalField::Emails,
|
||||
PrincipalValue::String(email.clone()),
|
||||
));
|
||||
new_emails.push(PrincipalData::Email(email));
|
||||
new_emails.push(PrincipalData::EmailAlias(email));
|
||||
}
|
||||
}
|
||||
self.data.extend(new_emails);
|
||||
|
|
@ -396,6 +456,27 @@ impl Principal {
|
|||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sort(&mut self) {
|
||||
self.data.sort_unstable_by_key(|d| d.rank());
|
||||
}
|
||||
}
|
||||
|
||||
impl PrincipalData {
|
||||
fn rank(&self) -> usize {
|
||||
match self {
|
||||
PrincipalData::Secret(v) => {
|
||||
if v.is_otp_auth() {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
PrincipalData::PrimaryEmail(_) => 2,
|
||||
PrincipalData::EmailAlias(_) => 3,
|
||||
_ => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_list<'x>(
|
||||
|
|
@ -432,7 +513,8 @@ impl PrincipalData {
|
|||
match self {
|
||||
PrincipalData::Secret(v)
|
||||
| PrincipalData::Description(v)
|
||||
| PrincipalData::Email(v)
|
||||
| PrincipalData::PrimaryEmail(v)
|
||||
| PrincipalData::EmailAlias(v)
|
||||
| PrincipalData::Picture(v)
|
||||
| PrincipalData::ExternalMember(v)
|
||||
| PrincipalData::Url(v)
|
||||
|
|
@ -955,8 +1037,9 @@ pub(crate) fn build_search_index(
|
|||
for word in [Some(current.name.as_str())]
|
||||
.into_iter()
|
||||
.chain(current.data.iter().map(|s| match s {
|
||||
ArchivedPrincipalData::Description(v) => Some(v.as_str()),
|
||||
ArchivedPrincipalData::Email(v) => Some(v.as_str()),
|
||||
ArchivedPrincipalData::Description(v)
|
||||
| ArchivedPrincipalData::PrimaryEmail(v)
|
||||
| ArchivedPrincipalData::EmailAlias(v) => Some(v.as_str()),
|
||||
_ => None,
|
||||
}))
|
||||
.flatten()
|
||||
|
|
@ -969,8 +1052,9 @@ pub(crate) fn build_search_index(
|
|||
for word in [Some(new.name.as_str())]
|
||||
.into_iter()
|
||||
.chain(new.data.iter().map(|s| match s {
|
||||
PrincipalData::Description(v) => Some(v.as_str()),
|
||||
PrincipalData::Email(v) => Some(v.as_str()),
|
||||
PrincipalData::Description(v)
|
||||
| PrincipalData::PrimaryEmail(v)
|
||||
| PrincipalData::EmailAlias(v) => Some(v.as_str()),
|
||||
_ => None,
|
||||
}))
|
||||
.flatten()
|
||||
|
|
|
|||
|
|
@ -60,7 +60,8 @@ pub enum PrincipalData {
|
|||
|
||||
// Profile data
|
||||
Description(String),
|
||||
Email(String),
|
||||
PrimaryEmail(String),
|
||||
EmailAlias(String),
|
||||
Picture(String),
|
||||
ExternalMember(String),
|
||||
Url(String),
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ impl SieveScriptIngest for Server {
|
|||
.caused_by(trc::location!())?
|
||||
.and_then(|p| {
|
||||
instance.set_user_full_name(p.description().unwrap_or_else(|| p.name()));
|
||||
p.into_emails().next()
|
||||
p.into_primary_email()
|
||||
});
|
||||
|
||||
// Set account address
|
||||
|
|
|
|||
|
|
@ -210,8 +210,7 @@ impl Autoconfig for Server {
|
|||
.query(QueryParams::id(id).with_return_member_of(false))
|
||||
.await
|
||||
&& principal
|
||||
.emails()
|
||||
.next()
|
||||
.primary_email()
|
||||
.is_some_and(|email| email.eq_ignore_ascii_case(emailaddress))
|
||||
{
|
||||
account_name = principal.name;
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ impl IdentityGet for Server {
|
|||
let mut description = None;
|
||||
for data in principal.data {
|
||||
match data {
|
||||
PrincipalData::Email(v) => emails.push(v),
|
||||
PrincipalData::PrimaryEmail(v) | PrincipalData::EmailAlias(v) => emails.push(v),
|
||||
PrincipalData::Description(v) => description = Some(v),
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ impl IdentitySet for Server {
|
|||
.directory()
|
||||
.query(QueryParams::id(account_id).with_return_member_of(false))
|
||||
.await?
|
||||
.is_none_or(|p| !p.emails().any(|e| e == &identity.email))
|
||||
.is_none_or(|p| !p.email_addresses().any(|e| e == &identity.email))
|
||||
{
|
||||
response.not_created.append(
|
||||
id,
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ impl ParticipantIdentityGet for Server {
|
|||
let mut description = None;
|
||||
for data in principal.data {
|
||||
match data {
|
||||
PrincipalData::Email(v) => emails.push(v),
|
||||
PrincipalData::PrimaryEmail(v) | PrincipalData::EmailAlias(v) => emails.push(v),
|
||||
PrincipalData::Description(v) => description = Some(v),
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ impl ParticipantIdentitySet for Server {
|
|||
.directory()
|
||||
.query(QueryParams::id(account_id).with_return_member_of(false))
|
||||
.await?
|
||||
.map(|p| p.into_emails().collect::<AHashSet<_>>())
|
||||
.map(|p| p.into_email_addresses().collect::<AHashSet<_>>())
|
||||
.unwrap_or_default();
|
||||
|
||||
// Process creates
|
||||
|
|
|
|||
|
|
@ -123,8 +123,7 @@ impl PrincipalGet for Server {
|
|||
.map(|v| Value::Str(v.to_string().into()))
|
||||
.unwrap_or(Value::Null),
|
||||
PrincipalProperty::Email => principal
|
||||
.emails()
|
||||
.next()
|
||||
.primary_email()
|
||||
.map(|email| Value::Str(email.to_string().into()))
|
||||
.unwrap_or(Value::Null),
|
||||
PrincipalProperty::Accounts => Value::Object(Map::from(vec![(
|
||||
|
|
@ -175,8 +174,7 @@ impl PrincipalGet for Server {
|
|||
Key::Borrowed("calendarAddress"),
|
||||
Value::Str(
|
||||
principal
|
||||
.emails()
|
||||
.next()
|
||||
.primary_email()
|
||||
.map(|email| format!("mailto:{}", email))
|
||||
.unwrap_or_default()
|
||||
.into(),
|
||||
|
|
|
|||
|
|
@ -189,11 +189,21 @@ impl FromLegacy for Principal {
|
|||
{
|
||||
principal.data.push(PrincipalData::Secret(secret));
|
||||
}
|
||||
for email in legacy
|
||||
for (idx, email) in legacy
|
||||
.take_str_array(PrincipalField::Emails)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
principal.data.push(PrincipalData::Email(email));
|
||||
if idx == 0 {
|
||||
principal
|
||||
.data
|
||||
.push(PrincipalData::PrimaryEmail(email.clone()));
|
||||
} else {
|
||||
principal
|
||||
.data
|
||||
.push(PrincipalData::EmailAlias(email.clone()));
|
||||
}
|
||||
}
|
||||
if let Some(picture) = legacy.take_str(PrincipalField::Picture) {
|
||||
principal.data.push(PrincipalData::Picture(picture));
|
||||
|
|
@ -361,7 +371,7 @@ pub(crate) fn build_search_index(batch: &mut BatchBuilder, principal_id: u32, ne
|
|||
|
||||
for word in [Some(new.name.as_str()), new.description()]
|
||||
.into_iter()
|
||||
.chain(new.emails().map(|s| Some(s.as_str())))
|
||||
.chain(new.email_addresses().map(|s| Some(s.as_str())))
|
||||
.flatten()
|
||||
{
|
||||
new_words.extend(WordTokenizer::new(word, MAX_TOKEN_LENGTH).map(|t| t.word));
|
||||
|
|
|
|||
|
|
@ -595,7 +595,7 @@ impl From<Principal> for TestPrincipal {
|
|||
roles: value.roles().map(|v| v.to_string()).collect(),
|
||||
lists: value.lists().map(|v| v.to_string()).collect(),
|
||||
secrets: value.secrets().map(|v| v.to_string()).collect(),
|
||||
emails: value.emails().map(|v| v.to_string()).collect(),
|
||||
emails: value.email_addresses().map(|v| v.to_string()).collect(),
|
||||
description: value.description().map(|v| v.to_string()),
|
||||
name: value.name,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ async fn oidc_directory() {
|
|||
.unwrap();
|
||||
assert_eq!(principal.name(), "jdoe");
|
||||
assert_eq!(
|
||||
principal.emails().next().map(|s| s.as_str()),
|
||||
principal.email_addresses().next().map(|s| s.as_str()),
|
||||
Some("john@example.org")
|
||||
);
|
||||
assert_eq!(principal.description(), Some("John Doe"));
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ async fn jmap_tests() {
|
|||
|
||||
server::webhooks::test(&mut params).await;
|
||||
|
||||
/*mail::get::test(&mut params).await;
|
||||
mail::get::test(&mut params).await;
|
||||
mail::set::test(&mut params).await;
|
||||
mail::parse::test(&mut params).await;
|
||||
mail::query::test(&mut params, delete).await;
|
||||
|
|
@ -116,7 +116,7 @@ async fn jmap_tests() {
|
|||
files::acl::test(&mut params).await;
|
||||
|
||||
calendar::calendars::test(&mut params).await;
|
||||
calendar::event::test(&mut params).await;*/
|
||||
calendar::event::test(&mut params).await;
|
||||
calendar::notification::test(&mut params).await;
|
||||
calendar::alarm::test(&mut params).await;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue