Search code examples
c#sharepointmicrosoft-graph-api

Getting permissions on SharePoint sub-sites with Graph API


I'm trying to get permissions on a SharePoint sub-site using Graph, but not having much joy. So far, I've tried:

var perms = await client.Sites[subSiteId].Permissions.GetAsync();

But this returns an "Operation not supported" error.

Trying to access the sub-site via the parent as below just returns an empty collection (no error):

var perms = client.Sites[parentSiteId].Sites[subSiteId].GetAsync().GetAwaiter().GetResult().Permissions;

Expanding on that to try and use Select or Expand similarly gives an empty collection:

var perms = client.Sites[parentSiteId].Sites[subSiteId].GetAsync((config) =>
{
    config.QueryParameters.Select = new string[] { "permissions" };
}).GetAwaiter().GetResult().Permissions;

I know there are permissions set on the sub-sites because I can see them in SharePoint. Am I doing something wrong here? Does anyone know if it is possible to get the permissions of a sub-site using Graph?


Solution

  • OK, after checking with a Microsoft rep, it seems this behaviour is by design, despite being reported as a bug. The /sites/{siteId}/permissions endpoint only returns permissions for Apps that have been granted permissions to the site, not the actual permissions that users or groups may have within SharePoint.

    I have found a solution to the problem that uses PnP and SharePoint CSOM, to make it work you need to register your App in SharePoint:

    1. Navigate to https://tenant.sharepoint.com/_layouts/15/appregnew.aspx
    2. Click Generate to create a Client Id and Client Secret and copy those out
    3. Enter your App name in Title
    4. Enter www.localhost.com as the App Domain
    5. Enter https://www.localhost.com as the Redirect URI
    6. Click Create

    Next, invite the App into the SharePoint tenant so it can be assigned permissions:

    1. Navigate to https://tenant-admin.sharepoint.com/_layouts/15/appinv.aspx
    2. In App Id, paste the Client Id generated earlier and click Lookup

    In the App Permission field, you will need to enter some XML. If you want your app to have full control of all SharePoint sites, enter the following:

    <AppPermissionRequests AllowAppOnlyPolicy="true">
        <AppPermissionRequest Scope="http://sharepoint/content/tenant" 
         Right="FullControl" />
     </AppPermissionRequests>
    

    A useful cheat-sheet for other scenarios can be found here: https://medium.com/ng-sp/sharepoint-add-in-permission-xml-cheat-sheet-64b87d8d7600

    1. Click Create
    2. Click to confirm you trust the application

    As a proof-of-concept I knocked up a simple WinForms app with a button to connect and a rich text box to contain the output of the request. The app will need the following packages from NuGet:

    • PnP.Core
    • PnP.FrameWork
    • Microsoft.SharePointOnline.CSOM (which may be installed as a dependency from the other two packages)

    My Code:

    using System;
    using System.Text;
    using System.Threading;
    using System.Windows.Forms;
    using PnP.Framework;
    using sp = Microsoft.SharePoint.Client;
    
    namespace CSOM_Perms2
    {
        public partial class Form1 : Form
        {
            private string siteUrl = "https://tenant.sharepoint.com/sites/SiteName";
            private string username = "<Your Client ID>";
            private string password = "<Your Client Secret>";
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void buttonGetPerms_Click(object sender, EventArgs e)
            {
                new Thread(() =>
                {
                    _ = Invoke(new Action(() => buttonGetPerms.Enabled = false));
    
                    using (var context = new AuthenticationManager().GetACSAppOnlyContext(siteUrl, username, password))
                    {
                        context.Load(context.Web);
                        context.ExecuteQuery();
    
                        sp.RoleAssignmentCollection roleAssignments = context.Web.RoleAssignments;
                        context.Load(roleAssignments);
                        context.ExecuteQuery();
    
                        StringBuilder sb = new StringBuilder();
                        foreach (sp.RoleAssignment roleAssignment in roleAssignments)
                        {
                            sp.Principal principal = roleAssignment.Member;
                            context.Load(principal);
                            context.ExecuteQuery();
                            sb.AppendLine($"Principal: {principal.LoginName}");
                            sb.AppendLine($"Type: {principal.PrincipalType}");
                            sp.RoleDefinitionBindingCollection roleDefinitions = roleAssignment.RoleDefinitionBindings;
                            context.Load(roleDefinitions);
                            context.ExecuteQuery();
                            foreach (sp.RoleDefinition roleDefinition in roleDefinitions)
                            {
                                sb.AppendLine($"Role Definition: {roleDefinition.Name}");
                            }
                            sb.AppendLine(new string('=', 80));
                        }
                        _ = Invoke(new Action(() =>
                        {
                            richTextBoxPerms.Text = sb.ToString();
                            buttonGetPerms.Enabled = true;
                        }));
                    }
                }).Start();
            }
        }
    }
    

    Hope this helps anyone having the same/similar issues as me!