Filter out non existent entities in permissions and link shares (#4955)
<!-- PR description--> --- #### Does this PR need a docs update or release note? - [ ] ✅ Yes, it's included - [ ] 🕐 Yes, but in a later PR - [x] ⛔ No #### Type of change <!--- Please check the type of change your PR introduces: ---> - [ ] 🌻 Feature - [x] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Supportability/Tests - [ ] 💻 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. --> * #<issue> #### Test Plan <!-- How will this be tested prior to merging.--> - [ ] 💪 Manual - [x] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
8067c72904
commit
ca50b1e908
@ -397,7 +397,7 @@ func UpdateLinkShares(
|
|||||||
|
|
||||||
newLS, err := upils.PostItemLinkShareUpdate(ictx, driveID, itemID, lsbody)
|
newLS, err := upils.PostItemLinkShareUpdate(ictx, driveID, itemID, lsbody)
|
||||||
if graph.IsErrUsersCannotBeResolved(err) {
|
if graph.IsErrUsersCannotBeResolved(err) {
|
||||||
oldLinkShareIDToNewID.Store(ls.ID, "") // empty to signify that we could not restore
|
oldLinkShareIDToNewID.Store(ls.ID, nonRestorablePermission)
|
||||||
logger.CtxErr(ictx, err).Info("unable to restore link share")
|
logger.CtxErr(ictx, err).Info("unable to restore link share")
|
||||||
|
|
||||||
continue
|
continue
|
||||||
@ -449,6 +449,102 @@ func UpdateLinkShares(
|
|||||||
return alreadyDeleted, nil
|
return alreadyDeleted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterUnavailableEntitiesInLinkShare(
|
||||||
|
ctx context.Context,
|
||||||
|
linkShares []metadata.LinkShare,
|
||||||
|
availableEntities ResourceIDNames,
|
||||||
|
oldLinkShareIDToNewID syncd.MapTo[string],
|
||||||
|
) []metadata.LinkShare {
|
||||||
|
filtered := []metadata.LinkShare{}
|
||||||
|
|
||||||
|
if availableEntities.Users == nil || availableEntities.Groups == nil {
|
||||||
|
// This should not be happening unless we missed to fill in the caches
|
||||||
|
logger.Ctx(ctx).Info("no available entities, not filtering link shares")
|
||||||
|
return linkShares
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range linkShares {
|
||||||
|
entities := []metadata.Entity{}
|
||||||
|
|
||||||
|
for _, e := range p.Entities {
|
||||||
|
available := false
|
||||||
|
|
||||||
|
switch e.EntityType {
|
||||||
|
case metadata.GV2User:
|
||||||
|
_, ok := availableEntities.Users.NameOf(e.ID)
|
||||||
|
available = available || ok
|
||||||
|
case metadata.GV2Group:
|
||||||
|
_, ok := availableEntities.Groups.NameOf(e.ID)
|
||||||
|
available = available || ok
|
||||||
|
default:
|
||||||
|
// We only know about users and groups
|
||||||
|
available = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if available {
|
||||||
|
entities = append(entities, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(entities) > 0 {
|
||||||
|
p.Entities = entities
|
||||||
|
filtered = append(filtered, p)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have no entities, we can't restore the link share
|
||||||
|
// and so we have to mark it as not restored.
|
||||||
|
// This is done to make sure we don't try to delete it later.
|
||||||
|
oldLinkShareIDToNewID.Store(p.ID, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterUnavailableEntitiesInPermissions(
|
||||||
|
ctx context.Context,
|
||||||
|
perms []metadata.Permission,
|
||||||
|
availableEntities ResourceIDNames,
|
||||||
|
oldPermIDToNewID syncd.MapTo[string],
|
||||||
|
) []metadata.Permission {
|
||||||
|
if availableEntities.Users == nil || availableEntities.Groups == nil {
|
||||||
|
// This should not be happening unless we missed to fill in the caches
|
||||||
|
logger.Ctx(ctx).Info("no available entities, not filtering link shares")
|
||||||
|
return perms
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := []metadata.Permission{}
|
||||||
|
|
||||||
|
for _, p := range perms {
|
||||||
|
available := false
|
||||||
|
|
||||||
|
switch p.EntityType {
|
||||||
|
case metadata.GV2User:
|
||||||
|
_, ok := availableEntities.Users.NameOf(p.EntityID)
|
||||||
|
available = available || ok
|
||||||
|
case metadata.GV2Group:
|
||||||
|
_, ok := availableEntities.Groups.NameOf(p.EntityID)
|
||||||
|
available = available || ok
|
||||||
|
default:
|
||||||
|
// We only know about users and groups
|
||||||
|
// TODO: extend the check to other entity types
|
||||||
|
available = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if available {
|
||||||
|
filtered = append(filtered, p)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have no entities, we can't restore the permission
|
||||||
|
// and so we have to mark it as not restored.
|
||||||
|
oldPermIDToNewID.Store(p.ID, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
// RestorePermissions takes in the permissions of an item, computes
|
// RestorePermissions takes in the permissions of an item, computes
|
||||||
// what permissions need to added and removed based on the parent
|
// what permissions need to added and removed based on the parent
|
||||||
// folder metas and uses that to add/remove the necessary permissions
|
// folder metas and uses that to add/remove the necessary permissions
|
||||||
@ -478,6 +574,7 @@ func RestorePermissions(
|
|||||||
|
|
||||||
if previousLinkShares != nil {
|
if previousLinkShares != nil {
|
||||||
lsAdded, lsRemoved := metadata.DiffLinkShares(previousLinkShares, current.LinkShares)
|
lsAdded, lsRemoved := metadata.DiffLinkShares(previousLinkShares, current.LinkShares)
|
||||||
|
lsAdded = filterUnavailableEntitiesInLinkShare(ctx, lsAdded, caches.AvailableEntities, caches.OldLinkShareIDToNewID)
|
||||||
|
|
||||||
// Link shares have to be updated before permissions as we have to
|
// Link shares have to be updated before permissions as we have to
|
||||||
// use the information about if we had to reset the inheritance to
|
// use the information about if we had to reset the inheritance to
|
||||||
@ -503,6 +600,7 @@ func RestorePermissions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
permAdded, permRemoved := metadata.DiffPermissions(previous.Permissions, current.Permissions)
|
permAdded, permRemoved := metadata.DiffPermissions(previous.Permissions, current.Permissions)
|
||||||
|
permAdded = filterUnavailableEntitiesInPermissions(ctx, permAdded, caches.AvailableEntities, caches.OldPermIDToNewID)
|
||||||
|
|
||||||
if didReset {
|
if didReset {
|
||||||
// In case we did a reset of permissions when restoring link
|
// In case we did a reset of permissions when restoring link
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package drive
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -11,6 +12,7 @@ import (
|
|||||||
"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/idname"
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/common/syncd"
|
"github.com/alcionai/corso/src/internal/common/syncd"
|
||||||
"github.com/alcionai/corso/src/internal/m365/collection/drive/metadata"
|
"github.com/alcionai/corso/src/internal/m365/collection/drive/metadata"
|
||||||
@ -326,3 +328,259 @@ func (suite *PermissionsUnitTestSuite) TestLinkShareRestoreNonExistentUser() {
|
|||||||
expectedSuccessValues := []bool{true, false, false, false}
|
expectedSuccessValues := []bool{true, false, false, false}
|
||||||
assert.Equal(t, expectedSuccessValues, successValues)
|
assert.Equal(t, expectedSuccessValues, successValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *PermissionsUnitTestSuite) TestFilterUnavailableEntitiesInPermissions() {
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
permissions []metadata.Permission
|
||||||
|
expected []metadata.Permission
|
||||||
|
availableEntities ResourceIDNames
|
||||||
|
skippedPermissions []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single item",
|
||||||
|
permissions: []metadata.Permission{
|
||||||
|
{ID: "p1", EntityID: "e1", EntityType: metadata.GV2User},
|
||||||
|
},
|
||||||
|
expected: []metadata.Permission{
|
||||||
|
{ID: "p1", EntityID: "e1", EntityType: metadata.GV2User},
|
||||||
|
},
|
||||||
|
availableEntities: ResourceIDNames{
|
||||||
|
Users: idname.NewCache(map[string]string{"e1": "e1"}),
|
||||||
|
Groups: idname.NewCache(map[string]string{}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single item with missing entity",
|
||||||
|
permissions: []metadata.Permission{
|
||||||
|
{ID: "p1", EntityID: "e1", EntityType: metadata.GV2User},
|
||||||
|
},
|
||||||
|
expected: []metadata.Permission{},
|
||||||
|
availableEntities: ResourceIDNames{
|
||||||
|
Users: idname.NewCache(map[string]string{}),
|
||||||
|
Groups: idname.NewCache(map[string]string{}),
|
||||||
|
},
|
||||||
|
skippedPermissions: []string{"p1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple items",
|
||||||
|
permissions: []metadata.Permission{
|
||||||
|
{ID: "p1", EntityID: "e1", EntityType: metadata.GV2User},
|
||||||
|
{ID: "p2", EntityID: "e2", EntityType: metadata.GV2User},
|
||||||
|
{ID: "p3", EntityID: "e3", EntityType: metadata.GV2User},
|
||||||
|
},
|
||||||
|
expected: []metadata.Permission{
|
||||||
|
{ID: "p1", EntityID: "e1", EntityType: metadata.GV2User},
|
||||||
|
{ID: "p2", EntityID: "e2", EntityType: metadata.GV2User},
|
||||||
|
{ID: "p3", EntityID: "e3", EntityType: metadata.GV2User},
|
||||||
|
},
|
||||||
|
availableEntities: ResourceIDNames{
|
||||||
|
Users: idname.NewCache(map[string]string{"e1": "e1", "e2": "e2", "e3": "e3"}),
|
||||||
|
Groups: idname.NewCache(map[string]string{}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple items with missing entity",
|
||||||
|
permissions: []metadata.Permission{
|
||||||
|
{ID: "p1", EntityID: "e1", EntityType: metadata.GV2User},
|
||||||
|
{ID: "p2", EntityID: "e2", EntityType: metadata.GV2User},
|
||||||
|
{ID: "p3", EntityID: "e3", EntityType: metadata.GV2Group},
|
||||||
|
},
|
||||||
|
expected: []metadata.Permission{
|
||||||
|
{ID: "p1", EntityID: "e1", EntityType: metadata.GV2User},
|
||||||
|
{ID: "p3", EntityID: "e3", EntityType: metadata.GV2Group},
|
||||||
|
},
|
||||||
|
availableEntities: ResourceIDNames{
|
||||||
|
Users: idname.NewCache(map[string]string{"e1": "e1"}),
|
||||||
|
Groups: idname.NewCache(map[string]string{"e3": "e3"}),
|
||||||
|
},
|
||||||
|
skippedPermissions: []string{"p2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple items with missing entity and multiple types",
|
||||||
|
permissions: []metadata.Permission{
|
||||||
|
{ID: "p1", EntityID: "e1", EntityType: metadata.GV2User},
|
||||||
|
{ID: "p2", EntityID: "e2", EntityType: metadata.GV2User},
|
||||||
|
{ID: "p3", EntityID: "e3", EntityType: metadata.GV2Group},
|
||||||
|
{ID: "p4", EntityID: "e4", EntityType: metadata.GV2Group},
|
||||||
|
{ID: "p5", EntityID: "e5", EntityType: metadata.GV2User},
|
||||||
|
},
|
||||||
|
expected: []metadata.Permission{
|
||||||
|
{ID: "p1", EntityID: "e1", EntityType: metadata.GV2User},
|
||||||
|
{ID: "p3", EntityID: "e3", EntityType: metadata.GV2Group},
|
||||||
|
{ID: "p5", EntityID: "e5", EntityType: metadata.GV2User},
|
||||||
|
},
|
||||||
|
availableEntities: ResourceIDNames{
|
||||||
|
Users: idname.NewCache(map[string]string{"e1": "e1", "e5": "e5"}),
|
||||||
|
Groups: idname.NewCache(map[string]string{"e3": "e3"}),
|
||||||
|
},
|
||||||
|
skippedPermissions: []string{"p2", "p4"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single item different type",
|
||||||
|
permissions: []metadata.Permission{
|
||||||
|
{ID: "p1", EntityID: "e1", EntityType: metadata.GV2Group},
|
||||||
|
},
|
||||||
|
expected: []metadata.Permission{},
|
||||||
|
availableEntities: ResourceIDNames{
|
||||||
|
Users: idname.NewCache(map[string]string{"e1": "e1"}),
|
||||||
|
Groups: idname.NewCache(map[string]string{}),
|
||||||
|
},
|
||||||
|
skippedPermissions: []string{"p1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unhandled types",
|
||||||
|
permissions: []metadata.Permission{
|
||||||
|
{ID: "p1", EntityID: "e1", EntityType: metadata.GV2Device},
|
||||||
|
{ID: "p2", EntityID: "e2", EntityType: metadata.GV2App},
|
||||||
|
{ID: "p3", EntityID: "e3", EntityType: metadata.GV2SiteUser},
|
||||||
|
{ID: "p4", EntityID: "e4", EntityType: metadata.GV2SiteGroup},
|
||||||
|
},
|
||||||
|
expected: []metadata.Permission{
|
||||||
|
{ID: "p1", EntityID: "e1", EntityType: metadata.GV2Device},
|
||||||
|
{ID: "p2", EntityID: "e2", EntityType: metadata.GV2App},
|
||||||
|
{ID: "p3", EntityID: "e3", EntityType: metadata.GV2SiteUser},
|
||||||
|
{ID: "p4", EntityID: "e4", EntityType: metadata.GV2SiteGroup},
|
||||||
|
},
|
||||||
|
availableEntities: ResourceIDNames{
|
||||||
|
// these are users and not what we have
|
||||||
|
Users: idname.NewCache(map[string]string{"e1": "e1", "e2": "e2", "e3": "e3", "e4": "e4"}),
|
||||||
|
Groups: idname.NewCache(map[string]string{}),
|
||||||
|
},
|
||||||
|
skippedPermissions: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
oldToNew := syncd.NewMapTo[string]()
|
||||||
|
filtered := filterUnavailableEntitiesInPermissions(ctx, test.permissions, test.availableEntities, oldToNew)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, filtered, "filtered permissions")
|
||||||
|
|
||||||
|
for _, id := range test.skippedPermissions {
|
||||||
|
_, ok := oldToNew.Load(id)
|
||||||
|
assert.True(t, ok, fmt.Sprintf("skipped id %s", id))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type eidtype struct {
|
||||||
|
id string
|
||||||
|
etype metadata.GV2Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *PermissionsUnitTestSuite) TestFilterUnavailableEntitiesInLinkShare() {
|
||||||
|
ls := func(lsid string, entities []eidtype) metadata.LinkShare {
|
||||||
|
ents := []metadata.Entity{}
|
||||||
|
for _, e := range entities {
|
||||||
|
ents = append(ents, metadata.Entity{ID: e.id, EntityType: e.etype})
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata.LinkShare{
|
||||||
|
ID: lsid,
|
||||||
|
Entities: ents,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ae := func(uids, gids []string) ResourceIDNames {
|
||||||
|
users := map[string]string{}
|
||||||
|
for _, id := range uids {
|
||||||
|
users[id] = id
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := map[string]string{}
|
||||||
|
for _, id := range gids {
|
||||||
|
groups[id] = id
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResourceIDNames{
|
||||||
|
Users: idname.NewCache(users),
|
||||||
|
Groups: idname.NewCache(groups),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
linkShares []metadata.LinkShare
|
||||||
|
expected []metadata.LinkShare
|
||||||
|
availableEntities ResourceIDNames
|
||||||
|
skippedLinkShares []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single item, single available entity",
|
||||||
|
linkShares: []metadata.LinkShare{ls("ls1", []eidtype{{"e1", metadata.GV2User}})},
|
||||||
|
expected: []metadata.LinkShare{ls("ls1", []eidtype{{"e1", metadata.GV2User}})},
|
||||||
|
availableEntities: ae([]string{"e1"}, []string{}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single item, single missing entity",
|
||||||
|
linkShares: []metadata.LinkShare{ls("ls1", []eidtype{{"e1", metadata.GV2User}})},
|
||||||
|
expected: []metadata.LinkShare{},
|
||||||
|
availableEntities: ae([]string{}, []string{}),
|
||||||
|
skippedLinkShares: []string{"ls1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple items, multiple available entities",
|
||||||
|
linkShares: []metadata.LinkShare{
|
||||||
|
ls("ls1", []eidtype{{"e1", metadata.GV2User}, {"e2", metadata.GV2User}}),
|
||||||
|
ls("ls2", []eidtype{{"e3", metadata.GV2User}, {"e4", metadata.GV2User}}),
|
||||||
|
},
|
||||||
|
expected: []metadata.LinkShare{
|
||||||
|
ls("ls1", []eidtype{{"e1", metadata.GV2User}, {"e2", metadata.GV2User}}),
|
||||||
|
ls("ls2", []eidtype{{"e3", metadata.GV2User}, {"e4", metadata.GV2User}}),
|
||||||
|
},
|
||||||
|
availableEntities: ae([]string{"e1", "e2", "e3", "e4"}, []string{}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple items, missing entities",
|
||||||
|
linkShares: []metadata.LinkShare{
|
||||||
|
ls("ls1", []eidtype{{"e1", metadata.GV2User}, {"e2", metadata.GV2Group}}),
|
||||||
|
ls("ls2", []eidtype{{"e3", metadata.GV2User}, {"e4", metadata.GV2Group}}),
|
||||||
|
},
|
||||||
|
expected: []metadata.LinkShare{
|
||||||
|
ls("ls1", []eidtype{{"e1", metadata.GV2User}}),
|
||||||
|
ls("ls2", []eidtype{{"e4", metadata.GV2Group}}),
|
||||||
|
},
|
||||||
|
availableEntities: ae([]string{"e1"}, []string{"e4"}),
|
||||||
|
skippedLinkShares: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unhandled items",
|
||||||
|
linkShares: []metadata.LinkShare{
|
||||||
|
ls("ls1", []eidtype{{"e1", metadata.GV2Device}, {"e2", metadata.GV2App}}),
|
||||||
|
ls("ls2", []eidtype{{"e3", metadata.GV2SiteUser}, {"e4", metadata.GV2SiteGroup}}),
|
||||||
|
},
|
||||||
|
expected: []metadata.LinkShare{
|
||||||
|
ls("ls1", []eidtype{{"e1", metadata.GV2Device}, {"e2", metadata.GV2App}}),
|
||||||
|
ls("ls2", []eidtype{{"e3", metadata.GV2SiteUser}, {"e4", metadata.GV2SiteGroup}}),
|
||||||
|
},
|
||||||
|
availableEntities: ae([]string{}, []string{}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
oldToNew := syncd.NewMapTo[string]()
|
||||||
|
filtered := filterUnavailableEntitiesInLinkShare(ctx, test.linkShares, test.availableEntities, oldToNew)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, filtered, "filtered link shares")
|
||||||
|
|
||||||
|
for _, id := range test.skippedLinkShares {
|
||||||
|
_, ok := oldToNew.Load(id)
|
||||||
|
assert.True(t, ok, fmt.Sprintf("skipped id %s", id))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/common/syncd"
|
"github.com/alcionai/corso/src/internal/common/syncd"
|
||||||
"github.com/alcionai/corso/src/internal/m365/collection/drive/metadata"
|
"github.com/alcionai/corso/src/internal/m365/collection/drive/metadata"
|
||||||
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
||||||
)
|
)
|
||||||
@ -21,6 +22,11 @@ type driveInfo struct {
|
|||||||
rootFolderID string
|
rootFolderID string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResourceIDNames struct {
|
||||||
|
Users idname.Cacher
|
||||||
|
Groups idname.Cacher
|
||||||
|
}
|
||||||
|
|
||||||
type restoreCaches struct {
|
type restoreCaches struct {
|
||||||
BackupDriveIDName idname.Cacher
|
BackupDriveIDName idname.Cacher
|
||||||
collisionKeyToItemID map[string]api.DriveItemIDType
|
collisionKeyToItemID map[string]api.DriveItemIDType
|
||||||
@ -30,6 +36,7 @@ type restoreCaches struct {
|
|||||||
OldLinkShareIDToNewID syncd.MapTo[string]
|
OldLinkShareIDToNewID syncd.MapTo[string]
|
||||||
OldPermIDToNewID syncd.MapTo[string]
|
OldPermIDToNewID syncd.MapTo[string]
|
||||||
ParentDirToMeta syncd.MapTo[metadata.Metadata]
|
ParentDirToMeta syncd.MapTo[metadata.Metadata]
|
||||||
|
AvailableEntities ResourceIDNames
|
||||||
|
|
||||||
pool sync.Pool
|
pool sync.Pool
|
||||||
}
|
}
|
||||||
@ -59,12 +66,18 @@ func (rc *restoreCaches) AddDrive(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetAllIDsAndNameser interface {
|
||||||
|
GetAllIDsAndNames(ctx context.Context, errs *fault.Bus) (idname.Cacher, error)
|
||||||
|
}
|
||||||
|
|
||||||
// Populate looks up drive items available to the protectedResource
|
// Populate looks up drive items available to the protectedResource
|
||||||
// and adds their info to the caches.
|
// and adds their info to the caches.
|
||||||
func (rc *restoreCaches) Populate(
|
func (rc *restoreCaches) Populate(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
usersGetter, groupsGetter GetAllIDsAndNameser,
|
||||||
gdparf GetDrivePagerAndRootFolderer,
|
gdparf GetDrivePagerAndRootFolderer,
|
||||||
protectedResourceID string,
|
protectedResourceID string,
|
||||||
|
errs *fault.Bus,
|
||||||
) error {
|
) error {
|
||||||
drives, err := api.GetAllDrives(
|
drives, err := api.GetAllDrives(
|
||||||
ctx,
|
ctx,
|
||||||
@ -79,6 +92,19 @@ func (rc *restoreCaches) Populate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
users, err := usersGetter.GetAllIDsAndNames(ctx, errs)
|
||||||
|
if err != nil {
|
||||||
|
return clues.Wrap(err, "getting users")
|
||||||
|
}
|
||||||
|
|
||||||
|
groups, err := groupsGetter.GetAllIDsAndNames(ctx, errs)
|
||||||
|
if err != nil {
|
||||||
|
return clues.Wrap(err, "getting groups")
|
||||||
|
}
|
||||||
|
|
||||||
|
rc.AvailableEntities.Users = users
|
||||||
|
rc.AvailableEntities.Groups = groups
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -428,6 +428,17 @@ func (m *mockGDPARF) NewDrivePager(
|
|||||||
return m.pager
|
return m.pager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockAllIDsAndNamesGetter struct {
|
||||||
|
kvs map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockAllIDsAndNamesGetter) GetAllIDsAndNames(
|
||||||
|
context.Context,
|
||||||
|
*fault.Bus,
|
||||||
|
) (idname.Cacher, error) {
|
||||||
|
return idname.NewCache(m.kvs), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *RestoreUnitSuite) TestRestoreCaches_Populate() {
|
func (suite *RestoreUnitSuite) TestRestoreCaches_Populate() {
|
||||||
rfID := "this-is-id"
|
rfID := "this-is-id"
|
||||||
driveID := "another-id"
|
driveID := "another-id"
|
||||||
@ -443,12 +454,14 @@ func (suite *RestoreUnitSuite) TestRestoreCaches_Populate() {
|
|||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
mock *apiMock.Pager[models.Driveable]
|
mock *apiMock.Pager[models.Driveable]
|
||||||
|
users map[string]string
|
||||||
|
groups map[string]string
|
||||||
expectErr require.ErrorAssertionFunc
|
expectErr require.ErrorAssertionFunc
|
||||||
expectLen int
|
expectLen int
|
||||||
checkValues bool
|
checkValues bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no results",
|
name: "no drive results",
|
||||||
mock: &apiMock.Pager[models.Driveable]{
|
mock: &apiMock.Pager[models.Driveable]{
|
||||||
ToReturn: []apiMock.PagerResult[models.Driveable]{
|
ToReturn: []apiMock.PagerResult[models.Driveable]{
|
||||||
{Values: []models.Driveable{}},
|
{Values: []models.Driveable{}},
|
||||||
@ -458,7 +471,7 @@ func (suite *RestoreUnitSuite) TestRestoreCaches_Populate() {
|
|||||||
expectLen: 0,
|
expectLen: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one result",
|
name: "one drive result",
|
||||||
mock: &apiMock.Pager[models.Driveable]{
|
mock: &apiMock.Pager[models.Driveable]{
|
||||||
ToReturn: []apiMock.PagerResult[models.Driveable]{
|
ToReturn: []apiMock.PagerResult[models.Driveable]{
|
||||||
{Values: []models.Driveable{md}},
|
{Values: []models.Driveable{md}},
|
||||||
@ -478,6 +491,32 @@ func (suite *RestoreUnitSuite) TestRestoreCaches_Populate() {
|
|||||||
expectErr: require.Error,
|
expectErr: require.Error,
|
||||||
expectLen: 0,
|
expectLen: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "multiple users",
|
||||||
|
mock: &apiMock.Pager[models.Driveable]{
|
||||||
|
ToReturn: []apiMock.PagerResult[models.Driveable]{
|
||||||
|
{Values: []models.Driveable{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
users: map[string]string{
|
||||||
|
"1": "one",
|
||||||
|
"2": "two",
|
||||||
|
},
|
||||||
|
expectErr: require.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple groups",
|
||||||
|
mock: &apiMock.Pager[models.Driveable]{
|
||||||
|
ToReturn: []apiMock.PagerResult[models.Driveable]{
|
||||||
|
{Values: []models.Driveable{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
groups: map[string]string{
|
||||||
|
"1": "one",
|
||||||
|
"2": "two",
|
||||||
|
},
|
||||||
|
expectErr: require.NoError,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
@ -491,8 +530,16 @@ func (suite *RestoreUnitSuite) TestRestoreCaches_Populate() {
|
|||||||
pager: test.mock,
|
pager: test.mock,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errs := fault.New(true)
|
||||||
|
|
||||||
rc := NewRestoreCaches(nil)
|
rc := NewRestoreCaches(nil)
|
||||||
err := rc.Populate(ctx, gdparf, "shmoo")
|
err := rc.Populate(
|
||||||
|
ctx,
|
||||||
|
mockAllIDsAndNamesGetter{test.users},
|
||||||
|
mockAllIDsAndNamesGetter{test.groups},
|
||||||
|
gdparf,
|
||||||
|
"shmoo",
|
||||||
|
errs)
|
||||||
test.expectErr(t, err, clues.ToCore(err))
|
test.expectErr(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
assert.Equal(t, rc.DriveIDToDriveInfo.Size(), test.expectLen)
|
assert.Equal(t, rc.DriveIDToDriveInfo.Size(), test.expectLen)
|
||||||
@ -509,6 +556,16 @@ func (suite *RestoreUnitSuite) TestRestoreCaches_Populate() {
|
|||||||
assert.Equal(t, name, nameResult.name, "drive name")
|
assert.Equal(t, name, nameResult.name, "drive name")
|
||||||
assert.Equal(t, rfID, nameResult.rootFolderID, "root folder id")
|
assert.Equal(t, rfID, nameResult.rootFolderID, "root folder id")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for key, val := range test.users {
|
||||||
|
name, _ := rc.AvailableEntities.Users.NameOf(key)
|
||||||
|
assert.Equal(t, name, val, "user")
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range test.groups {
|
||||||
|
name, _ := rc.AvailableEntities.Groups.NameOf(key)
|
||||||
|
assert.Equal(t, name, val, "group")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,7 +46,7 @@ func ConsumeRestoreCollections(
|
|||||||
el = errs.Local()
|
el = errs.Local()
|
||||||
)
|
)
|
||||||
|
|
||||||
err := caches.Populate(ctx, lrh, rcc.ProtectedResource.ID())
|
err := caches.Populate(ctx, ac.Users(), ac.Groups(), lrh, rcc.ProtectedResource.ID(), errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, clues.Wrap(err, "initializing restore caches")
|
return nil, clues.Wrap(err, "initializing restore caches")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -109,7 +109,7 @@ func (h *groupsHandler) ConsumeRestoreCollections(
|
|||||||
Selector: rcc.Selector,
|
Selector: rcc.Selector,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = caches.Populate(ictx, lrh, srcc.ProtectedResource.ID())
|
err = caches.Populate(ictx, h.apiClient.Users(), h.apiClient.Groups(), lrh, srcc.ProtectedResource.ID(), errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, clues.Wrap(err, "initializing restore caches")
|
return nil, nil, clues.Wrap(err, "initializing restore caches")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,7 +53,7 @@ func (h *onedriveHandler) ConsumeRestoreCollections(
|
|||||||
|
|
||||||
ctx = clues.Add(ctx, "backup_version", rcc.BackupVersion)
|
ctx = clues.Add(ctx, "backup_version", rcc.BackupVersion)
|
||||||
|
|
||||||
err := caches.Populate(ctx, rh, rcc.ProtectedResource.ID())
|
err := caches.Populate(ctx, h.apiClient.Users(), h.apiClient.Groups(), rh, rcc.ProtectedResource.ID(), errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, clues.Wrap(err, "initializing restore caches")
|
return nil, nil, clues.Wrap(err, "initializing restore caches")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -81,7 +81,7 @@ func (h *sharepointHandler) ConsumeRestoreCollections(
|
|||||||
|
|
||||||
switch dc.FullPath().Category() {
|
switch dc.FullPath().Category() {
|
||||||
case path.LibrariesCategory:
|
case path.LibrariesCategory:
|
||||||
err = caches.Populate(ctx, lrh, rcc.ProtectedResource.ID())
|
err = caches.Populate(ctx, h.apiClient.Users(), h.apiClient.Groups(), lrh, rcc.ProtectedResource.ID(), errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, clues.Wrap(err, "initializing restore caches")
|
return nil, nil, clues.Wrap(err, "initializing restore caches")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/microsoftgraph/msgraph-sdk-go/groups"
|
"github.com/microsoftgraph/msgraph-sdk-go/groups"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/common/idname"
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/common/str"
|
"github.com/alcionai/corso/src/internal/common/str"
|
||||||
"github.com/alcionai/corso/src/internal/common/tform"
|
"github.com/alcionai/corso/src/internal/common/tform"
|
||||||
@ -51,6 +52,25 @@ func (c Groups) GetAll(
|
|||||||
return getGroups(ctx, errs, service)
|
return getGroups(ctx, errs, service)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllIDsAndNames retrieves all groups in the tenant and returns them in an idname.Cacher
|
||||||
|
func (c Groups) GetAllIDsAndNames(ctx context.Context, errs *fault.Bus) (idname.Cacher, error) {
|
||||||
|
all, err := c.GetAll(ctx, errs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, clues.Wrap(err, "getting all users")
|
||||||
|
}
|
||||||
|
|
||||||
|
idToName := make(map[string]string, len(all))
|
||||||
|
|
||||||
|
for _, g := range all {
|
||||||
|
id := strings.ToLower(ptr.Val(g.GetId()))
|
||||||
|
name := ptr.Val(g.GetDisplayName())
|
||||||
|
|
||||||
|
idToName[id] = name
|
||||||
|
}
|
||||||
|
|
||||||
|
return idname.NewCache(idToName), nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetAll retrieves all groups.
|
// GetAll retrieves all groups.
|
||||||
func getGroups(
|
func getGroups(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user