SharePoint DriveItem permissions. Is there a more simple way to copy folders along with permissions ?

Stefan Thordarson 1 Reputation point
2021-11-28T19:25:49.487+00:00

Hello

I am using the Graph SDK to copy an Sharepoint folder to another location. The target-subfolders must have the same permissions as those from the sourcefolder. To make it work, I do a temporary removal of the SiteGroup permissions on the target subfolders, and at last I want to restore those permissions. Here I get the error "Invalid value for role"

The call to Invite throws an exception, when role is "owner", the exception message is : "Invalid value for role"

My workarround this is to rename "owner" to "write" - just to be enable to use the /invite. I really would like to get rid of this workarround.

I am also looking for an alternative method (more simple) of doing this sync of permissions.

Best regards, Stefan

My code follows here :

private async Task CopyTemplatePermissions(                                                  
                                                   DriveItem projectDir,
                                                   DriveItem templateDir)
        {
            try
            {
                var settings = settingsAndToken.Settings;

                var templateSubFolders = await GetChildDriveItems(settings, templateDir);
                var projectSubFolders = await GetChildDriveItems(settings, projectDir);

                foreach (var templateSub in templateSubFolders)
                {
                    var projectChild = projectSubFolders.FirstOrDefault(p => p.Name == templateSub.Name);

                    if (projectChild == null)
                    {
                        continue;
                    }

                    // Get permissions
                    var templateChildPermissions = await GetDriveItemPermissions(settings, templateSub);
                    var projectChildPermissions = await GetDriveItemPermissions(settings, projectChild);

                    var projectUserIds = projectChildPermissions.Select(pc => pc.GrantedToV2.User?.Id).ToList();

                    var templateHasSpecialPermissions =
                        templateChildPermissions.Any(p => !projectUserIds.Contains(p.GrantedToV2.User?.Id));

                    // First temporary remove all permissions not existing in the template
                    // ALL SiteGroup permissions must be removed in order to be able to use /invite 

                    List<Permission> deletedPermissions = new List<Permission>();
                    foreach (var pcPerm in projectChildPermissions)
                    {
                        if (templateHasSpecialPermissions || 
                            templateChildPermissions.All(p => p.GrantedToV2.User?.DisplayName != pcPerm.GrantedToV2.User?.DisplayName))
                        {
                            await graphClient
                                .Drives[settings.DriveId]
                                .Items[projectChild.Id]
                                .Permissions[pcPerm.Id]
                                .Request()
                                .DeleteAsync();
                            deletedPermissions.Add(pcPerm);
                        }
                    }
                    deletedPermissions.ForEach(p => projectChildPermissions.Remove(p));

                    // Then apply permissions from template: Patch or Add (Invite)
                    foreach (var tcPerm in templateChildPermissions.Where(p => p.GrantedToV2.User != null ))
                    {
                        var pcPerm = projectChildPermissions.FirstOrDefault(p =>
                            p.GrantedToV2.User?.DisplayName == tcPerm.GrantedToV2.User.DisplayName);

                        tcPerm.Roles = ReplaceOwnerRoles(tcPerm.Roles);

                        // Update
                        if (pcPerm != null)
                        {
                            var pcRolesStr = string.Join(";", pcPerm.Roles.OrderBy(p => p));
                            var tcRolesStr = string.Join(";", tcPerm.Roles.OrderBy(p => p));

                            if (pcRolesStr != tcRolesStr)
                            {
                                var updateResult =
                                    await graphClient
                                    .Drives[settings.DriveId]
                                    .Items[projectChild.Id]
                                    .Permissions[pcPerm.Id]
                                    .Request()
                                    .UpdateAsync(tcPerm);
                            }
                        }
                        else
                        {
                            // Invite
                            var userId = tcPerm.GrantedToV2.User.Id;

                            var recipients = new List<DriveRecipient>() { new DriveRecipient() { ObjectId = userId } };

                            var inviteResult = await graphClient
                                                .Drives[settings.DriveId].Items[projectChild.Id]
                                                .Invite(recipients, requireSignIn: true, roles: tcPerm.Roles, sendInvitation: false)
                                                .Request()
                                                .PostAsync();

                            // TODO : Consider to reduce round-trips by grouping by (recipients, roles)
                        }
                    }

                    // Restore the temporary removed SiteGroups
                    foreach (var permission in deletedPermissions.Where(p => p.GrantedToV2.SiteGroup != null))
                    {
                        var recipients = new List<DriveRecipient>() { new DriveRecipient() { Alias = permission.GrantedToV2.SiteGroup.DisplayName } };

                        //var roles = ReplaceOwnerRoles(permission.Roles);
                        var roles = permission.Roles; // trying... nope, invite throws exception when role is 'owner'

                        var inviteResult = await graphClient
                            .Drives[settings.DriveId].Items[projectChild.Id]
                            .Invite(recipients, requireSignIn: true, roles: roles, sendInvitation: false)
                            .Request()
                            .PostAsync();
                    };

                }
            }
            catch (Exception ex)
            {
                // logs the exception - not relevant here.
            }
        }

        private List<string> ReplaceOwnerRoles(IEnumerable<string> roles)
        {
            var rolesList = roles.ToList();

            for (int i = 0; i < rolesList.Count; ++i)
            {
                rolesList[i] = rolesList[i].Replace("owner", "write");
            }

            return rolesList;
        }
Microsoft Graph
Microsoft Graph
A Microsoft programmability model that exposes REST APIs and client libraries to access data on Microsoft 365 services.
10,007 questions
0 comments No comments
{count} votes