mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-02-14 16:21:18 -05:00
fix(groupware): fix JMAP error handling
* the JMAP error handling was not working properly, fixed it and added error definitions accordingly * add operations to retrieve mailbox roles and mailboxes by role for all accounts
This commit is contained in:
@@ -2,6 +2,7 @@ package jmap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/pkg/structs"
|
||||
@@ -15,41 +16,27 @@ type MailboxesResponse struct {
|
||||
}
|
||||
|
||||
// https://jmap.io/spec-mail.html#mailboxget
|
||||
func (j *Client) GetMailbox(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, ids []string) (map[string]MailboxesResponse, SessionState, Error) {
|
||||
func (j *Client) GetMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, ids []string) (MailboxesResponse, SessionState, Error) {
|
||||
logger = j.logger("GetMailbox", session, logger)
|
||||
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
n := len(uniqueAccountIds)
|
||||
if n < 1 {
|
||||
return map[string]MailboxesResponse{}, "", nil
|
||||
}
|
||||
|
||||
invocations := make([]Invocation, n)
|
||||
for i, accountId := range uniqueAccountIds {
|
||||
invocations[i] = invocation(CommandMailboxGet, MailboxGetCommand{AccountId: accountId, Ids: ids}, mcid(accountId, "0"))
|
||||
}
|
||||
|
||||
cmd, err := j.request(session, logger, invocations...)
|
||||
cmd, err := j.request(session, logger,
|
||||
invocation(CommandMailboxGet, MailboxGetCommand{AccountId: accountId, Ids: ids}, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return map[string]MailboxesResponse{}, "", err
|
||||
return MailboxesResponse{}, "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (map[string]MailboxesResponse, Error) {
|
||||
resp := map[string]MailboxesResponse{}
|
||||
for _, accountId := range uniqueAccountIds {
|
||||
var response MailboxGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "0"), &response)
|
||||
if err != nil {
|
||||
return map[string]MailboxesResponse{}, err
|
||||
}
|
||||
|
||||
resp[accountId] = MailboxesResponse{
|
||||
Mailboxes: response.List,
|
||||
NotFound: response.NotFound,
|
||||
State: response.State,
|
||||
}
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (MailboxesResponse, Error) {
|
||||
var response MailboxGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, "0", &response)
|
||||
if err != nil {
|
||||
return MailboxesResponse{}, err
|
||||
}
|
||||
return resp, nil
|
||||
return MailboxesResponse{
|
||||
Mailboxes: response.List,
|
||||
NotFound: response.NotFound,
|
||||
State: response.State,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -59,20 +46,40 @@ type AllMailboxesResponse struct {
|
||||
}
|
||||
|
||||
func (j *Client) GetAllMailboxes(accountIds []string, session *Session, ctx context.Context, logger *log.Logger) (map[string]AllMailboxesResponse, SessionState, Error) {
|
||||
resp, sessionState, err := j.GetMailbox(accountIds, session, ctx, logger, nil)
|
||||
logger = j.logger("GetAllMailboxes", session, logger)
|
||||
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
n := len(uniqueAccountIds)
|
||||
if n < 1 {
|
||||
return map[string]AllMailboxesResponse{}, "", nil
|
||||
}
|
||||
|
||||
invocations := make([]Invocation, n)
|
||||
for i, accountId := range uniqueAccountIds {
|
||||
invocations[i] = invocation(CommandMailboxGet, MailboxGetCommand{AccountId: accountId}, mcid(accountId, "0"))
|
||||
}
|
||||
|
||||
cmd, err := j.request(session, logger, invocations...)
|
||||
if err != nil {
|
||||
return map[string]AllMailboxesResponse{}, sessionState, err
|
||||
return map[string]AllMailboxesResponse{}, "", err
|
||||
}
|
||||
|
||||
mapped := make(map[string]AllMailboxesResponse, len(resp))
|
||||
for accountId, mailboxesResponse := range resp {
|
||||
mapped[accountId] = AllMailboxesResponse{
|
||||
Mailboxes: mailboxesResponse.Mailboxes,
|
||||
State: mailboxesResponse.State,
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (map[string]AllMailboxesResponse, Error) {
|
||||
resp := map[string]AllMailboxesResponse{}
|
||||
for _, accountId := range uniqueAccountIds {
|
||||
var response MailboxGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "0"), &response)
|
||||
if err != nil {
|
||||
return map[string]AllMailboxesResponse{}, err
|
||||
}
|
||||
|
||||
resp[accountId] = AllMailboxesResponse{
|
||||
Mailboxes: response.List,
|
||||
State: response.State,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mapped, sessionState, nil
|
||||
return resp, nil
|
||||
})
|
||||
}
|
||||
|
||||
type Mailboxes struct {
|
||||
@@ -92,7 +99,11 @@ func (j *Client) SearchMailboxes(accountIds []string, session *Session, ctx cont
|
||||
invocations[i*2+0] = invocation(CommandMailboxQuery, MailboxQueryCommand{AccountId: accountId, Filter: filter}, mcid(accountId, "0"))
|
||||
invocations[i*2+1] = invocation(CommandMailboxGet, MailboxGetRefCommand{
|
||||
AccountId: accountId,
|
||||
IdRef: &ResultReference{Name: CommandMailboxQuery, Path: "/ids/*", ResultOf: mcid(accountId, "0")},
|
||||
IdsRef: &ResultReference{
|
||||
Name: CommandMailboxQuery,
|
||||
Path: "/ids/*",
|
||||
ResultOf: mcid(accountId, "0"),
|
||||
},
|
||||
}, mcid(accountId, "1"))
|
||||
}
|
||||
cmd, err := j.request(session, logger, invocations...)
|
||||
@@ -288,3 +299,56 @@ func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, sessi
|
||||
return resp, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger) (map[string][]string, SessionState, Error) {
|
||||
logger = j.logger("GetMailboxRolesForMultipleAccounts", session, logger)
|
||||
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
n := len(uniqueAccountIds)
|
||||
if n < 1 {
|
||||
return map[string][]string{}, "", nil
|
||||
}
|
||||
|
||||
t := true
|
||||
|
||||
invocations := make([]Invocation, n*2)
|
||||
for i, accountId := range uniqueAccountIds {
|
||||
invocations[i*2+0] = invocation(CommandMailboxQuery, MailboxQueryCommand{
|
||||
AccountId: accountId,
|
||||
Filter: MailboxFilterCondition{
|
||||
HasAnyRole: &t,
|
||||
},
|
||||
}, mcid(accountId, "0"))
|
||||
invocations[i*2+1] = invocation(CommandMailboxGet, MailboxGetRefCommand{
|
||||
AccountId: accountId,
|
||||
IdsRef: &ResultReference{
|
||||
ResultOf: mcid(accountId, "0"),
|
||||
Name: CommandMailboxQuery,
|
||||
Path: "/ids",
|
||||
},
|
||||
}, mcid(accountId, "1"))
|
||||
}
|
||||
|
||||
cmd, err := j.request(session, logger, invocations...)
|
||||
if err != nil {
|
||||
return map[string][]string{}, "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (map[string][]string, Error) {
|
||||
resp := make(map[string][]string, n)
|
||||
for _, accountId := range uniqueAccountIds {
|
||||
var getResponse MailboxGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "1"), &getResponse)
|
||||
if err != nil {
|
||||
return map[string][]string{}, err
|
||||
}
|
||||
roles := make([]string, len(getResponse.List))
|
||||
for i, mailbox := range getResponse.List {
|
||||
roles[i] = mailbox.Role
|
||||
}
|
||||
slices.Sort(roles)
|
||||
resp[accountId] = roles
|
||||
}
|
||||
return resp, nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -18,9 +18,18 @@ const (
|
||||
JmapErrorInvalidSessionResponse
|
||||
JmapErrorInvalidJmapRequestPayload
|
||||
JmapErrorInvalidJmapResponsePayload
|
||||
JmapErrorMethodLevel
|
||||
JmapErrorSetError
|
||||
JmapErrorTooManyMethodCalls
|
||||
JmapErrorUnspecifiedType
|
||||
JmapErrorServerUnavailable
|
||||
JmapErrorServerFail
|
||||
JmapErrorUnknownMethod
|
||||
JmapErrorInvalidArguments
|
||||
JmapErrorInvalidResultReference
|
||||
JmapErrorForbidden
|
||||
JmapErrorAccountNotFound
|
||||
JmapErrorAccountNotSupportedByMethod
|
||||
JmapErrorAccountReadOnly
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -27,6 +27,28 @@ const (
|
||||
JmapKeywordJunk = "$junk"
|
||||
JmapKeywordNotJunk = "$notjunk"
|
||||
JmapKeywordMdnSent = "$mdnsent"
|
||||
|
||||
// https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml
|
||||
//JmapMailboxRoleAll = "all"
|
||||
//JmapMailboxRoleArchive = "archive"
|
||||
JmapMailboxRoleDrafts = "drafts"
|
||||
//JmapMailboxRoleFlagged = "flagged"
|
||||
//JmapMailboxRoleImportant = "important"
|
||||
JmapMailboxRoleInbox = "inbox"
|
||||
JmapMailboxRoleJunk = "junk"
|
||||
JmapMailboxRoleSent = "sent"
|
||||
//JmapMailboxRoleSubscribed = "subscribed"
|
||||
JmapMailboxRoleTrash = "trash"
|
||||
)
|
||||
|
||||
var (
|
||||
JmapMailboxRoles = []string{
|
||||
JmapMailboxRoleInbox,
|
||||
JmapMailboxRoleSent,
|
||||
JmapMailboxRoleDrafts,
|
||||
JmapMailboxRoleJunk,
|
||||
JmapMailboxRoleTrash,
|
||||
}
|
||||
)
|
||||
|
||||
type SessionMailAccountCapabilities struct {
|
||||
@@ -352,6 +374,52 @@ type SessionResponse struct {
|
||||
State SessionState `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
// Method level error types.
|
||||
const (
|
||||
// Some internal server resource was temporarily unavailable.
|
||||
//
|
||||
// Attempting the same operation later (perhaps after a backoff with a random factor) may succeed.
|
||||
MethodLevelErrorServerUnavailable = "serverUnavailable"
|
||||
|
||||
// An unexpected or unknown error occurred during the processing of the call.
|
||||
//
|
||||
// A description property should provide more details about the error. The method call made no changes
|
||||
// to the server’s state. Attempting the same operation again is expected to fail again.
|
||||
// Contacting the service administrator is likely necessary to resolve this problem if it is persistent.
|
||||
MethodLevelErrorServerFail = "serverFail"
|
||||
|
||||
// Some, but not all, expected changes described by the method occurred.
|
||||
//
|
||||
// The client MUST resynchronise impacted data to determine server state. Use of this error is strongly discouraged.
|
||||
MethodLevelErrorServerPartialFail = "serverPartialFail"
|
||||
|
||||
// The server does not recognise this method name.
|
||||
MethodLevelErrorUnknownMethod = "unknownMethod"
|
||||
|
||||
// One of the arguments is of the wrong type or is otherwise invalid, or a required argument is missing.
|
||||
//
|
||||
// A description property MAY be present to help debug with an explanation of what the problem was.
|
||||
// This is a non-localised string, and it is not intended to be shown directly to end users.
|
||||
MethodLevelErrorInvalidArguments = "invalidArguments"
|
||||
|
||||
// The method used a result reference for one of its arguments, but this failed to resolve.
|
||||
MethodLevelErrorInvalidResultReference = "invalidResultReference"
|
||||
|
||||
// The method and arguments are valid, but executing the method would violate an Access Control List
|
||||
// (ACL) or other permissions policy.
|
||||
MethodLevelErrorForbidden = "forbidden"
|
||||
|
||||
// The accountId does not correspond to a valid account.
|
||||
MethodLevelErrorAccountNotFound = "accountNotFound"
|
||||
|
||||
// The accountId given corresponds to a valid account, but the account does not support this method or data type.
|
||||
MethodLevelErrorAccountNotSupportedByMethod = "accountNotSupportedByMethod"
|
||||
|
||||
// This method modifies state, but the account is read-only (as returned on the corresponding Account object in
|
||||
// the JMAP Session resource).
|
||||
MethodLevelErrorAccountReadOnly = "accountReadOnly"
|
||||
)
|
||||
|
||||
// SetError type values.
|
||||
const (
|
||||
// The create/update/destroy would violate an ACL or other permissions policy.
|
||||
@@ -648,7 +716,7 @@ type MailboxGetCommand struct {
|
||||
|
||||
type MailboxGetRefCommand struct {
|
||||
AccountId string `json:"accountId"`
|
||||
IdRef *ResultReference `json:"#ids,omitempty"`
|
||||
IdsRef *ResultReference `json:"#ids,omitempty"`
|
||||
}
|
||||
|
||||
type MailboxChangesCommand struct {
|
||||
@@ -2654,7 +2722,13 @@ type SearchSnippetGetResponse struct {
|
||||
NotFound []string `json:"notFound,omitempty"`
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
ErrorCommand Command = "error" // only occurs in responses
|
||||
CommandBlobGet Command = "Blob/get"
|
||||
CommandBlobUpload Command = "Blob/upload"
|
||||
CommandEmailGet Command = "Email/get"
|
||||
@@ -2675,6 +2749,7 @@ const (
|
||||
)
|
||||
|
||||
var CommandResponseTypeMap = map[Command]func() any{
|
||||
ErrorCommand: func() any { return ErrorResponse{} },
|
||||
CommandBlobGet: func() any { return BlobGetResponse{} },
|
||||
CommandBlobUpload: func() any { return BlobUploadResponse{} },
|
||||
CommandMailboxQuery: func() any { return MailboxQueryResponse{} },
|
||||
|
||||
@@ -3,9 +3,11 @@ package jmap
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -66,7 +68,7 @@ func command[T any](api ApiClient,
|
||||
var response Response
|
||||
err := json.Unmarshal(responseBody, &response)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("failed to deserialize body JSON payload")
|
||||
logger.Error().Err(err).Msgf("failed to deserialize body JSON payload into a %T", response)
|
||||
var zero T
|
||||
return zero, "", SimpleError{code: JmapErrorDecodingResponseBody, err: err}
|
||||
}
|
||||
@@ -80,15 +82,50 @@ func command[T any](api ApiClient,
|
||||
// search for an "error" response
|
||||
// https://jmap.io/spec-core.html#method-level-errors
|
||||
for _, mr := range response.MethodResponses {
|
||||
if mr.Command == "error" {
|
||||
err := fmt.Errorf("found method level error in response '%v'", mr.Tag)
|
||||
if payload, ok := mr.Parameters.(map[string]any); ok {
|
||||
if errorType, ok := payload["type"]; ok {
|
||||
err = fmt.Errorf("found method level error in response '%v', type: '%v'", mr.Tag, errorType)
|
||||
if mr.Command == ErrorCommand {
|
||||
if errorParameters, ok := mr.Parameters.(ErrorResponse); ok {
|
||||
code := JmapErrorServerFail
|
||||
switch errorParameters.Type {
|
||||
case MethodLevelErrorServerUnavailable:
|
||||
code = JmapErrorServerUnavailable
|
||||
case MethodLevelErrorServerFail, MethodLevelErrorServerPartialFail:
|
||||
code = JmapErrorServerFail
|
||||
case MethodLevelErrorUnknownMethod:
|
||||
code = JmapErrorUnknownMethod
|
||||
case MethodLevelErrorInvalidArguments:
|
||||
code = JmapErrorInvalidArguments
|
||||
case MethodLevelErrorInvalidResultReference:
|
||||
code = JmapErrorInvalidResultReference
|
||||
case MethodLevelErrorForbidden:
|
||||
// there's a quirk here: when referencing an account that exists but that this
|
||||
// user has no access to, Stalwart returns the 'forbidden' error, but this might
|
||||
// leak the existence of an account to an attacker -- instead, we deem it safer to
|
||||
// return a "account does not exist" error instead
|
||||
if strings.HasPrefix(errorParameters.Description, "You do not have access to account") {
|
||||
code = JmapErrorAccountNotFound
|
||||
} else {
|
||||
code = JmapErrorForbidden
|
||||
}
|
||||
case MethodLevelErrorAccountNotFound:
|
||||
code = JmapErrorAccountNotFound
|
||||
case MethodLevelErrorAccountNotSupportedByMethod:
|
||||
code = JmapErrorAccountNotSupportedByMethod
|
||||
case MethodLevelErrorAccountReadOnly:
|
||||
code = JmapErrorAccountReadOnly
|
||||
}
|
||||
msg := fmt.Sprintf("found method level error in response '%v', type: '%v', description: '%v'", mr.Tag, errorParameters.Type, errorParameters.Description)
|
||||
err = errors.New(msg)
|
||||
logger.Warn().Int("code", code).Str("type", errorParameters.Type).Msg(msg)
|
||||
var zero T
|
||||
return zero, response.SessionState, SimpleError{code: code, err: err}
|
||||
} else {
|
||||
code := JmapErrorUnspecifiedType
|
||||
msg := fmt.Sprintf("found method level error in response '%v'", mr.Tag)
|
||||
err := errors.New(msg)
|
||||
logger.Warn().Int("code", code).Msg(msg)
|
||||
var zero T
|
||||
return zero, response.SessionState, SimpleError{code: code, err: err}
|
||||
}
|
||||
var zero T
|
||||
return zero, response.SessionState, SimpleError{code: JmapErrorMethodLevel, err: err}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,3 +134,19 @@ func TestMarshallingUnknown(t *testing.T) {
|
||||
require.NoError(err)
|
||||
require.Equal(`{"subject":"aaa","bodyStructure":{"header:a":"bc","header:x":"yz","partId":"b","type":"a"}}`, string(result))
|
||||
}
|
||||
|
||||
func TestUnmarshallingError(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
responseBody := `{"methodResponses":[["error",{"type":"forbidden","description":"You do not have access to account a"},"a:0"]],"sessionState":"3e25b2a0"}`
|
||||
var response Response
|
||||
err := json.Unmarshal([]byte(responseBody), &response)
|
||||
require.NoError(err)
|
||||
require.Len(response.MethodResponses, 1)
|
||||
require.Equal(ErrorCommand, response.MethodResponses[0].Command)
|
||||
require.Equal("a:0", response.MethodResponses[0].Tag)
|
||||
require.IsType(ErrorResponse{}, response.MethodResponses[0].Parameters)
|
||||
er, _ := response.MethodResponses[0].Parameters.(ErrorResponse)
|
||||
require.Equal("forbidden", er.Type)
|
||||
require.Equal("You do not have access to account a", er.Description)
|
||||
}
|
||||
|
||||
@@ -41,13 +41,12 @@ func (g *Groupware) GetMailbox(w http.ResponseWriter, r *http.Request) {
|
||||
return errorResponse(err)
|
||||
}
|
||||
|
||||
mailboxesByAccountId, sessionState, jerr := g.jmap.GetMailbox([]string{accountId}, req.session, req.ctx, req.logger, []string{mailboxId})
|
||||
mailboxes, sessionState, jerr := g.jmap.GetMailbox(accountId, req.session, req.ctx, req.logger, []string{mailboxId})
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
mailboxes, ok := mailboxesByAccountId[accountId]
|
||||
if ok && len(mailboxes.Mailboxes) == 1 {
|
||||
if len(mailboxes.Mailboxes) == 1 {
|
||||
return etagResponse(mailboxes.Mailboxes[0], sessionState, mailboxes.State)
|
||||
} else {
|
||||
return notFoundResponse(sessionState)
|
||||
@@ -209,6 +208,27 @@ func (g *Groupware) GetMailboxesForAllAccounts(w http.ResponseWriter, r *http.Re
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) GetMailboxByRoleForAllAccounts(w http.ResponseWriter, r *http.Request) {
|
||||
role := chi.URLParam(r, UriParamRole)
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountIds := structs.Keys(req.session.Accounts)
|
||||
if len(accountIds) < 1 {
|
||||
return noContentResponse("")
|
||||
}
|
||||
logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds)).Str("role", role))
|
||||
|
||||
filter := jmap.MailboxFilterCondition{
|
||||
Role: role,
|
||||
}
|
||||
|
||||
mailboxesByAccountId, sessionState, err := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, filter)
|
||||
if err != nil {
|
||||
return req.errorResponseFromJmap(err)
|
||||
}
|
||||
return response(mailboxesByAccountId, sessionState)
|
||||
})
|
||||
}
|
||||
|
||||
// When the request succeeds.
|
||||
// swagger:response MailboxChangesResponse200
|
||||
type SwaggerMailboxChangesResponse200 struct {
|
||||
@@ -308,3 +328,19 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht
|
||||
return response(changesByAccountId, sessionState)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) GetMailboxRoles(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
l := req.logger.With()
|
||||
allAccountIds := structs.Keys(req.session.Accounts) // TODO(pbleser-oc) do we need a limit for a maximum amount of accounts to query at once?
|
||||
l.Array(logAccountId, log.SafeStringArray(allAccountIds))
|
||||
logger := log.From(l)
|
||||
|
||||
rolesByAccountId, sessionState, jerr := g.jmap.GetMailboxRolesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger)
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
return response(rolesByAccountId, sessionState)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -138,6 +138,20 @@ func groupwareErrorFromJmap(j jmap.Error) *GroupwareError {
|
||||
return &ErrorInvalidRequestPayload
|
||||
case jmap.JmapErrorInvalidJmapResponsePayload:
|
||||
return &ErrorInvalidResponsePayload
|
||||
case jmap.JmapErrorUnspecifiedType, jmap.JmapErrorUnknownMethod, jmap.JmapErrorInvalidArguments, jmap.JmapErrorInvalidResultReference:
|
||||
return &ErrorInvalidGroupwareRequest
|
||||
case jmap.JmapErrorServerUnavailable:
|
||||
return &ErrorServerUnavailable
|
||||
case jmap.JmapErrorServerFail:
|
||||
return &ErrorServerFailure
|
||||
case jmap.JmapErrorForbidden:
|
||||
return &ErrorForbiddenOperation
|
||||
case jmap.JmapErrorAccountNotFound:
|
||||
return &ErrorAccountNotFound
|
||||
case jmap.JmapErrorAccountNotSupportedByMethod:
|
||||
return &ErrorAccountNotSupportedByMethod
|
||||
case jmap.JmapErrorAccountReadOnly:
|
||||
return &ErrorAccountReadOnly
|
||||
default:
|
||||
return &ErrorGeneric
|
||||
}
|
||||
@@ -167,6 +181,13 @@ const (
|
||||
ErrorCodeInvalidUserRequest = "INVURQ"
|
||||
ErrorCodeUsernameEmailDomainNotGreenListed = "UEDGRE"
|
||||
ErrorCodeUsernameEmailDomainRedListed = "UEDRED"
|
||||
ErrorCodeInvalidGroupwareRequest = "GPRERR"
|
||||
ErrorCodeServerUnavailable = "SRVUNA"
|
||||
ErrorCodeServerFailure = "SRVFLR"
|
||||
ErrorCodeForbiddenOperation = "FRBOPR"
|
||||
ErrorCodeAccountNotFound = "ACCNFD"
|
||||
ErrorCodeAccountNotSupportedByMethod = "ACCNSM"
|
||||
ErrorCodeAccountReadOnly = "ACCRDO"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -308,6 +329,48 @@ var (
|
||||
Title: "Domain is redlisted",
|
||||
Detail: "The username email address domain is redlisted.",
|
||||
}
|
||||
ErrorInvalidGroupwareRequest = GroupwareError{
|
||||
Status: http.StatusInternalServerError,
|
||||
Code: ErrorCodeInvalidGroupwareRequest,
|
||||
Title: "Internal Request Error",
|
||||
Detail: "The request constructed by the Groupware is regarded as invalid by the Mail server.",
|
||||
}
|
||||
ErrorServerUnavailable = GroupwareError{
|
||||
Status: http.StatusServiceUnavailable,
|
||||
Code: ErrorCodeServerUnavailable,
|
||||
Title: "Mail Server is unavailable",
|
||||
Detail: "The Mail Server is currently unable to process the request.",
|
||||
}
|
||||
ErrorServerFailure = GroupwareError{
|
||||
Status: http.StatusInternalServerError,
|
||||
Code: ErrorCodeServerFailure,
|
||||
Title: "Mail Server is unable to process the Request",
|
||||
Detail: "The Mail Server is unable to process the request.",
|
||||
}
|
||||
ErrorForbiddenOperation = GroupwareError{
|
||||
Status: http.StatusForbidden,
|
||||
Code: ErrorCodeForbiddenOperation,
|
||||
Title: "The Operation is forbidden by the Mail Server",
|
||||
Detail: "The Mail Server refuses to perform the request.",
|
||||
}
|
||||
ErrorAccountNotFound = GroupwareError{
|
||||
Status: http.StatusNotFound,
|
||||
Code: ErrorCodeAccountNotFound,
|
||||
Title: "The referenced Account does not exist",
|
||||
Detail: "The Account that was referenced in the request does not exist.",
|
||||
}
|
||||
ErrorAccountNotSupportedByMethod = GroupwareError{
|
||||
Status: http.StatusForbidden,
|
||||
Code: ErrorCodeAccountNotSupportedByMethod,
|
||||
Title: "The referenced Account does not supported the requested method",
|
||||
Detail: "The Account that was referenced in the request does not supported the requested method or data type.",
|
||||
}
|
||||
ErrorAccountReadOnly = GroupwareError{
|
||||
Status: http.StatusForbidden,
|
||||
Code: ErrorCodeAccountReadOnly,
|
||||
Title: "The referenced Account is read-only",
|
||||
Detail: "The Account that was referenced in the request only supports read-only operations.",
|
||||
}
|
||||
)
|
||||
|
||||
type ErrorOpt interface {
|
||||
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
UriParamBlobId = "blobid"
|
||||
UriParamBlobName = "blobname"
|
||||
UriParamStreamId = "stream"
|
||||
UriParamRole = "role"
|
||||
QueryParamMailboxSearchName = "name"
|
||||
QueryParamMailboxSearchRole = "role"
|
||||
QueryParamMailboxSearchSubscribed = "subscribed"
|
||||
@@ -48,8 +49,10 @@ func (g *Groupware) Route(r chi.Router) {
|
||||
r.Get("/accounts", g.GetAccounts)
|
||||
r.Route("/accounts/all", func(r chi.Router) {
|
||||
r.Route("/mailboxes", func(r chi.Router) {
|
||||
r.Get("/", g.GetMailboxesForAllAccounts)
|
||||
r.Get("/", g.GetMailboxesForAllAccounts) // ?role=
|
||||
r.Get("/changes", g.GetMailboxChangesForAllAccounts)
|
||||
r.Get("/roles", g.GetMailboxRoles) // ?role=
|
||||
r.Get("/roles/{role}", g.GetMailboxByRoleForAllAccounts) // ?role=
|
||||
})
|
||||
})
|
||||
r.Route("/accounts/{accountid}", func(r chi.Router) {
|
||||
|
||||
Reference in New Issue
Block a user