ERROR - 'Credentials' object has no attribute 'signer_email' - google-cloud-platform

I have a gRPC service deployed on Google Cloud Run which I want to call from Composer.
I have assigned the roles/iam.serviceAccountTokenCreator role to the service account which my composer worker nodes are running under, and I'm not mounting any custom service key files or setting the GOOGLE_APPLICATION_CREDENTIALS environment variable.
Using the JWT_GOOGLE authentication option in the airflow gRPC hook I get the following error:
[2022-05-31 14:20:16,082] {grpc.py:90} INFO - Calling gRPC service
[2022-05-31 14:20:16,097] {taskinstance.py:1152} ERROR - 'Credentials' object has no attribute 'signer_email'
Traceback (most recent call last):
File "/usr/local/lib/airflow/airflow/models/taskinstance.py", line 985, in _run_raw_task
result = task_copy.execute(context=context)
File "/usr/local/lib/airflow/airflow/providers/grpc/operators/grpc.py", line 95, in execute
for response in responses:
File "/usr/local/lib/airflow/airflow/providers/grpc/hooks/grpc.py", line 136, in run
with self.get_conn() as channel:
File "/usr/local/lib/airflow/airflow/providers/grpc/hooks/grpc.py", line 104, in get_conn
jwt_creds = google_auth_jwt.OnDemandCredentials.from_signing_credentials(credentials)
File "/opt/python3.6/lib/python3.6/site-packages/google/auth/jwt.py", line 695, in from_signing_credentials
kwargs.setdefault("issuer", credentials.signer_email)
AttributeError: 'Credentials' object has no attribute 'signer_email'
[2022-05-31 14:20:16,100] {taskinstance.py:1196} INFO - Marking task as FAILED. dag_id=example_dag, task_id=example_task, execution_date=20220531T135709, start_date=20220531T142015, end_date=20220531T142016
[2022-05-31 14:20:23,826] {local_task_job.py:102} INFO - Task exited with return code 1
Does anyone have any idea how/why my credentials aren't including the field I need?

Found a solution to this after discussing with Google Cloud - essentially, it looks like the JWT_GOOGLE authentication method isn't set up for GCE service accounts so I went down the CUSTOM authentication route instead:
import google.auth.transport.grpc
import google.auth.transport.requests
import google.oauth2.credentials
import google.oauth2.id_token
from airflow.providers.grpc.operators.grpc import GrpcOperator
def connection_func(conn):
"""Custom connection function for gRPC authentication.
Args:
conn: Airflow Connection object
Returns:
An instantiated gRPC channel for making calls to our remote service.
"""
request = google.auth.transport.requests.Request()
if not str(conn.host).startswith("https://"):
audience = f"https://{conn.host}"
else:
audience = conn.host
token = google.oauth2.id_token.fetch_id_token(request, audience)
creds = google.oauth2.credentials.Credentials(token)
base_url = conn.host
if conn.port:
base_url = f"{base_url}:{conn.port}"
channel = google.auth.transport.grpc.secure_authorized_channel(
creds, None, base_url
)
return channel
return GrpcOperator(
...
custom_connection_func=connection_func,
)
This uses the approach seen here to fetch an ID token for a given audience, then create a set of credentials from there and finally instantiate the gRPC secure channel for use in the operator.

Related

How to set credentials to use Gmail API from GCE VM Command Line?

I'm trying to enable my Linux VM on GCE to access my Gmail account in order to send emails.
I've found this article, https://developers.google.com/gmail/api/quickstart/python, which reads some Gmail account information (it's useful since I just want to test my connection).
from __future__ import print_function
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/gmail.readonly']
def main():
"""Shows basic usage of the Gmail API.
Lists the user's Gmail labels.
"""
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('gmail', 'v1', credentials=creds)
# Call the Gmail API
results = service.users().labels().list(userId='me').execute()
labels = results.get('labels', [])
if not labels:
print('No labels found.')
else:
print('Labels:')
for label in labels:
print(label['name'])
if __name__ == '__main__':
main()
However, I'm not sure which Credentials I need to set, as when I set and use:
Service Account: I receive the following message ValueError: Client secrets must be for a web or installed app.
OAuth client ID for Web Application type: the code runs well, however I receive the following message when trying to first authorize the application's access:
Erro 400: redirect_uri_mismatch
The redirect URI in the request, http://localhost:60443/, does not match the ones authorized for the OAuth client. To update the authorized redirect URIs, visit: https://console.developers.google.com/apis/credentials/oauthclient/${your_client_id}?project=${your_project_number}
OAuth client ID for Desktop type: the code runs well, however I receive the following message when trying to first authorize the application's access:
localhost connection has been refused
Does anyone know how is the correct setup and if I'm missing anything?
Thanks
[Nov17th]
After adding the gmail scope to my VM's scopes I ran the python script and I got the following error:
Traceback (most recent call last):
File "quickstart2.py", line 29, in <module>
main()
File "quickstart2.py", line 18, in main
results = service.users().labels().list(userId="107628233971924038182").execute()
File "/home/lucasnunesfe9/.local/lib/python3.7/site-packages/googleapiclient/_helpers.py", line 134, in positional_wrapper
return wrapped(*args, **kwargs)
File "/home/lucasnunesfe9/.local/lib/python3.7/site-packages/googleapiclient/http.py", line 915, in execute
raise HttpError(resp, content, uri=self.uri)
googleapiclient.errors.HttpError: <HttpError 400 when requesting https://gmail.googleapis.com/gmail/v1/users/107628233971924038182/labels?alt=json returned "Precondition check failed.">
I checked the error HTTP link and it shows:
{
"error": {
"code": 401,
"message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"errors": [
{
"message": "Login Required.",
"domain": "global",
"reason": "required",
"location": "Authorization",
"locationType": "header"
}
],
"status": "UNAUTHENTICATED"
}
}
Is any manual procedure needed in a "first authorization step"?
PS: reinforcing that I have already enabled my Service Account to "G Suite Domain-wide Delegation". This action generated an OAuth 2.0 Client ID, which is being used in the python script (variable userId).
Personally, I never understood this example. I think it's too old (and even compliant Python 2.6!!)
Anyway, you can forget the first part and get a credential like this
from googleapiclient.discovery import build
import google.auth
# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
def main():
creds, project_id = google.auth.default(scopes=SCOPES)
service = build('gmail', 'v1', credentials=creds)
# Call the Gmail API
results = service.users().labels().list(userId='me').execute()
labels = results.get('labels', [])
if not labels:
print('No labels found.')
else:
print('Labels:')
for label in labels:
print(label['name'])
if __name__ == '__main__':
main()
However, because you will use a service account to access to your GMAIL account, you have to change the userId with the ID that you want and to grand access from the GSuite admin console to the service account to have access to GMAIL API.
To achieve this, you need to grant the scope of your VM service account. Stop the VM, run this (long) command, and start your VM again. The command:
Takes the current scopes of your VM (and clean/format them)
Add the gmail scope
Set the scope to the VM (it's a BETA command)
gcloud beta compute instances set-scopes <YOUR_VM_NAME> --zone <YOUR_VM_ZONE> \
--scopes=https://www.googleapis.com/auth/gmail.readonly,\
$(gcloud compute instances describe <YOUR_VM_NAME> --zone <YOUR_VM_ZONE> \
--format json | jq -c ".serviceAccounts[0].scopes" | sed -E "s/\[(.*)\]/\1/g" | sed -E "s/\"//g")

How to use service account with private key file and impersonated user (DWD) in Google App Engine and local development server?

Application: Google App Engine Python standard environment
Purpose: Access Google APIs (not Cloud APIs) through the google-api-python-client, e.g. Sheets API v4, by using a service account and impersonate a user, because the app is supposed to act on behalf of this user. (2-legged auth, the user won't be asked to grant access)
I've got a setup running in production environment, but it runs only on the local development server (dev_appserver.py) for testing if a certain environment variable would be removed. I'm looking for a solution that would work without adding/removing the environment variable.
The service account was created for the app and configured with domain-wide delegation DWD in Admin Console. Sheets API is turned on for this project.
Of the many quick-starts, samples, and references available, it was only after reading the Google Auth Library for Python documentation (google-auth) that I've noticed the missing parts (an environment variable and the SSL library) and finally got the code running on production.
The app code will use the private key JSON file that was downloaded from Cloud Console IAM.
requirements.txt
# as suggested by almost all docs, but this isn't everything we need:
google-api-python-client==1.6.5
google-auth==1.4.0
google-auth-httplib2==0.0.3
app.yaml
env_variables:
# enable socket support of paid app, needed for OAuth2 service-accounts
# see google-auth documentation, v1.4.1, chapter 1.2.4
GAE_USE_SOCKETS_HTTPLIB : true
# some other stuff
libraries:
# to make HTTPS calls to other services, needed for OAuth2 service-accounts
# see google-auth documentation, v1.4.1, chapter 1.2.4
- name: ssl
version: latest
appengine_config.py (partial sample for Sheets API v4 access)
from google.oauth2 import service_account
SCOPES = ["https://www.googleapis.com/auth/spreadsheets"]
APP_ROOT_DIR = os.path.abspath(os.path.dirname(__file__))
SERVICE_ACCOUNT_FILE = "service-account-private-key.json"
import googleapiclient.discovery
credentials = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
# impersonate as user#example.com (G Suite domain account)
credentials = credentials.with_subject('user#example.com')
service = googleapiclient.discovery.build('sheets', 'v4', credentials=credentials)
# until here, the code works in production and local dev server
result = service.spreadsheets().values().get(spreadsheetId="DOC-ID-HERE", range="A1:C5").execute()
# execute() will work only in production,
# on local dev, it will raise an ResponseNotReady exception
traceback
ERROR 2018-03-05 16:32:03,183 wsgi.py:263]
Traceback (most recent call last):
File "/Users/user/google-cloud-sdk/platform/google_appengine/google/appengine/runtime/wsgi.py", line 240, in Handle
handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
File "/Users/user/google-cloud-sdk/platform/google_appengine/google/appengine/api/lib_config.py", line 351, in __getattr__
self._update_configs()
File "/Users/user/google-cloud-sdk/platform/google_appengine/google/appengine/api/lib_config.py", line 287, in _update_configs
self._registry.initialize()
File "/Users/user/google-cloud-sdk/platform/google_appengine/google/appengine/api/lib_config.py", line 160, in initialize
import_func(self._modname)
File "/Users/user/git/project/gae/appengine_config.py", line 143, in <module>
spreadsheetId=spreadsheetId, range=rangeName).execute()
File "/Users/user/git/project/gae/_lib/oauth2client/_helpers.py", line 133, in positional_wrapper
return wrapped(*args, **kwargs)
File "/Users/user/git/project/gae/_lib/googleapiclient/http.py", line 839, in execute
method=str(self.method), body=self.body, headers=self.headers)
File "/Users/user/git/project/gae/_lib/googleapiclient/http.py", line 166, in _retry_request
resp, content = http.request(uri, method, *args, **kwargs)
File "/Users/user/git/project/gae/_lib/google_auth_httplib2.py", line 187, in request
self._request, method, uri, request_headers)
File "/Users/user/git/project/gae/_lib/google/auth/credentials.py", line 121, in before_request
self.refresh(request)
File "/Users/user/git/project/gae/_lib/google/oauth2/service_account.py", line 322, in refresh
request, self._token_uri, assertion)
File "/Users/user/git/project/gae/_lib/google/oauth2/_client.py", line 145, in jwt_grant
response_data = _token_endpoint_request(request, token_uri, body)
File "/Users/user/git/project/gae/_lib/google/oauth2/_client.py", line 106, in _token_endpoint_request
method='POST', url=token_uri, headers=headers, body=body)
File "/Users/user/git/project/gae/_lib/google_auth_httplib2.py", line 116, in __call__
url, method=method, body=body, headers=headers, **kwargs)
File "/Users/user/git/project/gae/_lib/httplib2/__init__.py", line 1659, in request
(response, content) = self._request(conn, authority, uri, request_uri, method, body, headers, redirections, cachekey)
File "/Users/user/git/project/gae/_lib/httplib2/__init__.py", line 1399, in _request
(response, content) = self._conn_request(conn, request_uri, method, body, headers)
File "/Users/user/git/project/gae/_lib/httplib2/__init__.py", line 1355, in _conn_request
response = conn.getresponse()
File "/Users/user/google-cloud-sdk/platform/google_appengine/google/appengine/dist27/python_std_lib/httplib.py", line 1121, in getresponse
raise ResponseNotReady()
I have figured out that if I delete GAE_USE_SOCKETS_HTTPLIB from app.yaml's env_variables list, the code will work on local development server (but not in production anymore).
Am I doing something wrong here? Could I use the same code (maybe with a small switch) for both environments, without manually adding/removing the variable from app.yaml?
Purpose: Access Google APIs (not Cloud APIs) through the google-api-python-client, e.g. Sheets API v4, ….
Here they explain that:
Private, broadcast, multicast, and Google IP ranges (except those whitelisted below), are blocked:
Google Public DNS: 8.8.8.8, 8.8.4.4, 2001:4860:4860::8888, 2001:4860:4860::8844 port 53
Gmail SMTPS: smtp.gmail.com port 465 and 587
Gmail POP3S: pop.gmail.com port 995
Gmail IMAPS: imap.gmail.com port 993
I have figured out that if I delete GAE_USE_SOCKETS_HTTPLIB from app.yaml's env_variables list, the code will work on local development server (but not in production anymore).
This is explained here:
Using sockets with the development server
You can run and test code
using sockets on the development server, without using any special
command line parameters.
Finally, this question and the accepted answer describe a similar scenario.
Hope this helps you :-)

linkedin api - python - get_connections()

I am working on a simple python scraping script, I am trying to get connections from LinkedIn using their API without a redirect_uri. I worked once with some APIs, that don't require the redirect url or just https://localhost. I got the consumer_key, consumer_secret, user_secret, consumer_secret. Here's the code i am using from https://github.com/ozgur/python-linkedin:
RETURN_URL = ''
url = 'https://api.linkedin.com/v1/people/~'
# Instantiate the developer authentication class
authentication = linkedin.LinkedInDeveloperAuthentication(CONSUMER_KEY, CONSUMER_SECRET,
USER_TOKEN, USER_SECRET,
RETURN_URL, linkedin.PERMISSIONS.enums.values())
# Pass it in to the app...
application = linkedin.LinkedInApplication(authentication)
print application.get_profile() # works
print application.get_connections()
And here's the error I get:
Traceback (most recent call last):
File "getContacts.py", line 20, in <module>
print application.get_connections()
File "/home/imane/Projects/prjL/env/local/lib/python2.7/site-packages/linkedin/linkedin.py", line 219, in get_connections
raise_for_error(response)
File "/home/imane/Projects/prjL/env/local/lib/python2.7/site-packages/linkedin/utils.py", line 63, in raise_for_error
raise LinkedInError(message)
linkedin.exceptions.LinkedInError: 403 Client Error: Forbidden for url: https://api.linkedin.com/v1/people/~/connections: Unknown Error
This is my first question here, so excuse me if I didn't make it clear enough, and thank you for helping me out.
Here's what i tried with python_oauth2:
import oauth2 as oauth
import requests
url = 'https://api.linkedin.com/v1/people/~'
params = {}
token = oauth.Token(key=USER_TOKEN, secret=USER_SECRET)
consumer = oauth.Consumer(key=CONSUMER_KEY, secret=CONSUMER_SECRET)
# Set our token/key parameters
params['oauth_token'] = token.key
params['oauth_consumer_key'] = consumer.key
oauth_request = oauth.Request(method="GET", url=url, parameters=params)
oauth_request.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, token)
signed_url = oauth_request.to_url()
response = requests.get(signed_url)
Connections API calls are a restricted endpoint as of March, 2015. It's possible you're using sample code/documentation that was written at a time when anyone could access those endpoints. You are receiving a 403 response because your application legitimately does not have the permission required to make that request.

Google Python Admin SDK using Oauth2 for a Service Account (Education Edition)-"oauth2client.client.AccessTokenRefreshError: access_denied" exception

I have been trying to get the Service Account authentication working for the Google Admin SDK for a few days now to no avail. I am using the google-api-python-client-1.2 library freshly installed from Google.
I have been following Google's documentation on the topic. Links are here:
htps://developers.google.com/accounts/docs/OAuth2ServiceAccount
htps://developers.google.com/api-client-library/python/guide/aaa_oauth
htp://google-api-python-client.googlecode.com/hg/docs/epy/oauth2client.client.SignedJwtAssertionCredentials-class.html
And have the tasks.py Service Account example working which you can be found here:
htp://code.google.com/p/google-api-python-client/source/browse/samples/service_account/tasks.py?r=c21573904a2df1334d13b4380f63463c94c8d0e8
And have been closely studying these two Stack Overflow threads on a related topic here:
google admin sdk directory api 403 python
Google Admin API using Oauth2 for a Service Account (Education Edition) - 403 Error
And have studied the relevant code in gam.py (Dito GAM).
Yet I'm still missing something as I am getting an 'oauth2client.client.AccessTokenRefreshError: access_denied' exception in nearly every test case I write.
Here is a concise example of a test authentication:
import httplib2
from apiclient.discovery import build
from oauth2client.client import SignedJwtAssertionCredentials
f = file('myKey.p12', 'rb')
key = f.read()
f.close()
credentials = SignedJwtAssertionCredentials(
'myServiceAdmin#developer.gserviceaccount.com',
key,
sub='myAdminUser#my.googleDomain.edu',
scope = ['https://www.googleapis.com/auth/admin.directory.user',])
http = httplib2.Http()
http = credentials.authorize(http)
service = build('admin', 'directory_v1', http=http)
When I run the above code I get this stack dump and exception:
Traceback (most recent call last):
File "./test.py", line 17, in <module>
service = build('admin', 'directory_v1', http=http)
File "/usr/lib/python2.7/dist-packages/oauth2client/util.py", line 132, in positional_wrapper
return wrapped(*args, **kwargs)
File "/usr/lib/python2.7/dist-packages/apiclient/discovery.py", line 192, in build resp, content = http.request(requested_url)
File "/usr/lib/python2.7/dist-packages/oauth2client/util.py", line 132, in positional_wrapper
return wrapped(*args, **kwargs)
File "/usr/lib/python2.7/dist-packages/oauth2client/client.py", line 475, in new_request
self._refresh(request_orig)
File "/usr/lib/python2.7/dist-packages/oauth2client/client.py", line 653, in _refresh
self._do_refresh_request(http_request)
File "/usr/lib/python2.7/dist-packages/oauth2client/client.py", line 710, in _do_refresh_request
raise AccessTokenRefreshError(error_msg)
oauth2client.client.AccessTokenRefreshError: access_denied
I've tried multiple super user accounts, service accounts, and keys and always end up with the same exception. If I add sub to the tasks.py example I end up with the same error. Replacing sub with prn also generates this exception and adding private_key_password='notasecret' does nothing (it is the default). The Admin SDK is activated in the Google Developers Console and the target accounts have super user privileges. This makes me think something is missing on the Google domain side but I cannot think of anything else to check.
Any one have an idea what I am doing wrong?
Have you granted the third party client access in your Admin Console for your service account?
My to go instruction when it comes to setting up Service Account is the instruction Google has for Drive Api.
https://developers.google.com/drive/web/delegation
Take a look at the "Delegate domain-wide authority to your service account" part and see if you have completed those steps.
Maybe not OP's problem, but I had this same error and my issue was that I was setting the sub field in the credentials object
credentials = SignedJwtAssertionCredentials(SERVICE_ACCOUNT_EMAIL, key,
scope=SCOPES, sub=**<DON'T SET ME>**)
If you're using domain-wide delegation, you need to not set a sub (because your "user" is the domain administrator.) The docs are a bit confusing on this point. I just removed the sub field and it worked for me.

Error using api.update_status method in tweepy using Oauth2

Here is my code:-
I have double checked all the auth parameters.
import tweepy
CONSUMER_KEY ='#Omitted - you should not publish your actual key'
CONSUMER_SECRET ='#Omitted - you should not publish your actual secret'
ACCESS_KEY='#Omitted - you should not publish your access key'
ACCESS_SECRET = '#Omitted - you should not publish your access secret'
auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth.set_access_token(ACCESS_KEY, ACCESS_SECRET)
api = tweepy.API(auth)
api.update_status('Tweeting from command line')
Saved the file in home folder as status.py
after running python status.py follwing error comes:-
Traceback (most recent call last):
File "status.py", line 14, in <module>
api.update_status('Tweeting from command line')
File "/usr/local/lib/python2.7/dist-packages/tweepy-1.10-py2.7.egg/tweepy/binder.py", line 185, in _call
return method.execute()
File "/usr/local/lib/python2.7/dist-packages/tweepy-1.10-py2.7.egg/tweepy/binder.py", line 168, in execute
raise TweepError(error_msg, resp)
tweepy.error.TweepError: Could not authenticate with OAuth.
Please, help me out
I received this error under the same conditions - using tweepy, all of my keys/secrete were copy and pasted correctly. The problem was the time on my server. After running ntpdate -b pool.ntp.org I was to use tweepy just fine.
I am able to authenticate using tweepy, I have an extra line in my code though, it might help for you to change your code to this:
import tweepy
from tweepy import OAuthHandler
then proceede with the rest of your code. Also add a line in your code to print out to the shell to show your connect as follows:
print api.me().name
Make sure the line you see above this is right after api = tweepy.API(auth)
Try api.update_status(status='Tweeting from command line'). It helped me.