Reputation: 2325
I am trying to use OneDrive API, ADAL and Python request.post to upload file to Sharepoint folder but am getting 401 error.
The ADAL authorization is working as gets correct token response and produces a viable access_token
.
I can use the access_token
to download a file from the same Sharepoint folder.
Although this only works when I manually copy print file_url
url created by code into browser. However urllib.urlretrieve(file_url, local_file_name)
only creates an file named myfilename.csv
with contents 403 Forbidden
.
My authorization uses hard coded username and password, and I am saving refresh token in clear text, and always retrieving it when retrieving token response:
import adal
import urllib
import requests
## set variables
username = '[email protected]'
password = 'mypassword'
authorization_url = 'https://login.windows.net/mydomain.onmicrosoft.com' # Authority
redirect_uri = 'https://login.microsoftonline.com/login.srf'
client_id = 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx' # Client id
## use ADAL to create token response
token_response = adal.acquire_token_with_username_password(
authorization_url,
username,
password
)
## create refresh token and save it to use later
refresh_token = token_response['refreshToken']
refresh_token_file = open('refresh_token.txt', 'w')
refresh_token_file.write(refresh_token)
refresh_token_file.close()
## get saved refresh token and use it to get new token response
refresh_token = open('refresh_token.txt', 'r').read()
token_response = adal.acquire_token_with_refresh_token(authorization_url, str(refresh_token))
## get access_token from token response
access_token = token_response.get('accessToken')
The token response is correct and using the access token from it the following code produces a file_url
url that I can manually copy and paste into browser that downloads a file successfullly. However urllib.urlretrieve(file_url, local_file_name)
only creates an file named myfilename.csv
with contents 403 Forbidden
:
## download file
file_url = 'https://mydomain.sharepoint.com/Shared%20Documents/myfoldername/myfilename.csv?token_response=' + str(access_token)
local_file_name = 'myfilename.csv'
urllib.urlretrieve(file_url, local_file_name)
However I have not yet been able to successfully upload to this Sharepoint folder. Currently I have the following:
# upload file
site_url = 'https://mydomain.sharepoint.com/'
headers = {'Authorization':'BEARER ' + str(access_token)}
r = requests.post(site_url, files={'Shared%20Documents/myfoldername/myfilename.csv': open('myfilename.csv', 'rb')}, headers=headers)
print r.text
which produces response
401 UNAUTHORIZED
My Azure AD app has permissions:
Read and write all user files
Read and write items and lists in all site collections
(not sure both are needed to upload files)
Does my request.post look ok? Pretty sure I am sending header, folder and file correctly.
EDIT TO ADD:
Given fact that both download code returns 403 Forbidden
and upload code returns 401 Unauthorized
I suspect the issue is with how urllib
and requests
are sending the urls.
EDIT TO ADD:
Trying to build the file url to use in GET and PUT. After I am authenticated, I can manually enter this url into browser:
https://mydomain.sharepoint.com/_api/v1.0/files/root
which returns following XML:
{"@odata.context":"https://mydomain.sharepoint.com/_api/v1.0/$metadata#files/$entity"
,"@odata.type":"#Microsoft.FileServices.Folder"
,"@odata.id":"https://mydomain.sharepoint.com/_api/v1.0/files/01QEW7725BZO3N6Y2GOV54373IPWSELRRZ"
,"@odata.editLink":"files/01QEW7725BZO3N6Y2GOV54373IPWSELRRZ"
,"createdBy":null
,"eTag":null
,"id":"01QEW7725BZO3N6Y2GOV54373IPWSELRRZ"
,"lastModifiedBy":null
,"name":"/"
,"parentReference":null
,"size":0
,"dateTimeCreated":"2013-07-31T02:35:57Z"
,"dateTimeLastModified":"2016-05-23T03:55:46Z"
,"type":"Folder"
,"webUrl":"https://mydomain.sharepoint.com/Shared%20Documents"
,"childCount":1}
However haven't found the correct syntax for the reference to the file. For example this isn't working:
https://mydomain.sharepoint.com/_api/v1.0/files/root:/myfoldername/myfilename.csv:/content
This is returning error:
{"error":"invalid_client","error_description":"Invalid audience Uri 'https:\/\/m
anagement.core.windows.net\/'."}
I think that getting this specific reference to file is what I need to to make it work.
Upvotes: 0
Views: 2495
Reputation: 10652
There are several things that need fixing here.
First off, you need to pass along the client id and resource to your acquire_token_with_username_password. This will allow you to access the Graph (where you can access OneDrive), otherwise you're just getting sign-in permissions in that token.
resource_url = "https://graph.microsoft.com";
token_response = ada.acquire_token_with_username_password(
authorization_url,
username,
password,
client_id
resource_url
)
If you get an error saying you that client_secret o client_assertion is needed. You need to register a new Azure App and pick Native Client. If you get an error saying you need an interactive prompt, you'll need manually consent to the app for the first time in the browser by navigating to
https://login.microsoftonline.com/mydomain.com/oauth2/authorize?client_id=client_id&resource=resource_url&response_type=code+id_token&nonce=1234
Make sure you Replace mydomain.com, client_id and resource_url.
You'll only need to do this once, the first time. After signing in with the browser and consenting, you should no longer get that interactive prompt error.
To download a file, not sure where you got that "?token_response=x" approach but I don't think that works (didn't work when I tested it). You need to a different url and send the bearer token like you are doing on your post request.
site_url = 'https://graph.microsoft.com/v1.0/drive/root:/myfoldername/myfilename.csv:/content'
headers = {'Authorization':'BEARER ' + str(access_token)}
r = requests.get(site_url, headers=headers)
print r.text
More info on getting a file here: https://dev.onedrive.com/items/download.htm
As for uploading, you'll need to change your URL as well, to the same value as the GET case and also change from POST to PUT.
site_url = 'https://graph.microsoft.com/v1.0/drive/root:/myfoldername/myfilename.csv:/content'
headers = {'Authorization':'BEARER ' + str(access_token)}
r = requests.put(site_url, data = open('myfilename.csv', 'rb')}, headers=headers)
print r.text
More info on uploading a file here: https://dev.onedrive.com/items/upload_put.htm
Upvotes: 3