How to get Google-Calendar events using access token - python

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.

Related

Is there a workaround to prevent Gmail API for python from asking for a new token each time I run my python script?

I have a python script that sends emails with attachments using GMAIL's API. Each time(mostly after a day) I run the script, I get an error that the token's invalid.
The only solution I have identified so far is to download the json file each time I run the script but I was expecting this to be done only once as I intend to convert the script to a desktop application.
Google sends you an authToken and a RefreshToken, who need to be stored to refresh your token when he is no longer valid.
Check that :
https://developers.google.com/identity/protocols/oauth2
There are two types of tokens access tokens and refresh tokens.
Access tokens are only good for an hour. Refresh tokens are long lived and should work until the access has been removed. Or if your application is still in the testing phase then the token will only work for seven days. There is one other thing, if you are creating your tokens from google oauth2 playground I bleave they are only good for three hours give or take.
The best solution for all of the above is to ensure that your app is first off set to prodctuion, and second that you are properly storing your token after you have created it.
In the sample below the token is stored in the token.json for later use.
def Authorize(credentials_file_path, token_file_path):
"""Shows basic usage of authorization"""
try:
credentials = 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_file_path):
try:
credentials = Credentials.from_authorized_user_file(token_file_path, SCOPES)
credentials.refresh(Request())
except google.auth.exceptions.RefreshError as error:
# if refresh token fails, reset creds to none.
credentials = None
print(f'An refresh authorization error occurred: {error}')
# If there are no (valid) credentials available, let the user log in.
if not credentials or not credentials.valid:
if credentials and credentials.expired and credentials.refresh_token:
credentials.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
credentials_file_path, SCOPES)
credentials = flow.run_local_server(port=0)
# Save the credentials for the next run
with open(token_file_path, 'w') as token:
token.write(credentials.to_json())
except HttpError as error:
# Todo handle error
print(f'An authorization error occurred: {error}')
return credentials
if __name__ == '__main__':
creds = Authorize('C:\\YouTube\\dev\\credentials.json', "token.json")

Any way to create a new Google Calendar event without token.pickle

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.

Authorization Error Error 400: redirect_uri_mismatch

why this error comes & how can i solved it :
You can't sign in to this app because it doesn't comply with Google's OAuth 2.0 policy
when i click on authorize url after that url opened in browser.
I am following python Gmail API quickstart guide to authorize a user for the Gmail API. I've created a Web Application type app at Google console and generated it's credentials.json file. I've provided a redirect_uri .
When I run the quickstart.py.when i click on authorize url after that url opened in browser.
After I open the link in the browser it displays this error:-
https://accounts.google.com/signin/oauth/error?authError=ChVyZWRpcmVjdF91cmlfbWlzbWF0Y2gSsAEKWW91IGNhbid0IHNpZ24gaW4gdG8gdGhpcyBhcHAgYmVjYXVzZSBpdCBkb2Vzbid0IGNvbXBseSB3aXRoIEdvb2dsZSdzIE9BdXRoIDIuMCBwb2xpY3kuCgpJZiB5b3UncmUgdGhlIGFwcCBkZXZlbG9wZXIsIHJlZ2lzdGVyIHRoZSByZWRpcmVjdCBVUkkgaW4gdGhlIEdvb2dsZSBDbG91ZCBDb25zb2xlLgogIBptaHR0cHM6Ly9kZXZlbG9wZXJzLmdvb2dsZS5jb20vaWRlbnRpdHkvcHJvdG9jb2xzL29hdXRoMi93ZWItc2VydmVyI2F1dGhvcml6YXRpb24tZXJyb3JzLXJlZGlyZWN0LXVyaS1taXNtYXRjaCCQAyomCgxyZWRpcmVjdF91cmkSFmh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC8%3D&client_id=221990959960-40vsl59admu9j2v8lab1h7rgivo3o7ue.apps.googleusercontent.com
I'm unable to find why this issue coming.I want to call Gmail API.
Created cloud account :-
[1]: https://i.stack.imgur.com/hls5P.png
[2]: https://i.stack.imgur.com/2zHW0.png
[3]: https://i.stack.imgur.com/oeMgf.png
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
creds = None
if os.path.exists('token.json'):
creds =Credentials.from_authorized_user_file('token.json', SCOPES)
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( 'creds.json', SCOPES)
creds = flow.run_local_server()
with open('token.json', 'w') as token:
token.write(creds.to_json())
service = build('gmail', 'v1', credentials=creds)
results = service.users().labels().list(userId='me').execute()
As the API has stated in error page, you may have mis-configured the redirect_uri of the login page.
Please sure that you're not having any typo related to"http"-"https". Last year I faced the same issue when setting up my login page and instead of using the URL with https, I entered an URL with http.
You also should double check the redirect_uri and make sure the login url has redirect_uri parameter. Without it, Google cannot recognize where will the login page to be redirected.
If that doesn't help, go to the console for your project and look under API Access. You should see your client ID & client secret there, along with a list of redirect URIs. If the URI you want isn't listed, click edit settings and add the URI to the list.

How to get new token.json for google apps script API (or google cloud platform) through Flask app

I have a flask app that I want to authenticate to use a google apps script API. If I create a token.json file with this quickstart link it takes me to the authentication page and then creates a token.json file for me even if I've deleted it or its expired. This is what I want to do in my flask app (because tokens expire so I want to be able to refresh them). The code I'm using is
# Setup the Apps Script API scopes
SCOPES = ['https://www.googleapis.com/auth/script.projects',
'https://www.googleapis.com/auth/script.external_request',
'https://www.googleapis.com/auth/spreadsheets']
def getCredentialsAppScript():
store = oauth_file.Storage('token.json')
creds = store.get()
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('client_secret.json', SCOPES)
creds = tools.run_flow(flow, store)
return creds
then in getListItems (code snippet)
#app.route("/getListItems", methods=["POST", "GET"])
def getListItems():
"""Runs the sample.
"""
SCRIPT_ID = 'xxxxxxxxxxxxxx'
creds = getCredentialsAppScript()
service = build('script', 'v1', credentials=creds)
# Create an execution request object.
request = {"function": "getTokensForUser"}
the error simply says UserWarning: Cannot access token.json: No such file or directory what I don't understand is why I can't get a new token.json through the code block in the flask app but
when running the quickstart.py it takes me to the google sign-in page if I don't have a token.json.
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('client_secret.json', SCOPES)
creds = tools.run_flow(flow, store)
I think that you may lack some information about OAuth2 and its implementation in Google API's.
I would strongly encourage you to read the documentation for Server side OAuth there is even a full example of an authorization, token revoke and API test call in flask.
In specific the problem with your code is that you are copying the structure as the python quickstart but you have to remember that the quickstart is meant to be executed as a command line program, not as a server side as Flask.
So because you are now in a server environment you first need to redirect the user to a google authorization page:
# Copied from https://developers.google.com/identity/protocols/oauth2/web-server#python_5
#app.route('/authorize')
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)
# The URI created here must exactly match one of the authorized redirect URIs
# for the OAuth 2.0 client, which you configured in the API Console. If this
# value doesn't match an authorized URI, you will get a 'redirect_uri_mismatch'
# error.
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',
# 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)
And later receive the authorization object in the oauth2callback function:
# Copied from https://developers.google.com/identity/protocols/oauth2/web-server#python_5
#app.route('/oauth2callback')
def oauth2callback():
# Specify the state when creating the flow in the callback so that it can
# verified in the authorization server response.
state = flask.session['state']
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
CLIENT_SECRETS_FILE, scopes=SCOPES, state=state)
flow.redirect_uri = flask.url_for('oauth2callback', _external=True)
# Use the authorization server's response to fetch the OAuth 2.0 tokens.
authorization_response = flask.request.url
flow.fetch_token(authorization_response=authorization_response)
# Store credentials in the session.
# ACTION ITEM: In a production app, you likely want to save these
# credentials in a persistent database instead.
credentials = flow.credentials
flask.session['credentials'] = credentials_to_dict(credentials)
return flask.redirect(flask.url_for('<Your function calling the API>'))

GCP Authentication: RefreshError

In order to round-trip test mail sending code in our GCP backend I am sending an email to a GMail inbox and attempting to verify its arrival. The current mechanism for authentication to the GMail API is fairly standard, pasted from the GMail API documentation and embedded in a function:
def authenticate():
"""Authenticates to the Gmail API using data in credentials.json,
returning the service instance for use in queries etc."""
store = file.Storage('token.json')
creds = store.get()
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets(CRED_FILE_PATH, SCOPES)
creds = tools.run_flow(flow, store)
service = build('gmail', 'v1', http=creds.authorize(Http()))
return service
CRED_FILE_PATH points to a downloaded credentials file for the service. The absence of the token.json file triggers its re-creation after an authentication interaction via a browser window, as does the token's expiry.
This is an integration test that must run headless (i.e. with no interaction whatsoever). When re-authentication is required the test currently raises an exception when the authentication flow starts to access sys.argv, which means it sees the arguments to pytest!
I've been trying to find out how to authenticate reliably using a mechanism that does not require user interaction (such as an API key). Nothing in the documentation or on Stackoverflow seems to answer this question.
A more recent effort uses the keyfile from a service account with GMail delegation to avoid the interactive Oauth2 flows.
def authenticate():
"""Authenticates to the Gmail API using data in g_suite_access.json,
returning the service instance for use in queries etc."""
main_cred = service_account.Credentials.from_service_account_file(
CRED_FILE_PATH, scopes=SCOPES)
# Establish limited credential to minimise any damage.
credentials = main_cred.with_subject(GMAIL_USER)
service = build('gmail', 'v1', credentials=credentials)
return service
On trying to use this service with
response = service.users().messages().list(userId='me',
q=f'subject:{subject}').execute()
I get:
google.auth.exceptions.RefreshError:
('unauthorized_client: Client is unauthorized to retrieve access tokens using this method.',
'{\n "error": "unauthorized_client",\n "error_description": "Client is unauthorized to retrieve access tokens using this method."\n}')
I get the feeling there's something fundamental I'm not understanding.
The service account needs to be authorized or it cant access the emails for the domain.
"Client is unauthorized to retrieve access tokens using this method"
Means that you have not authorized it properly; check Delegating domain-wide authority to the service account
Source: Client is unauthorized to retrieve access tokens using this method Gmail API C#

Categories

Resources