Check whether the user has an exchange mailbox (#2156)
## Description This commit adds logic in discovery and backup to check whether the specified user has an exchange mailbox that is available/enabled. If so - the backup is short-circuited to succeed but with "no data" Going forward - we should be able to move the logic in the OneDrive connector that checks for a valid drive and license in here. ## Does this PR need a docs update or release note? - [x] ✅ Yes, it's included - [ ] 🕐 Yes, but in a later PR - [ ] ⛔ No ## Type of change <!--- Please check the type of change your PR introduces: ---> - [ ] 🌻 Feature - [x] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Test - [ ] 💻 CI/Deployment - [ ] 🧹 Tech Debt/Cleanup ## Issue(s) <!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. --> * #2145 ## Test Plan <!-- How will this be tested prior to merging.--> - [x] 💪 Manual - [ ] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
63b77e2bf5
commit
3a37584938
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased] (alpha)
|
## [Unreleased] (alpha)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Check if the user specified for an exchange backup operation has a mailbox.
|
||||||
|
|
||||||
|
|
||||||
## [v0.1.0] (alpha) - 2023-01-13
|
## [v0.1.0] (alpha) - 2023-01-13
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,9 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/connector/discovery"
|
||||||
"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/onedrive"
|
"github.com/alcionai/corso/src/internal/connector/onedrive"
|
||||||
"github.com/alcionai/corso/src/internal/connector/sharepoint"
|
"github.com/alcionai/corso/src/internal/connector/sharepoint"
|
||||||
"github.com/alcionai/corso/src/internal/connector/support"
|
"github.com/alcionai/corso/src/internal/connector/support"
|
||||||
@ -15,6 +17,7 @@ import (
|
|||||||
D "github.com/alcionai/corso/src/internal/diagnostics"
|
D "github.com/alcionai/corso/src/internal/diagnostics"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,6 +44,15 @@ func (gc *GraphConnector) DataCollections(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serviceEnabled, err := checkServiceEnabled(ctx, gc.Service, path.ServiceType(sels.Service), sels.DiscreteOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !serviceEnabled {
|
||||||
|
return []data.Collection{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
switch sels.Service {
|
switch sels.Service {
|
||||||
case selectors.ServiceExchange:
|
case selectors.ServiceExchange:
|
||||||
colls, err := exchange.DataCollections(
|
colls, err := exchange.DataCollections(
|
||||||
@ -124,6 +136,29 @@ func verifyBackupInputs(sels selectors.Selector, userPNs, siteIDs []string) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkServiceEnabled(
|
||||||
|
ctx context.Context,
|
||||||
|
gs graph.Servicer,
|
||||||
|
service path.ServiceType,
|
||||||
|
resource string,
|
||||||
|
) (bool, error) {
|
||||||
|
if service == path.SharePointService {
|
||||||
|
// No "enabled" check required for sharepoint
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, info, err := discovery.User(ctx, gs, resource)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := info.DiscoveredServices[service]; !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// OneDrive
|
// OneDrive
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
"github.com/alcionai/corso/src/internal/connector/support"
|
"github.com/alcionai/corso/src/internal/connector/support"
|
||||||
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -64,6 +65,49 @@ func Users(ctx context.Context, gs graph.Servicer, tenantID string) ([]models.Us
|
|||||||
return users, iterErrs
|
return users, iterErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserInfo struct {
|
||||||
|
DiscoveredServices map[path.ServiceType]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func User(ctx context.Context, gs graph.Servicer, userID string) (models.Userable, *UserInfo, error) {
|
||||||
|
user, err := gs.Client().UsersById(userID).Get(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.Wrapf(
|
||||||
|
err,
|
||||||
|
"retrieving resource for tenant: %s",
|
||||||
|
support.ConnectorStackErrorTrace(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume all services are enabled
|
||||||
|
userInfo := &UserInfo{
|
||||||
|
DiscoveredServices: map[path.ServiceType]struct{}{
|
||||||
|
path.ExchangeService: {},
|
||||||
|
path.OneDriveService: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover which services the user has enabled
|
||||||
|
|
||||||
|
// Exchange: Query `MailFolders`
|
||||||
|
_, err = gs.Client().UsersById(userID).MailFolders().Get(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
if !graph.IsErrExchangeMailFolderNotFound(err) {
|
||||||
|
return nil, nil, errors.Wrapf(
|
||||||
|
err,
|
||||||
|
"retrieving mail folders for tenant: %s",
|
||||||
|
support.ConnectorStackErrorTrace(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(userInfo.DiscoveredServices, path.ExchangeService)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: OneDrive
|
||||||
|
|
||||||
|
return user, userInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
// parseUser extracts information from `models.Userable` we care about
|
// parseUser extracts information from `models.Userable` we care about
|
||||||
func parseUser(item interface{}) (models.Userable, error) {
|
func parseUser(item interface{}) (models.Userable, error) {
|
||||||
m, ok := item.(models.Userable)
|
m, ok := item.(models.Userable)
|
||||||
|
|||||||
@ -15,11 +15,13 @@ import (
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const (
|
const (
|
||||||
errCodeItemNotFound = "ErrorItemNotFound"
|
errCodeItemNotFound = "ErrorItemNotFound"
|
||||||
errCodeEmailFolderNotFound = "ErrorSyncFolderNotFound"
|
errCodeEmailFolderNotFound = "ErrorSyncFolderNotFound"
|
||||||
errCodeResyncRequired = "ResyncRequired"
|
errCodeResyncRequired = "ResyncRequired"
|
||||||
errCodeSyncFolderNotFound = "ErrorSyncFolderNotFound"
|
errCodeSyncFolderNotFound = "ErrorSyncFolderNotFound"
|
||||||
errCodeSyncStateNotFound = "SyncStateNotFound"
|
errCodeSyncStateNotFound = "SyncStateNotFound"
|
||||||
|
errCodeResourceNotFound = "ResourceNotFound"
|
||||||
|
errCodeMailboxNotEnabledForRESTAPI = "MailboxNotEnabledForRESTAPI"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The folder or item was deleted between the time we identified
|
// The folder or item was deleted between the time we identified
|
||||||
@ -69,6 +71,10 @@ func asInvalidDelta(err error) bool {
|
|||||||
return errors.As(err, &e)
|
return errors.As(err, &e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsErrExchangeMailFolderNotFound(err error) bool {
|
||||||
|
return hasErrorCode(err, errCodeResourceNotFound, errCodeMailboxNotEnabledForRESTAPI)
|
||||||
|
}
|
||||||
|
|
||||||
// Timeout errors are identified for tracking the need to retry calls.
|
// Timeout errors are identified for tracking the need to retry calls.
|
||||||
// Other delay errors, like throttling, are already handled by the
|
// Other delay errors, like throttling, are already handled by the
|
||||||
// graph client's built-in retries.
|
// graph client's built-in retries.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user