Upload file to MS SharePoint using Python OneDrive SDK
Question: Is it possible to upload a file to the **Shared Documents** library of a
**Microsoft SharePoint** site with the **[Python OneDrive
SDK](https://github.com/OneDrive/onedrive-sdk-python)**?

**[This documentation](https://dev.onedrive.com/readme.htm)** says it should
be (in the first sentence), but I can't make it work.

I'm able to authenticate (with Azure AD) and upload to a **OneDrive** folder,
but when trying to upload to a **SharePoint** folder, I keep getting this
error:

> "Exception of type
> 'Microsoft.IdentityModel.Tokens.**AudienceUriValidationFailedException** '
> was thrown."

The code I'm using that returns an object with the error:

    
    
    (...authentication...)
    client = onedrivesdk.OneDriveClient('https://{tenant}.sharepoint.com/{site}/_api/v2.0/', auth, http)
    client.item(path='/drive/special/documents').children['test.xlsx'].upload('test.xlsx')
    

[![where I'd like to upload on the
web](http://i.stack.imgur.com/ZQEj4.png)](http://i.stack.imgur.com/ZQEj4.png)

I can successfully upload to `https://{tenant}-my.sharepoint.com/_api/v2.0/`
(notice the "**-my** " after the `{tenant}`) with the following code:

    
    
    client = onedrivesdk.OneDriveClient('https://{tenant}-my.sharepoint.com/_api/v2.0/', auth, http)
    returned_item = client.item(drive='me', id='root').children['test.xlsx'].upload('test.xlsx')
    

How could I upload the same file to a **SharePoint** site?

_(Answers to similar questions
([1](http://stackoverflow.com/questions/37451835/onedrive-api-refer-to-
sharepoint-file-to-upload-or-download-invalid-
audience),[2](http://stackoverflow.com/questions/37233669/onedrive-api-python-
sdk-points-to-login-live-com-not-mydomain-sharepoint-
com),[3](http://stackoverflow.com/questions/29635758/onedrive-sharepoint-
oauth-invalid-audience-
error),[4](http://stackoverflow.com/questions/39822092/which-sdk-or-api-
should-i-use-to-list-and-upload-files-into-office-365-sharepoin)) on Stack
Overflow are either too vague or suggest using a different API. My question is
if it's possible using the OneDrive Python SDK, and if so, how to do it.)_

* * *

**Update** : Here is my full code and output. (_Sensitive original data
replaced with similarly formatted gibberish._)

    
    
    import re
    import onedrivesdk
    from onedrivesdk.helpers.resource_discovery import ResourceDiscoveryRequest
    
    # our domain (not the original)
    redirect_uri = 'https://example.ourdomain.net/' 
    # our client id (not the original)
    client_id = "a1234567-1ab2-1234-a123-ab1234abc123"  
    # our client secret (not the original)
    client_secret = 'ABCaDEFGbHcd0e1I2fghJijkL3mn4M5NO67P8Qopq+r=' 
    resource = 'https://api.office.com/discovery/'
    auth_server_url = 'https://login.microsoftonline.com/common/oauth2/authorize'
    auth_token_url = 'https://login.microsoftonline.com/common/oauth2/token'
    http = onedrivesdk.HttpProvider()
    auth = onedrivesdk.AuthProvider(http_provider=http, client_id=client_id, 
                                    auth_server_url=auth_server_url, 
                                    auth_token_url=auth_token_url)
    
    should_authenticate_via_browser = False
    try:
        # Look for a saved session. If not found, we'll have to 
        # authenticate by opening the browser.
        auth.load_session()
        auth.refresh_token()
    except FileNotFoundError as e:
        should_authenticate_via_browser = True
        pass
    
    if should_authenticate_via_browser:
        auth_url = auth.get_auth_url(redirect_uri)
        code = ''
        while not re.match(r'[a-zA-Z0-9_-]+', code):
            # Ask for the code
            print('Paste this URL into your browser, approve the app\'s access.')
            print('Copy the resulting URL and paste it below.')
            print(auth_url)
            code = input('Paste code here: ')
            # Parse code from URL if necessary
            if re.match(r'.*?code=([a-zA-Z0-9_-]+).*', code):
                code = re.sub(r'.*?code=([a-zA-Z0-9_-]*).*', r'\1', code)
        auth.authenticate(code, redirect_uri, client_secret, resource=resource)
        # If you have access to more than one service, you'll need to decide
        # which ServiceInfo to use instead of just using the first one, as below.
        service_info = ResourceDiscoveryRequest().get_service_info(auth.access_token)[0]
        auth.redeem_refresh_token(service_info.service_resource_id)
        auth.save_session()  # Save session into a local file.
    
    # Doesn't work
    client = onedrivesdk.OneDriveClient(
        'https://{tenant}.sharepoint.com/sites/{site}/_api/v2.0/', auth, http)
    returned_item = client.item(path='/drive/special/documents')
                          .children['test.xlsx']
                          .upload('test.xlsx')
    print(returned_item._prop_dict['error_description'])
    
    # Works, uploads to OneDrive instead of SharePoint site
    client2 = onedrivesdk.OneDriveClient(
        'https://{tenant}-my.sharepoint.com/_api/v2.0/', auth, http)
    returned_item2 = client2.item(drive='me', id='root')
                            .children['test.xlsx']
                            .upload('test.xlsx')
    print(returned_item2.web_url)
    

Output:

    
    
    Exception of type 'Microsoft.IdentityModel.Tokens.AudienceUriValidationFailedException' was thrown.
    https://{tenant}-my.sharepoint.com/personal/user_domain_net/_layouts/15/WopiFrame.aspx?sourcedoc=%1ABCDE2345-67F8-9012-3G45-6H78IJKL9M01%2N&file=test.xlsx&action=default
    


Answer: I finally found a solution, with the help of (_SO user_) sytech.

The answer to my original question is that using the original **[Python
OneDrive SDK](https://github.com/OneDrive/onedrive-sdk-python)** , it's **not
possible** to upload a file to the `Shared Documents` folder of a `SharePoint
Online` site (at the moment of writing this): when the SDK queries the
[**resource discovery
service**](https://dev.onedrive.com/auth/aad_oauth.htm#step-3-discover-the-
onedrive-for-business-resource-uri), it drops all services whose
`service_api_version` is not `v2.0`. However, I get the SharePoint service
with `v1.0`, so it's dropped, although it could be accessed using API v2.0
too.

**However** , by extending the `ResourceDiscoveryRequest` class (in the
OneDrive SDK), we can create a workaround for this. I managed to **upload a
file** this way:

    
    
    import json
    import re
    import onedrivesdk
    import requests
    from onedrivesdk.helpers.resource_discovery import ResourceDiscoveryRequest, \
        ServiceInfo
    
    # our domain (not the original)
    redirect_uri = 'https://example.ourdomain.net/' 
    # our client id (not the original)
    client_id = "a1234567-1ab2-1234-a123-ab1234abc123"  
    # our client secret (not the original)
    client_secret = 'ABCaDEFGbHcd0e1I2fghJijkL3mn4M5NO67P8Qopq+r=' 
    resource = 'https://api.office.com/discovery/'
    auth_server_url = 'https://login.microsoftonline.com/common/oauth2/authorize'
    auth_token_url = 'https://login.microsoftonline.com/common/oauth2/token'
    
    # our sharepoint URL (not the original)
    sharepoint_base_url = 'https://{tenant}.sharepoint.com/'
    # our site URL (not the original)
    sharepoint_site_url = sharepoint_base_url + 'sites/{site}'
    
    file_to_upload = 'C:/test.xlsx'
    target_filename = 'test.xlsx'
    
    
    class AnyVersionResourceDiscoveryRequest(ResourceDiscoveryRequest):
    
        def get_all_service_info(self, access_token, sharepoint_base_url):
            headers = {'Authorization': 'Bearer ' + access_token}
            response = json.loads(requests.get(self._discovery_service_url,
                                               headers=headers).text)
            service_info_list = [ServiceInfo(x) for x in response['value']]
            # Get all services, not just the ones with service_api_version 'v2.0'
            # Filter only on service_resource_id
            sharepoint_services = \
                [si for si in service_info_list
                 if si.service_resource_id == sharepoint_base_url]
            return sharepoint_services
    
    
    http = onedrivesdk.HttpProvider()
    auth = onedrivesdk.AuthProvider(http_provider=http, client_id=client_id,
                                    auth_server_url=auth_server_url,
                                    auth_token_url=auth_token_url)
    
    should_authenticate_via_browser = False
    try:
        # Look for a saved session. If not found, we'll have to
        # authenticate by opening the browser.
        auth.load_session()
        auth.refresh_token()
    except FileNotFoundError as e:
        should_authenticate_via_browser = True
        pass
    
    if should_authenticate_via_browser:
        auth_url = auth.get_auth_url(redirect_uri)
        code = ''
        while not re.match(r'[a-zA-Z0-9_-]+', code):
            # Ask for the code
            print('Paste this URL into your browser, approve the app\'s access.')
            print('Copy the resulting URL and paste it below.')
            print(auth_url)
            code = input('Paste code here: ')
            # Parse code from URL if necessary
            if re.match(r'.*?code=([a-zA-Z0-9_-]+).*', code):
                code = re.sub(r'.*?code=([a-zA-Z0-9_-]*).*', r'\1', code)
    
        auth.authenticate(code, redirect_uri, client_secret, resource=resource)
        service_info = AnyVersionResourceDiscoveryRequest().\
            get_all_service_info(auth.access_token, sharepoint_base_url)[0]
        auth.redeem_refresh_token(service_info.service_resource_id)
        auth.save_session()
    
    client = onedrivesdk.OneDriveClient(sharepoint_site_url + '/_api/v2.0/',
                                        auth, http)
    # Get the drive ID of the Documents folder.
    documents_drive_id = [x['id']
                          for x
                          in client.drives.get()._prop_list
                          if x['name'] == 'Documents'][0]
    items = client.item(drive=documents_drive_id, id='root')
    # Upload file
    uploaded_file_info = items.children[target_filename].upload(file_to_upload)
    

Authenticating for a different service gives you a different token.

