Woody
Woody

Reputation: 51

Google Actions Push Notification with Python?

I'm trying to figure out how to initiate a push notification using DialogFlow/Google Actions. I've cobbled together some test code, but when I test it, I get a '403 Forbidden' response. Can somebody provide me a good example of how to do this using Python 3.

`

import urllib.request
import json

notification = {
    'userNotification': {
        'title': 'Test Notification',
    },
    'target': {
        'userId': 'user-id here',
        'intent': 'Notification Intent',
        'locale': 'en-US',
    }
}


my_url = 'https://actions.googleapis.com/v2/conversations:send'
access_token = 'access token here'

request = urllib.request.Request(my_url)
request.add_header('Content-Type', 'application/json; charset=utf-8')
payload = { 'auth': { 'bearer': access_token },
             'json': 'true',
             'body': { 'customPushMessage': notification, 'isInSandbox': 
             'true' } };

jsondata = json.dumps(payload)
jsondataasbytes = jsondata.encode('utf-8')
response = urllib.request.urlopen(request, jsondataasbytes)

`

Can anybody provide any suggestions about how to get this to work?

=============================================================

Update: I revised the auth header as suggested and now I'm getting '401:Unauthorized'. I'm not sure if I'm creating the access token properly. Here's how I'm doing it:

I created an RSA256 private key on the Google Console website. I use that key to encode a JWT containing these fields:

{
    "iss": [
        "My Service Name",
        "\"my_service-account@service_name.iam.gserviceaccount.com\""
    ],
    "iat": 1544018697,
    "exp": 1544019898,
    "aud": 
"https://www.googleapis.com/auth/actions.fulfillment.conversation\"",
    "sub": [
        "Push Notification",
        "\"my-service-account@service_name.iam.gserviceaccount.com\""
    ]
}

I don't know if this is correct: the documentation for all of this is hard to pull together.

UPDATE 2:

I modified the code suggested by Prisoner, and I'm now able to get what appears to be a valid access_token. This is the modified code:

from oauth2client.client import GoogleCredentials

service_account_file = 'path-to-service_account_file'
credentials = GoogleCredentials.from_stream(SERVICE_ACCOUNT_FILE)

access_token = credentials.get_access_token().access_token

When I try to use the access token, I'm still getting a '401 Unauthorized' response. Has anybody actually done this? If so, can you give me some specifics on the correct URL to use and how to format the urllib.request message?

Upvotes: 2

Views: 1444

Answers (2)

Woody
Woody

Reputation: 51

Ok, I've figured out how to get the the access_token, so that part of the problem is solved. I've run into another problem getting updates permission working for my test account, but I'm going to post a new question to cover that. I adapted code from another answer and this seems to work:

# Adapted from https://stackoverflow.com/questions/51821919/how-to-send-push-notification-from-google-assistant-through-dialogflow-fulfilmen/51961677#51961677

import io
import json

import requests
from google.oauth2 import service_account
import google.auth.transport.requests

def send_notification(path_to_service_account="default", intent="default"):
    if path_to_service_account == "default":
        PATH_TO_SERVICE_ACCOUNT = 'path to downloaded service account json file from Google Cloud Console'
    else:
        PATH_TO_SERVICE_ACCOUNT = path_to_service_account

    if intent == "default":
        INTENT = 'Notification Intent'
    else:
        INTENT = intent

    REQUIRED_SCOPE = 'https://www.googleapis.com/auth/actions.fulfillment.conversation'
    USER_ID = 'user id here'
    INTENT = 'Your intent name'

    # Get access token
    with io.open(PATH_TO_SERVICE_ACCOUNT, 'r', encoding='utf-8') as json_fi:
        credentials_info = json.load(json_fi)
    credentials = service_account.Credentials.from_service_account_info(
        credentials_info, scopes=[REQUIRED_SCOPE])
    request = google.auth.transport.requests.Request()
    credentials.refresh(request)

    headers = {
        'Authorization': 'Bearer ' + credentials.token
    }

    payload = {
        'customPushMessage': {
            'userNotification': {
                'title': 'App Title',
                'text': 'Simple Text'
            },
            'target': {
                'userId': USER_ID,
                'intent': INTENT,
                # Expects a IETF BCP-47 language code (i.e. en-US)
                'locale': 'en-US'
            }
        }
    }

    r = requests.request("POST", 'https://actions.googleapis.com/v2/conversations:send', data=json.dumps(payload), headers=headers)

    print(str(r.status_code) + ': ' + r.text)

Upvotes: 1

Prisoner
Prisoner

Reputation: 50701

You've placed some things into the body of the request that belong in the header. In particular, the "403 forbidden" suggests that the Authorization header is either wrong or missing, and in your case, it looks like it is missing since you're trying to put it in the body.

The body of what you're sending should just contain the JSON with the customPushMessage attribute.

I'm not very familiar with python, but I think something like this is more what you want:

request = urllib.request.Request(my_url)
request.add_header('Authorization', 'bearer '+access_token)
request.add_header('Content-Type', 'application/json; charset=utf-8')
payload = { 'customPushMessage': notification }

jsondata = json.dumps(payload)
jsondataasbytes = jsondata.encode('utf-8')
response = urllib.request.urlopen(request, jsondataasbytes)

If you continue to get the "403 Forbidden" message - make sure your access_token is current and is actually an access token. Access tokens are created from the service account key, but are not the key itself. They have a limited lifetime (usually 1 hour), while the key is long-lasting.

Update about generating an access token from the keys.

You can't just create and sign a JWT to use as an access token.

The easiest way is to use the Google APIs Client Library for Python, which includes a library to handle OAuth with service accounts.

I haven't tested, but you should be able to do something like this, setting SERVICE_ACCOUNT_FILE to the location of where the keys are stored.

from google.oauth2 import service_account

SCOPES = ['https://www.googleapis.com/auth/actions.fulfillment.conversation']
SERVICE_ACCOUNT_FILE = '/path/to/service.json'

credentials = service_account.Credentials.from_service_account_file(
        SERVICE_ACCOUNT_FILE, scopes=SCOPES)

access_token = credentials.get_access_token()

Upvotes: 1

Related Questions