Reputation: 2291
I have been trying to use the MSGraph API to access files in a shared OneDrive which is part of a 365 account. I need to create an app that gets client permissions and then makes the API calls. Specifically this must be done with client authentication (not delegated - on behalf of a signed in user)
The question is how can I get a directory list of what's in my oneDrive using MSGraph? Then I need to be able to download specific items.
The steps I did are
https://login.microsoftonline.com/{{TenantID}}/oauth2/V2.0/token
This correctly returns a bearer token
https://graph.microsoft.com/{{driveId}}/items
This returns: "code": "invalidRequest", "message": "The 'filter' query option must be provided."
GET /drives/{drive-id}/root/search(q='todo.txt')
This returns and empty array [] - when I can see that the drive contains this file
The question is how can I get a directory list of what's in my oneDrive using MSGraph? Then I need to be able to download specific items.
EDIT: I got the drive id from this Postman request: https://graph.microsoft.com/v1.0/drives which returned: "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#drives", "value": [ { "createdDateTime": "2022-02-12T14:09:19Z", "description": "", "id": "b!3JFpYb83RUidS4Xkt71Orcfzp9sQsf1Kmr_43UsnyqL4nAuvo6oYRIAe-gp_shCu", "lastModifiedDateTime": "2023-01-10T03:57:29Z", "name": "Documents", "webUrl": "https://agcwainc.sharepoint.com/Shared%20Documents",
I then used the id to try to find its contents: https://graph.microsoft.com/v1.0/drives/{{driveID}}/root/children which returns and empty array: "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#drives('b%213JFpYb83RUidS4Xkt71Orcfzp9sQsf1Kmr_43UsnyqL4nAuvo6oYRIAe-gp_shCu')/root/children", "value": []
Upvotes: 2
Views: 4234
Reputation: 77
Building up on the previous answers, and assuming one has a Client ID, Client Secret and Tenant ID as well as the necessary permissions on Microsoft Azure, here is the step-by-step process in Python showing how to download multiple files from a specific user's subfolder in OneDrive:
First, acquire an access token using the Microsoft Authentication Library:
import requests
import msal
import os
from office365.graph_client import GraphClient
# Configuration (Azure app registration details and user email)
CLIENT_ID = os.getenv('OFFICE365_CLIENT_ID')
CLIENT_SECRET = os.getenv('OFFICE365_SECRET_VALUE')
TENANT_ID = os.getenv('OFFICE365_TENANT_ID')
target_email = "[email protected]" # Replace with the target user's email
# Define the authority and scope
authority = f'https://login.microsoftonline.com/{TENANT_ID}'
scope = ['https://graph.microsoft.com/.default']
# Create an MSAL confidential client application
app = msal.ConfidentialClientApplication(
CLIENT_ID, authority=authority, client_credential=CLIENT_SECRET
)
# Acquire token for the Microsoft Graph API
result = app.acquire_token_for_client(scopes=scope)
if 'access_token' in result:
access_token = result['access_token']
else:
raise Exception(result['error_description'])
Second, use the access token to authenticate with the MS Graph API and find the specific user from a list of users:
# Construct the headers with your access token
headers = {
'Authorization': f'Bearer {access_token}'
}
# Get a list of users
users_url = "https://graph.microsoft.com/v1.0/users"
users_response = requests.get(users_url, headers=headers)
users_response.raise_for_status()
users = users_response.json().get('value', [])
# Find the specific user
user_id = None
for user in users:
if user['mail'] == target_email:
user_id = user['id']
break
if not user_id:
raise Exception("User not found")
Third, list drives for the user, access the desired drive and list its contents:
# List drives for the user
drives_url = f"https://graph.microsoft.com/v1.0/users/{user_id}/drives"
drives_response = requests.get(drives_url, headers=headers)
drives_response.raise_for_status()
drives = drives_response.json().get('value', [])
# Access the desired drive and list its contents
for drive in drives:
if drive['name'] == 'OneDrive':
drive_id = drive['id']
drive_items_url = f"https://graph.microsoft.com/v1.0/drives/{drive_id}/root/children"
drive_items_response = requests.get(drive_items_url, headers=headers)
drive_items_response.raise_for_status()
items = drive_items_response.json().get('value', [])
Fourth, find the desired folder and list its contents:
# Get a list of folders (and files)
for item in items:
if item['name'] == 'MyFolder':
break
folder_id = item['id']
# URL to get the children (items) of the datasets folder
datasets_url = f'https://graph.microsoft.com/v1.0/drives/{drive_id}/items/{folder_id}/children'
# Send the request to fetch folder contents
response = requests.get(datasets_url, headers=headers)
response.raise_for_status() # Check if the request was successful
folder_contents = response.json().get('value', [])
# Filter the contents to only include folders
folders = [item for item in folder_contents if 'folder' in item]
Fifth, find the desired subfolder and list its contents:
# Print the names of the folders
for folder in folders:
if folder['name'] == 'MySubFolder':
sub_folder_id = folder['id']
break
# URL to get the children (items) of the CME folder
cme_url = f'https://graph.microsoft.com/v1.0/drives/{drive_id}/items/{sub_folder_id}/children'
# Send the request to fetch folder contents
response = requests.get(cme_url, headers=headers)
response.raise_for_status() # Check if the request was successful
folder_contents = response.json().get('value', [])
# Filter the contents to only include files
files = [item for item in folder_contents if 'file' in item]
Finally, download all files from the subfolder to a local directory:
# Define your target directory
target_directory = "/path/to/local/directory"
# Make sure the target directory exists
os.makedirs(target_directory, exist_ok=True)
# Function to download a file
def download_file(headers, drive_id, file_id, file_name, target_directory):
target_path = os.path.join(target_directory, file_name)
# Construct the download URL
download_url = f'https://graph.microsoft.com/v1.0/drives/{drive_id}/items/{file_id}/content'
# Send the request to download the file
response = requests.get(download_url, headers=headers, stream=True)
response.raise_for_status() # Check if the request was successful
# Save the file locally
with open(target_path, 'wb') as file:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
file.write(chunk)
print(f"Downloaded {file_name} to {target_path}")
# Print the names and IDs of the files
for file in files:
print(f"File Name: {file['name']}, File ID: {file['id']}, Size: {file['size']} bytes")
file_name = file["name"]
file_id = file["id"]
download_file(headers, drive_id, file_id, file_name, target_directory)
Upvotes: 0
Reputation: 2291
The full process to access files in OneDrive from a client authenticated nodejs process is:
Register an app with Azure and get a client secret Put app parameters into postman as environment variables
Assign permissions ( needs admin authority) to access whatever Azure services you want to access. In this case its Files.ReadWrite.All To access target OneDrive files using MSGraph:
First request a token like below:
This should return an object with an access-token attribute.
Instead of looking for drives with https://graph.microsoft.com/v1.0/drives/
ask for the users instead https://graph.microsoft.com/v1.0/users
Note: You need to set the authorisation header in all MSGRAPH requests as below:
This will return a list of users and their userIDs along with other meta data.
Search the above array of user and find the user who owns the drive you want to access. The ask for that users drives with https://graph.microsoft.com/v1.0/users/{{user_id}}/drives
Then pick the drive you want and display its contents with https://graph.microsoft.com/v1.0/drives/{{drive_id}}/root/children
How to download a file depends on where the code is being run from nodejs or from a browser. For the case node there is an MSGraph command to download the content of a file. This wont work with Javascript because of CORS issues. Instead, the request in 6) includes a signed URL for each file that can be passed to fetch or axios etc to read the contents.
Acknowledgment: Brian Smith provided the guidance to solve this problem for me. Please +1 his reply and not mine!!
Upvotes: 3
Reputation: 1656
First validate your App Registration has the correct permissions and Admin Consent has been granted.
API / Permission Name | Type | Description |
---|---|---|
Files.Read.All | Application | If you only need to read files |
Files.ReadWrite.All | Application | If you need to read and upload files |
To get all files at the root level of the a drive you can use
https://graph.microsoft.com/v1.0/drives/{driveId}/root/children
Get all files inside of a folder of a drive
https://graph.microsoft.com/v1.0/drives/{driveId}/root:/{folderName}:/children
Reference: https://learn.microsoft.com/en-us/graph/api/driveitem-list-children?view=graph-rest-1.0&tabs=http
Note: See a full answer below from bcperth based on info provided by Brian Smith.
Upvotes: 3