groupware: add identities of all accounts to the index resource

This commit is contained in:
Pascal Bleser
2025-08-07 23:28:12 +02:00
parent a9e22fc2b8
commit 1309db5e43
5 changed files with 143 additions and 15 deletions

View File

@@ -2,8 +2,10 @@ package jmap
import (
"context"
"strconv"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/rs/zerolog"
)
// https://jmap.io/spec-mail.html#identityget
@@ -20,3 +22,49 @@ func (j *Client) GetIdentity(accountId string, session *Session, ctx context.Con
return response, simpleError(err, JmapErrorInvalidJmapResponsePayload)
})
}
type IdentitiesGetResponse struct {
State string `json:"state"`
Identities map[string][]Identity `json:"identities,omitempty"`
NotFound []string `json:"notFound,omitempty"`
}
func (j *Client) GetIdentities(accountIds []string, session *Session, ctx context.Context, logger *log.Logger) (IdentitiesGetResponse, Error) {
uniqueAccountIds := uniq(accountIds)
logger = j.loggerParams("", "GetIdentities", session, logger, func(l zerolog.Context) zerolog.Context {
return l.Array(logAccountId, logstrarray(uniqueAccountIds))
})
calls := make([]Invocation, len(uniqueAccountIds))
for i, accountId := range uniqueAccountIds {
calls[i] = invocation(IdentityGet, IdentityGetCommand{AccountId: accountId}, strconv.Itoa(i))
}
cmd, err := request(calls...)
if err != nil {
return IdentitiesGetResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (IdentitiesGetResponse, Error) {
identities := make(map[string][]Identity, len(uniqueAccountIds))
lastState := ""
notFound := []string{}
for i, accountId := range uniqueAccountIds {
var response IdentityGetResponse
err = retrieveResponseMatchParameters(body, IdentityGet, strconv.Itoa(i), &response)
if err != nil {
return IdentitiesGetResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
} else {
identities[accountId] = response.List
}
lastState = response.State
notFound = append(notFound, response.NotFound...)
}
return IdentitiesGetResponse{
Identities: identities,
State: lastState,
NotFound: uniq(notFound),
}, nil
})
}

View File

@@ -1568,7 +1568,7 @@ type IdentityGetResponse struct {
AccountId string `json:"accountId"`
State string `json:"state"`
List []Identity `json:"list,omitempty"`
NotFound []any `json:"notFound,omitempty"`
NotFound []string `json:"notFound,omitempty"`
}
type VacationResponseGetCommand struct {

View File

@@ -10,6 +10,7 @@ import (
"github.com/mitchellh/mapstructure"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/rs/zerolog"
)
type eventListeners[T any] struct {
@@ -222,3 +223,47 @@ func (i *Invocation) UnmarshalJSON(bs []byte) error {
i.Parameters = params
return nil
}
const logMaxStrLength = 1024
// Safely caps a string to a given size to avoid log bombing.
// Use this function to wrap strings that are user input (HTTP headers, path parameters, URI parameters, HTTP body, ...).
func logstr(text string) string {
runes := []rune(text)
if len(runes) <= logMaxStrLength {
return text
} else {
return string(runes[0:logMaxStrLength-1]) + `\u2026` // hellip
}
}
type SafeLogStringArrayMarshaller struct {
array []string
}
func (m SafeLogStringArrayMarshaller) MarshalZerologArray(a *zerolog.Array) {
for _, elem := range m.array {
a.Str(logstr(elem))
}
}
var _ zerolog.LogArrayMarshaler = SafeLogStringArrayMarshaller{}
func logstrarray(array []string) SafeLogStringArrayMarshaller {
return SafeLogStringArrayMarshaller{array: array}
}
func uniq[T comparable](ary []T) []T {
m := map[T]bool{}
for _, v := range ary {
m[v] = true
}
set := make([]T, len(m))
i := 0
for v := range m {
set[i] = v
i++
}
return set
}

View File

@@ -2,6 +2,8 @@ package groupware
import (
"net/http"
"github.com/opencloud-eu/opencloud/pkg/jmap"
)
type IndexLimits struct {
@@ -37,6 +39,7 @@ type IndexAccount struct {
IsPersonal bool `json:"isPersonal"`
IsReadOnly bool `json:"isReadOnly"`
Capabilities IndexAccountCapabilities `json:"capabilities"`
Identities []jmap.Identity `json:"identities,omitempty"`
}
type IndexPrimaryAccounts struct {
@@ -69,28 +72,46 @@ type SwaggerIndexResponse struct {
// 200: IndexResponse
func (g Groupware) Index(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountIds := make([]string, len(req.session.Accounts))
i := 0
for k := range req.session.Accounts {
accountIds[i] = k
i++
}
accountIds = uniq(accountIds)
identitiesResponse, err := g.jmap.GetIdentities(accountIds, req.session, req.ctx, req.logger)
if err != nil {
return req.errorResponseFromJmap(err)
}
accounts := make(map[string]IndexAccount, len(req.session.Accounts))
for i, a := range req.session.Accounts {
accounts[i] = IndexAccount{
Name: a.Name,
IsPersonal: a.IsPersonal,
IsReadOnly: a.IsReadOnly,
for accountId, account := range req.session.Accounts {
indexAccount := IndexAccount{
Name: account.Name,
IsPersonal: account.IsPersonal,
IsReadOnly: account.IsReadOnly,
Capabilities: IndexAccountCapabilities{
Mail: IndexAccountMailCapabilities{
MaxMailboxDepth: a.AccountCapabilities.Mail.MaxMailboxDepth,
MaxSizeMailboxName: a.AccountCapabilities.Mail.MaxSizeMailboxName,
MaxSizeAttachmentsPerEmail: a.AccountCapabilities.Mail.MaxSizeAttachmentsPerEmail,
MayCreateTopLevelMailbox: a.AccountCapabilities.Mail.MayCreateTopLevelMailbox,
MaxDelayedSend: a.AccountCapabilities.Submission.MaxDelayedSend,
MaxMailboxDepth: account.AccountCapabilities.Mail.MaxMailboxDepth,
MaxSizeMailboxName: account.AccountCapabilities.Mail.MaxSizeMailboxName,
MaxSizeAttachmentsPerEmail: account.AccountCapabilities.Mail.MaxSizeAttachmentsPerEmail,
MayCreateTopLevelMailbox: account.AccountCapabilities.Mail.MayCreateTopLevelMailbox,
MaxDelayedSend: account.AccountCapabilities.Submission.MaxDelayedSend,
},
Sieve: IndexAccountSieveCapabilities{
MaxSizeScriptName: a.AccountCapabilities.Sieve.MaxSizeScript,
MaxSizeScript: a.AccountCapabilities.Sieve.MaxSizeScript,
MaxNumberScripts: a.AccountCapabilities.Sieve.MaxNumberScripts,
MaxNumberRedirects: a.AccountCapabilities.Sieve.MaxNumberRedirects,
MaxSizeScriptName: account.AccountCapabilities.Sieve.MaxSizeScript,
MaxSizeScript: account.AccountCapabilities.Sieve.MaxSizeScript,
MaxNumberScripts: account.AccountCapabilities.Sieve.MaxNumberScripts,
MaxNumberRedirects: account.AccountCapabilities.Sieve.MaxNumberRedirects,
},
},
}
if identity, ok := identitiesResponse.Identities[accountId]; ok {
indexAccount.Identities = identity
}
accounts[accountId] = indexAccount
}
return response(IndexResponse{

View File

@@ -574,3 +574,17 @@ func (g Groupware) NotFound(w http.ResponseWriter, r *http.Request) {
render.Status(r, http.StatusNotFound)
w.WriteHeader(http.StatusNotFound)
}
func uniq[T comparable](ary []T) []T {
m := map[T]bool{}
for _, v := range ary {
m[v] = true
}
set := make([]T, len(m))
i := 0
for v := range m {
set[i] = v
i++
}
return set
}