Backup and restore link share information (#3655)

As of now we are only backing up and restoring actual permissions. This adds support for backing up and restoring link shares as well.

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [x] 🕐 Yes, but in a later PR
- [ ]  No

#### Type of change

<!--- Please check the type of change your PR introduces: --->
- [x] 🌻 Feature
- [ ] 🐛 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. -->
* fixes https://github.com/alcionai/corso/issues/3605

#### Test Plan

<!-- How will this be tested prior to merging.-->
- [ ] 💪 Manual
- [ ]  Unit test
- [x] 💚 E2E
This commit is contained in:
Abin Simon 2023-06-30 14:00:16 +05:30 committed by GitHub
parent 8683fbc067
commit 9d801efa03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1010 additions and 190 deletions

View File

@ -8,10 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] (beta)
### Added
- Drive items backup and restore link shares
### Fixed
- Return a ServiceNotEnabled error when a tenant has no active SharePoint license.
### Known issues
- If a link share is created for an item with inheritance disabled
(via the Graph API), the link shares restored in that item will
not be inheritable by children
- Link shares with password protection can't be restored
## [v0.10.0] (beta) - 2023-06-26
### Added

View File

@ -274,12 +274,14 @@ func generateAndRestoreDriveItems(
{
Name: fmt.Sprintf("file-1st-count-%d-at-%s", i, currentTime),
Data: fileAData,
Meta: odStub.MetaData{
Perms: odStub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
Roles: writePerm,
},
},
},
{
Name: fmt.Sprintf("file-2nd-count-%d-at-%s", i, currentTime),
Data: fileBData,
@ -291,14 +293,17 @@ func generateAndRestoreDriveItems(
},
{
Name: folderAName,
Meta: odStub.MetaData{
Perms: odStub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
Roles: readPerm,
},
},
},
{
Name: folderCName,
Meta: odStub.MetaData{
Perms: odStub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
@ -307,6 +312,7 @@ func generateAndRestoreDriveItems(
},
},
},
},
{
// a folder that has permissions with an item in the folder with
// the different permissions.
@ -315,6 +321,7 @@ func generateAndRestoreDriveItems(
{
Name: fmt.Sprintf("file-count-%d-at-%s", i, currentTime),
Data: fileEData,
Meta: odStub.MetaData{
Perms: odStub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
@ -322,12 +329,15 @@ func generateAndRestoreDriveItems(
},
},
},
},
Meta: odStub.MetaData{
Perms: odStub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
Roles: readPerm,
},
},
},
{
// a folder that has permissions with an item in the folder with
// no permissions.
@ -338,12 +348,14 @@ func generateAndRestoreDriveItems(
Data: fileAData,
},
},
Meta: odStub.MetaData{
Perms: odStub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
Roles: readPerm,
},
},
},
{
PathElements: folderBPath,
Files: []odStub.ItemData{
@ -352,6 +364,7 @@ func generateAndRestoreDriveItems(
// permissions.
Name: fmt.Sprintf("file-count-%d-at-%s", i, currentTime),
Data: fileBData,
Meta: odStub.MetaData{
Perms: odStub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
@ -359,9 +372,11 @@ func generateAndRestoreDriveItems(
},
},
},
},
Folders: []odStub.ItemData{
{
Name: folderAName,
Meta: odStub.MetaData{
Perms: odStub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
@ -370,6 +385,7 @@ func generateAndRestoreDriveItems(
},
},
},
},
}
cols = append(cols, col...)

View File

@ -684,6 +684,33 @@ func permissionEqual(expected metadata.Permission, got metadata.Permission) bool
return true
}
func linkSharesEqual(expected metadata.LinkShare, got metadata.LinkShare) bool {
if !strings.EqualFold(expected.Link.Scope, got.Link.Scope) {
return false
}
if !strings.EqualFold(expected.Link.Type, got.Link.Type) {
return false
}
if !slices.Equal(expected.Entities, got.Entities) {
return false
}
if (expected.Expiration == nil && got.Expiration != nil) ||
(expected.Expiration != nil && got.Expiration == nil) {
return false
}
if expected.Expiration != nil &&
got.Expiration != nil &&
!expected.Expiration.Equal(ptr.Val(got.Expiration)) {
return false
}
return true
}
func compareDriveItem(
t *testing.T,
expected map[string][]byte,
@ -773,6 +800,8 @@ func compareDriveItem(
return true
}
assert.Equal(t, expectedMeta.SharingMode, itemMeta.SharingMode, "sharing mode")
// We cannot restore owner permissions, so skip checking them
itemPerms := []metadata.Permission{}
@ -792,6 +821,13 @@ func compareDriveItem(
config.Service == path.SharePointService,
permissionEqual)
testElementsMatch(
t,
expectedMeta.LinkShares,
itemMeta.LinkShares,
false,
linkSharesEqual)
return true
}

View File

@ -80,13 +80,14 @@ type GetItemer interface {
// ---------------------------------------------------------------------------
type RestoreHandler interface {
DeleteItemPermissioner
GetFolderByNamer
GetRootFolderer
ItemInfoAugmenter
NewItemContentUploader
PostItemInContainerer
DeleteItemPermissioner
UpdateItemPermissioner
UpdateItemLinkSharer
}
type NewItemContentUploader interface {
@ -113,6 +114,14 @@ type UpdateItemPermissioner interface {
) (drives.ItemItemsItemInviteResponseable, error)
}
type UpdateItemLinkSharer interface {
PostItemLinkShareUpdate(
ctx context.Context,
driveID, itemID string,
body *drives.ItemItemsItemCreateLinkPostRequestBody,
) (models.Permissionable, error)
}
type PostItemInContainerer interface {
PostItemInContainer(
ctx context.Context,

View File

@ -109,6 +109,7 @@ func downloadItemMeta(
}
meta.Permissions = metadata.FilterPermissions(ctx, perm.GetValue())
meta.LinkShares = metadata.FilterLinkShares(ctx, perm.GetValue())
}
metaJSON, err := json.Marshal(meta)

View File

@ -169,6 +169,14 @@ func (h itemRestoreHandler) PostItemPermissionUpdate(
return h.ac.PostItemPermissionUpdate(ctx, driveID, itemID, body)
}
func (h itemRestoreHandler) PostItemLinkShareUpdate(
ctx context.Context,
driveID, itemID string,
body *drives.ItemItemsItemCreateLinkPostRequestBody,
) (models.Permissionable, error) {
return h.ac.PostItemLinkShareUpdate(ctx, driveID, itemID, body)
}
func (h itemRestoreHandler) PostItemInContainer(
ctx context.Context,
driveID, parentFolderID string,

View File

@ -5,6 +5,31 @@ import (
"time"
)
type Entity struct {
ID string `json:"id,omitempty"`
EntityType GV2Type `json:"entityType,omitempty"`
}
type LinkShareLink struct {
Scope string `json:"scope,omitempty"`
Type string `json:"type,omitempty"`
WebURL string `json:"webUrl,omitempty"` // we cannot restore this, but can be used for comparisons
PreventsDownload bool `json:"preventsDownload,omitempty"`
}
type LinkShare struct {
ID string `json:"id,omitempty"`
Link LinkShareLink `json:"link,omitempty"`
Roles []string `json:"roles,omitempty"`
Entities []Entity `json:"entities,omitempty"` // this is the resource owner's ID
HasPassword bool `json:"hasPassword,omitempty"` // We cannot restore ones with password
Expiration *time.Time `json:"expiration,omitempty"`
}
func (ls LinkShare) Equals(other LinkShare) bool {
return ls.Link.WebURL == other.Link.WebURL
}
// ItemMeta contains metadata about the Item. It gets stored in a
// separate file in kopia
type Metadata struct {
@ -14,6 +39,7 @@ type Metadata struct {
// - custom: use Permissions to set correct permissions ("shared" has value in delta)
SharingMode SharingMode `json:"permissionMode,omitempty"`
Permissions []Permission `json:"permissions,omitempty"`
LinkShares []LinkShare `json:"linkShares,omitempty"`
}
type Item struct {

View File

@ -40,13 +40,14 @@ type Permission struct {
Expiration *time.Time `json:"expiration,omitempty"`
}
// isSamePermission checks equality of two UserPermission objects
// Equal checks equality of two UserPermission objects
func (p Permission) Equals(other Permission) bool {
// EntityID can be empty for older backups and Email can be empty
// for newer ones. It is not possible for both to be empty. Also,
// if EntityID/Email for one is not empty then the other will also
// have EntityID/Email as we backup permissions for all the
// parents and children when we have a change in permissions.
// We cannot just compare id because of the problem described in #3117
if p.EntityID != "" && p.EntityID != other.EntityID {
return false
}
@ -64,19 +65,51 @@ func (p Permission) Equals(other Permission) bool {
return slices.Equal(p1r, p2r)
}
// DiffLinkShares is just a wrapper on top of DiffPermissions but we
// filter out link shares which do not have any associated users. This
// is useful for two reason:
// - When a user creates a link share on parent after creating a child
// link with `retainInheritedPermissisons`, all the previous link shares
// are inherited onto the child but without any users associated with
// the share. We have to drop the empty ones to make sure we reset.
// - We are restoring link shares so that we can restore permissions for
// the user, but restoring links without users is not useful.
func DiffLinkShares(current, expected []LinkShare) ([]LinkShare, []LinkShare) {
filteredCurrent := []LinkShare{}
filteredExpected := []LinkShare{}
for _, ls := range current {
if len(ls.Entities) == 0 {
continue
}
filteredCurrent = append(filteredCurrent, ls)
}
for _, ls := range expected {
if len(ls.Entities) == 0 {
continue
}
filteredExpected = append(filteredExpected, ls)
}
return DiffPermissions(filteredCurrent, filteredExpected)
}
// DiffPermissions compares the before and after set, returning
// the permissions that were added and removed (in that order)
// in the after set.
func DiffPermissions(before, after []Permission) ([]Permission, []Permission) {
func DiffPermissions[T interface{ Equals(T) bool }](current, expected []T) ([]T, []T) {
var (
added = []Permission{}
removed = []Permission{}
added = []T{}
removed = []T{}
)
for _, cp := range after {
for _, cp := range expected {
found := false
for _, pp := range before {
for _, pp := range current {
if cp.Equals(pp) {
found = true
break
@ -88,10 +121,10 @@ func DiffPermissions(before, after []Permission) ([]Permission, []Permission) {
}
}
for _, pp := range before {
for _, pp := range current {
found := false
for _, cp := range after {
for _, cp := range expected {
if cp.Equals(pp) {
found = true
break
@ -116,7 +149,6 @@ func FilterPermissions(ctx context.Context, perms []models.Permissionable) []Per
continue
}
var (
// Below are the mapping from roles to "Advanced" permissions
// screen entries:
//
@ -127,10 +159,81 @@ func FilterPermissions(ctx context.Context, perms []models.Permissionable) []Per
//
// helpful docs:
// https://devblogs.microsoft.com/microsoft365dev/controlling-app-access-on-specific-sharepoint-site-collections/
roles := p.GetRoles()
gv2t, entityID := getIdentityDetails(ctx, p.GetGrantedToV2())
// Technically GrantedToV2 can also contain devices, but the
// documentation does not mention about devices in permissions
if entityID == "" {
// This should ideally not be hit
continue
}
up = append(up, Permission{
ID: ptr.Val(p.GetId()),
Roles: roles,
EntityID: entityID,
EntityType: gv2t,
Expiration: p.GetExpirationDateTime(),
})
}
return up
}
func FilterLinkShares(ctx context.Context, perms []models.Permissionable) []LinkShare {
up := []LinkShare{}
for _, p := range perms {
link := p.GetLink()
if link == nil {
// Non link share based permissions are handled separately
continue
}
var (
roles = p.GetRoles()
gv2 = p.GetGrantedToV2()
entityID string
gv2 = p.GetGrantedToIdentitiesV2()
)
idens := []Entity{}
for _, g := range gv2 {
gv2t, entityID := getIdentityDetails(ctx, g)
// Technically GrantedToV2 can also contain devices, but the
// documentation does not mention about devices in permissions
if entityID == "" {
// This should ideally not be hit
continue
}
idens = append(idens, Entity{ID: entityID, EntityType: gv2t})
}
up = append(up, LinkShare{
ID: ptr.Val(p.GetId()),
Link: LinkShareLink{
Scope: ptr.Val(link.GetScope()),
Type: ptr.Val(link.GetType()),
WebURL: ptr.Val(link.GetWebUrl()),
PreventsDownload: ptr.Val(link.GetPreventsDownload()),
},
Roles: roles,
Entities: idens,
HasPassword: ptr.Val(p.GetHasPassword()),
Expiration: p.GetExpirationDateTime(),
})
}
return up
}
func getIdentityDetails(ctx context.Context, gv2 models.SharePointIdentitySetable) (GV2Type, string) {
var (
gv2t GV2Type
entityID string
)
switch true {
@ -156,21 +259,5 @@ func FilterPermissions(ctx context.Context, perms []models.Permissionable) []Per
logger.Ctx(ctx).Info("untracked permission")
}
// Technically GrantedToV2 can also contain devices, but the
// documentation does not mention about devices in permissions
if entityID == "" {
// This should ideally not be hit
continue
}
up = append(up, Permission{
ID: ptr.Val(p.GetId()),
Roles: roles,
EntityID: entityID,
EntityType: gv2t,
Expiration: p.GetExpirationDateTime(),
})
}
return up
return gv2t, entityID
}

View File

@ -149,6 +149,71 @@ func (suite *PermissionsUnitTestSuite) TestDiffPermissions() {
}
}
func (suite *PermissionsUnitTestSuite) TestDiffLinkShares() {
entities1 := []Entity{{ID: "e1"}}
ls1 := LinkShare{
ID: "id1",
Entities: entities1,
Link: LinkShareLink{WebURL: "id1"},
}
lsempty := LinkShare{
ID: "id2",
Link: LinkShareLink{WebURL: "id2"},
}
table := []struct {
name string
before []LinkShare
after []LinkShare
added []LinkShare
removed []LinkShare
}{
{
name: "single link share added",
before: []LinkShare{},
after: []LinkShare{ls1},
added: []LinkShare{ls1},
removed: []LinkShare{},
},
{
name: "empty filtered from before",
before: []LinkShare{lsempty},
after: []LinkShare{},
added: []LinkShare{},
removed: []LinkShare{},
},
{
name: "empty filtered from after",
before: []LinkShare{},
after: []LinkShare{lsempty},
added: []LinkShare{},
removed: []LinkShare{},
},
{
name: "empty filtered from both",
before: []LinkShare{lsempty, ls1},
after: []LinkShare{lsempty},
added: []LinkShare{},
removed: []LinkShare{ls1},
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
_, flush := tester.NewContext(t)
defer flush()
added, removed := DiffLinkShares(test.before, test.after)
assert.Equal(t, added, test.added, "added link shares")
assert.Equal(t, removed, test.removed, "removed link shares")
})
}
}
func getPermsAndResourceOwnerPerms(
permID, resourceOwner string,
gv2t GV2Type,

View File

@ -268,6 +268,14 @@ func (h RestoreHandler) PostItemPermissionUpdate(
return nil, clues.New("not implemented")
}
func (h RestoreHandler) PostItemLinkShareUpdate(
ctx context.Context,
driveID, itemID string,
body *drives.ItemItemsItemCreateLinkPostRequestBody,
) (models.Permissionable, error) {
return nil, clues.New("not implemented")
}
func (h RestoreHandler) PostItemInContainer(
context.Context,
string, string,

View File

@ -2,6 +2,7 @@ package onedrive
import (
"context"
"strings"
"github.com/alcionai/clues"
"github.com/microsoftgraph/msgraph-sdk-go/drives"
@ -11,6 +12,7 @@ import (
"github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/m365/onedrive/metadata"
"github.com/alcionai/corso/src/internal/version"
"github.com/alcionai/corso/src/pkg/logger"
"github.com/alcionai/corso/src/pkg/path"
)
@ -81,11 +83,58 @@ func getCollectionMetadata(
return meta, nil
}
// computeParentPermissions computes the parent permissions by
// Unlike permissions, link shares are inherited from all the parents
// of an item and not just the direct parent.
func computePreviousLinkShares(
ctx context.Context,
originDir path.Path,
parentMetas map[string]metadata.Metadata,
) ([]metadata.LinkShare, error) {
linkShares := []metadata.LinkShare{}
ctx = clues.Add(ctx, "origin_dir", originDir)
parent, err := originDir.Dir()
if err != nil {
return nil, clues.New("getting parent").WithClues(ctx)
}
for len(parent.Elements()) > 0 {
ictx := clues.Add(ctx, "current_ancestor_dir", parent)
drivePath, err := path.ToDrivePath(parent)
if err != nil {
return nil, clues.New("transforming dir to drivePath").WithClues(ictx)
}
if len(drivePath.Folders) == 0 {
break
}
meta, ok := parentMetas[parent.String()]
if !ok {
return nil, clues.New("no metadata found in parent").WithClues(ictx)
}
// Any change in permissions would change it to custom
// permission set and so we can filter on that.
if meta.SharingMode == metadata.SharingModeCustom {
linkShares = append(linkShares, meta.LinkShares...)
}
parent, err = parent.Dir()
if err != nil {
return nil, clues.New("getting parent").WithClues(ctx)
}
}
return linkShares, nil
}
// computePreviousMetadata computes the parent permissions by
// traversing parentMetas and finding the first item with custom
// permissions. parentMetas is expected to have all the parent
// directory metas for this to work.
func computeParentPermissions(
func computePreviousMetadata(
ctx context.Context,
originDir path.Path,
// map parent dir -> parent's metadata
@ -227,6 +276,128 @@ func UpdatePermissions(
return nil
}
type updateDeleteItemLinkSharer interface {
DeleteItemPermissioner // Deletion logic is same as permissions
UpdateItemLinkSharer
}
func UpdateLinkShares(
ctx context.Context,
upils updateDeleteItemLinkSharer,
driveID string,
itemID string,
lsAdded, lsRemoved []metadata.LinkShare,
oldLinkShareIDToNewID map[string]string,
) (bool, error) {
// You can only delete inherited sharing links the first time you
// create a sharing link which is done using
// `retainInheritedPermissions`. We cannot separately delete any
// inherited link shares via DELETE API call like for permissions.
alreadyDeleted := false
for _, ls := range lsAdded {
ictx := clues.Add(ctx, "link_share_id", ls.ID)
// Links with password are not shared with a specific user
// even when we select a particular user, plus we are not
// able to get the password or retain the original link and
// so restoring them makes no sense.
if ls.HasPassword {
continue
}
idens := []map[string]string{}
entities := []string{}
for _, iden := range ls.Entities {
// TODO: sitegroup support. Currently errors with "One or more users could not be resolved",
// likely due to the site group entityID consisting of a single integer (ex: 4)
if iden.EntityType == metadata.GV2SiteGroup {
continue
}
// Using DriveRecipient seems to error out on Graph end
idens = append(idens, map[string]string{"objectId": iden.ID})
entities = append(entities, iden.ID)
}
ictx = clues.Add(ictx, "link_share_entity_ids", strings.Join(entities, ","))
// https://learn.microsoft.com/en-us/graph/api/driveitem-createlink?view=graph-rest-beta&tabs=http
// v1.0 version of the graph API does not support creating a
// link without sending a notification to the user and so we
// use the beta API. Since we use the v1.0 API, we have to
// stuff some of the data into the AdditionalData fields as
// the actual fields don't exist in the stable sdk.
// Here is the data that we have to send:
// {
// "type": "view",
// "scope": "anonymous",
// "password": "String",
// "expirationDateTime": "...",
// "recipients": [{"@odata.type": "microsoft.graph.driveRecipient"}],
// "sendNotification": true,
// "retainInheritedPermissions": false
// }
lsbody := drives.NewItemItemsItemCreateLinkPostRequestBody()
lsbody.SetType(ptr.To(ls.Link.Type))
lsbody.SetScope(ptr.To(ls.Link.Scope))
lsbody.SetExpirationDateTime(ls.Expiration)
ad := map[string]any{
"sendNotification": false,
"recipients": idens,
}
lsbody.SetAdditionalData(ad)
if !alreadyDeleted {
// The only way to delete any is to use this and so if
// we have any deleted items, we can be sure that all the
// inherited permissions would have been removed.
lsbody.SetRetainInheritedPermissions(ptr.To(len(lsRemoved) == 0))
// This value only effective on the first call, but lets
// make sure to not send it on followups.
alreadyDeleted = true
}
newLS, err := upils.PostItemLinkShareUpdate(ictx, driveID, itemID, lsbody)
if err != nil {
return alreadyDeleted, clues.Stack(err)
}
oldLinkShareIDToNewID[ls.ID] = ptr.Val(newLS.GetId())
}
// It is possible to have empty link shares even though we should
// have inherited one if the user creates a link using
// `retainInheritedPermissions` as false, but then deleted it. We
// can recreate this by creating a link with no users and deleting it.
if len(lsRemoved) > 0 && len(lsAdded) == 0 {
lsbody := drives.NewItemItemsItemCreateLinkPostRequestBody()
lsbody.SetType(ptr.To("view"))
// creating a `users` link without any users ensure that even
// if we fail to delete the link there are no links lying
// around that could be used to access this
lsbody.SetScope(ptr.To("users"))
lsbody.SetRetainInheritedPermissions(ptr.To(false))
newLS, err := upils.PostItemLinkShareUpdate(ctx, driveID, itemID, lsbody)
if err != nil {
return alreadyDeleted, clues.Stack(err)
}
alreadyDeleted = true
err = upils.DeleteItemPermission(ctx, driveID, itemID, ptr.Val(newLS.GetId()))
if err != nil {
return alreadyDeleted, clues.Stack(err)
}
}
return alreadyDeleted, nil
}
// RestorePermissions takes in the permissions of an item, computes
// what permissions need to added and removed based on the parent
// folder metas and uses that to add/remove the necessary permissions
@ -246,14 +417,46 @@ func RestorePermissions(
ctx = clues.Add(ctx, "permission_item_id", itemID)
parents, err := computeParentPermissions(ctx, itemPath, caches.ParentDirToMeta)
previousLinkShares, err := computePreviousLinkShares(ctx, itemPath, caches.ParentDirToMeta)
if err != nil {
return clues.Wrap(err, "parent permissions")
return clues.Wrap(err, "previous link shares")
}
permAdded, permRemoved := metadata.DiffPermissions(parents.Permissions, current.Permissions)
lsAdded, lsRemoved := metadata.DiffLinkShares(previousLinkShares, current.LinkShares)
return UpdatePermissions(
// Link shares have to be updated before permissions as we have to
// use the information about if we had to reset the inheritance to
// decide if we have to restore all the permissions.
didReset, err := UpdateLinkShares(
ctx,
rh,
driveID,
itemID,
lsAdded,
lsRemoved,
caches.OldLinkShareIDToNewID)
if err != nil {
return clues.Wrap(err, "updating link shares")
}
previous, err := computePreviousMetadata(ctx, itemPath, caches.ParentDirToMeta)
if err != nil {
return clues.Wrap(err, "previous metadata")
}
permAdded, permRemoved := metadata.DiffPermissions(previous.Permissions, current.Permissions)
if didReset {
// In case we did a reset of permissions when restoring link
// shares, we have to make sure to restore all the permissions
// that an item has as they too will be removed.
logger.Ctx(ctx).Debug("link share creation reset all inherited permissions")
permRemoved = []metadata.Permission{}
permAdded = current.Permissions
}
err = UpdatePermissions(
ctx,
rh,
driveID,
@ -261,4 +464,9 @@ func RestorePermissions(
permAdded,
permRemoved,
caches.OldPermIDToNewID)
if err != nil {
return clues.Wrap(err, "updating permissions")
}
return nil
}

View File

@ -156,7 +156,7 @@ func runComputeParentPermissionsTest(
ctx, flush := tester.NewContext(t)
defer flush()
m, err := computeParentPermissions(ctx, test.item, test.parentPerms)
m, err := computePreviousMetadata(ctx, test.item, test.parentPerms)
require.NoError(t, err, "compute permissions")
assert.Equal(t, m, test.meta)

View File

@ -39,6 +39,7 @@ type restoreCaches struct {
Folders *folderCache
ParentDirToMeta map[string]metadata.Metadata
OldPermIDToNewID map[string]string
OldLinkShareIDToNewID map[string]string
DriveIDToRootFolderID map[string]string
pool sync.Pool
}
@ -48,6 +49,7 @@ func NewRestoreCaches() *restoreCaches {
Folders: NewFolderCache(),
ParentDirToMeta: map[string]metadata.Metadata{},
OldPermIDToNewID: map[string]string{},
OldLinkShareIDToNewID: map[string]string{},
DriveIDToRootFolderID: map[string]string{},
// Buffer pool for uploads
pool: sync.Pool{

View File

@ -3,6 +3,7 @@ package stub
import (
"encoding/json"
"fmt"
"strings"
"github.com/alcionai/clues"
"github.com/google/uuid"
@ -18,30 +19,53 @@ import (
// permission instead of email
const versionPermissionSwitchedToID = version.OneDrive4DirIncludesPermissions
func getMetadata(fileName string, perm PermData, permUseID bool) metadata.Metadata {
if len(perm.User) == 0 || len(perm.Roles) == 0 ||
perm.SharingMode != metadata.SharingModeCustom {
func getMetadata(fileName string, meta MetaData, permUseID bool) metadata.Metadata {
if meta.SharingMode != metadata.SharingModeCustom {
return metadata.Metadata{
FileName: fileName,
SharingMode: perm.SharingMode,
SharingMode: meta.SharingMode,
}
}
testMeta := metadata.Metadata{FileName: fileName}
if len(meta.Perms.User) != 0 {
// In case of permissions, the id will usually be same for same
// user/role combo unless deleted and readded, but we have to do
// this as we only have two users of which one is already taken.
id := uuid.NewString()
uperm := metadata.Permission{ID: id, Roles: perm.Roles}
uperm := metadata.Permission{ID: id, Roles: meta.Perms.Roles}
if permUseID {
uperm.EntityID = perm.EntityID
uperm.EntityID = meta.Perms.EntityID
} else {
uperm.Email = perm.User
uperm.Email = meta.Perms.User
}
testMeta := metadata.Metadata{
FileName: fileName,
Permissions: []metadata.Permission{uperm},
testMeta.Permissions = []metadata.Permission{uperm}
}
if len(meta.LinkShares) != 0 {
for _, ls := range meta.LinkShares {
id := strings.Join(ls.EntityIDs, "-") + ls.Scope + ls.Type
entities := []metadata.Entity{}
for _, e := range ls.EntityIDs {
entities = append(entities, metadata.Entity{ID: e, EntityType: "user"})
}
ls := metadata.LinkShare{
ID: id, // id is required for mapping from parent
Link: metadata.LinkShareLink{
Scope: ls.Scope,
Type: ls.Type,
WebURL: id,
},
Entities: entities,
}
testMeta.LinkShares = append(testMeta.LinkShares, ls)
}
}
return testMeta
@ -51,20 +75,31 @@ type PermData struct {
User string // user is only for older versions
EntityID string
Roles []string
}
type LinkShareData struct {
EntityIDs []string
Scope string
Type string
}
type MetaData struct {
SharingMode metadata.SharingMode
Perms PermData
LinkShares []LinkShareData
}
type ItemData struct {
Name string
Data []byte
Perms PermData
Meta MetaData
}
type ColInfo struct {
PathElements []string
Perms PermData
Files []ItemData
Folders []ItemData
Meta MetaData
}
type collection struct {
@ -115,20 +150,20 @@ func DataForInfo(
onedriveCol := NewCollection(service, c.PathElements, backupVersion)
for _, f := range c.Files {
_, err = onedriveCol.withFile(f.Name, f.Data, f.Perms)
_, err = onedriveCol.withFile(f.Name, f.Data, f.Meta)
if err != nil {
return res, err
}
}
for _, d := range c.Folders {
_, err = onedriveCol.withFolder(d.Name, d.Perms)
_, err = onedriveCol.withFolder(d.Name, d.Meta)
if err != nil {
return res, err
}
}
_, err = onedriveCol.withPermissions(c.Perms)
_, err = onedriveCol.withPermissions(c.Meta)
if err != nil {
return res, err
}
@ -139,7 +174,7 @@ func DataForInfo(
return res, nil
}
func (c *collection) withFile(name string, fileData []byte, perm PermData) (*collection, error) {
func (c *collection) withFile(name string, fileData []byte, meta MetaData) (*collection, error) {
switch c.BackupVersion {
case 0:
// Lookups will occur using the most recent version of things so we need
@ -171,7 +206,7 @@ func (c *collection) withFile(name string, fileData []byte, perm PermData) (*col
"",
name+metadata.MetaFileSuffix,
name+metadata.MetaFileSuffix,
perm,
meta,
c.BackupVersion >= versionPermissionSwitchedToID)
if err != nil {
return c, err
@ -196,7 +231,7 @@ func (c *collection) withFile(name string, fileData []byte, perm PermData) (*col
name,
name+metadata.MetaFileSuffix,
name,
perm,
meta,
c.BackupVersion >= versionPermissionSwitchedToID)
if err != nil {
return c, err
@ -212,7 +247,7 @@ func (c *collection) withFile(name string, fileData []byte, perm PermData) (*col
return c, nil
}
func (c *collection) withFolder(name string, perm PermData) (*collection, error) {
func (c *collection) withFolder(name string, meta MetaData) (*collection, error) {
switch c.BackupVersion {
case 0, version.OneDrive4DirIncludesPermissions, version.OneDrive5DirMetaNoName,
version.OneDrive6NameInMeta, version.OneDrive7LocationRef, version.All8MigrateUserPNToID:
@ -223,7 +258,7 @@ func (c *collection) withFolder(name string, perm PermData) (*collection, error)
"",
name+metadata.DirMetaFileSuffix,
name+metadata.DirMetaFileSuffix,
perm,
meta,
c.BackupVersion >= versionPermissionSwitchedToID)
c.Items = append(c.Items, item)
@ -241,7 +276,7 @@ func (c *collection) withFolder(name string, perm PermData) (*collection, error)
// withPermissions adds permissions to the folder represented by this
// onedriveCollection.
func (c *collection) withPermissions(perm PermData) (*collection, error) {
func (c *collection) withPermissions(meta MetaData) (*collection, error) {
// These versions didn't store permissions for the folder or didn't store them
// in the folder's collection.
if c.BackupVersion < version.OneDrive4DirIncludesPermissions {
@ -264,7 +299,7 @@ func (c *collection) withPermissions(perm PermData) (*collection, error) {
name,
metaName+metadata.DirMetaFileSuffix,
metaName+metadata.DirMetaFileSuffix,
perm,
meta,
c.BackupVersion >= versionPermissionSwitchedToID)
if err != nil {
return c, err
@ -304,10 +339,10 @@ func FileWithData(
func ItemWithMetadata(
fileName, itemID, lookupKey string,
perm PermData,
meta MetaData,
permUseID bool,
) (m365Stub.ItemInfo, error) {
testMeta := getMetadata(fileName, perm, permUseID)
testMeta := getMetadata(fileName, meta, permUseID)
testMetaJSON, err := json.Marshal(testMeta)
if err != nil {

View File

@ -231,6 +231,11 @@ func (suite *SharePointIntegrationSuite) TestPermissionsInheritanceRestoreAndBac
testPermissionsInheritanceRestoreAndBackup(suite, version.Backup)
}
func (suite *SharePointIntegrationSuite) TestLinkSharesInheritanceRestoreAndBackup() {
suite.T().Skip("Temporarily disabled due to CI issues")
testLinkSharesInheritanceRestoreAndBackup(suite, version.Backup)
}
func (suite *SharePointIntegrationSuite) TestRestoreFolderNamedFolderRegression() {
// No reason why it couldn't work with previous versions, but this is when it got introduced.
testRestoreFolderNamedFolderRegression(suite, version.All8MigrateUserPNToID)
@ -291,6 +296,10 @@ func (suite *OneDriveIntegrationSuite) TestPermissionsInheritanceRestoreAndBacku
testPermissionsInheritanceRestoreAndBackup(suite, version.Backup)
}
func (suite *OneDriveIntegrationSuite) TestLinkSharesInheritanceRestoreAndBackup() {
testLinkSharesInheritanceRestoreAndBackup(suite, version.Backup)
}
func (suite *OneDriveIntegrationSuite) TestRestoreFolderNamedFolderRegression() {
// No reason why it couldn't work with previous versions, but this is when it got introduced.
testRestoreFolderNamedFolderRegression(suite, version.All8MigrateUserPNToID)
@ -352,6 +361,10 @@ func (suite *OneDriveNightlySuite) TestPermissionsInheritanceRestoreAndBackup()
testPermissionsInheritanceRestoreAndBackup(suite, version.OneDrive4DirIncludesPermissions)
}
func (suite *OneDriveNightlySuite) TestLinkSharesInheritanceRestoreAndBackup() {
testLinkSharesInheritanceRestoreAndBackup(suite, version.Backup)
}
func (suite *OneDriveNightlySuite) TestRestoreFolderNamedFolderRegression() {
// No reason why it couldn't work with previous versions, but this is when it got introduced.
testRestoreFolderNamedFolderRegression(suite, version.All8MigrateUserPNToID)
@ -407,13 +420,17 @@ func testRestoreAndBackupMultipleFilesAndFoldersNoPermissions(
folderBName,
}
defaultMetadata := stub.MetaData{SharingMode: metadata.SharingModeInherited}
cols := []stub.ColInfo{
{
PathElements: rootPath,
Meta: defaultMetadata,
Files: []stub.ItemData{
{
Name: fileName,
Data: fileAData,
Meta: defaultMetadata,
},
},
Folders: []stub.ItemData{
@ -427,10 +444,12 @@ func testRestoreAndBackupMultipleFilesAndFoldersNoPermissions(
},
{
PathElements: folderAPath,
Meta: defaultMetadata,
Files: []stub.ItemData{
{
Name: fileName,
Data: fileBData,
Meta: defaultMetadata,
},
},
Folders: []stub.ItemData{
@ -441,10 +460,12 @@ func testRestoreAndBackupMultipleFilesAndFoldersNoPermissions(
},
{
PathElements: subfolderBPath,
Meta: defaultMetadata,
Files: []stub.ItemData{
{
Name: fileName,
Data: fileCData,
Meta: defaultMetadata,
},
},
Folders: []stub.ItemData{
@ -455,19 +476,23 @@ func testRestoreAndBackupMultipleFilesAndFoldersNoPermissions(
},
{
PathElements: subfolderAPath,
Meta: defaultMetadata,
Files: []stub.ItemData{
{
Name: fileName,
Data: fileDData,
Meta: defaultMetadata,
},
},
},
{
PathElements: folderBPath,
Meta: defaultMetadata,
Files: []stub.ItemData{
{
Name: fileName,
Data: fileEData,
Meta: defaultMetadata,
},
},
},
@ -557,38 +582,52 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
cols := []stub.ColInfo{
{
PathElements: rootPath,
Meta: stub.MetaData{
SharingMode: metadata.SharingModeInherited,
},
Files: []stub.ItemData{
{
// Test restoring a file that doesn't inherit permissions.
Name: fileName,
Data: fileAData,
Meta: stub.MetaData{
Perms: stub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
Roles: writePerm,
},
},
},
{
// Test restoring a file that doesn't inherit permissions and has
// no permissions.
Name: fileName2,
Data: fileBData,
Meta: stub.MetaData{
SharingMode: metadata.SharingModeInherited,
},
},
},
Folders: []stub.ItemData{
{
Name: folderBName,
Meta: stub.MetaData{
SharingMode: metadata.SharingModeInherited,
},
},
{
Name: folderAName,
Meta: stub.MetaData{
Perms: stub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
Roles: readPerm,
},
},
},
{
Name: folderCName,
Meta: stub.MetaData{
Perms: stub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
@ -597,14 +636,19 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
},
},
},
},
{
PathElements: folderBPath,
Meta: stub.MetaData{
SharingMode: metadata.SharingModeInherited,
},
Files: []stub.ItemData{
{
// Test restoring a file in a non-root folder that doesn't inherit
// permissions.
Name: fileName,
Data: fileBData,
Meta: stub.MetaData{
Perms: stub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
@ -612,9 +656,11 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
},
},
},
},
Folders: []stub.ItemData{
{
Name: folderAName,
Meta: stub.MetaData{
Perms: stub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
@ -623,6 +669,7 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
},
},
},
},
// TODO: We can't currently support having custom permissions
// with the same set of permissions internally
// {
@ -654,6 +701,7 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
{
Name: fileName,
Data: fileEData,
Meta: stub.MetaData{
Perms: stub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
@ -661,12 +709,15 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
},
},
},
},
Meta: stub.MetaData{
Perms: stub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
Roles: readPerm,
},
},
},
{
// Tests a folder that has permissions with an item in the folder with
// no permissions.
@ -675,14 +726,19 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
{
Name: fileName,
Data: fileAData,
Meta: stub.MetaData{
SharingMode: metadata.SharingModeInherited,
},
},
},
Meta: stub.MetaData{
Perms: stub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
Roles: readPerm,
},
},
},
}
expected, err := stub.DataForInfo(suite.Service(), cols, version.Backup)
@ -746,11 +802,14 @@ func testPermissionsBackupAndNoRestore(suite oneDriveSuite, startVersion int) {
{
Name: fileName,
Data: fileAData,
Meta: stub.MetaData{
Perms: stub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
Roles: writePerm,
},
SharingMode: metadata.SharingModeCustom,
},
},
},
},
@ -860,32 +919,44 @@ func testPermissionsInheritanceRestoreAndBackup(suite oneDriveSuite, startVersio
folderCName,
}
fileSet := []stub.ItemData{
{
fileCustom := stub.ItemData{
Name: "file-custom",
Data: fileAData,
Meta: stub.MetaData{
Perms: stub.PermData{
User: secondaryUserName,
EntityID: secondaryUserID,
Roles: writePerm,
},
SharingMode: metadata.SharingModeCustom,
},
},
{
Name: "file-inherited",
Data: fileAData,
Perms: stub.PermData{
SharingMode: metadata.SharingModeInherited,
},
},
{
Name: "file-empty",
Data: fileAData,
Perms: stub.PermData{
SharingMode: metadata.SharingModeCustom,
},
},
}
fileInherited := stub.ItemData{
Name: "file-inherited",
Data: fileAData,
Meta: stub.MetaData{
SharingMode: metadata.SharingModeInherited,
},
}
fileEmpty := stub.ItemData{
Name: "file-empty",
Data: fileAData,
Meta: stub.MetaData{
SharingMode: metadata.SharingModeCustom,
},
}
// If parent is empty, then empty permissions would be inherited
fileEmptyInherited := stub.ItemData{
Name: "file-empty",
Data: fileAData,
Meta: stub.MetaData{
SharingMode: metadata.SharingModeInherited,
},
}
fileSet := []stub.ItemData{fileCustom, fileInherited, fileEmpty}
fileSetEmpty := []stub.ItemData{fileCustom, fileInherited, fileEmptyInherited}
// Here is what this test is testing
// - custom-permission-folder
@ -912,6 +983,9 @@ func testPermissionsInheritanceRestoreAndBackup(suite oneDriveSuite, startVersio
Folders: []stub.ItemData{
{Name: folderAName},
},
Meta: stub.MetaData{
SharingMode: metadata.SharingModeInherited,
},
},
{
PathElements: folderAPath,
@ -921,33 +995,231 @@ func testPermissionsInheritanceRestoreAndBackup(suite oneDriveSuite, startVersio
{Name: folderBName},
{Name: folderCName},
},
Meta: stub.MetaData{
Perms: stub.PermData{
User: tertiaryUserName,
EntityID: tertiaryUserID,
Roles: readPerm,
},
SharingMode: metadata.SharingModeCustom,
},
},
{
PathElements: subfolderAAPath,
Files: fileSet,
Meta: stub.MetaData{
Perms: stub.PermData{
User: tertiaryUserName,
EntityID: tertiaryUserID,
Roles: writePerm,
},
SharingMode: metadata.SharingModeCustom,
},
},
{
PathElements: subfolderABPath,
Files: fileSet,
Perms: stub.PermData{
Meta: stub.MetaData{
SharingMode: metadata.SharingModeInherited,
},
},
{
PathElements: subfolderACPath,
Files: fileSetEmpty,
Meta: stub.MetaData{
SharingMode: metadata.SharingModeCustom,
},
},
}
expected, err := stub.DataForInfo(suite.Service(), cols, version.Backup)
require.NoError(suite.T(), err)
bss := suite.Service().String()
for vn := startVersion; vn <= version.Backup; vn++ {
suite.Run(fmt.Sprintf("%s-Version%d", bss, vn), func() {
t := suite.T()
// Ideally this can always be true or false and still
// work, but limiting older versions to use emails so as
// to validate that flow as well.
input, err := stub.DataForInfo(suite.Service(), cols, vn)
require.NoError(suite.T(), err)
testData := restoreBackupInfoMultiVersion{
service: suite.Service(),
resourceCat: suite.Resource(),
backupVersion: vn,
collectionsPrevious: input,
collectionsLatest: expected,
}
runRestoreBackupTestVersions(
t,
testData,
suite.Tenant(),
[]string{suite.ResourceOwner()},
control.Options{
RestorePermissions: true,
ToggleFeatures: control.Toggles{},
})
})
}
}
func testLinkSharesInheritanceRestoreAndBackup(suite oneDriveSuite, startVersion int) {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
_, secondaryUserID := suite.SecondaryUser()
_, tertiaryUserID := suite.TertiaryUser()
// Get the default drive ID for the test user.
driveID := mustGetDefaultDriveID(
t,
ctx,
suite.APIClient(),
suite.Service(),
suite.ResourceOwner())
folderAName := "custom"
folderBName := "inherited"
folderCName := "empty"
rootPath := []string{
odConsts.DrivesPathDir,
driveID,
odConsts.RootPathDir,
}
folderAPath := []string{
odConsts.DrivesPathDir,
driveID,
odConsts.RootPathDir,
folderAName,
}
subfolderAAPath := []string{
odConsts.DrivesPathDir,
driveID,
odConsts.RootPathDir,
folderAName,
folderAName,
}
subfolderABPath := []string{
odConsts.DrivesPathDir,
driveID,
odConsts.RootPathDir,
folderAName,
folderBName,
}
subfolderACPath := []string{
odConsts.DrivesPathDir,
driveID,
odConsts.RootPathDir,
folderAName,
folderCName,
}
fileSet := []stub.ItemData{
{
Name: "file-custom",
Data: fileAData,
Meta: stub.MetaData{
LinkShares: []stub.LinkShareData{
{
EntityIDs: []string{secondaryUserID},
Scope: "users",
Type: "edit",
},
},
SharingMode: metadata.SharingModeCustom,
},
},
{
Name: "file-inherited",
Data: fileBData,
Meta: stub.MetaData{
SharingMode: metadata.SharingModeInherited,
},
},
{
Name: "file-empty",
Data: fileCData,
Meta: stub.MetaData{
SharingMode: metadata.SharingModeCustom,
},
},
}
// Here is what this test is testing
// - custom-link-share-folder
// - custom-link-share-file
// - inherted-link-share-file
// - empty-link-share-file
// - custom-link-share-folder
// - custom-link-share-file
// - inherted-link-share-file
// - empty-link-share-file
// - inherted-link-share-folder
// - custom-link-share-file
// - inherted-link-share-file
// - empty-link-share-file
// - empty-link-share-folder
// - custom-link-share-file
// - inherted-link-share-file
// - empty-link-share-file
cols := []stub.ColInfo{
{
PathElements: rootPath,
Files: []stub.ItemData{},
Folders: []stub.ItemData{
{Name: folderAName},
},
},
{
PathElements: folderAPath,
Files: fileSet,
Folders: []stub.ItemData{
{Name: folderAName},
{Name: folderBName},
{Name: folderCName},
},
Meta: stub.MetaData{
LinkShares: []stub.LinkShareData{
{
EntityIDs: []string{tertiaryUserID},
Scope: "anonymous",
Type: "edit",
},
},
},
},
{
PathElements: subfolderAAPath,
Files: fileSet,
Meta: stub.MetaData{
LinkShares: []stub.LinkShareData{
{
EntityIDs: []string{tertiaryUserID},
Scope: "users",
Type: "edit",
},
},
SharingMode: metadata.SharingModeCustom,
},
},
{
PathElements: subfolderABPath,
Files: fileSet,
Meta: stub.MetaData{
SharingMode: metadata.SharingModeInherited,
},
},
{
PathElements: subfolderACPath,
Files: fileSet,
Perms: stub.PermData{
Meta: stub.MetaData{
SharingMode: metadata.SharingModeCustom,
},
},

View File

@ -195,6 +195,14 @@ func (h libraryRestoreHandler) PostItemPermissionUpdate(
return h.ac.PostItemPermissionUpdate(ctx, driveID, itemID, body)
}
func (h libraryRestoreHandler) PostItemLinkShareUpdate(
ctx context.Context,
driveID, itemID string,
body *drives.ItemItemsItemCreateLinkPostRequestBody,
) (models.Permissionable, error) {
return h.ac.PostItemLinkShareUpdate(ctx, driveID, itemID, body)
}
func (h libraryRestoreHandler) PostItemInContainer(
ctx context.Context,
driveID, parentFolderID string,

View File

@ -30,7 +30,10 @@ type Drives struct {
// Folders
// ---------------------------------------------------------------------------
const itemByPathRawURLFmt = "https://graph.microsoft.com/v1.0/drives/%s/items/%s:/%s"
const (
itemByPathRawURLFmt = "https://graph.microsoft.com/v1.0/drives/%s/items/%s:/%s"
createLinkShareURLFmt = "https://graph.microsoft.com/beta/drives/%s/items/%s/createLink"
)
var ErrFolderNotFound = clues.New("folder not found")
@ -307,6 +310,27 @@ func (c Drives) DeleteItemPermission(
return nil
}
func (c Drives) PostItemLinkShareUpdate(
ctx context.Context,
driveID, itemID string,
body *drives.ItemItemsItemCreateLinkPostRequestBody,
) (models.Permissionable, error) {
ctx = graph.ConsumeNTokens(ctx, graph.PermissionsLC)
// We are using the beta version of the endpoint. This allows us
// to add recipients in the same request as well as to make it not
// send out and email for every link share the user gets added to.
rawURL := fmt.Sprintf(createLinkShareURLFmt, driveID, itemID)
builder := drives.NewItemItemsItemCreateLinkRequestBuilder(rawURL, c.Stable.Adapter())
itm, err := builder.Post(ctx, body, nil)
if err != nil {
return nil, graph.Wrap(ctx, err, "creating link share")
}
return itm, nil
}
// DriveItemCollisionKeyy constructs a key from the item name.
// collision keys are used to identify duplicate item conflicts for handling advanced restoration config.
func DriveItemCollisionKey(item models.DriveItemable) string {

View File

@ -26,3 +26,11 @@ included in backup and restore.
* SharePoint document library data can't be restored after the library has been deleted.
* Sharing information of items in OneDrive/SharePoint using sharing links aren't backed up and restored.
* Permissions/Access given to a site group can't be restored
* If a link share is created for an item with inheritance disabled
(via the Graph API), the link shares restored in that item will
not be inheritable by children
* Link shares with password protection can't be restored