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
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

View File

@ -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() {

View File

@ -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

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() {
table := []struct {
name string

View File

@ -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)