Using token.pickle is quite frustrating, as every couple of weeks the token expires, and then I need to manually delete it from my source files in code, so it can regenerate itself, and then I need to re-authenticate it from my account.
Is there a way I can just create a new service without it? I know it's possible for Google sheets files. This is what that looks like:
def get_g_sheets_service():
SERVICE_ACCOUNT_FILE = 'key.json'
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
creds = None
creds = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
SAMPLE_SPREADSHEET_ID = 'ID_GOES_HERE'
service = build('sheets', 'v4', credentials=creds)
return service
but, the way to get a service for the calendar API looks like this:
import datetime
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/calendar']
CREDENTIALS_FILE = 'path_to_file/credentials.json'
def get_calendar_service():
creds = None
# The file token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
CREDENTIALS_FILE, SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('calendar', 'v3', credentials=creds)
return service
Notice the token.pickle file?
How can I not deal with it?
why is the token.pickle expring?
The token.pickle should contain an access token and a refresh token which was created with the user consented to your application accessing their data. Access tokens are used to access user data and expire after an hour, refresh tokens are use to request a new access token when it has expired. This is done automatically via the client library you are using. Refresh tokens for the most part should not be expiring see: experation.
You need to be sure you are always storing the most recent refresh token.
If your application is still in the testing phase refresh tokens will expire after seven days. see: experation
A Google Cloud Platform project with an OAuth consent screen configured for an external user type and a publishing status of "Testing" is issued a refresh token expiring in 7 days.
The solution is to go to google cloud console under the oauth2 consent screen and set your application to production.
service accounts
If this google calendar is part of google workspace. then your workspace admin could grant a service account domain wide delegation and allow you to impersonate a user on the domain with the service account. This form for authorization is much easer and will not have the same expiration token issues as the authorization is configured via workspace.
Service accounts only work though google calendar with workspace domain accounts.
Related
I have built a django app, which it includes google Oauth2.0 login.
I want to get google calendar events of every users when they login with Oauth2.0 and I wrote the following code. I saved the access token into UserAuth table and fetched it, then used it to get google calendar.
def get_events_server(request):
user = User.objects.get(username=request.user)
creds = UserAuth.objects.get(user=user).google_id_token
credentials = AccessTokenCredentials(creds, "")
http = httplib2.Http()
http = credentials.authorize(http)
service = build('calendar', 'v3', http=http)
return service
When I run the code, the following error has happened.
HttpError at /calendar/
<HttpError 403 when requesting https://www.googleapis.com/calendar/v3/calendars/primary/events?timeMin=2021-10-28T04%3A33%3A08.956703Z&timeMax=2021-11-04T04%3A33%3A08.956712Z&singleEvents=true&timeZone=GMT%2B9%3A00&orderBy=startTime&alt=json returned "Request had insufficient authentication scopes.". Details: "[{'message': 'Insufficient Permission', 'domain': 'global', 'reason': 'insufficientPermissions'}]">
Is there a solution to skip this issue?
You are a little confused here lets start by looking at the difference between authentication and authorization.
Authentication or Open Id connect is signin your letting a user signin to their google account and you get an id token back and you are able to access their profile information because the user signed in. You are authentication that the user who is behind the machine owns the account. In your code see the id_token you are using Open id connect to authentication the user.
creds = UserAuth.objects.get(user=user).google_id_token
In order to access a users private data your application needs to be authorized to access that data. Authorization is defined by scopes, or the scope of access you need. In order to use the google calendar api you will need an access token with a scope that will give you permission to access the users google calendar events
You should have a look at the Python quickstart for google calendar it will show you how to use Oauth2 to have your application request authorization from the user to access their google calendar data.
def main():
"""Shows basic usage of the Google Calendar API.
Prints the start and name of the next 10 events on the user's calendar.
"""
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.json', 'w') as token:
token.write(creds.to_json())
service = build('calendar', 'v3', credentials=creds)
# Call the Calendar API
now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
print('Getting the upcoming 10 events')
events_result = service.events().list(calendarId='primary', timeMin=now,
maxResults=10, singleEvents=True,
orderBy='startTime').execute()
events = events_result.get('items', [])
from comments
Your link consent screen request is returning an error. This is the redirect uri miss match errror and its one of the most common Errors you can get when you are setting up oauth2.
If you check the error it is telling you that there is an issue with this url redirect_uri: http://localhost:61668/ you are sending your request from that url. which means you need to go to google cloud console and add that redirect uri to your list of accepted redirect uris. Remember it must match exactly so the port number and trailing slash must be included.
These are your current redirect uris you need to add http://localhost:61668/
try setting
flow.run_local_server(port=0)
to
flow.run_local_server(port=8000)
then add
http://localhost:8000/
as your redirect uri.
If you don't know how this Video will show you how to fix it. Google OAuth2: How the fix redirect_uri_mismatch error. Part 2 server sided web applications.
So i have been trying to create a new blank google spreadsheet using python. I am using a python script i found online (also added a few modifications of my own) and i just can't get it to work the way i want. I've never actually used python before, neither google spreadsheets so i am a little confused!! The current issue is that whenever i run the code it seems to be working, but when i copy/paste the URL of the newly generated google spreadsheet, i don't even have permission to view it. Here is my code....Thank you in advance!!
"""
BEFORE RUNNING:
---------------
1. If not already done, enable the Google Sheets API
and check the quota for your project at
https://console.developers.google.com/apis/api/sheets
2. Install the Python client library for Google APIs by running
`pip install --upgrade google-api-python-client`
"""
from pprint import pprint
from googleapiclient import discovery
import gspread
from oauth2client.service_account import ServiceAccountCredentials
from google.oauth2 import service_account
# TODO: Change placeholder below to generate authentication credentials. See
# https://developers.google.com/sheets/quickstart/python#step_3_set_up_the_sample
#
# Authorize using one of the following scopes:
# 'https://www.googleapis.com/auth/drive'
# 'https://www.googleapis.com/auth/drive.file'
# 'https://www.googleapis.com/auth/spreadsheets'
SERVICE_ACCOUNT_FILE = 'client_secret.json'
f = open('client_secret.json','r')
print(f.readline())
f.close()
SCOPES = ["https://www.googleapis.com/auth/spreadsheets", "https://www.googleapis.com/auth/drive"]
credentials = None
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
service = discovery.build('sheets', 'v4', credentials=credentials)
spreadsheet_body = {
# TODO: Add desired entries to the request body.
}
request = service.spreadsheets().create(body=spreadsheet_body)
response = request.execute()
# TODO: Change code below to process the `response` dict:
pprint(response)
The issue is that the sheet is created with the serviceaccount as the owner, not under your personal Gmail account.
There's 2 options:
The bad way would be to give your personal account access to the generated GSheet. Issue with this is that the serviceaccount will still be the owner. I'm not going to tell you how to do this as this is absolutely the wrong way.
The right way would be to use proper credentials when creating the API client. This is explained in detail in this article.
Pay special attention to the piece of code that creates the credentials object.
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.json', 'w') as token:
token.write(creds.to_json())
service = build('sheets', 'v4', credentials=creds)
As you can see this code doesn't directly create a credentials object from the serviceacocunt. Instead it asks for permission to use your personal Gmail account to call the API.
Note that if you have a GSuite/Workspace account, you can use impersonation instead. This is actually the preferred way, but only works with said GSuite/Workspace accounts.
this is my first contribution here.
I'm trying to access Gmail through a python script. To do so, I've created a Google Apps Script function and used the Apps Script API between the 2.
(This doc displays what I'm trying to do)
So the python script correctly accesses the API, but fails to execute the function.
While it works in the Script Editor, in Python it raises a permissions issue:
'errorMessage': 'Exception: The script does not have permission to perform that action.
Required permissions: (
https://www.googleapis.com/auth/gmail.labels ||
https://www.googleapis.com/auth/gmail.metadata ||
https://www.googleapis.com/auth/gmail.readonly ||
https://www.googleapis.com/auth/gmail.modify ||
https://mail.google.com/
)',
'errorType': 'ScriptError'
I guess it is related to the Client ID OAuth, since I was not able to find where to grant it permissions. I've just :
created the credentials in Google Cloud Platform,
exported it as creds.json in my python script folder.
Here is my code, almost copy pasted from a tutorial:
import pickle
import os.path
from googleapiclient import errors
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
# Here I've edited the scopes of authorizations required
SCOPES = [
"https://www.googleapis.com/auth/gmail.labels",
"https://www.googleapis.com/auth/gmail.metadata",
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/gmail.modify",
"https://mail.google.com/"
]
def get_scripts_service():
creds = None
# The file token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
# Here I've placed the downloaded credentials .json file
flow = InstalledAppFlow.from_client_secrets_file(
'creds.json', SCOPES)
creds = flow.run_local_server(port=0)
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
return build('script', 'v1', credentials=creds)
service = get_scripts_service()
API_ID = # Here i've pasted my API_ID
request = {"function": # Here i've pasted my functionName}
try:
response = service.scripts().run(body=request, scriptId=API_ID).execute()
print (response)
except errors.HttpError as error:
# The API encountered a problem.
print(error.content)
How do I grant permissions to my script?
Simple as Aerials said! Thanks.
It was because the Client ID was created before I edited the scopes. I've deleted the token and created a new one.
I'm trying to get familiar with the google calendar api. In the getting started guide they have this code example:
from __future__ import print_function
import datetime
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
def main():
"""Shows basic usage of the Google Calendar API.
Prints the start and name of the next 10 events on the user's calendar.
"""
creds = None
# The file token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('calendar', 'v3', credentials=creds)
# Call the Calendar API
now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
print('Getting the upcoming 10 events')
events_result = service.events().list(calendarId='primary', timeMin=now,
maxResults=10, singleEvents=True,
orderBy='startTime').execute()
events = events_result.get('items', [])
if not events:
print('No upcoming events found.')
for event in events:
start = event['start'].get('dateTime', event['start'].get('date'))
print(start, event['summary'])
if __name__ == '__main__':
main()
In this example we automatically open a window to ask the user to access their calendar, if we don't already have access through the pickle file. The thing is I don't want this window to open automatically, I want to print a link instead that the user can click to authenticate. I have looked around in the documentation but can't seem to find anything usefull. I would appriciate any help i could get, thanks!
For authorizing process, you want to show only the URL. You don't want to automatically open the browser.
You want to achieve this using googleapis with python.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
In this case, please use Flow.from_client_secrets_file instead of InstalledAppFlow.from_client_secrets_file.
Modified script:
When your script is modified, please modify as follows.
From:
from google_auth_oauthlib.flow import InstalledAppFlow
To:
from google_auth_oauthlib.flow import Flow
and
From:
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('calendar', 'v3', credentials=creds)
To:
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
# Create the flow using the client secrets file from the Google API
# Console.
flow = Flow.from_client_secrets_file('client_secret.json', SCOPES, redirect_uri='urn:ietf:wg:oauth:2.0:oob')
# Tell the user to go to the authorization URL.
auth_url, _ = flow.authorization_url(prompt='consent')
print('Please go to this URL: {}'.format(auth_url))
# The user will get an authorization code. This code is used to get the
# access token.
code = input('Enter the authorization code: ')
flow.fetch_token(code=code)
creds = flow.credentials
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('calendar', 'v3', credentials=creds)
In this case, when you run the script under token.pickle is not existing, the URL for the authorization is displayed to the console. The browser is not opened. So please access to the URL by opening the browser and authorize the scopes. Then, please the copied authorization code to the console and input enter key. By this, the access token is retrieved and the file of token.pickle is created.
Note:
If an error related to the redirect uri occurs, please modify it to http://localhost and test it again.
Reference:
google_auth_oauthlib.flow module
If I misunderstood your question and this was not the direction you want, I apologize.
Added:
From I want to print a link instead that the user can click to authenticate in your question, I proposed above sample script.
From some way not to manually confirm authorization codes in your replying, I think that that above sample script is not suitable.
In this case, how about using the service account? When the service account is used, no authorization code is required. The script for using the service account is as follows.
Sample script:
from google.oauth2 import service_account
from googleapiclient.discovery import build
SERVICE_ACCOUNT_FILE = 'service-account-credentials.json' # Here, please set the creadential file of the service account.
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
creds = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
service = build('calendar', 'v3', credentials=creds)
Note:
In order to access to the Google calendar using the service account, at first, please share the Google calendar with the email of the service account. Please be careful this.
Reference:
Creating a service account
I have a script that checks a calendar for some events on a private calendar, and I want to have it run in GCF. Right now, I followed the Calendar API quickstart guide, and my authentication method is as follows:
# relevant imports
from apiclient.discovery import build
from oauth2client import file, client, tools
from httplib2 import Http
# Setup the Calendar API
SCOPES = 'https://www.googleapis.com/auth/calendar.readonly'
store = file.Storage('token.json')
creds = store.get()
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('credentials.json', SCOPES)
creds = tools.run_flow(flow, store)
service = build('calendar', 'v3', http=creds.authorize(Http()))
Here, token.json and credentials.json are from the configuration file after enabling the Calendar API on my project.
If I copy this directly to a function, I of course hit an error when it tried to load credentials.json, since there are no external files.
Given that my goal is to make this accessible from a GCF url, how can I make this authentication work?
Note: I do have a service account associated with the function, so is there a way I can give that SA access to the private calendar and somehow authenticate with that SA in the python script?