Upload file to MS SharePoint using Python OneDrive SDK
<p>Is it possible to upload a file to the <strong>Shared Documents</strong> library of a <strong>Microsoft SharePoint</strong> site with the <strong><a href="https://github.com/OneDrive/onedrive-sdk-python" rel="nofollow">Python OneDrive SDK</a></strong>? </p>

<p><strong><a href="https://dev.onedrive.com/readme.htm" rel="nofollow">This documentation</a></strong> says it should be (in the first sentence), but I can't make it work.</p>

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

<blockquote>
  <p>"Exception of type 'Microsoft.IdentityModel.Tokens.<strong>AudienceUriValidationFailedException</strong>' was thrown."</p>
</blockquote>

<p>The code I'm using that returns an object with the error:</p>

<pre><code>(...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')
</code></pre>

<p><a href="http://i.stack.imgur.com/ZQEj4.png" rel="nofollow"><img src="http://i.stack.imgur.com/ZQEj4.png" alt="where I&#39;d like to upload on the web"></a></p>

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

<pre><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')
</code></pre>

<p>How could I upload the same file to a <strong>SharePoint</strong> site?</p>

<p><em>(Answers to similar questions (<a href="http://stackoverflow.com/questions/37451835/onedrive-api-refer-to-sharepoint-file-to-upload-or-download-invalid-audience">1</a>,<a href="http://stackoverflow.com/questions/37233669/onedrive-api-python-sdk-points-to-login-live-com-not-mydomain-sharepoint-com">2</a>,<a href="http://stackoverflow.com/questions/29635758/onedrive-sharepoint-oauth-invalid-audience-error">3</a>,<a href="http://stackoverflow.com/questions/39822092/which-sdk-or-api-should-i-use-to-list-and-upload-files-into-office-365-sharepoin">4</a>) 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.)</em></p>

<hr>

<p><strong>Update</strong>: Here is my full code and output. (<em>Sensitive original data replaced with similarly formatted gibberish.</em>)</p>

<pre><code>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)
</code></pre>

<p>Output:</p>

<pre><code>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&amp;file=test.xlsx&amp;action=default
</code></pre>

<p>I finally found a solution, with the help of (<em>SO user</em>) sytech.</p>

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

<p><strong>However</strong>, by extending the <code>ResourceDiscoveryRequest</code> class (in the OneDrive SDK), we can create a workaround for this. I managed to <strong>upload a file</strong> this way:</p>

<pre><code>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)
</code></pre>

<p>Authenticating for a different service gives you a different token.</p>

python sharepoint onedrive onedrive-api