GC: Backup: OneDrive: Call Backup on User without OneDrive Access FIX (#1197)
## Description Corso should be able to have `Backup` called on a user that does not have OneDrive or does not have OneDrive files without producing an error. This feature branch creates OneDriveIntegration testing to be able to fetch OneDrive data for a particular user. Logic changed within `drive.go` ## Type of change <!--- Please check the type of change your PR introduces: ---> - [x] 🌻 Feature - [x] 🐛 Bugfix ## Issue(s) <!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. --> **closes** #966 ## Test Plan - [x] ⚡ Unit test
This commit is contained in:
parent
17221f23d0
commit
5ff890b760
@ -3,7 +3,6 @@ package onedrive
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go"
|
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -207,17 +206,3 @@ func driveItem(name string, path string, isFile, isFolder, isPackage bool) model
|
|||||||
|
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
type MockGraphService struct{}
|
|
||||||
|
|
||||||
func (ms *MockGraphService) Client() *msgraphsdk.GraphServiceClient {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms *MockGraphService) Adapter() *msgraphsdk.GraphRequestAdapter {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms *MockGraphService) ErrPolicy() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/drives/item/items"
|
"github.com/microsoftgraph/msgraph-sdk-go/drives/item/items"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/drives/item/items/item"
|
"github.com/microsoftgraph/msgraph-sdk-go/drives/item/items/item"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/drives/item/root/delta"
|
"github.com/microsoftgraph/msgraph-sdk-go/drives/item/root/delta"
|
||||||
@ -17,7 +18,43 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errFolderNotFound = errors.New("folder not found")
|
var (
|
||||||
|
errFolderNotFound = errors.New("folder not found")
|
||||||
|
|
||||||
|
// nolint:lll
|
||||||
|
// OneDrive associated SKUs located at:
|
||||||
|
// https://learn.microsoft.com/en-us/azure/active-directory/enterprise-users/licensing-service-plan-reference
|
||||||
|
skuIDs = []string{
|
||||||
|
// Microsoft 365 Apps for Business 0365
|
||||||
|
"cdd28e44-67e3-425e-be4c-737fab2899d3",
|
||||||
|
// Microsoft 365 Apps for Business SMB_Business
|
||||||
|
"b214fe43-f5a3-4703-beeb-fa97188220fc",
|
||||||
|
// Microsoft 365 Apps for enterprise
|
||||||
|
"c2273bd0-dff7-4215-9ef5-2c7bcfb06425",
|
||||||
|
// Microsoft 365 Apps for Faculty
|
||||||
|
"12b8c807-2e20-48fc-b453-542b6ee9d171",
|
||||||
|
// Microsoft 365 Apps for Students
|
||||||
|
"c32f9321-a627-406d-a114-1f9c81aaafac",
|
||||||
|
// OneDrive for Business (Plan 1)
|
||||||
|
"e6778190-713e-4e4f-9119-8b8238de25df",
|
||||||
|
// OneDrive for Business (Plan 2)
|
||||||
|
"ed01faf2-1d88-4947-ae91-45ca18703a96",
|
||||||
|
// Visio Plan 1
|
||||||
|
"ca7f3140-d88c-455b-9a1c-7f0679e31a76",
|
||||||
|
// Visio Plan 2
|
||||||
|
"38b434d2-a15e-4cde-9a98-e737c75623e1",
|
||||||
|
// Visio Online Plan 1
|
||||||
|
"4b244418-9658-4451-a2b8-b5e2b364e9bd",
|
||||||
|
// Visio Online Plan 2
|
||||||
|
"c5928f49-12ba-48f7-ada3-0d743a3601d5",
|
||||||
|
// Visio Plan 2 for GCC
|
||||||
|
"4ae99959-6b0f-43b0-b1ce-68146001bdba",
|
||||||
|
// ONEDRIVEENTERPRISE
|
||||||
|
"afcafa6a-d966-4462-918c-ec0b4e0fe642",
|
||||||
|
// Microsoft 365 E5 Developer
|
||||||
|
"c42b9cae-ea4f-4ab7-9717-81576235ccac",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// nextLinkKey is used to find the next link in a paged
|
// nextLinkKey is used to find the next link in a paged
|
||||||
@ -26,12 +63,30 @@ const (
|
|||||||
itemChildrenRawURLFmt = "https://graph.microsoft.com/v1.0/drives/%s/items/%s/children"
|
itemChildrenRawURLFmt = "https://graph.microsoft.com/v1.0/drives/%s/items/%s/children"
|
||||||
itemByPathRawURLFmt = "https://graph.microsoft.com/v1.0/drives/%s/items/%s:/%s"
|
itemByPathRawURLFmt = "https://graph.microsoft.com/v1.0/drives/%s/items/%s:/%s"
|
||||||
itemNotFoundErrorCode = "itemNotFound"
|
itemNotFoundErrorCode = "itemNotFound"
|
||||||
|
userDoesNotHaveDrive = "BadRequest Unable to retrieve user's mysite URL"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enumerates the drives for the specified user
|
// Enumerates the drives for the specified user
|
||||||
func drives(ctx context.Context, service graph.Service, user string) ([]models.Driveable, error) {
|
func drives(ctx context.Context, service graph.Service, user string) ([]models.Driveable, error) {
|
||||||
|
var hasDrive bool
|
||||||
|
|
||||||
|
hasDrive, err := hasDriveLicense(ctx, service, user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasDrive {
|
||||||
|
logger.Ctx(ctx).Debugf("User %s does not have a license for OneDrive", user)
|
||||||
|
return make([]models.Driveable, 0), nil // no license
|
||||||
|
}
|
||||||
|
|
||||||
r, err := service.Client().UsersById(user).Drives().Get(ctx, nil)
|
r, err := service.Client().UsersById(user).Drives().Get(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if strings.Contains(support.ConnectorStackErrorTrace(err), userDoesNotHaveDrive) {
|
||||||
|
logger.Ctx(ctx).Debugf("User %s does not have a drive", user)
|
||||||
|
return make([]models.Driveable, 0), nil // no license
|
||||||
|
}
|
||||||
|
|
||||||
return nil, errors.Wrapf(err, "failed to retrieve user drives. user: %s, details: %s",
|
return nil, errors.Wrapf(err, "failed to retrieve user drives. user: %s, details: %s",
|
||||||
user, support.ConnectorStackErrorTrace(err))
|
user, support.ConnectorStackErrorTrace(err))
|
||||||
}
|
}
|
||||||
@ -239,3 +294,56 @@ func DeleteItem(
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hasDriveLicense utility function that queries M365 server
|
||||||
|
// to investigate the user's includes access to OneDrive.
|
||||||
|
func hasDriveLicense(
|
||||||
|
ctx context.Context,
|
||||||
|
service graph.Service,
|
||||||
|
user string,
|
||||||
|
) (bool, error) {
|
||||||
|
var hasDrive bool
|
||||||
|
|
||||||
|
resp, err := service.Client().UsersById(user).LicenseDetails().Get(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return false,
|
||||||
|
errors.Wrap(err, "failure obtaining license details for user")
|
||||||
|
}
|
||||||
|
|
||||||
|
iter, err := msgraphgocore.NewPageIterator(
|
||||||
|
resp, service.Adapter(),
|
||||||
|
models.CreateLicenseDetailsCollectionResponseFromDiscriminatorValue,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cb := func(pageItem any) bool {
|
||||||
|
entry, ok := pageItem.(models.LicenseDetailsable)
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("casting item to models.MailFolderable")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sku := entry.GetSkuId()
|
||||||
|
if sku == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, license := range skuIDs {
|
||||||
|
if *sku == license {
|
||||||
|
hasDrive = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := iter.Iterate(ctx, cb); err != nil {
|
||||||
|
return false,
|
||||||
|
errors.Wrap(err, support.ConnectorStackErrorTrace(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasDrive, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -5,61 +5,15 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common"
|
"github.com/alcionai/corso/src/internal/common"
|
||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(ashmrtn): Merge with similar structs in graph and exchange packages.
|
|
||||||
type testService struct {
|
|
||||||
adapter msgraphsdk.GraphRequestAdapter
|
|
||||||
client msgraphsdk.GraphServiceClient
|
|
||||||
failFast bool
|
|
||||||
credentials account.M365Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *testService) Client() *msgraphsdk.GraphServiceClient {
|
|
||||||
return &ts.client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *testService) Adapter() *msgraphsdk.GraphRequestAdapter {
|
|
||||||
return &ts.adapter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *testService) ErrPolicy() bool {
|
|
||||||
return ts.failFast
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(ashmrtn): Merge with similar functions in connector and exchange
|
|
||||||
// packages.
|
|
||||||
func loadService(t *testing.T) *testService {
|
|
||||||
a := tester.NewM365Account(t)
|
|
||||||
m365, err := a.M365Config()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
adapter, err := graph.CreateAdapter(
|
|
||||||
m365.AzureTenantID,
|
|
||||||
m365.AzureClientID,
|
|
||||||
m365.AzureClientSecret,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
service := &testService{
|
|
||||||
adapter: *adapter,
|
|
||||||
client: *msgraphsdk.NewGraphServiceClient(adapter),
|
|
||||||
failFast: false,
|
|
||||||
credentials: m365,
|
|
||||||
}
|
|
||||||
|
|
||||||
return service
|
|
||||||
}
|
|
||||||
|
|
||||||
type OneDriveSuite struct {
|
type OneDriveSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
userID string
|
userID string
|
||||||
@ -86,7 +40,7 @@ func (suite *OneDriveSuite) TestCreateGetDeleteFolder() {
|
|||||||
folderIDs := []string{}
|
folderIDs := []string{}
|
||||||
folderName1 := "Corso_Folder_Test_" + common.FormatNow(common.SimpleTimeTesting)
|
folderName1 := "Corso_Folder_Test_" + common.FormatNow(common.SimpleTimeTesting)
|
||||||
folderElements := []string{folderName1}
|
folderElements := []string{folderName1}
|
||||||
gs := loadService(t)
|
gs := loadTestService(t)
|
||||||
|
|
||||||
drives, err := drives(ctx, gs, suite.userID)
|
drives, err := drives(ctx, gs, suite.userID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -144,3 +98,45 @@ func (suite *OneDriveSuite) TestCreateGetDeleteFolder() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *OneDriveSuite) TestOneDriveNewCollections() {
|
||||||
|
ctx, flush := tester.NewContext()
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
creds, err := tester.NewM365Account(suite.T()).M365Config()
|
||||||
|
require.NoError(suite.T(), err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name, user string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test User w/ Drive",
|
||||||
|
user: suite.userID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test User w/out Drive",
|
||||||
|
user: "testevents@8qzvrj.onmicrosoft.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
|
service := loadTestService(t)
|
||||||
|
scope := selectors.
|
||||||
|
NewOneDriveBackup().
|
||||||
|
Users([]string{test.user})[0]
|
||||||
|
odcs, err := NewCollections(
|
||||||
|
creds.AzureTenantID,
|
||||||
|
test.user,
|
||||||
|
scope,
|
||||||
|
service,
|
||||||
|
service.updateStatus,
|
||||||
|
).Get(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
for _, entry := range odcs {
|
||||||
|
assert.NotEmpty(t, entry.FullPath())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
85
src/internal/connector/onedrive/service_test.go
Normal file
85
src/internal/connector/onedrive/service_test.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package onedrive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/support"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockGraphService struct{}
|
||||||
|
|
||||||
|
func (ms *MockGraphService) Client() *msgraphsdk.GraphServiceClient {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MockGraphService) Adapter() *msgraphsdk.GraphRequestAdapter {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MockGraphService) ErrPolicy() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(ashmrtn): Merge with similar structs in graph and exchange packages.
|
||||||
|
type oneDriveService struct {
|
||||||
|
client msgraphsdk.GraphServiceClient
|
||||||
|
adapter msgraphsdk.GraphRequestAdapter
|
||||||
|
credentials account.M365Config
|
||||||
|
status support.ConnectorOperationStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ods *oneDriveService) Client() *msgraphsdk.GraphServiceClient {
|
||||||
|
return &ods.client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ods *oneDriveService) Adapter() *msgraphsdk.GraphRequestAdapter {
|
||||||
|
return &ods.adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ods *oneDriveService) ErrPolicy() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOneDriveService(credentials account.M365Config) (*oneDriveService, error) {
|
||||||
|
adapter, err := graph.CreateAdapter(
|
||||||
|
credentials.AzureTenantID,
|
||||||
|
credentials.AzureClientID,
|
||||||
|
credentials.AzureClientSecret,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
service := oneDriveService{
|
||||||
|
adapter: *adapter,
|
||||||
|
client: *msgraphsdk.NewGraphServiceClient(adapter),
|
||||||
|
credentials: credentials,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &service, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ods *oneDriveService) updateStatus(status *support.ConnectorOperationStatus) {
|
||||||
|
if status == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ods.status = support.MergeStatus(ods.status, *status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadTestService(t *testing.T) *oneDriveService {
|
||||||
|
a := tester.NewM365Account(t)
|
||||||
|
m365, err := a.M365Config()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
service, err := NewOneDriveService(m365)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return service
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user