Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::propfind::PrincipalPropFind;
|
||||
use crate::{
|
||||
DavError,
|
||||
common::{
|
||||
DavQuery, DavQueryResource,
|
||||
propfind::PropFindRequestHandler,
|
||||
uri::{DavUriResource, UriResource},
|
||||
},
|
||||
};
|
||||
use common::{Server, auth::AccessToken};
|
||||
use dav_proto::{
|
||||
RequestHeaders,
|
||||
schema::{
|
||||
property::{DavProperty, WebDavProperty},
|
||||
request::{PrincipalMatch, PropFind},
|
||||
response::MultiStatus,
|
||||
},
|
||||
};
|
||||
use http_proto::HttpResponse;
|
||||
use hyper::StatusCode;
|
||||
use store::roaring::RoaringBitmap;
|
||||
use types::collection::Collection;
|
||||
|
||||
pub(crate) trait PrincipalMatching: Sync + Send {
|
||||
fn handle_principal_match(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: &RequestHeaders<'_>,
|
||||
request: PrincipalMatch,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
||||
impl PrincipalMatching for Server {
|
||||
async fn handle_principal_match(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
headers: &RequestHeaders<'_>,
|
||||
mut request: PrincipalMatch,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
let resource = self.validate_uri(access_token, headers.uri).await?;
|
||||
|
||||
match resource.collection {
|
||||
Collection::AddressBook | Collection::Calendar | Collection::FileNode => {
|
||||
if request.properties.is_empty() {
|
||||
request
|
||||
.properties
|
||||
.push(DavProperty::WebDav(WebDavProperty::Owner));
|
||||
}
|
||||
if let Some(account_id) = resource.account_id {
|
||||
return self
|
||||
.handle_dav_query(
|
||||
access_token,
|
||||
DavQuery {
|
||||
resource: DavQueryResource::Uri(UriResource {
|
||||
collection: resource.collection,
|
||||
account_id,
|
||||
resource: resource.resource,
|
||||
}),
|
||||
propfind: PropFind::Prop(request.properties),
|
||||
depth: usize::MAX,
|
||||
ret: headers.ret,
|
||||
depth_no_root: headers.depth_no_root,
|
||||
uri: headers.uri,
|
||||
sync_type: Default::default(),
|
||||
limit: Default::default(),
|
||||
vcard_version: Default::default(),
|
||||
expand: Default::default(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
Collection::Principal => {}
|
||||
_ => return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)),
|
||||
}
|
||||
|
||||
let mut response = MultiStatus::new(Vec::with_capacity(16));
|
||||
if request.properties.is_empty() {
|
||||
request
|
||||
.properties
|
||||
.push(DavProperty::WebDav(WebDavProperty::DisplayName));
|
||||
}
|
||||
let request = PropFind::Prop(request.properties);
|
||||
self.prepare_principal_propfind_response(
|
||||
access_token,
|
||||
resource.collection,
|
||||
RoaringBitmap::from_iter(access_token.all_ids()).into_iter(),
|
||||
&request,
|
||||
&mut response,
|
||||
)
|
||||
.await?;
|
||||
Ok(HttpResponse::new(StatusCode::MULTI_STATUS).with_xml_body(response.to_string()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::auth::AccountCache;
|
||||
use dav_proto::schema::response::Href;
|
||||
use groupware::RFC_3986;
|
||||
|
||||
use crate::DavResourceName;
|
||||
|
||||
pub mod matching;
|
||||
pub mod propfind;
|
||||
pub mod propsearch;
|
||||
|
||||
pub trait CurrentUserPrincipal {
|
||||
fn current_user_principal(&self) -> Href;
|
||||
}
|
||||
|
||||
impl CurrentUserPrincipal for AccountCache {
|
||||
fn current_user_principal(&self) -> Href {
|
||||
Href(format!(
|
||||
"{}/{}/",
|
||||
DavResourceName::Principal.base_path(),
|
||||
percent_encoding::utf8_percent_encode(self.name(), RFC_3986)
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::CurrentUserPrincipal;
|
||||
use crate::{
|
||||
DavResourceName,
|
||||
common::propfind::{PropFindRequestHandler, SyncTokenUrn},
|
||||
};
|
||||
use common::{
|
||||
Server,
|
||||
auth::{AccessToken, AccountCache},
|
||||
};
|
||||
use dav_proto::schema::{
|
||||
Namespace,
|
||||
property::{
|
||||
DavProperty, DavValue, PrincipalProperty, Privilege, ReportSet, ResourceType,
|
||||
WebDavProperty,
|
||||
},
|
||||
request::{DavPropertyValue, PropFind},
|
||||
response::{Href, MultiStatus, PropStat, Response},
|
||||
};
|
||||
use groupware::RFC_3986;
|
||||
use groupware::cache::GroupwareCache;
|
||||
use hyper::StatusCode;
|
||||
use std::borrow::Cow;
|
||||
use trc::AddContext;
|
||||
use types::collection::Collection;
|
||||
|
||||
pub(crate) trait PrincipalPropFind: Sync + Send {
|
||||
fn prepare_principal_propfind_response(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
collection: Collection,
|
||||
documents: impl Iterator<Item = u32> + Sync + Send,
|
||||
request: &PropFind,
|
||||
response: &mut MultiStatus,
|
||||
) -> impl Future<Output = crate::Result<()>> + Send;
|
||||
|
||||
fn expand_principal(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
account_id: u32,
|
||||
propfind: &PropFind,
|
||||
) -> impl Future<Output = crate::Result<Option<Response>>> + Send;
|
||||
|
||||
fn owner_href(
|
||||
&self,
|
||||
account_info: &AccountCache,
|
||||
account_id: u32,
|
||||
) -> impl Future<Output = trc::Result<Href>> + Send;
|
||||
}
|
||||
|
||||
impl PrincipalPropFind for Server {
|
||||
async fn prepare_principal_propfind_response(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
collection: Collection,
|
||||
account_ids: impl Iterator<Item = u32> + Sync + Send,
|
||||
request: &PropFind,
|
||||
response: &mut MultiStatus,
|
||||
) -> crate::Result<()> {
|
||||
let access_account_info = self
|
||||
.account(access_token.account_id())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
let properties = match request {
|
||||
PropFind::PropName => {
|
||||
let props = all_props(collection, None);
|
||||
for property in &props {
|
||||
response.set_namespace(property.namespace());
|
||||
}
|
||||
for account_id in account_ids {
|
||||
response.add_response(Response::new_propstat(
|
||||
self.owner_href(&access_account_info, account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?,
|
||||
vec![PropStat::new_list(
|
||||
props.iter().cloned().map(DavPropertyValue::empty).collect(),
|
||||
)],
|
||||
));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
PropFind::AllProp(items) => Cow::Owned(all_props(collection, items.as_slice().into())),
|
||||
PropFind::Prop(items) => Cow::Borrowed(items),
|
||||
};
|
||||
for property in properties.as_slice() {
|
||||
response.set_namespace(property.namespace());
|
||||
}
|
||||
let is_principal = match collection {
|
||||
Collection::AddressBook | Collection::ContactCard => {
|
||||
response.set_namespace(Namespace::CardDav);
|
||||
false
|
||||
}
|
||||
Collection::Calendar
|
||||
| Collection::CalendarEvent
|
||||
| Collection::CalendarEventNotification => {
|
||||
response.set_namespace(Namespace::CalDav);
|
||||
false
|
||||
}
|
||||
Collection::Principal => true,
|
||||
_ => false,
|
||||
};
|
||||
let base_path = DavResourceName::from(collection).base_path();
|
||||
let needs_quota = properties.iter().any(|property| {
|
||||
matches!(
|
||||
property,
|
||||
DavProperty::WebDav(
|
||||
WebDavProperty::QuotaAvailableBytes | WebDavProperty::QuotaUsedBytes
|
||||
)
|
||||
)
|
||||
});
|
||||
|
||||
for account_id in account_ids {
|
||||
let mut fields = Vec::with_capacity(properties.len());
|
||||
let mut fields_not_found = Vec::new();
|
||||
|
||||
let account = self.account(account_id).await.caused_by(trc::location!())?;
|
||||
|
||||
// Fetch quota
|
||||
let quota = if needs_quota {
|
||||
self.dav_quota(account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
for property in properties.as_slice() {
|
||||
match property {
|
||||
DavProperty::WebDav(dav_property) => match dav_property {
|
||||
WebDavProperty::DisplayName => {
|
||||
fields.push(DavPropertyValue::new(
|
||||
property.clone(),
|
||||
account.description().unwrap_or(account.name()).to_string(),
|
||||
));
|
||||
}
|
||||
WebDavProperty::ResourceType => {
|
||||
let resource_type = if !is_principal {
|
||||
vec![ResourceType::Collection]
|
||||
} else {
|
||||
vec![ResourceType::Principal, ResourceType::Collection]
|
||||
};
|
||||
|
||||
fields.push(DavPropertyValue::new(property.clone(), resource_type));
|
||||
}
|
||||
WebDavProperty::SupportedReportSet => {
|
||||
let reports = match collection {
|
||||
Collection::Principal => ReportSet::principal(),
|
||||
Collection::Calendar | Collection::CalendarEvent => {
|
||||
ReportSet::calendar()
|
||||
}
|
||||
Collection::AddressBook | Collection::ContactCard => {
|
||||
ReportSet::addressbook()
|
||||
}
|
||||
_ => ReportSet::file(),
|
||||
};
|
||||
|
||||
fields.push(DavPropertyValue::new(property.clone(), reports));
|
||||
}
|
||||
WebDavProperty::CurrentUserPrincipal => {
|
||||
fields.push(DavPropertyValue::new(
|
||||
property.clone(),
|
||||
vec![access_account_info.current_user_principal()],
|
||||
));
|
||||
}
|
||||
WebDavProperty::QuotaAvailableBytes if !is_principal => {
|
||||
if let Some(available) = quota.available {
|
||||
fields.push(DavPropertyValue::new(property.clone(), available));
|
||||
} else {
|
||||
fields_not_found.push(DavPropertyValue::empty(property.clone()));
|
||||
}
|
||||
}
|
||||
WebDavProperty::QuotaUsedBytes if !is_principal => {
|
||||
fields.push(DavPropertyValue::new(property.clone(), quota.used));
|
||||
}
|
||||
WebDavProperty::SyncToken if !is_principal => {
|
||||
let sync_token = self
|
||||
.fetch_dav_resources(
|
||||
access_token.account_id(),
|
||||
account_id,
|
||||
collection.into(),
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.sync_token();
|
||||
|
||||
fields.push(DavPropertyValue::new(property.clone(), sync_token));
|
||||
}
|
||||
WebDavProperty::GetCTag if !is_principal => {
|
||||
let ctag = self
|
||||
.fetch_dav_resources(
|
||||
access_token.account_id(),
|
||||
account_id,
|
||||
collection.into(),
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.highest_change_id;
|
||||
|
||||
fields.push(DavPropertyValue::new(
|
||||
property.clone(),
|
||||
DavValue::String(format!("\"{ctag}\"")),
|
||||
));
|
||||
}
|
||||
WebDavProperty::Owner => {
|
||||
fields.push(DavPropertyValue::new(
|
||||
property.clone(),
|
||||
vec![Href(format!(
|
||||
"{}/{}/",
|
||||
DavResourceName::Principal.base_path(),
|
||||
percent_encoding::utf8_percent_encode(account.name(), RFC_3986),
|
||||
))],
|
||||
));
|
||||
}
|
||||
WebDavProperty::Group if !is_principal => {
|
||||
fields.push(DavPropertyValue::empty(property.clone()));
|
||||
}
|
||||
WebDavProperty::CurrentUserPrivilegeSet if !is_principal => {
|
||||
fields.push(DavPropertyValue::new(
|
||||
property.clone(),
|
||||
if access_token.is_member(account_id) {
|
||||
Privilege::all(matches!(
|
||||
collection,
|
||||
Collection::Calendar | Collection::CalendarEvent
|
||||
))
|
||||
} else {
|
||||
vec![Privilege::Read]
|
||||
},
|
||||
));
|
||||
}
|
||||
WebDavProperty::PrincipalCollectionSet => {
|
||||
fields.push(DavPropertyValue::new(
|
||||
property.clone(),
|
||||
vec![Href(
|
||||
DavResourceName::Principal.collection_path().to_string(),
|
||||
)],
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
fields_not_found.push(DavPropertyValue::empty(property.clone()));
|
||||
}
|
||||
},
|
||||
DavProperty::Principal(principal_property) => match principal_property {
|
||||
PrincipalProperty::AlternateURISet
|
||||
| PrincipalProperty::GroupMemberSet
|
||||
| PrincipalProperty::GroupMembership => {
|
||||
fields.push(DavPropertyValue::empty(property.clone()));
|
||||
}
|
||||
PrincipalProperty::PrincipalURL => {
|
||||
fields.push(DavPropertyValue::new(
|
||||
property.clone(),
|
||||
vec![Href(format!(
|
||||
"{}/{}/",
|
||||
DavResourceName::Principal.base_path(),
|
||||
percent_encoding::utf8_percent_encode(account.name(), RFC_3986),
|
||||
))],
|
||||
));
|
||||
}
|
||||
PrincipalProperty::CalendarHomeSet => {
|
||||
let hrefs = build_home_set(
|
||||
self,
|
||||
access_token,
|
||||
account.name(),
|
||||
account_id,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
fields.push(DavPropertyValue::new(property.clone(), hrefs));
|
||||
}
|
||||
PrincipalProperty::AddressbookHomeSet => {
|
||||
let hrefs = build_home_set(
|
||||
self,
|
||||
access_token,
|
||||
account.name(),
|
||||
account_id,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
fields.push(DavPropertyValue::new(property.clone(), hrefs));
|
||||
}
|
||||
|
||||
PrincipalProperty::PrincipalAddress => {
|
||||
fields_not_found.push(DavPropertyValue::empty(property.clone()));
|
||||
}
|
||||
PrincipalProperty::CalendarUserAddressSet => {
|
||||
fields.push(DavPropertyValue::new(
|
||||
property.clone(),
|
||||
vec![Href(format!("mailto:{}", account.name()))],
|
||||
));
|
||||
}
|
||||
PrincipalProperty::CalendarUserType => {
|
||||
fields.push(DavPropertyValue::new(
|
||||
property.clone(),
|
||||
if account.is_user_account() {
|
||||
DavValue::String("INDIVIDUAL".to_string())
|
||||
} else {
|
||||
DavValue::String("GROUP".to_string())
|
||||
},
|
||||
));
|
||||
}
|
||||
PrincipalProperty::ScheduleInboxURL => {
|
||||
fields.push(DavPropertyValue::new(
|
||||
property.clone(),
|
||||
vec![Href(format!(
|
||||
"{}/{}/inbox/",
|
||||
DavResourceName::Scheduling.base_path(),
|
||||
percent_encoding::utf8_percent_encode(account.name(), RFC_3986),
|
||||
))],
|
||||
));
|
||||
}
|
||||
PrincipalProperty::ScheduleOutboxURL => {
|
||||
fields.push(DavPropertyValue::new(
|
||||
property.clone(),
|
||||
vec![Href(format!(
|
||||
"{}/{}/outbox/",
|
||||
DavResourceName::Scheduling.base_path(),
|
||||
percent_encoding::utf8_percent_encode(account.name(), RFC_3986),
|
||||
))],
|
||||
));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
fields_not_found.push(DavPropertyValue::empty(property.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut prop_stats = Vec::with_capacity(2);
|
||||
|
||||
if !fields_not_found.is_empty() {
|
||||
prop_stats
|
||||
.push(PropStat::new_list(fields_not_found).with_status(StatusCode::NOT_FOUND));
|
||||
}
|
||||
|
||||
if !fields.is_empty() || prop_stats.is_empty() {
|
||||
prop_stats.push(PropStat::new_list(fields));
|
||||
}
|
||||
|
||||
response.add_response(Response::new_propstat(
|
||||
Href(format!(
|
||||
"{}/{}/",
|
||||
base_path,
|
||||
percent_encoding::utf8_percent_encode(account.name(), RFC_3986),
|
||||
)),
|
||||
prop_stats,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn expand_principal(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
account_id: u32,
|
||||
propfind: &PropFind,
|
||||
) -> crate::Result<Option<Response>> {
|
||||
let mut status = MultiStatus::new(vec![]);
|
||||
self.prepare_principal_propfind_response(
|
||||
access_token,
|
||||
Collection::Principal,
|
||||
[account_id].into_iter(),
|
||||
propfind,
|
||||
&mut status,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(status.response.0.into_iter().next())
|
||||
}
|
||||
|
||||
async fn owner_href(&self, account_info: &AccountCache, account_id: u32) -> trc::Result<Href> {
|
||||
if account_info.account_id() == account_id {
|
||||
Ok(account_info.current_user_principal())
|
||||
} else {
|
||||
let account_info = self.account(account_id).await.caused_by(trc::location!())?;
|
||||
Ok(Href(format!(
|
||||
"{}/{}/",
|
||||
DavResourceName::Principal.base_path(),
|
||||
percent_encoding::utf8_percent_encode(account_info.name(), RFC_3986),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn build_home_set(
|
||||
server: &Server,
|
||||
access_token: &AccessToken,
|
||||
name: &str,
|
||||
account_id: u32,
|
||||
is_calendar: bool,
|
||||
) -> trc::Result<Vec<Href>> {
|
||||
let (collection, resource_name) = if is_calendar {
|
||||
(Collection::Calendar, DavResourceName::Cal)
|
||||
} else {
|
||||
(Collection::AddressBook, DavResourceName::Card)
|
||||
};
|
||||
|
||||
let mut hrefs = Vec::new();
|
||||
hrefs.push(Href(format!(
|
||||
"{}/{}/",
|
||||
resource_name.base_path(),
|
||||
percent_encoding::utf8_percent_encode(name, RFC_3986),
|
||||
)));
|
||||
|
||||
if !server.core.groupware.assisted_discovery && account_id == access_token.account_id() {
|
||||
for account_id in access_token.all_ids_by_collection(collection) {
|
||||
if account_id != access_token.account_id() {
|
||||
let other = server
|
||||
.account(account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
hrefs.push(Href(format!(
|
||||
"{}/{}/",
|
||||
resource_name.base_path(),
|
||||
percent_encoding::utf8_percent_encode(other.name(), RFC_3986),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(hrefs)
|
||||
}
|
||||
|
||||
fn all_props(collection: Collection, all_props: Option<&[DavProperty]>) -> Vec<DavProperty> {
|
||||
if collection == Collection::Principal {
|
||||
vec![
|
||||
DavProperty::WebDav(WebDavProperty::DisplayName),
|
||||
DavProperty::WebDav(WebDavProperty::ResourceType),
|
||||
DavProperty::WebDav(WebDavProperty::SupportedReportSet),
|
||||
DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal),
|
||||
DavProperty::WebDav(WebDavProperty::PrincipalCollectionSet),
|
||||
DavProperty::Principal(PrincipalProperty::AlternateURISet),
|
||||
DavProperty::Principal(PrincipalProperty::PrincipalURL),
|
||||
DavProperty::Principal(PrincipalProperty::GroupMemberSet),
|
||||
DavProperty::Principal(PrincipalProperty::GroupMembership),
|
||||
]
|
||||
} else {
|
||||
let mut props = vec![
|
||||
DavProperty::WebDav(WebDavProperty::DisplayName),
|
||||
DavProperty::WebDav(WebDavProperty::ResourceType),
|
||||
DavProperty::WebDav(WebDavProperty::SupportedReportSet),
|
||||
DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal),
|
||||
DavProperty::WebDav(WebDavProperty::SyncToken),
|
||||
DavProperty::WebDav(WebDavProperty::Owner),
|
||||
DavProperty::WebDav(WebDavProperty::PrincipalCollectionSet),
|
||||
];
|
||||
|
||||
if let Some(all_props) = all_props {
|
||||
props.extend(all_props.iter().filter(|p| !p.is_all_prop()).cloned());
|
||||
props
|
||||
} else {
|
||||
props
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::propfind::PrincipalPropFind;
|
||||
use common::{Server, auth::AccessToken};
|
||||
use dav_proto::schema::{
|
||||
property::{DavProperty, WebDavProperty},
|
||||
request::{PrincipalPropertySearch, PropFind},
|
||||
response::MultiStatus,
|
||||
};
|
||||
use http_proto::HttpResponse;
|
||||
use hyper::StatusCode;
|
||||
use registry::schema::prelude::{ObjectType, Property};
|
||||
use store::{registry::RegistryQuery, roaring::RoaringBitmap};
|
||||
use trc::AddContext;
|
||||
use types::collection::Collection;
|
||||
|
||||
pub(crate) trait PrincipalPropSearch: Sync + Send {
|
||||
fn handle_principal_property_search(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
request: PrincipalPropertySearch,
|
||||
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
||||
}
|
||||
|
||||
impl PrincipalPropSearch for Server {
|
||||
async fn handle_principal_property_search(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
mut request: PrincipalPropertySearch,
|
||||
) -> crate::Result<HttpResponse> {
|
||||
let mut search_for = None;
|
||||
|
||||
for prop_search in request.property_search {
|
||||
if matches!(
|
||||
prop_search.property,
|
||||
DavProperty::WebDav(WebDavProperty::DisplayName)
|
||||
) && !prop_search.match_.is_empty()
|
||||
{
|
||||
search_for = Some(prop_search.match_);
|
||||
}
|
||||
}
|
||||
|
||||
let mut response = MultiStatus::new(Vec::with_capacity(16));
|
||||
if let Some(search_for) = search_for {
|
||||
let ids = self
|
||||
.registry()
|
||||
.query::<RoaringBitmap>(
|
||||
RegistryQuery::new(ObjectType::Account)
|
||||
.with_tenant(access_token.tenant_id())
|
||||
.text(Property::Text, search_for),
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
if !ids.is_empty() {
|
||||
if request.properties.is_empty() {
|
||||
request
|
||||
.properties
|
||||
.push(DavProperty::WebDav(WebDavProperty::DisplayName));
|
||||
}
|
||||
let request = PropFind::Prop(request.properties);
|
||||
self.prepare_principal_propfind_response(
|
||||
access_token,
|
||||
Collection::Principal,
|
||||
ids.into_iter(),
|
||||
&request,
|
||||
&mut response,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(HttpResponse::new(StatusCode::MULTI_STATUS).with_xml_body(response.to_string()))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user