403 error from Google Admin SDK with AppAssertionCredentials - python

I'm trying to list users via Google admin directory API.
import logging
import os
from google.appengine.api import memcache
from googleapiclient import discovery
from oauth2client.contrib.appengine import AppAssertionCredentials
import httplib2
from flask import Flask
credentials = AppAssertionCredentials(scope='https://www.googleapis.com/auth/admin.directory.user')
auth_http = credentials.authorize(httplib2.Http(cache=memcache))
service = discovery.build('admin', 'directory_v1', http=auth_http)
#app.route('/list')
def list():
results = service.users().list(domain='example.com', maxResults=10, orderBy='email').execute()
return 'success'
app = Flask(__name__)
I'm running this in App Engine and have enabled domain-wide delegation for App Engine default service account, as instructed in https://developers.google.com/api-client-library/python/auth/service-accounts
This is the error I'm getting: HttpError: https://www.googleapis.com/admin/directory/v1/users?orderBy=email&domain=example.com&alt=json&maxResults=10 returned "Not Authorized to access this resource/api">

Follow the steps indicated in Delegating domain-wide authority to the service account:
Then, an administrator of the G Suite domain must complete the following steps:
Go to your G Suite domain’s Admin console.
Select Security from the list of controls. If you don't see Security listed, select More controls from the gray bar at the bottom of the page, then select Security from the list of controls. If you can't see the controls, make sure you're signed in as an administrator for the domain.
Select Show more and then Advanced settings from the list of options.
Select Manage API client access in the Authentication section.
In the Client Name field enter the service account's Client ID. You can find your service account's client ID in the Service accounts page.
In the One or More API Scopes field enter the list of scopes that your application should be granted access to. For example, if your application needs domain-wide access to the Google Drive API and the Google Calendar API, enter: https://www.googleapis.com/auth/drive, https://www.googleapis.com/auth/calendar.
Click Authorize.
Make sure your service account is set to Administrator.

Related

Google Calendar api permissions - Service account

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.

Using Python client to verify license of GSuite (Google Apps) domain for a GSuite Marketplace App

I created a service account, instead of using the default one, under my google cloud project and I enabled dwd.
I then enabled Google Apps Marketplace API from the google cloud console.
Then I published a marketplace app as available only to users of my GSuite domain with api_console_project_id in manifest.json set to my project's id.
The status in the developer dashboard is
Published
GAM: Pending
I then had my domain admin install the app.
I believe I am now all set to use the appsmarket v2 Python client to verify license of my domain for the app now.
Below is how I am doing it
from httplib2 import Http
from oauth2client.service_account import ServiceAccountCredentials
from apiclient.discovery import build
path_to_key = 'path/to/my/service_account_key.json'
scopes = ['https://www.googleapis.com/auth/appsmarketplace.license']
APPLICATION_ID = 'xxxxxxxxxxxx'
domain = 'xxxxxx.com'
credentials = ServiceAccountCredentials.from_json_keyfile_name(
path_to_key, scopes)
authorized_http = credentials.authorize(Http())
appsmarket_api_client = build(
'appsmarket', 'v2', http=authorized_http)
appsmarket_license_info = appsmarket_api_client.customerLicense().get(
applicationId=APPLICATION_ID, customerId=domain).execute()
domain_is_licensed = (appsmarket_license_info['state'] == 'ACTIVE')
But my code breaks at
appsmarket_license_info = appsmarket_api_client.customerLicense().get(
applicationId=APPLICATION_ID, customerId=domain).execute()
with HttpError 403 when requesting https://www.googleapis.com/appsmarket/v2/customerLicense/xxxxxxxxxxxx/xxxxxx.com?alt=json returned "Not authorized to access the application ID
I am unable to debug why this is happening.
What I missed was that I didn't add the service account as a member of the project and give it edit access to the project. Now it's working.

How to build a service for Google Calendar API using a service account?

I am using OAuth2.0 with a Google Service Account to try and manage a calendar. I have the sample code in Python provided below:
from oauth2client.client import SignedJwtAssertionCredentials
from httplib2 import Http
from apiclient.discovery import build
client_email = "<service_account_client_email>"
with open("key.p12") as f:
priv_key = f.read()
cred = SignedJwtAssertionCredentials(client_email, priv_key, "https://www.googleapis.com/auth/calendar", sub="<user_to_impersonate>")
http_auth = cred.authorize(Http())
service = build(serviceName='calendar', version='v3', http=http_auth)
So far, I get the exception "Requested client not authorized" when running the code.
I have followed the steps in Google's documentation:
Delegate domain-wide authority to your service account (I created the service account, but I am not an administrator for the domain. However, a user who is an administrator performed this step).
Verified that the scope is set to use the calendar, and the API is enabled in developer console.
Shared the calendar with the service account (though the option to manage the calendar is unavailable).
What steps am I missing? Is my scope incorrect just for building the service?

Service Accounts, web OAuth and the Directory API

I'm having issues with the Directory API + Service Accounts (Google APIs). This is my current setup:
A web page has an OAuth2 login link like this: https://accounts.google.com/o/oauth2/auth?access_type=offline&state=%2Fprofile&redirect_uri=##REDIR##&response_type=code&client_id=##CLIENTID##&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fadmin.directory.user.readonly
Users log in there, authorizing the app to access the Directory API in read-only mode on their behalf.
I then try to retrieve the users of the domain of a given user (by knowing its email address), using the Directory API.
Python code:
from apiclient.discovery import build
from oauth2client.client import SignedJwtAssertionCredentials
import httplib2
CLIENT_ID = "xzxzxzxzxzxz.apps.googleusercontent.com"
APP_EMAIL = "xzxzxzxzxzxz#developer.gserviceaccount.com"
SCOPES = ('https://www.googleapis.com/auth/admin.directory.user.readonly')
f = file('key.p12', 'rb')
key = f.read()
f.close()
credentials = SignedJwtAssertionCredentials(APP_EMAIL, key, SCOPES, sub="user#example.com")
http = httplib2.Http()
http = credentials.authorize(http)
directory_service = build('admin', 'directory_v1', http=http)
users = directory_service.users().list(domain="example.com").execute()
print users
I have also tried setting sub="user#example.com" to the app owner like this sub="appowner#company.com", to no avail.
Another thing I have tried is not using impersonation at all (ie. removing the sub=xx part), which leads me to this error:
apiclient.errors.HttpError: https://www.googleapis.com/admin/directory/v1/users?domain=example.com&alt=json returned "Not Authorized to access this resource/api">
Using impersonation always yields me this. I have verified it has to do with the scopes and the api which I try to call:
oauth2client.client.AccessTokenRefreshError: access_denied
Now, the actual questions:
Should I be using service accounts? For me, it is the most convenient way as I don't have to be storing tokens which can be outdated altogether.
If service accounts are the way to go, what am I doing wrong in the way I use them? Impersonation with either the Google Apps administrator account (which logs in via OAuth web) or the app owner account does not seem to work.

Working with Google Drive API on the google cloud

I have a google app engine site, and what I want to do, is get access to the files on my drive and publish them. Note that, my account owns both the drive and the app engine page.
I have tried looking at the google drive api, and the problem is that I don't know where to start with the following boilerplate code located in their documentation.
If you take a look at this function:
def get_credentials(authorization_code, state):
"""Retrieve credentials using the provided authorization code.
This function exchanges the authorization code for an access token and queries
the UserInfo API to retrieve the user's e-mail address.
If a refresh token has been retrieved along with an access token, it is stored
in the application database using the user's e-mail address as key.
If no refresh token has been retrieved, the function checks in the application
database for one and returns it if found or raises a NoRefreshTokenException
with the authorization URL to redirect the user to.
Args:
authorization_code: Authorization code to use to retrieve an access token.
state: State to set to the authorization URL in case of error.
Returns:
oauth2client.client.OAuth2Credentials instance containing an access and
refresh token.
Raises:
CodeExchangeError: Could not exchange the authorization code.
NoRefreshTokenException: No refresh token could be retrieved from the
available sources.
"""
email_address = ''
try:
credentials = exchange_code(authorization_code)
user_info = get_user_info(credentials)
email_address = user_info.get('email')
user_id = user_info.get('id')
if credentials.refresh_token is not None:
store_credentials(user_id, credentials)
return credentials
else:
credentials = get_stored_credentials(user_id)
if credentials and credentials.refresh_token is not None:
return credentials
except CodeExchangeException, error:
logging.error('An error occurred during code exchange.')
# Drive apps should try to retrieve the user and credentials for the current
# session.
# If none is available, redirect the user to the authorization URL.
error.authorization_url = get_authorization_url(email_address, state)
raise error
except NoUserIdException:
logging.error('No user ID could be retrieved.')
# No refresh token has been retrieved.
authorization_url = get_authorization_url(email_address, state)
raise NoRefreshTokenException(authorization_url)
This is a part of the boilerplate code. However, where am I supposed to get authorisation_code from?
I recently had to implement something similar, and it is quite tricky to find the relevant pieces of documentation.
This is what worked for me.
One-time setup to enable Google Drive for your Google App Engine project
Go to the Google APIs Console and select your App Engine project. If you don't see your App Engine project listed, you need to enable the cloud integration in the App Engine admin tool first (Administration > Application Settings > Cloud Integration > Create project)
In Google APIs Console, now go to Services and look for the "Drive API" in that long list. Turn it on.
Go to the API Access section on Google APIs Console, and find back the "Simple API Access" API Key. (see screenshot below)
Getting and installing the Python Drive API Client
Download the Python Drive API Client: https://developers.google.com/api-client-library/python/start/installation#appengine
Documentation on this Python API: https://google-api-client-libraries.appspot.com/documentation/drive/v2/python/latest/
Using the Python Drive API Client
To create the Drive service object, I use this:
import httplib2
def createDriveService():
"""Builds and returns a Drive service object authorized with the
application's service account.
Returns:
Drive service object.
"""
from oauth2client.appengine import AppAssertionCredentials
from apiclient.discovery import build
credentials = AppAssertionCredentials(scope='https://www.googleapis.com/auth/drive')
http = httplib2.Http()
http = credentials.authorize(http)
return build('drive', 'v2', http=http, developerKey=API_KEY)
You can then use this service object to execute Google Drive API calls, for example, to create a folder:
service = createDriveService()
res = {'title': foldername,
'mimeType': "application/vnd.google-apps.folder"}
service.files().insert(body=res).execute()
Caveats
I was not able to get the Drive API to work in unittesting, nor on the dev_appserver. I always get an error that my credentials are not valid. However, it works fine on the real app engine server.

Categories

Resources