Oauth access to external Google Brand Account - google-cloud-platform

My company has a Google Brand Account to manage our YouTube (YT) channel. We are trying to use the YouTube Analytics & Reporting API to automatically export metrics about our channel. To do so, we have created an App in our GCP Organisation and in that App we have created an Oauth client. Everything works fine when retrieving YouTube data for a channel owned by a user within our GCP Org. However, the Brand Account that owns the YT channel we are interested in is not a member of our GCP Org. This means that when trying to access that channel using our Oauth client we get the following error:
Error 403: org_internal
This client is restricted to users within its organization.
From searching online documentation it seems that we can do one of the following:
Make our App external from the APIs & Services -> OAuth consent screen section
Migrate the Brand Account to our GCP Org
I don't know how to do 2. and whether it is actually feasible at all. And 1. seems a bit overboard to me as we don't really want to access data from any user with a Google Account but it might be the only way. So I am looking for help on how best to proceed so that we can use an OAuth client within our GCP Org to get analytics data from our YT channel.

Looks like this was first documented here. It's makred "won't fix" by Google, not sure why.
I was able to use this OAuth Playground (OAP) workaround to get my app to work. It's a pretty sad workaround because the token will only work for an hour and then you must manually refresh in the playground.
Once you get the token from OAP, here is the code I'm using.
import os, json
from pathlib import Path
import google.oauth2.credentials
from googleapiclient.discovery import build
# pasted from OAP, note only access_token is actually needed
Path('oap.json').write_text('''
{
"access_token": "tokenstring",
"scope": "https://www.googleapis.com/auth/youtube.readonly https://www.googleapis.com/auth/yt-analytics.readonly",
"token_type": "Bearer",
"expires_in": 3599,
"refresh_token": "refreshtoken"
}
''')
TOKEN_FILE = 'oap.json'
# For Reference
# SCOPES = ['https://www.googleapis.com/auth/yt-analytics.readonly',
# 'https://www.googleapis.com/auth/youtube.readonly']
API_SERVICES = [
('youtubeAnalytics', 'v2'),
('youtube', 'v3')
]
oap = json.load(open(TOKEN_FILE, 'r'))
creds = google.oauth2.credentials.Credentials(oap['access_token'])
service_list = []
for API_SERVICE_NAME,API_VERSION in api_services:
service = build(API_SERVICE_NAME, API_VERSION, credentials = creds)
service_list.append(service)
ytAnalytics, ytData = service_list
# test ytData
req = ytData.channels().list(
part = 'id,snippet',
mine=True)
res = req.execute()
print(res)
for channel in res['items']:
print('Channel:',channel['snippet']['title'])

Related

Programmatically get current Service Account on GCP

Is there a way to programmatically access the email of the currently used Service Account on a GCP instance when no GOOGLE_APPLICATION_CREDENTIALS is set? (ie. when using the default Service Account)
I've looked through the GCP documentation, but the only resource I found can't be used with the default Service Account when no GOOGLE_APPLICATION_CREDENTIALS is set. I know that it is possible to do so using gcloud (see this SO question or documentation), however these solutions aren't applicable when running on a ContainerOptimisedOS. I've spent a couple of weeks back and forth with the GCP support team, but they concluded with not being able to help me and redirected me to Stack Overflow for help.
The solution of John works great, on any language without any external library. However, it works only on Google Cloud environment, when a metadata server is deployed. You can't perform this test on your computer.
I propose just bellow a piece of Python code (with Google OAuth library, but it works in other languages that have this library) to ask the library the current credential. If the credential is a service account (from GOOGLE_APPLICATION_CREDENTIALS on your computer, the ADC (Application Default Credential) or from the metadata server), you have the email printed, else, you have warning message because you use your user account credential
import google.auth
credentials, project_id = google.auth.default()
if hasattr(credentials, "service_account_email"):
print(credentials.service_account_email)
else:
print("WARNING: no service account credential. User account credential?")
Note that if the default service account is used this method will print default instead of the entire email address.
EDIT 1
ctx := context.Background()
credential,err := google.FindDefaultCredentials(ctx)
content := map[string]interface{}{}
json.Unmarshal(credential.JSON,&content)
if content["client_email"] != nil {
fmt.Println(content["client_email"])
} else {
fmt.Println("WARNING: no service account credential. User account credential?")
}
Just adding to the accepted answer. As stated in the answer this seems to return "default":
import google.auth
credentials, project_id = google.auth.default()
# returns "default"
print(credentials.service_account_email)
I found to get the email name of the GSA currently active (via the metadata api) I had to manually refresh first:
import google.auth
import google.auth.transport.requests
credentials, project_id = google.auth.default()
request = google.auth.transport.requests.Request()
credentials.refresh(request=request)
# returns "mygsa#myproject.iam.gserviceaccount.com"
print(credentials.service_account_email)
I'm using workload ID. I think maybe there is a race condition and I'm trying to read the service_account_email property before the creds get initialized for the first time when the pod starts?
The _retrieve_info() function is called when refresh() is called and it appears this is the function that grabs the email name.
If I had my script sleep for a few seconds on start up would service_account_email eventually be populated with the email name of the GSA or does that not happen until you manually refresh or initialize a client API or something?
If you are interested in getting the exact email and not just the "default" alias (when you are using the compute engine), you can fetch it using the credentials metadata. This was particularly helpful in determining which service account is being used by AI Platform jobs.
import google.auth
from google.auth.transport.requests import Request
from google.auth.compute_engine import _metadata
if hasattr(credentials, "service_account_email"):
print(credentials.service_account_email)
# edits made on top of the top answer's code
info = _metadata.get_service_account_info(Request(),
service_account=credentials.service_account_email)
print(f"Service account email: {info.email}")
print(f"Service account aliases: {info.aliases}")
print(f"Service account scopes: {info.scopes}")
else:
print("WARNING: no service account credentials available.")

GCP - Get ID token in environment-independent way

I have an application that must verify the identity of its caller. For this I need ID tokens (the JWTs from Google's OpenID Connect implementation) from each client calling the service.
I would like to write client code that works both locally using the default user credentials—for testing and development—and on a Compute Engine instance in production. The official Python auth SDK generally does a good job of handling those cases and saving me the trouble of checking the environment, e.g. I can just call google.auth.default and it figures out where to get credentials.
However, that google.auth package only seems to be able to give me auth tokens, not ID tokens in an environment-independent way. Here is what I tried:
import google.auth
from google.auth.transport import requests
credentials, project = google.auth.default(scopes=["openid"])
req = requests.Request()
credentials.refresh(req)
print(credentials.id_token)
This works on my laptop with my default credentials, but on the Compute Engine instance I instead get an error AttributeError: 'Credentials' object has no attribute 'id_token'
According to this page in the docs, you are supposed to fetch an ID token for an instance by requesting it from the metadata server...
import requests
audience = 'service_identifier'
metadata_server_token_url = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience='
token_request_url = metadata_server_token_url + audience
token_request_headers = {'Metadata-Flavor': 'Google'}
token_response = requests.get(token_request_url, headers=token_request_headers)
jwt = token_response.content.decode("utf-8")
print(jwt)
I don't want to do that. I don't want to manually check the environment. The SDK is supposed to handle that complexity for me. Shouldn't there be a way to leverage the google-auth SDK to generate an ID token in an environment-independent way?
EDIT 1: Why I need this
The application is based on Cloud Functions, and it returns highly sensitive data. Only a specific set of subjects—some trusted devs and services—should be able to access that data. So, the cloud functions must verify the ID of the caller (user or service account) using an ID token signed by Google. Specifically, I need to know the sub claim, the "subject" in the JWT. It is effectively the same issue that the IAM features are meant to solve, documented here. However, I cannot use these because they are still in beta. So, I'm writing identity checks manually into the cloud functions for the time being.
I think I have an answer to this question. I could always get it to work locally or in the cloud, the trick was to find a way of combining the two. I colleague of mine actually showed me how to do this and I just wanted to share this with others who are looking for a solution.
import google.auth
from google.auth.transport.requests import AuthorizedSession, Request
from google.oauth2.id_token import fetch_id_token
import requests
def GetIdToken(audience):
credentials, _ = google.auth.default()
session = AuthorizedSession(credentials)
request = Request(session)
credentials.refresh(request)
if hasattr(credentials, "id_token"):
return credentials.id_token
return fetch_id_token(request, audience)
def ProcessPayload(url, payload):
# Get the ID Token
id_token = GetIdToken(url)
# Process post request
headers = {'Authorization': f'Bearer {id_token}'}
response = requests.post(url, json=payload, headers=headers)

Why service_info is empty when using Python OneDrive sdk

I'm doing my internship in a company has very high security settings, sometimes something in the network will be blocked without any notice. And I don't really have admin role for my computer. Right now I met a problem, and not sure whether it's my permission issue.
I'm trying to upload files in Python code to OneDrive for Business, it's the company account.
This is the tutorial I'm using, check that OneDrive for Business part.
Before uploading the item, I should pass the authentication.
I have tested my code line by line, it worked well,
import onedrivesdk
from onedrivesdk.helpers import GetAuthCodeServer
from onedrivesdk.helpers.resource_discovery import ResourceDiscoveryRequest
redirect_uri = 'http://localhost:8080'
client_secret = '[my client secret]'
client_id='[my client id]'
resourceId = "https://api.office.com/discovery/"
auth_server_url='https://login.microsoftonline.com/common/oauth2/authorize'
auth_token_url='https://login.microsoftonline.com/common/oauth2/token'
http = onedrivesdk.HttpProvider()
auth = onedrivesdk.AuthProvider(http,
client_id,
auth_server_url=auth_server_url,
auth_token_url=auth_token_url)
auth_url = auth.get_auth_url(redirect_uri)
code = GetAuthCodeServer.get_auth_code(auth_url, redirect_uri)
auth.authenticate(code, redirect_uri, client_secret, resource=resourceId)
service_info = ResourceDiscoveryRequest().get_service_info(auth.access_token)
until when I am trying to get the service_info, it's empty....
Do you know why it's empty?
Or how can i keep writing the code here so that I could upload files to OneDrive for business?
I get the feeling that this is happening because our SDK drops all services where the service_api_version is less than 2.0. Assuming that the service you are trying to access works with API 2.0, then you can use this workaround to get past your issue.

Google Admin API using Oauth2 for a Service Account (Education Edition) - 403 Error

I'm having difficulties using Google new Admin SDK. In particular the Directory API using Oauth2.
I think I'm almost there but I've got stuck trying to retrieve a users details using the Directory API (I'm using a Google Education Edition domain).
Basically what I'm trying to do is write a python script that provisions or de-provisions users based on their enrollment status which is managed by our AD. I've got a script that does this using Oauth1 but want to update it to use Oauth2.
Here is a code snippet based on some examples I found.
f = file('test_key.p12', 'rb')
key = f.read()
f.close()
credentials = SignedJwtAssertionCredentials(
'606346240424-10stfco1ukj9h4m4b4r40#developer.gserviceaccount.com',
key,
scope= 'https://www.googleapis.com/auth/admin.directory.user')
http = httplib2.Http()
http = credentials.authorize(http)
service = build(serviceName='admin', version='directory_v1', http=http)
lists = service.users().get(userKey='joe.blogs#mydomain.com').execute(http=http)
pprint.pprint(lists)
This piece of code appears to connect correctly but when I try to execute the query I get a 403 error.
ERROR: https://www.googleapis.com/admin/directory/v1/users/joe.blogs#mydomain.com?alt=json returned "Not Authorized to access this resource/api">
My first thought was because I haven't turned on this API on the administrators console (Google API's console) but I have. (Actually I turned on the Admin SDK and not the Directory API because there is no Directory API to turn on and seeing that it's part of the Admin SDK it would work?).
Is there another step I'm missing or have I made a silly mistake somewhere?
Bruce,
you're pretty close.
Couple of items:
If you're using App Engine, need to convert p12 key to pem and strip header
Need to include user with super user credentials (who has permission to do these operations) whom you're impersonating (not the user who is being changed) using the sub= parameter
So full code will look a bit like this:
# domain configuration settings
import domainconfig
f = file(domainconfig.KEY_FILE, "rb") # b reads file in binary mode; not strictly necessary, but safer to avoid strange Windows EOL characters: https://stackoverflow.com/questions/9644110/difference-between-parsing-a-text-file-in-r-and-rb-mode
key = f.read()
f.close()
credentials = SignedJwtAssertionCredentials(
domainconfig.SERVICE_ACCOUNT_EMAIL,
key,
scope = domainconfig.SCOPE,
sub=domainconfig.SUB_ACCOUNT_EMAIL # 'sub' supercedes the deprecated 'prn'
)
http = httplib2.Http()
http = credentials.authorize(http)
directoryservice = build("admin", "directory_v1", http=http)
users = directoryservice.users()
response = users.get(userKey='joe.blogs#mydomain.com').execute()
This should be of help: https://developers.google.com/drive/delegation
When asserting the credentials you need to connect it to the user that is going to be changed. From the link above, note this section:
credentials = SignedJwtAssertionCredentials(SERVICE_ACCOUNT_EMAIL, key,
scope='https://www.googleapis.com/auth/drive', sub=user_email)

How to proceed payment with paypal in django

there is many methods to pay money in the internet and on of those is PayPal.i'm working on django project which i need to integrate paypal so i use this code her in my views :
from paypal.pro.views import PayPalPro
def buy_it_now(request):
item = {
"amt": "10.00",
"inv": "inventory",
"custom": "tracking",
"cancelurl": "http://...",
"returnurl": "http://..."}
kw = {"item": item,
"payment_template": "payment.html",
"confirm_template": "confirmation.html",
"success_url": "/success/"}
ppp = PayPalPro(**kw)
return ppp(request)
but i get this in console :
PayPal Response:
{'ack': 'Failure',
'build': '5715372',
'correlationid': 'd328871dd352',
'l_errorcode0': '10002',
'l_longmessage0': 'Security header is not valid',
'l_severitycode0': 'Error',
'l_shortmessage0': 'Security error',
'timestamp': '2013-05-03T13:10:14Z',
'version': '54.0'}
and i also chek my test account in paypal sandbox, there is no transaction
A 10002 error typically means that you are either not setting the endpoint correctly, the API credentials are not correct, or you do not have permissions to run the API call on the account that you are trying to.
Check your endpoints to make sure they are correct. Make sure your code reflects the sandbox endpoint if trying to point towards the sandbox, and live if trying to run a transaction on the live site.
Check your API credentials, re copy them over to your code. Make sure you do not have any type of white space before or after your credentials. Also make sure if you are trying to point to the live site that you are passing over your live credentials and not your sandbox credentials, and vise verse. The live credentials and sandbox credentials will not be the same.
If you are trying to process the API call on another account, make sure they have granted 3rd party access permissions to your API in their account. Make sure they have granted the correct permissions to your API username.
Make sure that you are not passing across a variable "SUBJECT" and populating it with an email address. You would only do this if you are trying to run an API on that account, and not your own that your API credentials were generated for. This would be what you used for 3rd party access mentioned in step 3.