Using additional Google APIs in my Glassware (Sharing to g+ accounts) - python

I'm trying to share a card ( the html inside it ) from my Glassware using python and the Python Mirror API Quickstart code.
creds = StorageByKeyName(Credentials, '#####', 'credentials').get()
plus_service = util.create_service('plus', 'v1', creds)
moment = {"type":"http://schemas.google.com/AddActivity",
"target": {
"id": "target-id-1",
"type":"http://schemas.google.com/AddActivity",
"name": "The Google+ Platform",
"description": "A page that describes just how awesome Google+ is!",
"image": "https://developers.google.com/+/plugins/snippet/examples/thing.png"
}
}
google_request = plus_service.moments().insert(userId='me', collection='vault', body=moment)
result = google_request.execute()
I got this response back:
HttpError: <HttpError 403 when requesting https://www.googleapis.com/plus/v1/people/me/moments/vault?alt=json returned "Insufficient Permission">
I can understand that is a permission problem but my question is, what is the suggested UI to ask to a glass user for G+ permissions?
Furthermore, by adding "https://www.googleapis.com/auth/plus.login" in the requested permissions I got this:
https://www.googleapis.com/plus/v1/people/me/moments/vault?alt=json returned "Unauthorized">
Thanks in advance

To get G+ access, you can piggyback on the authorization process that Mirror API uses. Make the following modifications to the Mirror API Python Quickstart project:
First, enable the Google+ API in the Google API Console for your project.
Second, in oauth/hander.py, add your G+ scope to the SCOPES list:
SCOPES = ('https://www.googleapis.com/auth/glass.timeline '
'https://www.googleapis.com/auth/glass.location '
'https://www.googleapis.com/auth/userinfo.profile '
'https://www.googleapis.com/auth/plus.login')
Third, revoke your old auth tokens and get fresh ones. Do this by signing out of and signing back into your Quickstart instance's web front end. When you sign in, the sign in page should be updated to list the new Google+ permission:
With these steps, the code you posted should work. Comment if it doesn't and I can help you continue debugging.

Related

Unexpectedly got ACCESS_TOKEN_SCOPE_INSUFFICIENT response when making a request using the Google Chat API

I'm working on a simple "bot" using the Google Chat API and I'm unable to send a message in a chat group.
In the Google Cloud Console I created a project, enabled the Google Chat API, generated OAuth credentials and in the OAuth consent screen I added this scope:
.../auth/chat
by manually entering:
https://www.googleapis.com/auth/chat
EDIT:
Initially I tried to enter the documentation suggested scope:
https://www.googleapis.com/auth/chat.bot
but it was not accepted and I got and error for an invalid scope from the cloud console interface. Also to double check this, when I tried to put it in a request I got the same error in the browser, so I came up with the working scope simply by trial and error.
Then I downloaded the credentials from the Credentials section:
client_secret_XXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com.json
Locally I'm using a Google.py python wrapper around the google python modules, located here:
https://learndataanalysis.org/google-py-file-source-code/
I made a slight change in this file to use console authentication instead of browser:
flow.run_local_server() -> flow.run_console()
My code looks like this:
from Google import Create_Service
CLIENT_SECRET_FILE = 'client_secret_XXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com.json'
API_NAME = 'chat'
API_VERSION = 'v1'
SCOPES = ['https://www.googleapis.com/auth/chat']
service = Create_Service(CLIENT_SECRET_FILE, API_NAME, API_VERSION, SCOPES)
text = 'My message'
space = 'spaces/XXXXXXXXXX'
chat_metadata = {
'text': text,
}
service.spaces().messages().create(parent=space, body=chat_metadata).execute()
The Create_Service call guides me through a console authorization, e.g.:
Please visit this URL to authorize this application: https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=...
Enter the authorization code:
After I visit the link, I receive a token, which I enter in the console and I get this Python error:
googleapiclient.errors.HttpError: <HttpError 403 when requesting https://chat.googleapis.com/v1/spaces/XXXXXXX/messages?alt=json returned "Request had insufficient authentication scopes.". Details: "[{'#type': 'type.googleapis.com/google.rpc.ErrorInfo', 'reason': 'ACCESS_TOKEN_SCOPE_INSUFFICIENT', 'domain': 'googleapis.com', 'metadata': {'service': 'chat.googleapis.com', 'method': 'google.chat.v1.ChatService.CreateMessage'}}]">
Apparently I supplied the chat scope with the request and also, as I mentioned, I made sure it is added to the OAuth consent screen. I couldn't find any other chat scopes.
Is there some other step I'm missing or are there any other scopes, which I should use for creating messages?
BTW, because I noticed that in the Google Cloud Console this scope's description is "View and manage chat conversations and user state" and doesn't explicitly mention "creating", I tried other calls like: service.spaces().get(), or service.spaces().members().list(). However I still got the same error.
I should also mention that the same approach works fine with the Google Drive API for creating files, folders, creating permissions, etc.
I appreciate any help with this.
Always check the documentation for the method it will tell you what scope of permissions it needs.
In this case Method: spaces.messages.create needs the following scope.
Which is not the one you are using.

Power BI Rest API Requests Not Authorizing as expected

I have built a python application to access read only Power BI Rest API’s. I am automating the collection of tenant activity. However despite configuring my Azure App and using the service principal to generate an access token, the response I receive from the API request is one of an unauthorised response:
{"error": {"code": "PowerBINotAuthorizedException", "pbi.error": {"code":
"PowerBINotAuthorizedException", "parameters": {}, "details": [], "exceptionCulprit": 1}}}
I have found a number of similar issues posted online, however feel that I have done everything that is suggested but am still not able to get it working. I would appreciate any guidance.
The steps that I have taken are:
Configured an Azure App, adding the Application Permission for Power Bi Service-Tenant.Read.All
Screenshot of App Settings in Azure Portal
Requested my access token based upon the Client Credentials Flow using my app's client_ID and client_Secret as documented in the below link:
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
I successfully receive a token using the script below:
import requests
azureTenantID = "xxxxxxxxxxxxxxxxx"
azureClientId = "xxxxxxxxxxxxxxxxx"
azureClientSecret = "xxxxxxxxxxxxxxxxxx"
url = f"https://login.microsoftonline.com/{azureTenantID}/oauth2/v2.0/token"
payload = {
"grant_type": "client_credentials",
"client_id": azureClientId,
"client_secret": azureClientSecret,
"scope": "https://analysis.windows.net/powerbi/api/.default"
}
# Header HAS to be x-www-form-urlencoded for MS to accept it.
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
# Return POST content as JSON.
r = requests.post(url, data=payload, headers=headers).json()
# Grab the access token.
response = r.get("access_token")
# Concatenate with Bearer string
access_token = "Bearer {r['access_token']}"
Configured my Power BI Tenant Settings to enable Service Principals to use API's.
Screenshot of Admin API Setting
Screenshot of Developer API Setting
Note that I added the Service Principal as a member of the Security Group for which both of these settings are enabled
Execute my Get request to the API
The followings script returns a good response when I take an access token from the Power BI REST API Documentation's 'Try it out' feature, but not when I generate the token as above.
import requests
# Initialise parameters.
url = "https://api.powerbi.com/v1.0/myorg/admin/groups?$top=1000&$expand=datasets,dataflows,reports,users,dashboards"
headers = {'Authorization': get_access_token2()}
# Get response.
response = requests.get(url, headers=headers)
response = response.json()
Any assistance would be appreciated !
I just went through this exact scenario that you described, and in the end we had to engage Microsoft support to solve it.
Although extremely counter intuitive, if the app that you create for your service principal authentication has any Power BI permissions assigned to it then the access token that is generated (when passed to Power BI REST Admin API) will return an error response that reports PowerBINotAuthorizedException.
To be even more specific, if the access token that you pass to the Power BI API has a roles key/value pair, then you will get a PowerBINotAuthorizedException.
In your case, the issue is easier because you have listed out what permissions you granted. You mentioned that you Configured an Azure App, adding the Application Permission for Power Bi Service-Tenant.Read.All. In order to resolve this issue, you will need to remove that permission.
For future readers, you can troubleshoot this by decoding your access token using a JWT token decoder like one found at jstoolset.com. If your app has permissions allocated to the scope that you have requested (https://analysis.windows.net/powerbi/api/.default is the typical Power BI scope that you request in your authorization) and you decode your JWT token then you will see a roles key/value pair. The presence of this roles is essentially the issue. It does not matter that the values there might match up to the Required Scope in the Power BI REST Admin API documentation. It was described to us as if there is a roles value in your access token then when the token is presented to the Power BI API the roles that are granted are attempted to be used, which ultimately results in a PowerBINotAuthorizedException because service principals are not allowed to use a certain role.
If you have an app that you have removed all permissions from, but still has a value coming through in your access token for the roles key/value pair, then I would suggest starting with a new app with no permissions allocated to it, and simply add the new app to the existing security group that you originally created. This is how we realized that this truly was the issue, and were then able to reconcile from there.
EDIT: Microsoft has now updated their API documentation on the relevant endpoints to reflect this information. For example, in Admin - Groups GetGroupUsersAsAdmin the Required Scope now reads:
Tenant.Read.All or Tenant.ReadWrite.All
Relevant only when authenticating via a standard delegated admin access token. Must not be present when authentication via a service principal is used.

Python connection to OneDrive - Unauthorized Access

Here's my problem:
I have a 365 Family OneDrive subscription with 3 members, my account being the admin.
I am trying to build a python application to read/extract the content of the files I have on this onedrive space based on specific criterias. I want to build it as a command line application, running locally on my PC. I am aware some tools may exist for this but I'd like to code my own solution.
After going through tons of different documentation, I ended up doing the following
Registered my application on the Azure portal
Granted some permission on the Microsoft Graph API (User.read, Files.Read and Files.ReadAll)
Created a secret
Grabbed the sample code provided by Microsoft
Replaces some variables with my Client_Id and Secret
Ran the code
The code returns an access token but the authorization requests fails with 401 - Unauthorized: Access is denied due to invalid credentials.
Here's the Python code I'm using.
import msal
config = {
"authority": "https://login.microsoftonline.com/consumers",
"client_id": "<my client ID>",
"scope": ["https://graph.microsoft.com/.default"],
"secret": "<My secret stuff>",
"endpoint": "https://graph.microsoft.com/v1.0/users"
}
# Create a preferably long-lived app instance which maintains a token cache.
app = msal.ConfidentialClientApplication(
config["client_id"], authority=config["authority"],
client_credential=config["secret"],
)
result = None
result = app.acquire_token_silent(config["scope"], account=None)
if not result:
result = app.acquire_token_for_client(scopes=config["scope"])
if "access_token" in result:
# Calling graph using the access token
graph_data = requests.get( # Use token to call downstream service
config["endpoint"],
headers={'Authorization': 'Bearer ' + result['access_token']}, ).json()
print("Graph API call result: ")
print(json.dumps(graph_data, indent=2))
else:
print(result.get("error"))
print(result.get("error_description"))
print(result.get("correlation_id")) # You may need this when reporting a bug
According to the error message, I'm obviously missing something in the authorization process but can't tell what. I'm not even sure about the Authority and Endpoints I should use. My account being a personal one, I have no tenant.
Do I need to set-up / configure some URI somewhere?
Any help would be welcome.
Thank you in advance.
In your client app you need to store the token that you are getting from the MSAL. and then send the token with an authorized request.
For OneDrive, download the OneDrive for python. You can see the different option for Authentication.
The reason you are getting an access token, ID token, and a refresh token is because of the flow you're using. My suggestion is to review the flows for a better understanding of how the authentication process works and what will be returned accordingly. You can use this MSAL library for python.

YouTube API without user OAuth process

I am trying to fetch captions from YouTube video using YouTube Data API (v3)
https://developers.google.com/youtube/v3/guides/implementation/captions
So, first I tried to retrieve a captions list using this url:
https://www.googleapis.com/youtube/v3/captions?part=snippet&videoId=KK9bwTlAvgo&key={My API KEY}
I could retrieve the caption id that I'd like to download (jEDP-pmNCIqoB8QGlXWQf4Rh3faalD_l) from the above link.
Then, I followed this instruction to download the caption:
https://developers.google.com/youtube/v3/docs/captions/download
However, even though I input the caption id and my api key correctly, it shows "Login Required" error.
I suppose I need OAuth authentication, but what I am trying to do is not related to my users's account, but simply downloading public caption data automatically.
My question is: Is there any way to process OAuth authentication just once to get an access token of my own YouTube account and then reuse it whenever I need it in my application?
I can't speak to the permissions needed for the captions API in particular, but in general, yes, you can OAuth to your app once using your own account and use the access and refresh tokens to make subsequent OAuth'd requests to the API. You can find the details of generating tokens here:
https://developers.google.com/youtube/v3/guides/auth/server-side-web-apps#Obtaining_Access_Tokens
To perform the steps manually (fortunately, you only need to do this once):
If access has already been granted for an app, it needs to be removed so that new auth credentials can be established. Go to https://security.google.com/settings/security/permissions (while logged into your account) and remove access to the app. If the client ID or secret change (or you need to create one), find them at https://console.developers.google.com under API Manager.
To grant access and receive a temporary code, enter this URL in a browser:
https://accounts.google.com/o/oauth2/auth?
client_id=<client_id>&
redirect_uri=http://www.google.com&
scope=https://www.googleapis.com/auth/youtube.force-ssl&
response_type=code&
access_type=offline&
approval_prompt=force
Follow the prompt to grant access to the app.
This will redirect to google.com with a code parameter (e.g.,
https://www.google.com/?code=4/ux5gNj-_mIu4DOD_gNZdjX9EtOFf&gws_rd=ssl#). Save the code.
Send a POST request (e.g., via Postman Chrome plugin) to https://accounts.google.com/o/oauth2/token with the following in the request body:
code=<code>&
client_id=<client_id>&
client_secret=<client_secret>&
redirect_uri=http://www.google.com&
grant_type=authorization_code
The response will contain both an access token and refresh token. Save both, but particularly the refresh token (because the access token will expire in 1 hour).
You can then use the access token to send an OAuth'd request manually, following one of the options here, essentially:
curl -H "Authorization: Bearer ACCESS_TOKEN" https://www.googleapis.com/youtube/v3/captions/<id>
or
curl https://www.googleapis.com/youtube/v3/captions/<id>?access_token=ACCESS_TOKEN
(When I tried the second option for captions, however, I got the message: "The OAuth token was received in the query string, which this API forbids for response formats other than JSON or XML. If possible, try sending the OAuth token in the Authorization header instead.")
You can also use the refresh token in your code to create the credential needed when building your YouTube object. In Java, this looks like the following:
String clientId = <your client ID>
String clientSecret = <your client secret>
String refreshToken = <refresh token>
HttpTransport transport = new NetHttpTransport();
JsonFactory jsonFactory = new JacksonFactory();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(transport)
.setJsonFactory(jsonFactory)
.setClientSecrets(clientId, clientSecret)
.build()
.setRefreshToken(refreshToken);
try {
credential.refreshToken();
} catch (IOException e) {
e.printStackTrace();
}
youtube = new YouTube.Builder(transport, jsonFactory, credential).build();
I imagine you can do something similar in Python with the API Client Libraries, although I haven't tried Python.

inserting a moment into g+ from python

My question is very similar to moment.insert from python code but Fausto has already managed to get further than me so I'm unable to apply that answer at this time.
My code looks like this, and my question is "how do I post to my g+ profile"
user = 'awarner#######.com'
key = open(keyFile, 'r').read()
credentials = SignedJwtAssertionCredentials(
'##########developer.gserviceaccount.com',
key,
scope='https://www.googleapis.com/auth/plus.login',
sub=user)
http = httplib2.Http()
http = credentials.authorize(http)
service = build(serviceName='plus', version='v1', http=http)
# Not really required here but these calls demonstrate that we are authenticated
searchResults = service.people().search(query='AlistairWarner').execute()
listMoments = service.moments().list(userId='me', collection='vault').execute()
moment = { "type" : "http://schemas.google.com/AddActivity",
"target" : {
"id" : "target-id-1",
"type" : "http://schemas.google.com/AddActivity",
"name" : "The Google+ Platform",
"description" : "A page that describes just how awesome Google+ is!",
"image" : "https://developers.google.com/+/plugins/snippet/examples/thing.png"
}
}
# This call fails with a "401 Unauthorized" error
insertResults = service.moments().insert(userId='me', collection='vault', body=moment).execute()
Just for the record I have confirmed that I have correctly delegated domain-wide authority to my service account for the necessary scopes - although I don't believe that this is really an authorization issue per se because the list call is working. I have also confirmed that my "App" appears on my g+ profile under Apps as mentioned by Prisoner in response to Fausto's question (referenced above).
Looking at the API reference page it does warn that you will get the 401 error that I am seeing. It, and other posts here on StackOverflow (I'd give the ref but not allowed to), say that you need to add the data-requestvisibleactions (again, can't give ref) attribute but I am struggling to see the relevance of the Sign-In Button that they describe attaching it to in my code - which has no UI, I just run it from the command line. So, requestvisibleactions does seem to be the best candidate, I just don't know how to translate that into something that I can use here.
Does anyone have a clue how to get this working?
You need to add this as part of your OAuth request credentials. So it might look something like
credentials = SignedJwtAssertionCredentials(
'##########developer.gserviceaccount.com',
key,
scope='https://www.googleapis.com/auth/plus.login',
requestvisibleactions='http://schemas.google.com/AddActivity',
sub=user)

Categories

Resources