fix mailbox user not found handling

This commit is contained in:
ryanfkeepers 2024-01-30 14:41:37 -07:00
parent 8bdf86bbad
commit a915cd1dc1
5 changed files with 135 additions and 52 deletions

View File

@ -23,7 +23,8 @@ func IsServiceEnabled(
) (bool, error) { ) (bool, error) {
_, err := gmi.GetMailInbox(ctx, resource) _, err := gmi.GetMailInbox(ctx, resource)
if err != nil { if err != nil {
if err := api.EvaluateMailboxError(err); err != nil { ignorable := api.IsMailboxErrorIgnorable(err)
if !ignorable {
logger.CtxErr(ctx, err).Error("getting user's mail folder") logger.CtxErr(ctx, err).Error("getting user's mail folder")
return false, clues.Stack(err) return false, clues.Stack(err)
} }
@ -54,10 +55,10 @@ func GetMailboxInfo(
// First check whether the user is able to access their inbox. // First check whether the user is able to access their inbox.
inbox, err := gmb.GetMailInbox(ctx, userID) inbox, err := gmb.GetMailInbox(ctx, userID)
if err != nil { if err != nil {
if err := api.EvaluateMailboxError(clues.Stack(err)); err != nil { ignorable := api.IsMailboxErrorIgnorable(clues.Stack(err))
if !ignorable {
logger.CtxErr(ctx, err).Error("getting user's mail folder") logger.CtxErr(ctx, err).Error("getting user's mail folder")
return mi, clues.Stack(err)
return mi, err
} }
logger.Ctx(ctx).Info("resource owner does not have a mailbox enabled") logger.Ctx(ctx).Info("resource owner does not have a mailbox enabled")

View File

@ -127,6 +127,8 @@ var (
// error categorization // error categorization
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
var ErrUserNotFound = clues.New("user not found")
func stackWithCoreErr(ctx context.Context, err error, traceDepth int) error { func stackWithCoreErr(ctx context.Context, err error, traceDepth int) error {
if err == nil { if err == nil {
return nil return nil
@ -141,7 +143,10 @@ func stackWithCoreErr(ctx context.Context, err error, traceDepth int) error {
case isErrApplicationThrottled(ode, err): case isErrApplicationThrottled(ode, err):
err = clues.Stack(core.ErrApplicationThrottled, err) err = clues.Stack(core.ErrApplicationThrottled, err)
case isErrUserNotFound(ode, err): case isErrUserNotFound(ode, err):
err = clues.Stack(core.ErrNotFound, err) // stack both userNotFound and notFound to ensure some graph api
// internals can distinguish between the two cases (where possible).
// layers above the api should still handle only the core notFound.
err = clues.Stack(ErrUserNotFound, core.ErrNotFound, err)
case isErrResourceLocked(ode, err): case isErrResourceLocked(ode, err):
err = clues.Stack(core.ErrResourceNotAccessible, err) err = clues.Stack(core.ErrResourceNotAccessible, err)
case isErrInsufficientAuthorization(ode, err): case isErrInsufficientAuthorization(ode, err):

View File

@ -1111,7 +1111,6 @@ func (suite *GraphErrorsUnitSuite) TestToErrByRespCode() {
for _, test := range table { for _, test := range table {
suite.Run(test.name, func() { suite.Run(test.name, func() {
t := suite.T() t := suite.T()
err := toErrByRespCode(parseODataErr(test.err), test.err) err := toErrByRespCode(parseODataErr(test.err), test.err)
if test.expectNoStack { if test.expectNoStack {
@ -1122,3 +1121,96 @@ func (suite *GraphErrorsUnitSuite) TestToErrByRespCode() {
}) })
} }
} }
func (suite *GraphErrorsUnitSuite) TestIsErrItemAlreadyExists() {
table := []struct {
name string
err error
expect assert.BoolAssertionFunc
}{
{
name: "nil",
err: nil,
expect: assert.False,
},
{
name: "non-matching",
err: assert.AnError,
expect: assert.False,
},
{
name: "non-matching oDataErr",
err: graphTD.ODataErrWithMsg("InvalidRequest", "item already exists"),
expect: assert.False,
},
{
name: "matching oDataErr code",
err: graphTD.ODataInner(string(nameAlreadyExists)),
expect: assert.True,
},
}
for _, test := range table {
suite.Run(test.name, func() {
ode := parseODataErr(test.err)
test.expect(suite.T(), isErrItemAlreadyExists(ode, test.err))
})
}
}
func (suite *GraphErrorsUnitSuite) TestStackWithCoreErr() {
table := []struct {
name string
err error
expect []error
}{
{
name: "bad jwt",
err: graphTD.ODataErr(string(invalidAuthenticationToken)),
expect: []error{core.ErrAuthTokenExpired},
},
{
name: "throttled",
err: graphTD.ODataErr(string(ApplicationThrottled)),
expect: []error{core.ErrApplicationThrottled},
},
{
name: "user not found",
err: graphTD.ODataErrWithMsg(string(ResourceNotFound), "User not found"),
expect: []error{ErrUserNotFound, core.ErrNotFound},
},
{
name: "resource locked",
err: graphTD.ODataErr(string(NotAllowed)),
expect: []error{core.ErrResourceNotAccessible},
},
{
name: "insufficient auth",
err: graphTD.ODataErr(string(AuthorizationRequestDenied)),
expect: []error{core.ErrInsufficientAuthorization},
},
{
name: "already exists",
err: graphTD.ODataInner(string(nameAlreadyExists)),
expect: []error{core.ErrAlreadyExists},
},
{
name: "not found",
err: graphTD.ODataErr(string(ItemNotFound)),
expect: []error{core.ErrNotFound},
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
result := stackWithCoreErr(ctx, test.err, 1)
for _, ex := range test.expect {
assert.ErrorIs(t, result, ex, clues.ToCore(result))
}
})
}
}

View File

@ -185,26 +185,18 @@ func appendIfErr(errs []error, err error) []error {
return append(errs, err) return append(errs, err)
} }
// EvaluateMailboxError checks whether the provided error can be interpreted // IsMailboxErrorIgnorable checks whether the provided error can be interpreted
// as "user does not have a mailbox", or whether it is some other error. If // as "user does not have a mailbox", or whether it is some other error. If
// the former (no mailbox), returns nil, otherwise returns an error. // the former (no mailbox), returns nil, otherwise returns an error.
func EvaluateMailboxError(err error) error { func IsMailboxErrorIgnorable(err error) bool {
if err == nil {
return nil
}
// must occur before MailFolderNotFound, due to overlapping cases. // must occur before MailFolderNotFound, due to overlapping cases.
if errors.Is(err, core.ErrResourceNotAccessible) { if errors.Is(err, core.ErrResourceNotAccessible) || errors.Is(err, graph.ErrUserNotFound) {
return err return false
} }
if errors.Is(err, core.ErrNotFound) || return err != nil && (errors.Is(err, core.ErrNotFound) ||
graph.IsErrExchangeMailFolderNotFound(err) || graph.IsErrExchangeMailFolderNotFound(err) ||
graph.IsErrAuthenticationError(err) { graph.IsErrAuthenticationError(err))
return nil
}
return err
} }
// IsAnyErrMailboxNotFound inspects the secondary errors inside MailboxInfo and // IsAnyErrMailboxNotFound inspects the secondary errors inside MailboxInfo and

View File

@ -70,54 +70,47 @@ func (suite *UsersUnitSuite) TestEvaluateMailboxError() {
table := []struct { table := []struct {
name string name string
err error err error
expect func(t *testing.T, err error) expect assert.BoolAssertionFunc
}{ }{
{ {
name: "nil", name: "nil",
err: nil, err: nil,
expect: func(t *testing.T, err error) { expect: assert.False,
assert.NoError(t, err, clues.ToCore(err))
},
}, },
{ {
name: "mail inbox err - user not found", name: "user not found - corso sentinel",
err: core.ErrNotFound, err: graph.ErrUserNotFound,
expect: func(t *testing.T, err error) { expect: assert.False,
assert.NoError(t, err, clues.ToCore(err))
},
}, },
{ {
name: "mail inbox err - resourceLocked", name: "not found - corso sentinel",
err: core.ErrResourceNotAccessible, err: core.ErrNotFound,
expect: func(t *testing.T, err error) { expect: assert.True,
assert.ErrorIs(t, err, core.ErrResourceNotAccessible, clues.ToCore(err))
},
}, },
{ {
name: "mail inbox err - user not found", name: "mailbox not enabled - graph api error",
err: graphTD.ODataErr(string(graph.MailboxNotEnabledForRESTAPI)), err: graphTD.ODataErr(string(graph.MailboxNotEnabledForRESTAPI)),
expect: func(t *testing.T, err error) { expect: assert.True,
assert.NoError(t, err, clues.ToCore(err))
},
}, },
{ {
name: "mail inbox err - authenticationError", name: "resourceLocked",
err: graphTD.ODataErr(string(graph.AuthenticationError)), err: core.ErrResourceNotAccessible,
expect: func(t *testing.T, err error) { expect: assert.False,
assert.NoError(t, err, clues.ToCore(err))
},
}, },
{ {
name: "mail inbox err - other error", name: "authenticationError",
err: graphTD.ODataErrWithMsg("somecode", "somemessage"), err: graphTD.ODataErr(string(graph.AuthenticationError)),
expect: func(t *testing.T, err error) { expect: assert.True,
assert.Error(t, err, clues.ToCore(err)) },
}, {
name: "other error",
err: graphTD.ODataErrWithMsg("somecode", "somemessage"),
expect: assert.False,
}, },
} }
for _, test := range table { for _, test := range table {
suite.Run(test.name, func() { suite.Run(test.name, func() {
test.expect(suite.T(), EvaluateMailboxError(test.err)) test.expect(suite.T(), IsMailboxErrorIgnorable(test.err))
}) })
} }
} }