diff --git a/src/pkg/errs/errs.go b/src/pkg/errs/errs.go index d01ec9da6..de7256aad 100644 --- a/src/pkg/errs/errs.go +++ b/src/pkg/errs/errs.go @@ -13,12 +13,13 @@ import ( type errEnum string const ( - ApplicationThrottled errEnum = "application-throttled" - BackupNotFound errEnum = "backup-not-found" - RepoAlreadyExists errEnum = "repository-already-exists" - ResourceNotAccessible errEnum = "resource-not-accesible" - ResourceOwnerNotFound errEnum = "resource-owner-not-found" - ServiceNotEnabled errEnum = "service-not-enabled" + ApplicationThrottled errEnum = "application-throttled" + BackupNotFound errEnum = "backup-not-found" + InsufficientAuthorization errEnum = "insufficient-authorization" + RepoAlreadyExists errEnum = "repository-already-exists" + ResourceNotAccessible errEnum = "resource-not-accesible" + ResourceOwnerNotFound errEnum = "resource-owner-not-found" + ServiceNotEnabled errEnum = "service-not-enabled" ) // map of enums to errors. We might want to re-use an enum for multiple @@ -41,9 +42,10 @@ type ErrCheck func(error) bool // checks. This allows us to apply those comparison checks instead of relying // only on sentinels. var externalToInternalCheck = map[errEnum][]ErrCheck{ - ApplicationThrottled: {graph.IsErrApplicationThrottled}, - ResourceNotAccessible: {graph.IsErrResourceLocked}, - ResourceOwnerNotFound: {graph.IsErrItemNotFound}, + ApplicationThrottled: {graph.IsErrApplicationThrottled}, + ResourceNotAccessible: {graph.IsErrResourceLocked}, + ResourceOwnerNotFound: {graph.IsErrItemNotFound}, + InsufficientAuthorization: {graph.IsErrInsufficientAuthorization}, } // Internal returns the internal errors and error checking functions which diff --git a/src/pkg/errs/errs_test.go b/src/pkg/errs/errs_test.go index 8b91910c6..7846609de 100644 --- a/src/pkg/errs/errs_test.go +++ b/src/pkg/errs/errs_test.go @@ -10,6 +10,7 @@ import ( "github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/pkg/repository" "github.com/alcionai/corso/src/pkg/services/m365/api/graph" + graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata" ) type ErrUnitSuite struct { @@ -103,6 +104,12 @@ func (suite *ErrUnitSuite) TestInternal_checks() { expectHasChecks: assert.NotEmpty, expect: assert.True, }, + { + get: InsufficientAuthorization, + err: graphTD.ODataErr(string(graph.AuthorizationRequestDenied)), + expectHasChecks: assert.NotEmpty, + expect: assert.True, + }, } for _, test := range table { suite.Run(string(test.get), func() { @@ -155,6 +162,10 @@ func (suite *ErrUnitSuite) TestIs() { target: ResourceNotAccessible, err: graph.ErrResourceLocked, }, + { + target: InsufficientAuthorization, + err: graphTD.ODataErr(string(graph.AuthorizationRequestDenied)), + }, } for _, test := range table { suite.Run(string(test.target), func() { diff --git a/src/pkg/services/m365/api/graph/errors.go b/src/pkg/services/m365/api/graph/errors.go index 71bebe134..3488ba06a 100644 --- a/src/pkg/services/m365/api/graph/errors.go +++ b/src/pkg/services/m365/api/graph/errors.go @@ -30,9 +30,12 @@ type errorCode string const ( applicationThrottled errorCode = "ApplicationThrottled" - // this auth error is a catch-all used by graph in a variety of cases: + // this authN error is a catch-all used by graph in a variety of cases: // users without licenses, bad jwts, missing account permissions, etc. AuthenticationError errorCode = "AuthenticationError" + // on the other hand, authZ errors apply specifically to authenticated, + // but unauthorized, user requests + AuthorizationRequestDenied errorCode = "Authorization_RequestDenied" // cannotOpenFileAttachment happen when an attachment is // inaccessible. The error message is usually "OLE conversion // failed for an attachment." @@ -137,6 +140,10 @@ func IsErrAuthenticationError(err error) bool { return hasErrorCode(err, AuthenticationError) } +func IsErrInsufficientAuthorization(err error) bool { + return hasErrorCode(err, AuthorizationRequestDenied) +} + func IsErrDeletedInFlight(err error) bool { if errors.Is(err, ErrDeletedInFlight) { return true diff --git a/src/pkg/services/m365/api/graph/errors_test.go b/src/pkg/services/m365/api/graph/errors_test.go index 2c2357071..72e855458 100644 --- a/src/pkg/services/m365/api/graph/errors_test.go +++ b/src/pkg/services/m365/api/graph/errors_test.go @@ -124,6 +124,40 @@ func (suite *GraphErrorsUnitSuite) TestIsErrAuthenticationError() { } } +func (suite *GraphErrorsUnitSuite) TestIsErrInsufficientAuthorization() { + 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.ODataErr("fnords"), + expect: assert.False, + }, + { + name: "AuthorizationRequestDenied oDataErr", + err: graphTD.ODataErr(string(AuthorizationRequestDenied)), + expect: assert.True, + }, + } + for _, test := range table { + suite.Run(test.name, func() { + test.expect(suite.T(), IsErrInsufficientAuthorization(test.err)) + }) + } +} + func (suite *GraphErrorsUnitSuite) TestIsErrDeletedInFlight() { table := []struct { name string diff --git a/src/pkg/services/m365/api/graph/logging.go b/src/pkg/services/m365/api/graph/logging.go index 4f96ae926..2b6e536c9 100644 --- a/src/pkg/services/m365/api/graph/logging.go +++ b/src/pkg/services/m365/api/graph/logging.go @@ -22,9 +22,7 @@ const ( func shouldLogRespBody(resp *http.Response) bool { return logger.DebugAPIFV || os.Getenv(logGraphRequestsEnvKey) != "" || - resp.StatusCode == http.StatusBadRequest || - resp.StatusCode == http.StatusForbidden || - resp.StatusCode == http.StatusConflict + resp.StatusCode > 399 } func logResp(ctx context.Context, resp *http.Response) { @@ -47,6 +45,7 @@ func logResp(ctx context.Context, resp *http.Response) { // Log api calls according to api debugging configurations. switch respClass { case 2: + // only log 2xx's if we want the full response body. if logBody { // only dump the body if it's under a size limit. We don't want to copy gigs into memory for a log. dump := getRespDump(ctx, resp, os.Getenv(log2xxGraphResponseEnvKey) != "" && resp.ContentLength < logMBLimit)