add insufficient auth to graph, errs (#4776)

#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🌻 Feature

#### Test Plan

- [x]  Unit test
This commit is contained in:
Keepers 2023-12-01 12:03:21 -07:00 committed by GitHub
parent 79a37bfb91
commit 37dcb952fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 66 additions and 13 deletions

View File

@ -13,12 +13,13 @@ import (
type errEnum string type errEnum string
const ( const (
ApplicationThrottled errEnum = "application-throttled" ApplicationThrottled errEnum = "application-throttled"
BackupNotFound errEnum = "backup-not-found" BackupNotFound errEnum = "backup-not-found"
RepoAlreadyExists errEnum = "repository-already-exists" InsufficientAuthorization errEnum = "insufficient-authorization"
ResourceNotAccessible errEnum = "resource-not-accesible" RepoAlreadyExists errEnum = "repository-already-exists"
ResourceOwnerNotFound errEnum = "resource-owner-not-found" ResourceNotAccessible errEnum = "resource-not-accesible"
ServiceNotEnabled errEnum = "service-not-enabled" 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 // 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 // checks. This allows us to apply those comparison checks instead of relying
// only on sentinels. // only on sentinels.
var externalToInternalCheck = map[errEnum][]ErrCheck{ var externalToInternalCheck = map[errEnum][]ErrCheck{
ApplicationThrottled: {graph.IsErrApplicationThrottled}, ApplicationThrottled: {graph.IsErrApplicationThrottled},
ResourceNotAccessible: {graph.IsErrResourceLocked}, ResourceNotAccessible: {graph.IsErrResourceLocked},
ResourceOwnerNotFound: {graph.IsErrItemNotFound}, ResourceOwnerNotFound: {graph.IsErrItemNotFound},
InsufficientAuthorization: {graph.IsErrInsufficientAuthorization},
} }
// Internal returns the internal errors and error checking functions which // Internal returns the internal errors and error checking functions which

View File

@ -10,6 +10,7 @@ import (
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/repository" "github.com/alcionai/corso/src/pkg/repository"
"github.com/alcionai/corso/src/pkg/services/m365/api/graph" "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 { type ErrUnitSuite struct {
@ -103,6 +104,12 @@ func (suite *ErrUnitSuite) TestInternal_checks() {
expectHasChecks: assert.NotEmpty, expectHasChecks: assert.NotEmpty,
expect: assert.True, expect: assert.True,
}, },
{
get: InsufficientAuthorization,
err: graphTD.ODataErr(string(graph.AuthorizationRequestDenied)),
expectHasChecks: assert.NotEmpty,
expect: assert.True,
},
} }
for _, test := range table { for _, test := range table {
suite.Run(string(test.get), func() { suite.Run(string(test.get), func() {
@ -155,6 +162,10 @@ func (suite *ErrUnitSuite) TestIs() {
target: ResourceNotAccessible, target: ResourceNotAccessible,
err: graph.ErrResourceLocked, err: graph.ErrResourceLocked,
}, },
{
target: InsufficientAuthorization,
err: graphTD.ODataErr(string(graph.AuthorizationRequestDenied)),
},
} }
for _, test := range table { for _, test := range table {
suite.Run(string(test.target), func() { suite.Run(string(test.target), func() {

View File

@ -30,9 +30,12 @@ type errorCode string
const ( const (
applicationThrottled errorCode = "ApplicationThrottled" 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. // users without licenses, bad jwts, missing account permissions, etc.
AuthenticationError errorCode = "AuthenticationError" 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 // cannotOpenFileAttachment happen when an attachment is
// inaccessible. The error message is usually "OLE conversion // inaccessible. The error message is usually "OLE conversion
// failed for an attachment." // failed for an attachment."
@ -137,6 +140,10 @@ func IsErrAuthenticationError(err error) bool {
return hasErrorCode(err, AuthenticationError) return hasErrorCode(err, AuthenticationError)
} }
func IsErrInsufficientAuthorization(err error) bool {
return hasErrorCode(err, AuthorizationRequestDenied)
}
func IsErrDeletedInFlight(err error) bool { func IsErrDeletedInFlight(err error) bool {
if errors.Is(err, ErrDeletedInFlight) { if errors.Is(err, ErrDeletedInFlight) {
return true return true

View File

@ -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() { func (suite *GraphErrorsUnitSuite) TestIsErrDeletedInFlight() {
table := []struct { table := []struct {
name string name string

View File

@ -22,9 +22,7 @@ const (
func shouldLogRespBody(resp *http.Response) bool { func shouldLogRespBody(resp *http.Response) bool {
return logger.DebugAPIFV || return logger.DebugAPIFV ||
os.Getenv(logGraphRequestsEnvKey) != "" || os.Getenv(logGraphRequestsEnvKey) != "" ||
resp.StatusCode == http.StatusBadRequest || resp.StatusCode > 399
resp.StatusCode == http.StatusForbidden ||
resp.StatusCode == http.StatusConflict
} }
func logResp(ctx context.Context, resp *http.Response) { 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. // Log api calls according to api debugging configurations.
switch respClass { switch respClass {
case 2: case 2:
// only log 2xx's if we want the full response body.
if logBody { 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. // 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) dump := getRespDump(ctx, resp, os.Getenv(log2xxGraphResponseEnvKey) != "" && resp.ContentLength < logMBLimit)