blob: 33bf407ba01276c3c423af41482796822ae9de44 [file] [log] [blame]
/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
package org.apache.james.jmap.mail
import javax.inject.Inject
import org.apache.james.jmap.core.UnsignedInt
import org.apache.james.jmap.mail.MailboxName.MailboxName
import org.apache.james.jmap.utils.quotas.QuotaLoader
import org.apache.james.mailbox._
import org.apache.james.mailbox.exception.{MailboxNameException, MailboxNotFoundException}
import org.apache.james.mailbox.model.MailboxACL.EntryKey
import org.apache.james.mailbox.model.{MailboxCounters, MailboxId, MailboxMetaData, MailboxPath, MailboxACL => JavaMailboxACL}
import reactor.core.scala.publisher.SMono
import scala.jdk.CollectionConverters._
import scala.jdk.OptionConverters._
object MailboxValidation {
private def retrieveMailboxName(mailboxPath: MailboxPath, pathDelimiter: Char): Either[MailboxNameException, MailboxName] =
mailboxPath.getName
.split(pathDelimiter)
.lastOption match {
case Some(name) => MailboxName.validate(name)
case None => Left(new MailboxNameException("No name for the mailbox found"))
}
def validate(mailboxPath: MailboxPath,
pathDelimiter: Char,
unreadEmails: Long,
unreadThreads: Long,
totalEmails: Long,
totalThreads: Long): Either[Exception, MailboxValidation] = {
for {
validatedName <- retrieveMailboxName(mailboxPath, pathDelimiter)
validatedUnreadEmails <- UnsignedInt.validate(unreadEmails).map(UnreadEmails)
validatedUnreadThreads <- UnsignedInt.validate(unreadThreads).map(UnreadThreads)
validatedTotalEmails <- UnsignedInt.validate(totalEmails).map(TotalEmails)
validatedTotalThreads <- UnsignedInt.validate(totalThreads).map(TotalThreads)
} yield MailboxValidation(
mailboxName = validatedName,
unreadEmails = validatedUnreadEmails,
unreadThreads = validatedUnreadThreads,
totalEmails = validatedTotalEmails,
totalThreads = validatedTotalThreads)
}
}
case class MailboxValidation(mailboxName: MailboxName,
unreadEmails: UnreadEmails,
unreadThreads: UnreadThreads,
totalEmails: TotalEmails,
totalThreads: TotalThreads)
case class Subscriptions(subscribedNames: Set[MailboxPath]) {
def isSubscribed(name: MailboxPath): IsSubscribed = IsSubscribed(subscribedNames.contains(name))
def isSubscribed(metaData: MailboxMetaData): IsSubscribed = isSubscribed(metaData.getPath)
}
class MailboxFactory @Inject() (mailboxManager: MailboxManager,
namespaceFactory: NamespaceFactory,
sortOrderProvider: SortOrderProvider) {
private def getRole(mailboxPath: MailboxPath, mailboxSession: MailboxSession): Option[Role] = Role.from(mailboxPath.getName)
.filter(_ => mailboxPath.belongsTo(mailboxSession)).toScala
private def getSortOrder(role: Option[Role]): SortOrder = role.map(sortOrderProvider.retrieveSortOrder).getOrElse(SortOrder.defaultSortOrder)
private def getRights(resolveMailboxACL: JavaMailboxACL): Rights = Rights.fromACL(MailboxACL.fromJava(resolveMailboxACL))
private def getNamespace(mailboxPath: MailboxPath, mailboxSession: MailboxSession): MailboxNamespace =
namespaceFactory.from(mailboxPath, mailboxSession)
private def getParentPath(mailboxPath: MailboxPath, mailboxSession: MailboxSession): Option[MailboxPath] = mailboxPath
.getHierarchyLevels(mailboxSession.getPathDelimiter)
.asScala
.reverse
.drop(1)
.headOption
private def aclEntryKey(mailboxSession: MailboxSession): EntryKey = EntryKey.createUserEntryKey(mailboxSession.getUser)
private def getMyRights(mailboxPath: MailboxPath, resolveMailboxACL: JavaMailboxACL, mailboxSession: MailboxSession): MailboxRights = mailboxPath.belongsTo(mailboxSession) match {
case true => MailboxRights.FULL
case false =>
val rights = Rfc4314Rights.fromJava(resolveMailboxACL
.getEntries
.getOrDefault(aclEntryKey(mailboxSession), JavaMailboxACL.NO_RIGHTS))
.toRights
MailboxRights(
mayReadItems = MayReadItems(rights.contains(Right.Read)),
mayAddItems = MayAddItems(rights.contains(Right.Insert)),
mayRemoveItems = MayRemoveItems(rights.contains(Right.DeleteMessages)),
maySetSeen = MaySetSeen(rights.contains(Right.Seen)),
maySetKeywords = MaySetKeywords(rights.contains(Right.Write)),
mayCreateChild = MayCreateChild(false),
mayRename = MayRename(false),
mayDelete = MayDelete(false),
maySubmit = MaySubmit(false))
}
def create(mailboxMetaData: MailboxMetaData,
mailboxSession: MailboxSession,
subscriptions: Subscriptions,
allMailboxesMetadata: Map[MailboxPath, MailboxMetaData],
quotaLoader: QuotaLoader): SMono[Mailbox] = {
val sanitizedCounters: MailboxCounters.Sanitized = mailboxMetaData.getCounters.sanitize()
MailboxValidation.validate(mailboxMetaData.getPath, mailboxSession.getPathDelimiter, sanitizedCounters.getUnseen, sanitizedCounters.getUnseen, sanitizedCounters.getCount, sanitizedCounters.getCount) match {
case Left(error) => SMono.error(error)
case scala.Right(mailboxValidation) =>
SMono.fromPublisher(quotaLoader.getQuotas(mailboxMetaData.getPath))
.map(quotas => {
val id: MailboxId = mailboxMetaData.getId
val role: Option[Role] = getRole(mailboxMetaData.getPath, mailboxSession)
val sortOrder: SortOrder = getSortOrder(role)
val rights: Rights = getRights(mailboxMetaData.getResolvedAcls)
val namespace: MailboxNamespace = getNamespace(mailboxMetaData.getPath, mailboxSession)
val parentPath: Option[MailboxPath] = getParentPath(mailboxMetaData.getPath, mailboxSession)
val parentId: Option[MailboxId] = parentPath.flatMap(path => allMailboxesMetadata.get(path)).map(_.getId)
val myRights: MailboxRights = getMyRights(mailboxMetaData.getPath, mailboxMetaData.getResolvedAcls, mailboxSession)
val isSubscribed: IsSubscribed = subscriptions.isSubscribed(mailboxMetaData)
Mailbox(
id = id,
name = mailboxValidation.mailboxName,
parentId = parentId,
role = role,
sortOrder = sortOrder,
unreadEmails = mailboxValidation.unreadEmails,
totalEmails = mailboxValidation.totalEmails,
unreadThreads = mailboxValidation.unreadThreads,
totalThreads = mailboxValidation.totalThreads,
myRights = myRights,
namespace = namespace,
rights = rights,
quotas = quotas,
isSubscribed = isSubscribed)
})
}
}
def create(id: MailboxId, mailboxSession: MailboxSession, quotaLoader: QuotaLoader, subscriptions: Subscriptions): SMono[Mailbox] =
for {
messageManager <- SMono(mailboxManager.getMailboxReactive(id, mailboxSession))
counters <- SMono(messageManager.getMailboxCountersReactive(mailboxSession))
sanitizedCounters = counters.sanitize()
mailbox <- instanciateMailbox(id, mailboxSession, quotaLoader, messageManager, sanitizedCounters, subscriptions)
} yield {
mailbox
}
private def instanciateMailbox(id: MailboxId, mailboxSession: MailboxSession, quotaLoader: QuotaLoader, messageManager: MessageManager, sanitizedCounters: MailboxCounters.Sanitized, subscriptions: Subscriptions): SMono[Mailbox] = {
try {
MailboxValidation.validate(messageManager.getMailboxPath, mailboxSession.getPathDelimiter, sanitizedCounters.getUnseen, sanitizedCounters.getUnseen, sanitizedCounters.getCount, sanitizedCounters.getCount) match {
case Left(error) => SMono.error(error)
case scala.Right(mailboxValidation) =>
SMono.zip(array => (array(0).asInstanceOf[Option[MailboxId]],
array(1).asInstanceOf[Quotas]),
getParentId(messageManager, mailboxSession),
SMono.fromPublisher(quotaLoader.getQuotas(messageManager.getMailboxPath)))
.map(tuple2 => {
val resolvedACL = messageManager.getResolvedAcl(mailboxSession)
val role: Option[Role] = getRole(messageManager.getMailboxPath, mailboxSession)
val sortOrder: SortOrder = getSortOrder(role)
val rights: Rights = getRights(resolvedACL)
val namespace: MailboxNamespace = getNamespace(messageManager.getMailboxPath, mailboxSession)
val myRights: MailboxRights = getMyRights(messageManager.getMailboxPath, resolvedACL, mailboxSession)
val isSubscribed: IsSubscribed = subscriptions.isSubscribed(messageManager.getMailboxPath)
Mailbox(
id = id,
name = mailboxValidation.mailboxName,
parentId = tuple2._1,
role = role,
sortOrder = sortOrder,
unreadEmails = mailboxValidation.unreadEmails,
totalEmails = mailboxValidation.totalEmails,
unreadThreads = mailboxValidation.unreadThreads,
totalThreads = mailboxValidation.totalThreads,
myRights = myRights,
namespace = namespace,
rights = rights,
quotas = tuple2._2,
isSubscribed = isSubscribed)
})
}
} catch {
case error: Exception => SMono.error(error)
}
}
private def getParentId(messageManager: MessageManager, mailboxSession: MailboxSession): SMono[Option[MailboxId]] =
getParentPath(messageManager.getMailboxPath, mailboxSession)
.map(parentPath => SMono(mailboxManager.getMailboxReactive(parentPath, mailboxSession))
.map(_.getId)
.map(Some(_))
.onErrorResume {
case _: MailboxNotFoundException => SMono.just(None)
case e => SMono.error(e)
})
.getOrElse(SMono.just(None))
}