In a django application, I try to have RW access to a google calendar which I own myself.
Tried several ways with a service account & client secrets, but all resulting in authentication errors.
The API explorer works, but it requests consent in a popup window, which is obviously not acceptable.
Documentation on google OAuth2 describes several scenarios. Probably "web server application" applies here? It says:
"The authorization sequence begins when your application redirects a
browser to a Google URL; the URL includes query parameters that
indicate the type of access being requested. Google handles the user
authentication, session selection, and user consent. The result is an
authorization code, which the application can exchange for an access
token and a refresh token."
Again, we do not want a browser redirection, we want direct access to the google calendar.
So question is: how can a django server access a google calendar, on which I have full rights, view events and add events using a simple server stored key or similar mechanism?
With help of DalmTo and this great article, I got RW access to a google calendar working from python code. I will summarize the solution here.
Here are the steps:
First of all register for a google service account: Service accounts are pre-authorized accounts that avoid you need to get consent or refresh keys every time:
https://developers.google.com/identity/protocols/OAuth2ServiceAccount
(The part on G-suite can be ignored)
Download the service account credentials and store them safely. Your python code will need access to this file.
Go to your google calendar you want to get access to.
e.g. https://calendar.google.com/calendar/r/month
On the right side you see your calendars. Create an additional one for testing (since we'll write to it soon). Then point to this new calendar: click the 3 dots next to it and edit the sharing settings. Add the service account email address to the share under "share with specific people". (you can find the service account email address in the file downloaded previously under "client_email")
In the same screen, note the "calendar ID", you'll need it in below code.
Now you service account has the RW rights to the calendar.
Add at least one event to the calendar using the web UI (https://calendar.google.com/calendar/r/month) so we can read and change it from below code.
Then use following python code to read the calendar and change an event.
from google.oauth2 import service_account
import googleapiclient.discovery
SCOPES = ['https://www.googleapis.com/auth/calendar']
SERVICE_ACCOUNT_FILE = '<path to your service account file>'
CAL_ID = '<your calendar ID>'
credentials = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
service = googleapiclient.discovery.build('calendar', 'v3', credentials=credentials)
events_result = service.events().list(calendarId=CAL_ID).execute()
events = events_result.get('items', [])
event_id = events[0]['id']
event = events[0]
service.events().update(calendarId=CAL_ID, eventId=event_id, body={"end":{"date":"2018-03-25"},"start":{"date":"2018-03-25"},"summary":"Kilroy was here"}).execute()
And there you go... read an event and updated the event.
Related
I have a script which modifies my work supplied GCal. For authentication I use an access/refresh token (like this: https://developers.google.com/people/quickstart/python).
I want to run the script in Docker now. For authentication I have decided to use a service account.
I have created the service account, shared my calendar with it and accepted the calendar. In the Google Console where you create the service account, I set the permission to "owner".
When I try to run the script using the service account (not in Docker yet) it returns only a subset of attributes for each calendar event. I can see that accessRole = freeBusyReader.
How do I grant write access to this service account? I have tried:
rule = service.acl().get(calendarId="myId", ruleId='user:service#myApp-351310.iam.gserviceaccount.com').execute() # Get this from acl_items
rule["role"] = "owner"
service.acl().update(calendarId="myId", ruleId="user:service#myApp-351310.iam.gserviceaccount.com", body=rule).execute()
I have read about firmwide delegation and impersonation of users. I'm not sure if this is requred or not. Does anyone know how to do this?
The code to authenticate a service account is slightly different then the sample you were using for an installed application it is as follows.
credentials = ServiceAccountCredentials.from_json_keyfile_name(
key_file_location, scopes=scopes)
credentials = credentials.create_delegated(user_email)
This video shows How to create Google Oauth2 Service account credentials. just make sure to enable the google calendar api.
Remember service accounts are only supported via domain wide delegation to users on your google workspace domain. You cant use a standard google gmail user.
I recommend following the Delegate domain-wide authority to your service account sample it shows how to set up the delegation to a service account from your workspace domain. Just change out the section about admin sdk to that of google calendar as this is the api you are trying to connect to.
You add the user_Email being the user on your domain you want the service account to impersonate.
I'm making a desktop app in Python that sends mail from Gmail. The problem is that after receiving consent (OAuth 2) through the browser, the user for whom the software receives the consent, continues to be logged in to the browser in Gmail. Is there a way to go through the authorization process without staying logged in to Gmail in your browser?
What you are referring to is Oauth2. Oauth2 gives users the ability to grant applications like yours consent to access their private data. Private data is data that is owned by someone. My gmail data is mine your application can not use it unless I grant you access.
Is there a way to go through the authorization process without staying logged in to Gmail in your browser?
Lets clear up some confusion in this statement you mention authorization which is correct a user is authorizing your application to access their data. Yet you also mention logged in which has nothing to do with authorization. Logging in a user is authentication and is not part of Oauth2. It is part of something else called openid connect.
As for how to request authorization of a user without using the browser. Once the user has consented to your application accessing my data once then your application should have what its called a refresh token, this refresh token can be used at a latter time for your application to request a new access token. Granting you access to may data without using the browser to access my data again. So you could store this refresh token in the backend some where and use that to continue to access the users data without needing to use the browser again.
storing user credentials in an installed application
It is hard to know exactly what you are doing since you did not include any code in your question, and your question is a little unclear.
In the following example please note how the users credentials are stored in gmail.dat using this code in an installed application will cause it to load the refresh token the next time the user runs the app meaning that the consent screen should not be shown, as the credentials are already stored for that user.
def initialize_gmail():
"""Initializes the gmail service object.
Returns:
analytics an authorized gmail service object.
"""
# Parse command-line arguments.
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
parents=[tools.argparser])
flags = parser.parse_args([])
# Set up a Flow object to be used if we need to authenticate.
flow = client.flow_from_clientsecrets(
CLIENT_SECRETS_PATH, scope=SCOPES,
message=tools.message_if_missing(CLIENT_SECRETS_PATH))
# Prepare credentials, and authorize HTTP object with them.
# If the credentials don't exist or are invalid run through the native client
# flow. The Storage object will ensure that if successful the good
# credentials will get written back to a file.
storage = file.Storage('gmail.dat')
credentials = storage.get()
if credentials is None or credentials.invalid:
credentials = tools.run_flow(flow, storage, flags)
http = credentials.authorize(http=httplib2.Http())
# Build the service object.
service = build('gmail ', 'v1', http=http)
return service
I am trying to build an flask app on AWS lambda, which needs to access https://www.googleapis.com/auth/admin.directory.device.chromeos.readonly to get the Mac Address of chrome devices by domain name and chrome device id.
Basically the working flow is blew:
A Chrome Extension deployed by G-Suit sends an request with domain name and device id to AWS(as we are using AWS), then the AWS sends an request with domain name and device id to Google Cloud to get Mac Address.
I started with using an access the directory API as a service account like service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES, subject="username#domainName.com"), and it works. However, I realised it was wrong implementation as the domainName would be changed and subject will be different for each domain as the Chrome Extension can be deployed via different domains.
Then I started to use the sample code from Google(https://developers.google.com/api-client-library/python/auth/web-app), and I was able to access the domain and get the Mac Address of each device.
However, the problem is it requires to choose the email when you call the google api for the first time. But as I mentioned above, this app is going to run on AWS, so clearly users cannot choose the email.
So is that possible that we just use the domainName instead of choosing email to do the authentication and access the different directories? If so, is there any examples or documentations I need to read?
I suspect I need to modify this part from the sample, but I am still getting confused how it works.
#app.route('/adminlogin')
def authorize():
# Create flow instance to manage the OAuth 2.0 Authorization Grant Flow steps.
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
CLIENT_SECRETS_FILE, scopes=SCOPES)
flow.redirect_uri = flask.url_for('oauth2callback', _external=True)
authorization_url, state = flow.authorization_url(
# Enable offline access so that you can refresh an access token without
# re-prompting the user for permission. Recommended for web server apps.
access_type='offline',
approval_prompt="force",
# Enable incremental authorization. Recommended as a best practice.
#include_granted_scopes='true'
)
# Store the state so the callback can verify the auth server response.
flask.session['state'] = state
return flask.redirect(authorization_url)
Any hint will be helpful.
Not very experience with API authentication but I cannot figure out how to read a Google Sheet share with me from Python.
I've tried:
import gspread
from oauth2client.service_account import ServiceAccountCredentials
scope = ['https://spreadsheets.google.com/feeds',
'https://www.googleapis.com/auth/drive']
credentials = ServiceAccountCredentials.from_json_keyfile_name('/Users/alexiseggermont/Downloads/Accountable-7b29dac08324.json', scope)
gc = gspread.authorize(credentials)
wks = gc.open("mysheet").sheet1
This gives me ImportError: cannot import name 'opentype' on line 2.
I then tried:
import oauth2client.client, oauth2client.file, oauth2client.tools
import gspread
flow = oauth2client.client.OAuth2WebServerFlow(client_id, client_secret, 'https://spreadsheets.google.com/feeds')
storage = oauth2client.file.Storage('credentials.dat')
credentials = storage.get()
if credentials is None or credentials.invalid:
import argparse
flags = argparse.ArgumentParser(parents=[oauth2client.tools.argparser]).parse_args([])
credentials = oauth2client.tools.run_flow(flow, storage, flags)
gc = gspread.authorize(credentials)
# when this cell is run, your browser will take you to a Google authorization page.
# this authorization is complete, the credentials will be cached in a file named credentials.dat
This opens a window asking if I want to give my app access to my sheets, and I click yes. But then sheet = gc.open("mysheet").sheet1 gives me a permission error:
APIError: {
"error": {
"errors": [
{
"domain": "global",
"reason": "insufficientPermissions",
"message": "Insufficient Permission"
}
],
"code": 403,
"message": "Insufficient Permission"
}
}
The only suggestion I find to solve this error is to change the 'scope' variable, but there is no scope variable used in that code, so I am confused.
You can add several scopes using spaces. So can you try the following modification?
From :
flow = oauth2client.client.OAuth2WebServerFlow(client_id, client_secret, 'https://spreadsheets.google.com/feeds')
To :
flow = oauth2client.client.OAuth2WebServerFlow(client_id, client_secret, 'https://spreadsheets.google.com/feeds https://www.googleapis.com/auth/drive')
Note :
Before you run the modified script, please remove credentials.dat. By this, credentials.dat is created using new scopes.
In my environment, I confirmed that when the scopes is only https://spreadsheets.google.com/feeds, the same error occurs. When the scopes are https://spreadsheets.google.com/feeds https://www.googleapis.com/auth/drive, no error occurs.
there is also Gspread-Pandas library itself, where the docs have a simple intro to the Oauth part, and in a simpler way:
https://github.com/aiguofer/gspread-pandas
Before using, you will need to download Google client credentials for your app.
Client Credentials To allow a script to use Google Drive API we need
to authenticate our self towards Google. To do so, we need to create a
project, describing the tool and generate credentials. Please use your
web browser and go to Google console and :
Choose Create Project in popup menu on the top. A dialog box appears,
so give your project a name and click on Create button. On the
left-side menu click on API Manager. A table of available APIs is
shown. Switch Drive API and click on Enable API button. Do the same
for Sheets API. Other APIs might be switched off, for our purpose. On
the left-side menu click on Credentials. In section OAuth consent
screen select your email address and give your product a name. Then
click on Save button. In section Credentials click on Add credentials
and switch OAuth 2.0 client ID. A dialog box Create Cliend ID appears.
Select Application type item as Other. Click on Create button. Click
on Download JSON icon on the right side of created OAuth 2.0 client
IDs and store the downloaded file on your file system. Please be
aware, the file contains your private credentials, so take care of the
file in the same way you care of your private SSH key; i.e. move
downloaded JSON to ~/.config/gspread_pandas/google_secret.json (or you
can configure the directory and file name by directly calling
gspread_pandas.conf.get_config Thanks to similar project df2gspread
for this great description of how to get the client credentials.
User Credentials Once you have your client credentials, you can have
multiple user credentials stored in the same machine. This can be
useful when you have a shared server (for example with a Jupyter
notebook server) with multiple people that may want to use the
library. The first parameter to Spread must be the key identifying a
user's credentials. The first time this is called for a specific key,
you will have to authenticate through a text based OAuth prompt; this
makes it possible to run on a headless server through ssh or through a
Jupyter notebook. After this, the credentials for that user will be
stored (by default in ~/.config/gspread_pandas/creds or you can
manually set it in GSPREAD_PANDAS_CONFIG_DIR env var) and the tokens
will berefreshed automatically any time the tool is used.
Users will only be able to interact with Spreadsheets that they have
access to.
Handling Authentication In the backend, the library is leveraging
Google's oauth2client
<http://oauth2client.readthedocs.io/en/latest/__ to handle
authentication. It conveniently stores everything as described above
so that you don't have to worry about boiler plate code to handle
auth. However, if you need to customize how you handle authentication
you can do so in a few different ways. You can change the directory
where everything is stored using the GSPREAD_PANDAS_CONFIG_DIR env
var. You can also generate your own
oauth2client.client.OAuth2Credentials and pass them in when
instanciating a Client or Spread object. For other ways to customize
authentication, see gspread_pandas.conf.get_config and
gspread_pandas.conf.get_creds
I am try to access dbm api , I am authenticating the url using service account please find the sample code below
from oauth2client.service_account import ServiceAccountCredentials
from apiclient.discovery import build
from httplib2 import Http
scopes =['https://www.googleapis.com/auth/doubleclickbidmanager']
credentials = ServiceAccountCredentials.from_json_keyfile_name(
'path/to/key/.jsonfile', scopes=scopes)
http_auth = credentials.authorize(Http())
body={}
dbm = build('doubleclickbidmanager', 'v1', http=http_auth)
print dbm
request = dbm.lineitems().downloadlineitems(body=body).execute()
print request
If I use oauth mechanism to authenticate the url the code is running properly, since I don't want user interaction, I need server to server mechanism so I used service account
Steps which I tried:
I have created the service account and downloaded the json key file and used in the code but when I try to run my code it throws the following error:
googleapiclient.errors.HttpError: <HttpError 403 when requesting https://www.googleapis.com/doubleclickbidmanager/v1/lineitems/downloadlineitems?alt=json returned "You are not authorized to use DoubleClick Bid Manager API. Please contact dbm-support#google.com.">
Please help , thanks in advance.
As others have said here, you want to log in to the DBM site and add your service account as a user:
Then, per this documentation, you can set up service account credentials using your client secrets json file. If you want that service account to be able to access reports you've created in DBM under your user account (what you log in with) you need to delegate domain-wide authority:
delegated_credentials = credentials.create_delegated('user#example.org')
http_auth = delegated_credentials.authorize(Http())
dbm = build('doubleclickbidmanager', 'v1', http=http_auth)
queries = dbm.queries().listqueries().execute()['queries']
A service account isn't you its a dummy user it has its on Google drive account for example, and by default it doesn't have access to any DoubleClick Bid Manager APIs. Service accounts need to be pre authorized to be able to access private data. So for it to be able to access your double click data you are going to have to grant it access.
Normally with any other API I would say you take the service account email address and add it as a user. I don't have access to double click so I am not even sure if you can add other users manually. They don't have anything in the documentation about service accounts kind of makes me think its not supported. Let us know if you manage to get it to work.