IMAP mailbox synchronization

This commit is contained in:
Mauro D
2023-06-22 17:29:39 +00:00
parent 5fda550642
commit e1c3190b48
23 changed files with 1849 additions and 91 deletions

View File

@@ -192,12 +192,7 @@ impl JMAP {
session.add_account(
(*id).into(),
self.directory
.principal_by_id(*id)
.await
.unwrap_or_default()
.map(|p| p.name)
.unwrap_or_else(|| Id::from(*id).to_string()),
self.get_account_name(*id).await,
is_personal,
is_readonly,
Some(&[Capability::Core, Capability::Mail, Capability::WebSocket]),
@@ -206,6 +201,15 @@ impl JMAP {
Ok(session)
}
pub async fn get_account_name(&self, account_id: u32) -> String {
self.directory
.principal_by_id(account_id)
.await
.unwrap_or_default()
.map(|p| p.name)
.unwrap_or_else(|| Id::from(account_id).to_string())
}
}
impl crate::Config {

View File

@@ -50,20 +50,7 @@ impl JMAP {
.and_then(|h| h.split_once(' ').map(|(l, t)| (l, t.trim().to_string())))
{
let session = if let Some(account_id) = self.sessions.get(&token) {
if let Some(access_token) = self.access_tokens.get(&account_id) {
access_token.into()
} else {
// Refresh ACL token
self.get_access_token(account_id).await.map(|access_token| {
let access_token = Arc::new(access_token);
self.access_tokens.insert(
account_id,
access_token.clone(),
Instant::now() + self.config.session_cache_ttl,
);
access_token
})
}
self.get_cached_access_token(account_id).await
} else {
let addr = self.build_remote_addr(req, remote_ip);
if mechanism.eq_ignore_ascii_case("basic") {
@@ -108,19 +95,11 @@ impl JMAP {
self.is_anonymous_allowed(addr)?;
None
}
.map(|session| {
let session = Arc::new(session);
self.sessions.insert(
token,
session.primary_id(),
Instant::now() + self.config.session_cache_ttl,
);
self.access_tokens.insert(
session.primary_id(),
session.clone(),
Instant::now() + self.config.session_cache_ttl,
);
session
.map(|access_token| {
let access_token = Arc::new(access_token);
self.cache_session(token, &access_token);
self.cache_access_token(access_token.clone());
access_token
})
};
@@ -138,6 +117,35 @@ impl JMAP {
}
}
pub fn cache_session(&self, session_id: String, access_token: &AccessToken) {
self.sessions.insert(
session_id,
access_token.primary_id(),
Instant::now() + self.config.session_cache_ttl,
);
}
pub fn cache_access_token(&self, access_token: Arc<AccessToken>) {
self.access_tokens.insert(
access_token.primary_id(),
access_token,
Instant::now() + self.config.session_cache_ttl,
);
}
pub async fn get_cached_access_token(&self, primary_id: u32) -> Option<Arc<AccessToken>> {
if let Some(access_token) = self.access_tokens.get(&primary_id) {
access_token.into()
} else {
// Refresh ACL token
self.get_access_token(primary_id).await.map(|access_token| {
let access_token = Arc::new(access_token);
self.cache_access_token(access_token.clone());
access_token
})
}
}
pub fn build_remote_addr(
&self,
req: &hyper::Request<hyper::body::Incoming>,

View File

@@ -101,6 +101,10 @@ impl AccessToken {
|| self.member_of.contains(&SUPERUSER_ID)
}
pub fn is_primary_id(&self, account_id: u32) -> bool {
self.primary_id == account_id
}
pub fn is_super_user(&self) -> bool {
self.primary_id == SUPERUSER_ID || self.member_of.contains(&SUPERUSER_ID)
}
@@ -109,6 +113,19 @@ impl AccessToken {
!self.is_member(account_id) && self.access_to.iter().any(|(id, _)| *id == account_id)
}
pub fn shared_accounts(&self, collection: impl Into<Collection>) -> impl Iterator<Item = &u32> {
let collection = collection.into();
self.member_of
.iter()
.chain(self.access_to.iter().filter_map(move |(id, cols)| {
if cols.contains(collection) {
id.into()
} else {
None
}
}))
}
pub fn has_access(&self, to_account_id: u32, to_collection: impl Into<Collection>) -> bool {
let to_collection = to_collection.into();
self.is_member(to_account_id)

View File

@@ -85,10 +85,7 @@ impl JMAP {
let (items_sent, mut changelog) = match &request.since_state {
State::Initial => {
let changelog = self
.changes_(account_id, collection, Query::All)
.await?
.unwrap();
let changelog = self.changes_(account_id, collection, Query::All).await?;
if changelog.changes.is_empty() && changelog.from_change_id == 0 {
return Ok(response);
}
@@ -98,12 +95,7 @@ impl JMAP {
State::Exact(change_id) => (
0,
self.changes_(account_id, collection, Query::Since(*change_id))
.await?
.ok_or_else(|| {
MethodError::InvalidArguments(
"The specified stateId does could not be found.".to_string(),
)
})?,
.await?,
),
State::Intermediate(intermediate_state) => {
let mut changelog = self
@@ -112,12 +104,7 @@ impl JMAP {
collection,
Query::RangeInclusive(intermediate_state.from_id, intermediate_state.to_id),
)
.await?
.ok_or_else(|| {
MethodError::InvalidArguments(
"The specified stateId does could not be found.".to_string(),
)
})?;
.await?;
if intermediate_state.items_sent >= changelog.changes.len() {
(
0,
@@ -126,12 +113,7 @@ impl JMAP {
collection,
Query::Since(intermediate_state.to_id),
)
.await?
.ok_or_else(|| {
MethodError::InvalidArguments(
"The specified stateId does could not be found.".to_string(),
)
})?,
.await?,
)
} else {
changelog.changes.drain(
@@ -189,12 +171,12 @@ impl JMAP {
Ok(response)
}
async fn changes_(
pub async fn changes_(
&self,
account_id: u32,
collection: Collection,
query: Query,
) -> Result<Option<Changes>, MethodError> {
) -> Result<Changes, MethodError> {
self.store
.changes(account_id, collection, query)
.await

View File

@@ -279,7 +279,7 @@ impl JMAP {
}
}
async fn mailbox_unread_tags(
pub async fn mailbox_unread_tags(
&self,
account_id: u32,
document_id: u32,

View File

@@ -495,7 +495,14 @@ impl JMAP {
#[inline(always)]
pub fn is_valid_role(role: &str) -> bool {
[
"inbox", "trash", "spam", "junk", "drafts", "archive", "sent",
"inbox",
"trash",
"spam",
"junk",
"drafts",
"archive",
"sent",
"important",
]
.contains(&role)
}