2

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

3 Answers 3

3

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.

Sign up to request clarification or add additional context in comments.

10 Comments

Thanks Brian, I read all the doco including the reference you provided. I have all permissions. This is what I get from the /root/children which is an empty array. "@odata.context": "graph.microsoft.com/v1.0/…", "value": []
Sounds like a permissions issue. Verify permissions are correct on the app registration, you are getting a token that is from that app registration and for Microsoft Graph scope (jwt.io to verify), admin consent is granted on that app registration for the API Permissions requested, drive Id being used is correct, and lastly there truely are files in that drive. You should have a return there.
Ok thanks for persisting. If the token was not there or bad, it returns a "token invalid" error. If the permissions were bad it returns an "unauthorised" error. See the edits for details of the setup. I'm guessing the driveID returned is not not what I think it is - although its a valid drive id or the API would say invalid id!. Is there any other way to get the driveID for say the drive shown in the image above? Apologies I'm a React/AWS programmer and not that familiar with MS.
I should also confirm the decoded token, shows it came from "aud": "graph.microsoft.com", and its permissions are "roles": [ "User.ReadWrite.All", "People.Read.All", "Application.ReadWrite.All", "Directory.ReadWrite.All", "Files.ReadWrite.All", "Sites.FullControl.All" ],
Also "graph.microsoft.com/v1.0/users" returns the correct array of users (of the 365 account" , verifying the app/authentication/authorization is set up correctly. "graph.microsoft.com/v1.0/drives" returns a single drive, but graph.microsoft.com/v1.0/drives{{driveID}}/root/children shows an empty array but as per edited question it contains the file todo.txt.
|
3

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!!

1 Comment

Thank you for the question and for consolidating the answer. I spent two days on this, and I can finally get my code working :)
0

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)  

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.