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.
Related
I'm trying to consume Google's Workspace APIs, but I'm having extreme difficulty with the documentation to create my first code, following the first steps I did the following
I created a project within Google Cloud
I enabled the Admin SDK API
I created a service account
I created a key in Json format
in the Workspace dashboard under delegation across domain I added the unique id and the following scope
[
'https://www.googleapis.com/auth/apps.order',
'https://www.googleapis.com/auth/siteverification',
'https://www.googleapis.com/auth/directory.readonly',
'https://www.googleapis.com/auth/admin.directory.user',
'https://www.googleapis.com/auth/admin.reports.usage.readonly',
'https://www.googleapis.com/auth/admin.reports.audit.readonly',
'https://www.googleapis.com/auth/gmail.send'
]
I would like to use the document from the link https://developers.google.com/admin-sdk/reports/reference/rest to consult the activities of a specific user but I can't find an example code to consume this API using these credentials in Python , I'm new in this area and would like some help.
Generate a token and when I tried to use an api it didn't work and it was unauthorized, below is the code I used
import requests
url = "https://admin.googleapis.com/admin/reports/v1/activity/users/usuario#exemplo.com/applications/calendar"
payload = ""
headers = {"Authorization": "Bearer xptoz_exemple_test=PHQbcdddx3xxxxxxxxxxxxddddddddd"}
response = requests.request("GET", url, data=payload, headers=headers)
print(response.text)
You are getting unauthorized because the service account doesn't have permission to do what ever it is you are trying to do. To get permission you need to be using the proper scope, and the service account needs to not only have permission to use this scope but it must have delegated to a user on the domain that has access to the data.
First you need to be sure that your authorization code is delegating to a user on your domain.
#!/usr/bin/python
from oauth2client.service_account import ServiceAccountCredentials
from httplib2 import Http
from apiclient.discovery import build
scopes = [ 'https://www.googleapis.com/auth/admin.reports.usage.readonly' ]
credentials = ServiceAccountCredentials.from_json_keyfile_name('/path/to/my/key.json', scopes)
delegated_credentials = credentials.create_delegated('me#mydomain.com')
http_auth = credentials.authorize(Http())
service = build('admin', 'directory_v1', credentials=creds)
Then you should consider having a look at Python quickstart the auth in this is set up to use an installed app. However the rest of the code should show you how to use the Client library rather then sending all the requests manually like you are now.
I'm trying to list all Google Drive files Domain-wide, both users that still work here, and those that have moved on. With that, we can grep the output for certain terms (former customers) to delete customer-related files.
I believe I have a successful way to list all users using the Admin SDK Quickstart, since we have only about 200 total users (max is 500). I also have a way to list all files for a user using the Drive REST API's files.list() method. What I need to know is how to impersonate each user iteratively, in order to run the file listing script.
I have found the blurb .setServiceAccountUser(someone#domain.com) but I'm not really sure where to implement this, either in the service account authorization step, or in a separate middle-man script.
Have a look at https://github.com/pinoyyid/googleDriveTransferOwnership/blob/master/src/couk/cleverthinking/tof/Main.java
Specifically lines 285-299 which deal with generating a credential for an impersonated user.
GoogleCredential.Builder builder = new GoogleCredential.Builder()
.setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(serviceAccountEmailAddress)
.setServiceAccountPrivateKeyFromP12File(f)
.setServiceAccountScopes(Collections.singleton(SCOPE));
// if requested, impresonate a domain user
if (!"ServiceAccount".equals(impersonatedAccountEmailAddress)) {
builder.setServiceAccountUser(impersonatedAccountEmailAddress);
}
// build the Drive service
Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, null)
.setApplicationName("TOF")
.setHttpRequestInitializer(builder.build()).build();
This is Java, but should at least tell you what the steps are.
You need to implement the authorization flow for Service Accounts.
Once you create a service account in a GCP project (console.developers.google.com), enable DWD (domain-wide delegation), then authorize that service account in your G Suite admin console, that key can then be used to "impersonate" any account in the G Suite instance:
Create the credentials object from the json file
from oauth2client.service_account import ServiceAccountCredentials
scopes = ['https://www.googleapis.com/auth/gmail.readonly']
credentials = ServiceAccountCredentials.from_json_keyfile_name(
'/path/to/keyfile.json', scopes=scopes)
Create a credential that can impersonate user#example.org (could be any user in the domain though)
delegated_credentials = credentials.create_delegated('user#example.org')
Authorize the credential object (i.e. get an access_token)
from httplib2 import Http
http_auth = credentials.authorize(Http())
Call the Gmail API:
from apiclient import discovery
service = discovery.build('gmail', 'v1', http=http)
response = service.users().messages().list(userId='user#example.org').execute()
I've been having trouble over the past few days using the Google Directory API in the Admin SDK for Google Apps. The documentation leaves a lot to be desired and when I contacted Google Apps Enterprise support they indicated they do not support the API. I am using the most recent Python API client library provided by Google as they suggest this is the best way to go. I've logged in to the Google API Console and created a Service Account and downloaded the OAuth2 key. I've also turned on the Admin SDK in the console. Here is my code:
f = file("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-privatekey.p12", "rb")
key = f.read()
f.close()
credentials = SignedJwtAssertionCredentials(
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx#developer.gserviceaccount.com",
key,
scope = "https://www.googleapis.com/auth/admin.directory.orgunit"
)
http = httplib2.Http()
http = credentials.authorize(http)
directoryservice = build("admin", "directory_v1", http=http)
orgunits = directoryservice.orgunits().list(customerId='XXXXXXX').execute(http=http)
pprint.pprint(orgunits)
Note that customerId is our Google Apps customer ID. I tried it with "my_customer" as Google seems to indicate should work when using an account that is super admin, but I receive the return "invalid customerId" when I try it that way. So I hardcoded our actual customerId.
When harcoded always receive the return "Login Required" but it seems as if the authentication process is working as the directory object gets created via the build command. Am I doing something wrong?
Note, I also read somewhere that sometimes the request needs to come from a domain account rather than the Service Account and to do this you need to add:
sub = "domain_account_superadmin#example.com"
In the SignedJwtAssertionCredentials call... which I tried, but then receive the message "access_denied"
Thanks in advance for suggestions.
See the google drive example here: https://developers.google.com/drive/delegation
Don't forget to delegate domain wide authority for the service account and scopes.
Here is an example for listing organization units via service account:
import sys
import apiclient.discovery
import oauth2client.client
import httplib2
import pprint
# see example for using service account here:
# https://developers.google.com/drive/delegation
def main (argv):
scopes = ('https://www.googleapis.com/auth/admin.directory.orgunit')
service_account_email = 'xxx#developer.gserviceaccount.com'
acting_as_user = 'yyy#zzz' # must have the privileges to view the org units
f = file('key.p12', 'rb')
key = f.read()
f.close()
credentials = oauth2client.client.SignedJwtAssertionCredentials(
service_account_email,
key,
scope=scopes,
sub=acting_as_user
)
http = httplib2.Http()
http = credentials.authorize(http)
directoryservice = apiclient.discovery.build('admin', 'directory_v1', http=http)
response = directoryservice.orgunits().list(customerId='my_customer').execute(http=http)
pprint.pprint(response)
if __name__ == '__main__':
main(sys.argv)
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.
I'm looking for a good way to retrieve every emails address of my contacts from a google account for a "desktop" application in Python.
In a first time, I created an app via Google Code. I toggled Google Plus API, retrieving most of my user data, but not any of my contacts.
I started investigate, and I found a lot of stuff, but most of them was outdated.
I found a good way to retrieve my contacts, using gdata library but granting me a full read/write access on it, via https://www.google.com/m8/feeds with no feedback.
self.gd_client = gdata.contacts.client.ContactsClient(source='MyAppliName')
self.gd_client.ClientLogin(email, password, self.gd_client.source)
According to the official 'google contact api' google group, which migrated to stackoverflow, read only access is broken.
By the way, I'm not a huge fan of 'Trust my application, I use read only access, I swear."
I found the google api playground at https://developers.google.com/oauthplayground in which they use OAuth2.0 token with most of apis, including contact, toggling a webpage:
Google OAuth 2.0 Playground is requesting permission to:
Manage your contacts
According to this playground, it's possible to use OAuth2.0 with google contact api, but I have no idea how to add https:// www.google.com/m8/feeds to my scope, which doesn't appear on the list.
Is there an other way to do that ?
If this question is still open for you, here is some sample code how to use oauth2 and Google Contact API v3:
import gdata.contacts.client
from gdata.gauth import AuthSubToken
from oauth2client import tools
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
def oauth2_authorize_application(client_secret_file, scope, credential_cache_file='credentials_cache.json'):
"""
authorize an application to the requested scope by asking the user in a browser.
:param client_secret_file: json file containing the client secret for an offline application
:param scope: scope(s) to authorize the application for
:param credential_cache_file: if provided or not None, the credenials will be cached in a file.
The user does not need to be reauthenticated
:return OAuth2Credentials object
"""
FLOW = flow_from_clientsecrets(client_secret_file,
scope=scope)
storage = Storage(credential_cache_file)
credentials = storage.get()
if credentials is None or credentials.invalid:
# Run oauth2 flow with default arguments.
credentials = tools.run_flow(FLOW, storage, tools.argparser.parse_args([]))
return credentials
SCOPES = ['https://www.google.com/m8/feeds/', 'https://www.googleapis.com/auth/userinfo.email']
credentials = oauth2_authorize_application('client-secret.json', scope=SCOPES)
token_string = credentials.get_access_token().access_token
# deprecated!
# auth_token = AuthSubToken(token_string, SCOPES)
with open('client-secret.json') as f:
oauth2_client_secret = json.load(f)
auth_token = gdata.gauth.OAuth2Token(
client_id=oauth2_client_secret['web']['client_id'],
client_secret=oauth2_client_secret['web']['client_secret'],
scope=SCOPES,
user_agent='MyUserAgent/1.0',
access_token=credentials.get_access_token().access_token,
refresh_token=credentials.refresh_token)
client = gdata.contacts.client.ContactsClient(auth_token=auth_token)
query = gdata.contacts.client.ContactsQuery()
The request should look like:
https://accounts.google.com/o/oauth2/auth?
scope=https%3A%2F%2Fwww.google.com%2Fm8%2Ffeeds&
state=<myState>&
redirect_uri=<Redirect URI>&
response_type=code&
client_id=<my Client ID>&approval_prompt=force
This will obtain read/write access to the user's contacts.