bcperth
bcperth

Reputation: 2291

Access OneDrive Directories and files using MSGraph API with client Authentication

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

  1. Register an Azure and copied the account details and secret (works fine) added all the permissions I need.
  2. In Postman, I requested the required Auth2 bearer token (works fine)
https://login.microsoftonline.com/{{TenantID}}/oauth2/V2.0/token

This correctly returns a bearer token

  1. In postman I used this to get my drives https://graph.microsoft.com/v1.0/drives/ Shows one drive with Name "Documents" and an id value
  2. In postman I tried to list the items in the drives with
https://graph.microsoft.com/{{driveId}}/items

This returns: "code": "invalidRequest", "message": "The 'filter' query option must be provided."

  1. In postMan I tried to search to find a specific file I know is in the oneDrive
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": []

The app permissions are: App Permissions

The Onedrive contents is: OneDrive Contents

Upvotes: 2

Views: 4234

Answers (3)

cpowr
cpowr

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

bcperth
bcperth

Reputation: 2291

The full process to access files in OneDrive from a client authenticated nodejs process is:

  1. Register an app with Azure and get a client secret Put app parameters into postman as environment variables

  2. 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:

  3. First request a token like below: https://login.microsoftonline.com/{{TenantID}}/oauth2/V2.0/token
where TenantID is one of the variables from Step 1. This should return an object with an access-token attribute.

  4. 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: enter image description here

This will return a list of users and their userIDs along with other meta data.

  1. 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

  2. Then pick the drive you want and display its contents with https://graph.microsoft.com/v1.0/drives/{{drive_id}}/root/children

  3. 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

Brian Smith
Brian Smith

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

Related Questions