Is it possible to upload a file to the Shared Documents library of a Microsoft SharePoint site with the Python OneDrive SDK?
This documentation 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')
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,2,3,4) 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
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, 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, 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.
Related
I am trying to create a python script that adds some tasks to my Microsoft ToDo List by using the Microsoft Graph API from python.
So far I was able to achieve this such that every time I run the script I have to log into my account and give permission such that the script can access my tasks.
However, I now want to achieve the same result but without having to log into my account every time.
My script looks as follows
import msal
import requests
client_id = '....'
client_secret = '....'
authority = 'https://login.microsoftonline.com/....'
scope = ['https://graph.microsoft.com/.default']
client = msal.ConfidentialClientApplication(client_id, authority=authority, client_credential=client_secret)
# First, try to lookup an access token in cache
token_result = client.acquire_token_silent(scope, account=None)
# If the token is available in cache, save it to a variable
if token_result:
access_token = 'Bearer ' + token_result['access_token']
print('Access token was loaded from cache')
# If the token is not available in cache, acquire a new one from Azure AD and save it to a variable
if not token_result:
token_result = client.acquire_token_for_client(scopes=scope)
access_token = 'Bearer ' + token_result['access_token']
# print('New access token was acquired from Azure AD')
# print(access_token)
url = 'https://graph.microsoft.com/v1.0/users/ae294107-3a57-448f-be95-f58390836cca/todo/lists'
headers = {
'Authorization': access_token
}
graph_result = requests.get(url=url, headers=headers)
print(graph_result.json())
By using this script I do not have to log in every time. However I can only access my user information but not my task list.
Is there a possibility to access my task lists without having to log in every time?
In that case you need to use ROPC flow and hard-code the username and password in the back-end code. please follow the doc - https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc
Hope this helps
Thanks
Is there any Python code sample available to get Google ads campaign data using Python via service account.
The information available on https://developers.google.com/google-ads/api/docs/oauth/service-accounts
is not sufficient enough to start. I have created service code and trying to use following code, but I am not even sure of this is depreciated on or the new one
from google.oauth2.service_account import Credentials
from google.ads.googleads.client import GoogleAdsClient
SCOPES = ['https://www.googleapis.com/auth/adwords']
PATH_TO_SERVICE_ACCOUNT_JSON = ''
CUSTOMER_ID = ''
DEVELOPER_TOKEN = ''
QUERY = ''
credentials = Credentials.from_service_account_file(PATH_TO_SERVICE_ACCOUNT_JSON, scopes=SCOPES, subject="<AN ACTUAL USER'S EMAIL HERE>")
googleads_client = GoogleAdsClient(credentials=credentials, developer_token=DEVELOPER_TOKEN, version="v7")
ga_service = googleads_client.get_service("GoogleAdsService")
response = ga_service.search(customer_id=CUSTOMER_ID, query=QUERY)
I am currently using Oauth2.0 to log into my Flask app. I have that working as intended. Now I want to use the same creds I got from logging in to be able to send requests to the Google drive api. Im not really sure where to start, the docs are difficult to follow.
note: I have activated the Google Drive api in the developer console.
Here is the code for the login callback.
globally:
GOOGLE_CLIENT_ID = config['google_client_id']
GOOGLE_CLIENT_SECRET = config['google_client_secret']
GOOGLE_DISCOVERY_URL = (
"https://accounts.google.com/.well-known/openid-configuration"
)
client = WebApplicationClient(GOOGLE_CLIENT_ID)
view function:
#bp.route('/login/callback')
def callback():
# Get authorization code Google sent back to you
code = request.args.get("code")
# Find out what URL to hit to get tokens that allow you to ask for
# things on behalf of a user
google_provider_cfg = get_google_provider_cfg()
token_endpoint = google_provider_cfg["token_endpoint"]
token_url, headers, body = client.prepare_token_request(
token_endpoint,
authorization_response=request.url,
redirect_url=request.base_url,
code=code
)
token_response = requests.post(
token_url,
headers=headers,
data=body,
auth=(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET),
)
# Parse the tokens!
client.parse_request_body_response(json.dumps(token_response.json()))
# Now that you have tokens (yay) let's find and hit the URL
# from Google that gives you the user's profile information,
# including their Google profile image and email
userinfo_endpoint = google_provider_cfg["userinfo_endpoint"]
uri, headers, body = client.add_token(userinfo_endpoint)
userinfo_response = requests.get(uri, headers=headers, data=body)
# You want to make sure their email is verified.
# The user authenticated with Google, authorized your
# app, and now you've verified their email through Google!
if userinfo_response.json().get("email_verified"):
unique_id = userinfo_response.json()["sub"]
users_email = userinfo_response.json()["email"]
picture = userinfo_response.json()["picture"]
username = userinfo_response.json()["given_name"]
else:
return "User email not available or not verified by Google.", 400
user = User.query.filter(User.google_sub==unique_id).first()
login_user(user)
def get_google_provider_cfg():
return requests.get(GOOGLE_DISCOVERY_URL).json()
My thoughts are maybe I have to set up a google drive endpoint to be able to send api requests to with the same token? Im not quite sure.
I want to upload files and create folders on OneDrive with Python. So i copied the code from the OnDrive GitHub GitHub, registered my App at Azure, copied the ID and created an secret. So far so good.
But now, if i run my code. The Browser opens asking for the permission to login automatically, agreed and then i get this error:
Exception: invalid_request
I think it has something to do with the redirect_uri because if i copy this into my browser i cant access it.
Here is my code:
import onedrivesdk
from onedrivesdk.helpers import GetAuthCodeServer
redirect_uri = 'http://localhost:8080/'
client_secret = 'The secret i created on Azure'
scopes=['wl.signin', 'wl.offline_access', 'onedrive.readwrite']
client = onedrivesdk.get_default_client(
client_id='The ID Azure created for me', scopes=scopes)
auth_url = client.auth_provider.get_auth_url(redirect_uri)
#this will block until we have the code
code = GetAuthCodeServer.get_auth_code(auth_url, redirect_uri)
client.auth_provider.authenticate(code, redirect_uri, client_secret)
I also tried it with an Proxy:
import onedrivesdk
from onedrivesdk.helpers import GetAuthCodeServer
from onedrivesdk.helpers import http_provider_with_proxy
redirect_uri = 'http://localhost:8080'
client_secret = 'Secret created with Azure'
client_id = 'ID id got from Azure'
scopes=['wl.signin', 'wl.offline_access', 'onedrive.readwrite']
client = onedrivesdk.get_default_client(client_id, scopes=scopes)
auth_url = client.auth_provider.get_auth_url(redirect_uri)
code = GetAuthCodeServer.get_auth_code(auth_url, redirect_uri)
proxy = {
'http': 'http://localhost:8888',
'https': 'https://localhost:8888'
}
http = http_provider_with_proxy.HttpProviderWithProxy(proxy, verify_ssl=True)
auth = onedrivesdk.AuthProvider(http, client_id, ['onedrive.readwrite'])
client = onedrivesdk.OneDriveClient(redirect_uri, auth, http)
f = onedrivesdk.Folder()
i = onedrivesdk.Item()
i.name = 'New Folder'
i.folder = f
returned_item = client.item(drive='me', id='root').children.add(i)
That gives me this error message:
RuntimeError: Session must be authenticated
before applying authentication to a request.
Your code works - e.g. it sends the info you want to send. However the credentials you have entered will of course return an invalid request - you're trying to connect to azure with:
client_id: 'The ID Azure created for me'
Which I'm pretty sure doesn't exists. The issue is you need an account & pass your script those (valid) account informations to connect to it.
I'm trying to access LinkedIn data via API (I don't have an app, I just want to access company data - or see what can be accessed). There are other questions here on this topic, but most are out of date (using packagaes which precede LinkedIn's current authorisation process).
I followed the LinkedIn documentation on authorisation: https://developer.linkedin.com/docs/oauth2
I created an application (using a nonsense website url as I do not have a website). This gave me a Client ID and Client Secret.
Using (out of date) stuff from LinkedIn (https://github.com/linkedin/api-get-started/blob/master/python/tutorial.py) I wrote:
import oauth2 as oauth
import urllib.parse as urlparse
consumer_key = 'my client id e.g. sjd6ffdf6262d'
consumer_secret = 'my customer secret e.g. d77373hhfh'
request_token_url = 'https://api.linkedin.com/uas/oauth/requestToken'
access_token_url = 'https://api.linkedin.com/uas/oauth/accessToken'
authorize_url = 'https://api.linkedin.com/uas/oauth/authorize'
consumer = oauth.Consumer(consumer_key, consumer_secret)
client = oauth.Client(consumer)
resp,content = client.request(request_token_url, "POST")
request_token = dict(urlparse.parse_qsl(content))
clean_request_token = {}
for key in request_token.keys():
clean_request_token[key.decode('ascii')] = request_token[key].decode('ascii')
request_token = clean_request_token
print ("Go to the following link in your browser:")
print ("%s?oauth_token=%s" % (authorize_url, request_token['oauth_token']
This link takes me to a website where I 'give permission', and am then shown a pin code. Using this pin (called oauth_verifier here):
oauth_verifier = 12345
token = oauth.Token(request_token['oauth_token'],
request_token['oauth_token_secret'])
token.set_verifier(oauth_verifier)
client = oauth.Client(consumer, token)
content = client.request(access_token_url,"POST")
access_token = dict(urlparse.parse_qsl(content[1]))
clean_access_token = {}
for key in access_token.keys():
clean_access_token[key.decode('ascii')] = access_token[key].decode('ascii')
access_token = clean_request_token
token = oauth.Token(key=access_token['oauth_token'],secret=access_token['oauth_token_secret'])
client = oauth.Client(consumer, token)
response = client.request("http://api.linkedin.com/v1/companies/barclays")
This response has a 401 code, due to "The token used in the OAuth request has been revoked."
The underlying problems are:
I don't really get how APIs work, how they work with python, how authorisation works or how to know the api url I need.
In case relevant, I have experience web scraping (using requests plus beautiful soup to parse) but not with APIs.
I eventually worked it out, posting here in case anyone comes this way. Before you invest time, I also found out that the freely available API now only allows you to access your own profile or company page. So you can write an app that allows a user to post to their own page, but you can't write something to grab data. See here:
LinkedIn API unable to view _any_ company profile
Anyway, to get the limited API working, you need to:
Create a LinkedIn account, create an application and add a redirect URL to your application page (I used http://localhost:8000). This doc says how to set up the app: https://developer.linkedin.com/docs/oauth2
Following the steps in the above link, but in python, you make a request to gain an "access code".
html = requests.get("https://www.linkedin.com/oauth/v2/authorization",
params = {'response_type':'code','client_id':client_id,
'redirect_uri':'http://localhost:8000',
'state':'somestring'})
print html.url to get a huge link - click on it. You'll be asked to login and allow access, and then you'll be redirected to your redirect url. There'll be nothing there, but the url will have a long "access code" on the end of it. Pull this out and send it to LinkedIn with a Post request:
token = requests.post('https://www.linkedin.com/oauth/v2/accessToken',
data = {'grant_type':'authorization_code','code':access_code,
'redirect_uri':'http://localhost:8000',
'client_id':client_id,'client_secret':client_secret})
token.content will contain an "access_token". This is what is needed to access the API. e.g. to access your own profile:
headers = {'x-li-format': 'json', 'Content-Type': 'application/json'}
params = {'oauth2_access_token': access_token}
html = requests.get("https://api.linkedin.com/v1/people/~",headers=headers,params = params)
Hopefully that's useful to someone starting from scratch, the info is mostly out there but there are lots of assumed steps (like how to use the access token with requests).