adds tenant site lookup on new graph conn (#1540)

## Description

Adds a lookup for all tenant site ids when a new
graph connector is created.  Also adds an enum
to flag which resource set (users, sites, or all)
that the connector should initialize.

## Type of change

- [x] 🌻 Feature

## Issue(s)

* #1506

## Test Plan

- [x] 💚 E2E
This commit is contained in:
Keepers 2022-11-17 17:31:41 -07:00 committed by GitHub
parent 9fe9cd1ce6
commit 07a8d13d1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 250 additions and 175 deletions

View File

@ -176,7 +176,7 @@ func getGCAndVerifyUser(ctx context.Context, userID string) (*connector.GraphCon
} }
// build a graph connector // build a graph connector
gc, err := connector.NewGraphConnector(ctx, acct) gc, err := connector.NewGraphConnector(ctx, acct, connector.Users)
if err != nil { if err != nil {
return nil, "", errors.Wrap(err, "connecting to graph api") return nil, "", errors.Wrap(err, "connecting to graph api")
} }

View File

@ -44,7 +44,8 @@ var (
// Supports: // Supports:
// - exchange (contacts, email, and events) // - exchange (contacts, email, and events)
// Input: go run ./getItem.go --user <user> // Input: go run ./getItem.go --user <user>
// --m365ID <m365ID> --category <oneof: contacts, email, events> //
// --m365ID <m365ID> --category <oneof: contacts, email, events>
func main() { func main() {
ctx, _ := logger.SeedLevel(context.Background(), logger.Development) ctx, _ := logger.SeedLevel(context.Background(), logger.Development)
ctx = SetRootCmd(ctx, getCmd) ctx = SetRootCmd(ctx, getCmd)
@ -170,7 +171,7 @@ func getGC(ctx context.Context) (*connector.GraphConnector, error) {
return nil, Only(ctx, errors.Wrap(err, "finding m365 account details")) return nil, Only(ctx, errors.Wrap(err, "finding m365 account details"))
} }
gc, err := connector.NewGraphConnector(ctx, acct) gc, err := connector.NewGraphConnector(ctx, acct, connector.Users)
if err != nil { if err != nil {
return nil, Only(ctx, errors.Wrap(err, "connecting to graph API")) return nil, Only(ctx, errors.Wrap(err, "connecting to graph API"))
} }

View File

@ -255,7 +255,7 @@ func getGC(ctx context.Context) (*connector.GraphConnector, error) {
} }
// build a graph connector // build a graph connector
gc, err := connector.NewGraphConnector(ctx, acct) gc, err := connector.NewGraphConnector(ctx, acct, connector.Users)
if err != nil { if err != nil {
return nil, Only(ctx, errors.Wrap(err, "connecting to graph api")) return nil, Only(ctx, errors.Wrap(err, "connecting to graph api"))
} }

View File

@ -42,7 +42,7 @@ func (suite *ConnectorDataCollectionIntegrationSuite) SetupSuite() {
_, err := tester.GetRequiredEnvVars(tester.M365AcctCredEnvs...) _, err := tester.GetRequiredEnvVars(tester.M365AcctCredEnvs...)
require.NoError(suite.T(), err) require.NoError(suite.T(), err)
suite.connector = loadConnector(ctx, suite.T()) suite.connector = loadConnector(ctx, suite.T(), AllResources)
suite.user = tester.M365UserID(suite.T()) suite.user = tester.M365UserID(suite.T())
suite.site = tester.M365SiteID(suite.T()) suite.site = tester.M365SiteID(suite.T())
tester.LogTimeOfTest(suite.T()) tester.LogTimeOfTest(suite.T())
@ -58,7 +58,7 @@ func (suite *ConnectorDataCollectionIntegrationSuite) TestExchangeDataCollection
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
connector := loadConnector(ctx, suite.T()) connector := loadConnector(ctx, suite.T(), Users)
tests := []struct { tests := []struct {
name string name string
getSelector func(t *testing.T) selectors.Selector getSelector func(t *testing.T) selectors.Selector
@ -123,7 +123,7 @@ func (suite *ConnectorDataCollectionIntegrationSuite) TestInvalidUserForDataColl
defer flush() defer flush()
invalidUser := "foo@example.com" invalidUser := "foo@example.com"
connector := loadConnector(ctx, suite.T()) connector := loadConnector(ctx, suite.T(), Users)
tests := []struct { tests := []struct {
name string name string
getSelector func(t *testing.T) selectors.Selector getSelector func(t *testing.T) selectors.Selector
@ -162,7 +162,7 @@ func (suite *ConnectorDataCollectionIntegrationSuite) TestSharePointDataCollecti
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
connector := loadConnector(ctx, suite.T()) connector := loadConnector(ctx, suite.T(), Users)
tests := []struct { tests := []struct {
name string name string
getSelector func(t *testing.T) selectors.Selector getSelector func(t *testing.T) selectors.Selector
@ -230,7 +230,7 @@ func (suite *ConnectorCreateExchangeCollectionIntegrationSuite) SetupSuite() {
_, err := tester.GetRequiredEnvVars(tester.M365AcctCredEnvs...) _, err := tester.GetRequiredEnvVars(tester.M365AcctCredEnvs...)
require.NoError(suite.T(), err) require.NoError(suite.T(), err)
suite.connector = loadConnector(ctx, suite.T()) suite.connector = loadConnector(ctx, suite.T(), Users)
suite.user = tester.M365UserID(suite.T()) suite.user = tester.M365UserID(suite.T())
suite.site = tester.M365SiteID(suite.T()) suite.site = tester.M365SiteID(suite.T())
tester.LogTimeOfTest(suite.T()) tester.LogTimeOfTest(suite.T())
@ -263,7 +263,7 @@ func (suite *ConnectorCreateExchangeCollectionIntegrationSuite) TestMailFetch()
}, },
} }
gc := loadConnector(ctx, t) gc := loadConnector(ctx, t, Users)
for _, test := range tests { for _, test := range tests {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
@ -292,7 +292,7 @@ func (suite *ConnectorCreateExchangeCollectionIntegrationSuite) TestMailSerializ
defer flush() defer flush()
t := suite.T() t := suite.T()
connector := loadConnector(ctx, t) connector := loadConnector(ctx, t, Users)
sel := selectors.NewExchangeBackup() sel := selectors.NewExchangeBackup()
sel.Include(sel.MailFolders([]string{suite.user}, []string{exchange.DefaultMailFolder}, selectors.PrefixMatch())) sel.Include(sel.MailFolders([]string{suite.user}, []string{exchange.DefaultMailFolder}, selectors.PrefixMatch()))
collection, err := connector.createExchangeCollections(ctx, sel.Scopes()[0]) collection, err := connector.createExchangeCollections(ctx, sel.Scopes()[0])
@ -326,7 +326,7 @@ func (suite *ConnectorCreateExchangeCollectionIntegrationSuite) TestContactSeria
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
connector := loadConnector(ctx, suite.T()) connector := loadConnector(ctx, suite.T(), Users)
tests := []struct { tests := []struct {
name string name string
@ -379,7 +379,7 @@ func (suite *ConnectorCreateExchangeCollectionIntegrationSuite) TestEventsSerial
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
connector := loadConnector(ctx, suite.T()) connector := loadConnector(ctx, suite.T(), Users)
tests := []struct { tests := []struct {
name, expected string name, expected string
@ -447,7 +447,7 @@ func (suite *ConnectorCreateExchangeCollectionIntegrationSuite) TestAccessOfInbo
defer flush() defer flush()
t := suite.T() t := suite.T()
connector := loadConnector(ctx, t) connector := loadConnector(ctx, t, Users)
sel := selectors.NewExchangeBackup() sel := selectors.NewExchangeBackup()
sel.Include(sel.MailFolders(selectors.Any(), []string{exchange.DefaultMailFolder}, selectors.PrefixMatch())) sel.Include(sel.MailFolders(selectors.Any(), []string{exchange.DefaultMailFolder}, selectors.PrefixMatch()))
scopes := sel.DiscreteScopes(connector.GetUsers()) scopes := sel.DiscreteScopes(connector.GetUsers())
@ -488,7 +488,7 @@ func (suite *ConnectorCreateSharePointCollectionIntegrationSuite) SetupSuite() {
_, err := tester.GetRequiredEnvVars(tester.M365AcctCredEnvs...) _, err := tester.GetRequiredEnvVars(tester.M365AcctCredEnvs...)
require.NoError(suite.T(), err) require.NoError(suite.T(), err)
suite.connector = loadConnector(ctx, suite.T()) suite.connector = loadConnector(ctx, suite.T(), Sites)
suite.user = tester.M365UserID(suite.T()) suite.user = tester.M365UserID(suite.T())
tester.LogTimeOfTest(suite.T()) tester.LogTimeOfTest(suite.T())
} }
@ -502,7 +502,7 @@ func (suite *ConnectorCreateSharePointCollectionIntegrationSuite) TestCreateShar
userID = tester.M365UserID(t) userID = tester.M365UserID(t)
) )
gc := loadConnector(ctx, t) gc := loadConnector(ctx, t, Sites)
scope := selectors.NewSharePointBackup().Folders( scope := selectors.NewSharePointBackup().Folders(
[]string{userID}, []string{userID},
[]string{"foo"}, []string{"foo"},

View File

@ -39,9 +39,9 @@ func checkRequiredValues(c graph.Container) error {
return nil return nil
} }
//====================================== // =========================================
// cachedContainer Implementations // cachedContainer Implementations
//====================== // =========================================
var _ graph.CachedContainer = &cacheFolder{} var _ graph.CachedContainer = &cacheFolder{}
@ -50,9 +50,9 @@ type cacheFolder struct {
p *path.Builder p *path.Builder
} }
//========================================= // =========================================
// Required Functions to satisfy interfaces // Required Functions to satisfy interfaces
//===================================== // =========================================
func (cf cacheFolder) Path() *path.Builder { func (cf cacheFolder) Path() *path.Builder {
return cf.p return cf.p
@ -79,6 +79,7 @@ func (c CalendarDisplayable) GetDisplayName() *string {
// GetParentFolderId returns the default calendar name address // GetParentFolderId returns the default calendar name address
// EventCalendars have a flat hierarchy and Calendars are rooted // EventCalendars have a flat hierarchy and Calendars are rooted
// at the default // at the default
//
//nolint:revive //nolint:revive
func (c CalendarDisplayable) GetParentFolderId() *string { func (c CalendarDisplayable) GetParentFolderId() *string {
return nil return nil

View File

@ -5,6 +5,7 @@ import (
"testing" "testing"
"time" "time"
absser "github.com/microsoft/kiota-abstractions-go/serialization"
"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"
@ -252,8 +253,10 @@ func (suite *ExchangeServiceSuite) TestGraphQueryFunctions() {
function: GetAllFolderNamesForUser, function: GetAllFolderNamesForUser,
}, },
{ {
name: "GraphQuery: Get All Users", name: "GraphQuery: Get All Users",
function: GetAllUsersForTenant, function: func(ctx context.Context, gs graph.Service, toss string) (absser.Parsable, error) {
return GetAllUsersForTenant(ctx, gs)
},
}, },
{ {
name: "GraphQuery: Get All ContactFolders", name: "GraphQuery: Get All ContactFolders",

View File

@ -6,7 +6,8 @@ package exchange
// Legacy Value Tags and constants are used to override certain values within // Legacy Value Tags and constants are used to override certain values within
// M365 objects. // M365 objects.
// Master Property Value Document: // Master Property Value Document:
// https://interoperability.blob.core.windows.net/files/MS-OXPROPS/%5bMS-OXPROPS%5d.pdf //
// https://interoperability.blob.core.windows.net/files/MS-OXPROPS/%5bMS-OXPROPS%5d.pdf
const ( const (
// MailRestorePropertyTag inhibits exchange.Mail.Message from being "resent" through the server. // MailRestorePropertyTag inhibits exchange.Mail.Message from being "resent" through the server.
// DEFINED: Section 2.791 PidTagMessageFlags // DEFINED: Section 2.791 PidTagMessageFlags
@ -27,9 +28,9 @@ const (
// Section: 2.789 PidTagMessageDeliveryTime // Section: 2.789 PidTagMessageDeliveryTime
MailReceiveDateTimeOverriveProperty = "SystemTime 0x0E06" MailReceiveDateTimeOverriveProperty = "SystemTime 0x0E06"
//---------------------------------- // ----------------------------------
// Default Folder Names // Default Folder Names
//------------------------ // ----------------------------------
// Mail Definitions: https://docs.microsoft.com/en-us/graph/api/resources/mailfolder?view=graph-rest-1.0 // Mail Definitions: https://docs.microsoft.com/en-us/graph/api/resources/mailfolder?view=graph-rest-1.0
// inbox and root // inbox and root
@ -38,9 +39,9 @@ const (
DefaultContactFolder = "Contacts" DefaultContactFolder = "Contacts"
DefaultCalendar = "Calendar" DefaultCalendar = "Calendar"
//--------------------- // ----------------------------------
// Paging // Paging
//----------------- // ----------------------------------
// nextDataLink definition https://docs.microsoft.com/en-us/graph/paging // nextDataLink definition https://docs.microsoft.com/en-us/graph/paging
nextDataLink = "@odata.nextLink" nextDataLink = "@odata.nextLink"
) )

View File

@ -21,11 +21,11 @@ import (
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
//----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Constant Section // Constant Section
// Defines the allowable strings that can be passed into // Defines the allowable strings that can be passed into
// selectors for M365 objects // selectors for M365 objects
//------------------------------------------------------------ // -----------------------------------------------------------------------
var ( var (
fieldsForCalendars = map[string]int{ fieldsForCalendars = map[string]int{
"changeKey": 1, "changeKey": 1,
@ -118,12 +118,12 @@ func CategoryToOptionIdentifier(category path.CategoryType) optionIdentifier {
} }
} }
//--------------------------------------------------------------------------- // -----------------------------------------------------------------------
// exchange.Query Option Section // exchange.Query Option Section
// These functions can be used to filter a response on M365 // These functions can be used to filter a response on M365
// Graph queries and reduce / filter the amount of data returned // Graph queries and reduce / filter the amount of data returned
// which reduces the overall latency of complex calls // which reduces the overall latency of complex calls
//---------------------------------------------------------------- // -----------------------------------------------------------------------
func optionsForFolderMessages(moreOps []string) (*msmfmessage.MessagesRequestBuilderGetRequestConfiguration, error) { func optionsForFolderMessages(moreOps []string) (*msmfmessage.MessagesRequestBuilderGetRequestConfiguration, error) {
selecting, err := buildOptions(moreOps, messages) selecting, err := buildOptions(moreOps, messages)

View File

@ -23,9 +23,10 @@ type exchangeService struct {
credentials account.M365Config credentials account.M365Config
} }
// /------------------------------------------------------------ // ------------------------------------------------------------
// Functions to comply with graph.Service Interface // Functions to comply with graph.Service Interface
// ------------------------------------------------------- // ------------------------------------------------------------
func (es *exchangeService) Client() *msgraphsdk.GraphServiceClient { func (es *exchangeService) Client() *msgraphsdk.GraphServiceClient {
return &es.client return &es.client
} }

View File

@ -74,9 +74,8 @@ func GetAllContactFolderNamesForUser(ctx context.Context, gs graph.Service, user
return gs.Client().UsersById(user).ContactFolders().Get(ctx, options) return gs.Client().UsersById(user).ContactFolders().Get(ctx, options)
} }
// GetAllUsersForTenant is a GraphQuery for retrieving all the UserCollectionResponse with // GetAllUsersForTenant makes a GraphQuery request retrieving all the users in the tenant.
// that contains the UserID and email for each user. All other information is omitted func GetAllUsersForTenant(ctx context.Context, gs graph.Service) (absser.Parsable, error) {
func GetAllUsersForTenant(ctx context.Context, gs graph.Service, user string) (absser.Parsable, error) {
selecting := []string{"userPrincipalName"} selecting := []string{"userPrincipalName"}
options, err := optionsForUsers(selecting) options, err := optionsForUsers(selecting)

View File

@ -37,9 +37,9 @@ func CheckRequiredValues(c Container) error {
return nil return nil
} }
//====================================== // ======================================
// cachedContainer Implementations // cachedContainer Implementations
//====================================== // ======================================
var _ CachedContainer = &CacheFolder{} var _ CachedContainer = &CacheFolder{}
@ -58,9 +58,9 @@ func NewCacheFolder(c Container, pb *path.Builder) CacheFolder {
return cf return cf
} }
//========================================= // =========================================
// Required Functions to satisfy interfaces // Required Functions to satisfy interfaces
//========================================= // =========================================
func (cf CacheFolder) Path() *path.Builder { func (cf CacheFolder) Path() *path.Builder {
return cf.p return cf.p
@ -86,6 +86,7 @@ func (c CalendarDisplayable) GetDisplayName() *string {
// GetParentFolderId returns the default calendar name address // GetParentFolderId returns the default calendar name address
// EventCalendars have a flat hierarchy and Calendars are rooted // EventCalendars have a flat hierarchy and Calendars are rooted
// at the default // at the default
//
//nolint:revive //nolint:revive
func (c CalendarDisplayable) GetParentFolderId() *string { func (c CalendarDisplayable) GetParentFolderId() *string {
return &c.parentID return &c.parentID

View File

@ -4,10 +4,11 @@ package connector
import ( import (
"context" "context"
"fmt"
"runtime/trace" "runtime/trace"
"strings"
"sync" "sync"
"github.com/microsoft/kiota-abstractions-go/serialization"
msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go" msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go"
msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core" msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
@ -16,6 +17,7 @@ import (
"github.com/alcionai/corso/src/internal/connector/exchange" "github.com/alcionai/corso/src/internal/connector/exchange"
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/connector/onedrive" "github.com/alcionai/corso/src/internal/connector/onedrive"
"github.com/alcionai/corso/src/internal/connector/sharepoint"
"github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/data"
D "github.com/alcionai/corso/src/internal/diagnostics" D "github.com/alcionai/corso/src/internal/diagnostics"
@ -73,7 +75,16 @@ func (gs graphService) ErrPolicy() bool {
return gs.failFast return gs.failFast
} }
func NewGraphConnector(ctx context.Context, acct account.Account) (*GraphConnector, error) { type resource int
const (
UnknownResource resource = iota
AllResources
Users
Sites
)
func NewGraphConnector(ctx context.Context, acct account.Account, r resource) (*GraphConnector, error) {
m365, err := acct.M365Config() m365, err := acct.M365Config()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "retrieving m365 account configuration") return nil, errors.Wrap(err, "retrieving m365 account configuration")
@ -93,15 +104,16 @@ func NewGraphConnector(ctx context.Context, acct account.Account) (*GraphConnect
gc.graphService = *aService gc.graphService = *aService
err = gc.setTenantUsers(ctx) if r == AllResources || r == Users {
if err != nil { if err = gc.setTenantUsers(ctx); err != nil {
return nil, errors.Wrap(err, "retrieving tenant user list") return nil, errors.Wrap(err, "retrieving tenant user list")
}
} }
// TODO: users or sites, one or the other, not both. if r == AllResources || r == Sites {
err = gc.setTenantSites(ctx) if err = gc.setTenantSites(ctx); err != nil {
if err != nil { return nil, errors.Wrap(err, "retrieveing tenant site list")
return nil, errors.Wrap(err, "retrieveing tenant site list") }
} }
return &gc, nil return &gc, nil
@ -133,59 +145,41 @@ func (gs *graphService) EnableFailFast() {
// setTenantUsers queries the M365 to identify the users in the // setTenantUsers queries the M365 to identify the users in the
// workspace. The users field is updated during this method // workspace. The users field is updated during this method
// iff the return value is nil // iff the returned error is nil
func (gc *GraphConnector) setTenantUsers(ctx context.Context) error { func (gc *GraphConnector) setTenantUsers(ctx context.Context) error {
ctx, end := D.Span(ctx, "gc:setTenantUsers") ctx, end := D.Span(ctx, "gc:setTenantUsers")
defer end() defer end()
response, err := exchange.GetAllUsersForTenant(ctx, gc.graphService, "") users, err := getResources(
if err != nil { ctx,
return errors.Wrapf( gc.graphService,
err, gc.tenant,
"tenant %s M365 query: %s", exchange.GetAllUsersForTenant,
gc.tenant,
support.ConnectorStackErrorTrace(err),
)
}
userIterator, err := msgraphgocore.NewPageIterator(
response,
&gc.graphService.adapter,
models.CreateUserCollectionResponseFromDiscriminatorValue, models.CreateUserCollectionResponseFromDiscriminatorValue,
identifyUser,
) )
if err != nil { if err != nil {
return errors.Wrap(err, support.ConnectorStackErrorTrace(err)) return err
} }
callbackFunc := func(userItem interface{}) bool { gc.Users = users
user, ok := userItem.(models.Userable)
if !ok {
err = support.WrapAndAppend(gc.graphService.adapter.GetBaseUrl(), errors.New("received non-User on iteration"), err)
return true
}
if user.GetUserPrincipalName() == nil { return nil
err = support.WrapAndAppend( }
gc.graphService.adapter.GetBaseUrl(),
fmt.Errorf("no email address for User: %s", *user.GetId()),
err,
)
return true // Transforms an interface{} into a key,value pair representing
} // userPrincipalName:userID.
func identifyUser(item any) (string, string, error) {
// *user.GetId() is populated for every M365 entityable object by M365 backstore m, ok := item.(models.Userable)
gc.Users[*user.GetUserPrincipalName()] = *user.GetId() if !ok {
return "", "", errors.New("iteration retrieved non-User item")
return true
} }
iterateError := userIterator.Iterate(ctx, callbackFunc) if m.GetUserPrincipalName() == nil {
if iterateError != nil { return "", "", errors.Errorf("no principal name for User: %s", *m.GetId())
err = support.WrapAndAppend(gc.graphService.adapter.GetBaseUrl(), iterateError, err)
} }
return err return *m.GetUserPrincipalName(), *m.GetId(), nil
} }
// GetUsers returns the email address of users within tenant. // GetUsers returns the email address of users within tenant.
@ -199,68 +193,53 @@ func (gc *GraphConnector) GetUsersIds() []string {
} }
// setTenantSites queries the M365 to identify the sites in the // setTenantSites queries the M365 to identify the sites in the
// workspace. The sitets field is updated during this method // workspace. The sites field is updated during this method
// iff the return value is nil // iff the returned error is nil.
func (gc *GraphConnector) setTenantSites(ctx context.Context) error { func (gc *GraphConnector) setTenantSites(ctx context.Context) error {
// TODO
gc.Sites = map[string]string{} gc.Sites = map[string]string{}
// ctx, end := D.Span(ctx, "gc:setTenantSites") ctx, end := D.Span(ctx, "gc:setTenantSites")
// defer end() defer end()
// response, err := exchange.GetAllUsersForTenant(ctx, gc.graphService, "") sites, err := getResources(
// if err != nil { ctx,
// return errors.Wrapf( gc.graphService,
// err, gc.tenant,
// "tenant %s M365 query: %s", sharepoint.GetAllSitesForTenant,
// gc.tenant, models.CreateSiteCollectionResponseFromDiscriminatorValue,
// support.ConnectorStackErrorTrace(err), identifySite,
// ) )
// } if err != nil {
return err
}
// userIterator, err := msgraphgocore.NewPageIterator( gc.Sites = sites
// response,
// &gc.graphService.adapter,
// models.CreateUserCollectionResponseFromDiscriminatorValue,
// )
// if err != nil {
// return errors.Wrap(err, support.ConnectorStackErrorTrace(err))
// }
// callbackFunc := func(userItem interface{}) bool {
// user, ok := userItem.(models.Userable)
// if !ok {
// err = support.WrapAndAppend(gc.graphService.adapter.GetBaseUrl(),
// errors.New("received non-User on iteration"), err)
// return true
// }
// if user.GetUserPrincipalName() == nil {
// err = support.WrapAndAppend(
// gc.graphService.adapter.GetBaseUrl(),
// fmt.Errorf("no email address for User: %s", *user.GetId()),
// err,
// )
// return true
// }
// // *user.GetId() is populated for every M365 entityable object by M365 backstore
// gc.Users[*user.GetUserPrincipalName()] = *user.GetId()
// return true
// }
// iterateError := userIterator.Iterate(ctx, callbackFunc)
// if iterateError != nil {
// err = support.WrapAndAppend(gc.graphService.adapter.GetBaseUrl(), iterateError, err)
// }
// return err
return nil return nil
} }
var errKnownSkippableCase = errors.New("case is known and skippable")
// Transforms an interface{} into a key,value pair representing
// siteName:siteID.
func identifySite(item any) (string, string, error) {
m, ok := item.(models.Siteable)
if !ok {
return "", "", errors.New("iteration retrieved non-Site item")
}
if m.GetName() == nil {
// the built-in site at "htps://{tenant-domain}/search" never has a name.
if m.GetWebUrl() != nil && strings.HasSuffix(*m.GetWebUrl(), "/search") {
return "", "", errKnownSkippableCase
}
return "", "", errors.Errorf("no name for Site: %s", *m.GetId())
}
return *m.GetName(), *m.GetId(), nil
}
// GetSites returns the siteIDs of sharepoint sites within tenant. // GetSites returns the siteIDs of sharepoint sites within tenant.
func (gc *GraphConnector) GetSites() []string { func (gc *GraphConnector) GetSites() []string {
return buildFromMap(true, gc.Sites) return buildFromMap(true, gc.Sites)
@ -365,6 +344,57 @@ func (gc *GraphConnector) incrementAwaitingMessages() {
// Helper Funcs // Helper Funcs
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
func getResources(
ctx context.Context,
gs graph.Service,
tenantID string,
query func(context.Context, graph.Service) (serialization.Parsable, error),
parser func(parseNode serialization.ParseNode) (serialization.Parsable, error),
identify func(any) (string, string, error),
) (map[string]string, error) {
resources := map[string]string{}
response, err := query(ctx, gs)
if err != nil {
return nil, errors.Wrapf(
err,
"retrieving resources for tenant %s: %s",
tenantID,
support.ConnectorStackErrorTrace(err),
)
}
iter, err := msgraphgocore.NewPageIterator(response, gs.Adapter(), parser)
if err != nil {
return nil, errors.Wrap(err, support.ConnectorStackErrorTrace(err))
}
var iterErrs error
callbackFunc := func(item any) bool {
k, v, err := identify(item)
if err != nil {
if errors.Is(err, errKnownSkippableCase) {
return true
}
iterErrs = support.WrapAndAppend(gs.Adapter().GetBaseUrl(), err, iterErrs)
return true
}
resources[k] = v
return true
}
if err := iter.Iterate(ctx, callbackFunc); err != nil {
return nil, errors.Wrap(err, support.ConnectorStackErrorTrace(err))
}
return resources, iterErrs
}
// IsRecoverableError returns true iff error is a RecoverableGCEerror // IsRecoverableError returns true iff error is a RecoverableGCEerror
func IsRecoverableError(e error) bool { func IsRecoverableError(e error) bool {
var recoverable support.RecoverableGCError var recoverable support.RecoverableGCError

View File

@ -65,7 +65,7 @@ func (suite *DisconnectedGraphConnectorSuite) TestBadConnection() {
for _, test := range table { for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
gc, err := NewGraphConnector(ctx, test.acct(t)) gc, err := NewGraphConnector(ctx, test.acct(t), Users)
assert.Nil(t, gc, test.name+" failed") assert.Nil(t, gc, test.name+" failed")
assert.NotNil(t, err, test.name+"failed") assert.NotNil(t, err, test.name+"failed")
}) })

View File

@ -162,6 +162,7 @@ type restoreBackupInfo struct {
name string name string
service path.ServiceType service path.ServiceType
collections []colInfo collections []colInfo
resource resource
} }
func attachmentEqual( func attachmentEqual(
@ -858,15 +859,16 @@ func getSelectorWith(service path.ServiceType) selectors.Selector {
case path.OneDriveService: case path.OneDriveService:
s = selectors.ServiceOneDrive s = selectors.ServiceOneDrive
} }
// TODO: ^ sharepoint
return selectors.Selector{ return selectors.Selector{
Service: s, Service: s,
} }
} }
func loadConnector(ctx context.Context, t *testing.T) *GraphConnector { func loadConnector(ctx context.Context, t *testing.T, r resource) *GraphConnector {
a := tester.NewM365Account(t) a := tester.NewM365Account(t)
connector, err := NewGraphConnector(ctx, a) connector, err := NewGraphConnector(ctx, a, r)
require.NoError(t, err) require.NoError(t, err)
return connector return connector

View File

@ -39,7 +39,7 @@ func (suite *GraphConnectorIntegrationSuite) SetupSuite() {
_, err := tester.GetRequiredEnvVars(tester.M365AcctCredEnvs...) _, err := tester.GetRequiredEnvVars(tester.M365AcctCredEnvs...)
require.NoError(suite.T(), err) require.NoError(suite.T(), err)
suite.connector = loadConnector(ctx, suite.T()) suite.connector = loadConnector(ctx, suite.T(), Users)
suite.user = tester.M365UserID(suite.T()) suite.user = tester.M365UserID(suite.T())
tester.LogTimeOfTest(suite.T()) tester.LogTimeOfTest(suite.T())
} }
@ -63,8 +63,8 @@ func (suite *GraphConnectorIntegrationSuite) TestSetTenantUsers() {
suite.Equal(len(newConnector.Users), 0) suite.Equal(len(newConnector.Users), 0)
err = newConnector.setTenantUsers(ctx) err = newConnector.setTenantUsers(ctx)
assert.NoError(suite.T(), err) suite.NoError(err)
suite.Greater(len(newConnector.Users), 0) suite.Less(0, len(newConnector.Users))
} }
// TestSetTenantUsers verifies GraphConnector's ability to query // TestSetTenantUsers verifies GraphConnector's ability to query
@ -86,10 +86,8 @@ func (suite *GraphConnectorIntegrationSuite) TestSetTenantSites() {
suite.Equal(0, len(newConnector.Sites)) suite.Equal(0, len(newConnector.Sites))
err = newConnector.setTenantSites(ctx) err = newConnector.setTenantSites(ctx)
assert.NoError(suite.T(), err) suite.NoError(err)
// TODO: should be non-zero once implemented. suite.Less(0, len(newConnector.Sites))
// suite.Greater(len(newConnector.Users), 0)
suite.Equal(0, len(newConnector.Sites))
} }
func (suite *GraphConnectorIntegrationSuite) TestEmptyCollections() { func (suite *GraphConnectorIntegrationSuite) TestEmptyCollections() {
@ -194,7 +192,7 @@ func runRestoreBackupTest(
start := time.Now() start := time.Now()
restoreGC := loadConnector(ctx, t) restoreGC := loadConnector(ctx, t, test.resource)
restoreSel := getSelectorWith(test.service) restoreSel := getSelectorWith(test.service)
deets, err := restoreGC.RestoreDataCollections(ctx, restoreSel, dest, collections) deets, err := restoreGC.RestoreDataCollections(ctx, restoreSel, dest, collections)
require.NoError(t, err) require.NoError(t, err)
@ -228,7 +226,7 @@ func runRestoreBackupTest(
}) })
} }
backupGC := loadConnector(ctx, t) backupGC := loadConnector(ctx, t, test.resource)
backupSel := backupSelectorForExpected(t, test.service, expectedDests) backupSel := backupSelectorForExpected(t, test.service, expectedDests)
t.Logf("Selective backup of %s\n", backupSel) t.Logf("Selective backup of %s\n", backupSel)
@ -253,8 +251,9 @@ func (suite *GraphConnectorIntegrationSuite) TestRestoreAndBackup() {
table := []restoreBackupInfo{ table := []restoreBackupInfo{
{ {
name: "EmailsWithAttachments", name: "EmailsWithAttachments",
service: path.ExchangeService, service: path.ExchangeService,
resource: Users,
collections: []colInfo{ collections: []colInfo{
{ {
pathElements: []string{"Inbox"}, pathElements: []string{"Inbox"},
@ -279,8 +278,9 @@ func (suite *GraphConnectorIntegrationSuite) TestRestoreAndBackup() {
}, },
}, },
{ {
name: "MultipleEmailsMultipleFolders", name: "MultipleEmailsMultipleFolders",
service: path.ExchangeService, service: path.ExchangeService,
resource: Users,
collections: []colInfo{ collections: []colInfo{
{ {
pathElements: []string{"Inbox"}, pathElements: []string{"Inbox"},
@ -324,8 +324,9 @@ func (suite *GraphConnectorIntegrationSuite) TestRestoreAndBackup() {
}, },
}, },
{ {
name: "MultipleContactsSingleFolder", name: "MultipleContactsSingleFolder",
service: path.ExchangeService, service: path.ExchangeService,
resource: Users,
collections: []colInfo{ collections: []colInfo{
{ {
pathElements: []string{"Contacts"}, pathElements: []string{"Contacts"},
@ -351,8 +352,9 @@ func (suite *GraphConnectorIntegrationSuite) TestRestoreAndBackup() {
}, },
}, },
{ {
name: "MultipleContactsMutlipleFolders", name: "MultipleContactsMutlipleFolders",
service: path.ExchangeService, service: path.ExchangeService,
resource: Users,
collections: []colInfo{ collections: []colInfo{
{ {
pathElements: []string{"Work"}, pathElements: []string{"Work"},
@ -475,8 +477,9 @@ func (suite *GraphConnectorIntegrationSuite) TestRestoreAndBackup() {
func (suite *GraphConnectorIntegrationSuite) TestMultiFolderBackupDifferentNames() { func (suite *GraphConnectorIntegrationSuite) TestMultiFolderBackupDifferentNames() {
table := []restoreBackupInfo{ table := []restoreBackupInfo{
{ {
name: "Contacts", name: "Contacts",
service: path.ExchangeService, service: path.ExchangeService,
resource: Users,
collections: []colInfo{ collections: []colInfo{
{ {
pathElements: []string{"Work"}, pathElements: []string{"Work"},
@ -574,7 +577,7 @@ func (suite *GraphConnectorIntegrationSuite) TestMultiFolderBackupDifferentNames
dest.ContainerName, dest.ContainerName,
) )
restoreGC := loadConnector(ctx, t) restoreGC := loadConnector(ctx, t, test.resource)
deets, err := restoreGC.RestoreDataCollections(ctx, restoreSel, dest, collections) deets, err := restoreGC.RestoreDataCollections(ctx, restoreSel, dest, collections)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, deets) require.NotNil(t, deets)
@ -592,7 +595,7 @@ func (suite *GraphConnectorIntegrationSuite) TestMultiFolderBackupDifferentNames
// Run a backup and compare its output with what we put in. // Run a backup and compare its output with what we put in.
backupGC := loadConnector(ctx, t) backupGC := loadConnector(ctx, t, test.resource)
backupSel := backupSelectorForExpected(t, test.service, expectedDests) backupSel := backupSelectorForExpected(t, test.service, expectedDests)
t.Log("Selective backup of", backupSel) t.Log("Selective backup of", backupSel)
@ -622,8 +625,9 @@ func (suite *GraphConnectorIntegrationSuite) TestMultiuserRestoreAndBackup() {
} }
table := []restoreBackupInfo{ table := []restoreBackupInfo{
{ {
name: "Email", name: "Email",
service: path.ExchangeService, service: path.ExchangeService,
resource: Users,
collections: []colInfo{ collections: []colInfo{
{ {
pathElements: []string{"Inbox"}, pathElements: []string{"Inbox"},
@ -658,8 +662,9 @@ func (suite *GraphConnectorIntegrationSuite) TestMultiuserRestoreAndBackup() {
}, },
}, },
{ {
name: "Contacts", name: "Contacts",
service: path.ExchangeService, service: path.ExchangeService,
resource: Users,
collections: []colInfo{ collections: []colInfo{
{ {
pathElements: []string{"Work"}, pathElements: []string{"Work"},

View File

@ -0,0 +1,21 @@
package sharepoint
import (
"context"
absser "github.com/microsoft/kiota-abstractions-go/serialization"
mssite "github.com/microsoftgraph/msgraph-sdk-go/sites"
"github.com/alcionai/corso/src/internal/connector/graph"
)
// GetAllSitesForTenant makes a GraphQuery request retrieving all sites in the tenant.
func GetAllSitesForTenant(ctx context.Context, gs graph.Service) (absser.Parsable, error) {
options := &mssite.SitesRequestBuilderGetRequestConfiguration{
QueryParameters: &mssite.SitesRequestBuilderGetQueryParameters{
Select: []string{"id", "name", "weburl"},
},
}
return gs.Client().Sites().Get(ctx, options)
}

View File

@ -406,9 +406,9 @@ func SetAdditionalDataToEventMessage(
// ToEventSimplified transforms an event to simplifed restore format // ToEventSimplified transforms an event to simplifed restore format
// To overcome some of the MS Graph API challenges, the event object is modified in the following ways: // To overcome some of the MS Graph API challenges, the event object is modified in the following ways:
// * Instead of adding attendees and generating spurious notifications, // - Instead of adding attendees and generating spurious notifications,
// add a summary of attendees at the beginning to the event before the original body content // add a summary of attendees at the beginning to the event before the original body content
// * event.attendees is set to an empty list // - event.attendees is set to an empty list
func ToEventSimplified(orig models.Eventable) models.Eventable { func ToEventSimplified(orig models.Eventable) models.Eventable {
attendees := FormatAttendees(orig, *orig.GetBody().GetContentType() == models.HTML_BODYTYPE) attendees := FormatAttendees(orig, *orig.GetBody().GetContentType() == models.HTML_BODYTYPE)
orig.SetAttendees([]models.Attendeeable{}) orig.SetAttendees([]models.Attendeeable{})

View File

@ -128,7 +128,12 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
defer close(complete) defer close(complete)
// retrieve data from the producer // retrieve data from the producer
gc, err := connector.NewGraphConnector(ctx, op.account) resource := connector.Users
if op.Selectors.Service == selectors.ServiceSharePoint {
resource = connector.Sites
}
gc, err := connector.NewGraphConnector(ctx, op.account, resource)
if err != nil { if err != nil {
err = errors.Wrap(err, "connecting to graph api") err = errors.Wrap(err, "connecting to graph api")
opStats.readErr = err opStats.readErr = err

View File

@ -166,7 +166,12 @@ func (op *RestoreOperation) Run(ctx context.Context) (restoreDetails *details.De
defer close(gcComplete) defer close(gcComplete)
// restore those collections using graph // restore those collections using graph
gc, err := connector.NewGraphConnector(ctx, op.account) resource := connector.Users
if op.Selectors.Service == selectors.ServiceSharePoint {
resource = connector.Sites
}
gc, err := connector.NewGraphConnector(ctx, op.account, resource)
if err != nil { if err != nil {
err = errors.Wrap(err, "connecting to microsoft servers") err = errors.Wrap(err, "connecting to microsoft servers")
opStats.writeErr = err opStats.writeErr = err

View File

@ -12,7 +12,7 @@ import (
// Users returns a list of users in the specified M365 tenant // Users returns a list of users in the specified M365 tenant
// TODO: Implement paging support // TODO: Implement paging support
func Users(ctx context.Context, m365Account account.Account) ([]string, error) { func Users(ctx context.Context, m365Account account.Account) ([]string, error) {
gc, err := connector.NewGraphConnector(ctx, m365Account) gc, err := connector.NewGraphConnector(ctx, m365Account, connector.Users)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "could not initialize M365 graph connection") return nil, errors.Wrap(err, "could not initialize M365 graph connection")
} }
@ -23,7 +23,7 @@ func Users(ctx context.Context, m365Account account.Account) ([]string, error) {
// UserIDs returns a list of user IDs for the specified M365 tenant // UserIDs returns a list of user IDs for the specified M365 tenant
// TODO: Implement paging support // TODO: Implement paging support
func UserIDs(ctx context.Context, m365Account account.Account) ([]string, error) { func UserIDs(ctx context.Context, m365Account account.Account) ([]string, error) {
gc, err := connector.NewGraphConnector(ctx, m365Account) gc, err := connector.NewGraphConnector(ctx, m365Account, connector.Users)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "could not initialize M365 graph connection") return nil, errors.Wrap(err, "could not initialize M365 graph connection")
} }
@ -32,7 +32,7 @@ func UserIDs(ctx context.Context, m365Account account.Account) ([]string, error)
} }
func GetEmailAndUserID(ctx context.Context, m365Account account.Account) (map[string]string, error) { func GetEmailAndUserID(ctx context.Context, m365Account account.Account) (map[string]string, error) {
gc, err := connector.NewGraphConnector(ctx, m365Account) gc, err := connector.NewGraphConnector(ctx, m365Account, connector.Users)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "could not initialize M365 graph connection") return nil, errors.Wrap(err, "could not initialize M365 graph connection")
} }