Tag Archives: SelectedOperations

Python connect to SharePoint via Graph API with Delegated Permissions

Below is the sample Python code to authenticate against Microsoft 365 as current user with MSA library and to call Microsoft Graph API – specifically get SharePoint Site, get Site lists with requests library.

But first, you have to have an App Registration in Azure (Entra ID) with delegated permissions consented and authentication configured.

Delegated Permissions

If your solution needs access to all SharePoint sites – consider Sites.FullControl.All or Sites.Manage.All or Sites.ReadWrite.All or Sites.Read.All depending on access level you need. Effective permissions would be a min from both – permissions configured for app registration and permissions current user have. Once consented at the app registration – these permissions will work right away.

If your solution needs access to one (or a few) SharePoint sites – consider Sites.Selected API permissions as it will scope down access to only sites that are required for your solution to work. Remember, Sites.Selected API permissions, even consented at the app registration, does require second step – SharePoint admin should provide (read or write or manage or fullcontrol) permissions for the app registration to a specific site or sites.

Authentication

You’d also need to configure authentication blade. How? It depends on the kind of application you are building etc. For example for native apps I do:
– add platform – “Mobile and Desktop app”
– select “https://login.microsoftonline.com/common/oauth2/nativeclient”
– select “msal096fd951-7285-4e4f-9c1f-23a393556b19://auth (MSAL only)”
– add custom Redirect URI: “http://localhost”

This config works for Python code below

Python Code

You’d need to install/import the libraries: json, configparser, msal, requests

Here is the code:

import json
import configparser
import msal
import requests

config = configparser.ConfigParser()
config.read('config.cfg')
client_id = config.get('delegated','clientId')
authority = config.get('delegated','authority')
scopes = config.get('delegated','scope').split()
siteId = config.get('delegated','siteId')
print( client_id)
print( authority)
print( scopes)
print( siteId)

global_token_cache = msal.TokenCache()
global_app = msal.PublicClientApplication(
    client_id,
    authority=authority,  # For Entra ID or External ID
    token_cache=global_token_cache,  
    )

def acquire_and_use_token():
    # The pattern to acquire a token looks like this.
    result = None
    result = global_app.acquire_token_interactive(scopes)

    if "access_token" in result:
        print("Token was obtained from:", result["token_source"])  # Since MSAL 1.25
        # print("Token acquisition result", json.dumps(result, indent=2))
        return result["access_token"]
    else:
        print("Token acquisition failed", result)  # Examine result["error_description"] etc. to diagnose error
        return None


token = acquire_and_use_token()


http_headers = {'Authorization': 'Bearer ' + token,
                'Accept': 'application/json',
                'Content-Type': 'application/json'}

graph_url = 'https://graph.microsoft.com/v1.0/sites/' + siteId + '?$select=id,webUrl,displayName'

siteResponse = requests.get(graph_url, headers=http_headers)
print(siteResponse.status_code)
site = json.loads(siteResponse.content)
# print("Site (raw) : ")
# print(site)
print("Site webUrl : ", site["webUrl"])
print("Site displayName : ", site["displayName"])

# Lists
graph_url = 'https://graph.microsoft.com/v1.0/sites/' + siteId + '/lists'
listsResponse = requests.get(graph_url, headers=http_headers)
print(listsResponse.status_code)
lists = json.loads(listsResponse.content)
# print("Site lists (raw):")
# print(lists)
print("Site lists:")
for list in lists["value"]:
    print("  Display Name:", list["displayName"])
    print("   Id:", list["id"])
    print("   Web Url:", list["webUrl"])
    print("   Created Date:", list["createdDateTime"])
    print("   Last Modified Date:", list["lastModifiedDateTime"])

Application permissions

If your scenario is to call Graph API from Python with application permissions (aka unattended or daemon app) – the main difference is authentication. It is described here. It also requires App registration configured like this.

SelectedOperations.Selected permissions in SharePoint

Microsoft says “Initially, Sites.Selected existed to restrict an application’s access to a single site collection. Now, lists, list items, folders, and files are also supported, and all Selected scopes now support delegated and application modes.”. This article deep-dives into providing and using SelectedOperations.Selected granular permissions to SharePoint:

Lists.SelectedOperations.SelectedProvides application access to a specific list
ListItems.SelectedOperations.SelectedProvides application access to one or more list items, files, or folders
Files.SelectedOperations.SelectedProvides application access to to one or more files or library folders

Set of SelectedOperations permissions is exactly what Microsoft promised a few years ago. And this is great, as we really need granular access to SharePoint sites. I’ve been supporting enterprise SharePoint for more than 10 years now, and I know that it was always a concern when application require access to a list/library or a folder or even document, but admins have to provide access to entire site.

Especially, I believe, this feature becomes more in demand because of Copilot for Microsoft 365. As for now – it’s mostly developers and data analytics who needs unattended application access to SharePoint, but what if regular users powered with m365 Copilot license start creating autonomous agents?

So below is my lab setup, PowerShell scripts and guides with screenshots on how to provide granular (not to entire site but to library/list or folder, or even just one document or list item) permissions to SharePoint and how to use provided permissions.

Admin App

First, we need an Admin App – an app we will use to provide permissions.

The only requirement – this app should have Microsoft.Graph Sites.FullControl.All API permissions consented:

Target Site and Dev Setup

For this lab/demo setup, I have created a team under Microsoft Teams (so it’s a group-based Teams-connected SharePoint site), then test list and test library:

There must be an App Registration for client application – application that will have access to Test-List-01 and Test-Lib-01 only. This app registration should have Microsoft Graph “Lists.SelectedOperations.Selected” API permissions consented:

and I will use Python to access SharePoint programmatically.

At this moment (we have a client app and secret, and “” API permissions, but did not provide for this app access to specific sites or libraries) – we should be able to authenticate to Microsoft 365, but not able to get any kind of data (we can get token, but other call to Graph API would return 403 error – Error: b'{“error”:{“code”:”accessDenied”,”message”:”Request Doesn\’t have the required Permission scopes to access a site.”,):

PowerShell script to provide selectedoperations.selected access for an app to a specific list would be as below. Here we use plain calls to MS Graph API. Full script for your refence is available at GitHub, but here is the essential part:

$apiUrl = "https://graph.microsoft.com/beta/sites/$targetSiteId/lists/$targetSiteListId/permissions"
$apiUrl 
$params = @{
	roles = @(
	    "read"
    )
    grantedTo = @{
        application = @{
            id = $clientAppClientId
        }
    }
}
$body = $params | ConvertTo-Json
$response = Invoke-RestMethod -Headers $Headers -Uri $apiUrl -Method Post -Body $body -ContentType "application/json"

Client Application

I use Python console app as a client application. Link to the code at the GitHub is shared below under References, but the core part of the Python code is (I do not use any Microsoft or other libraries here, just plain requests to Microsoft Graph for authentication and for data):

import requests
import json
from secrets import clientSc, clientId, tenantId, siteId, listId 

# specify client id, client secret and tenant id
# clientId = ""
# clientSc = "" 
# tenantId = "" 

apiUri = "https://login.microsoftonline.com/" + tenantId + "/oauth2/v2.0/token"

body = {
    "client_id"     : clientId,
    "client_secret" : clientSc,
    "scope"         : "https://graph.microsoft.com/.default",
    "grant_type"    : "client_credentials" 
}

try: 
    response = requests.post(apiUri, data=body)
    token = json.loads(response.content)["access_token"]
except:
    print("Error: ", json.loads(response.content)["error_description"])
    exit()

print("Got token: ", token[0:10], "...")
headers={'Authorization': 'Bearer {0}'.format(token)}

# Get specific site list
print("Geting specific site list")
# graph_url = 'https://graph.microsoft.com/v1.0/sites/' + siteId + '/lists/' + listId
graph_url = 'https://graph.microsoft.com/beta/sites/' + siteId + '/lists/' + listId
graphResponse = requests.get(   graph_url, headers=headers )
print(" Response status code: ", graphResponse.status_code)
if graphResponse.status_code == 200:
    list = json.loads(graphResponse.content)
    print(" List display name: ", list["displayName"])

Note.
I’m not sure if it’s a bug or my incorrect setup, but I noticed that if I provide access for the app to the list – app can read site.

TBC…

References

Granular Application Permissions to SharePoint

In 2021 Microsoft implemented “Sites.Selected” Graph API permissions to allow application access (without a signed in user) to specific sites (entire site only). In 2024 Microsoft implemented granular access – to specific list/libraries, as well as to specific documents/files and list items. Now name convention is *.SelectedOperations.Selected.
Permissions come in two flavors – delegated and application:

  • Files.SelectedOperations.Selected – Allow the application to access a subset of files (files explicitly permissioned to the application). The specific files and the permissions granted will be configured in SharePoint Online or OneDrive.
  • ListItems.SelectedOperations.Selected – Allow the application to access a subset of lists. The specific lists and the permissions granted will be configured in SharePoint Online.
  • Lists.SelectedOperations.Selected – Allow the application to access a subset of lists. The specific lists and the permissions granted will be configured in SharePoint Online.