Comment on page
Google Cloud Functions
An example integration where a Data Connector forwards events to a Google Cloud Function.
This example uses a Data Connector to forward the events of all devices in a project to a Google Cloud Function. When receiving the HTTPS POST request, our function will verify both the origin and content of the request using a signature secret, then decode the data.
The following points are assumed.
While there are many advantages to using a local environment for development, this guide will be using the browser portal to minimize setup requirements.
Python 3.11
Python API
Node.js 20
Go 1.20
- Environment: 2nd gen
- Function name: As desired.
- Region: As desired.
- Authentication: Allow unauthenticated invocations.
Runtime, build, connections, and security settings
Add a new runtime environment variable with the following values.
- Name:
DT_SIGNATURE_SECRET
- Value: A unique password. We will need it later, so write it down.
- Runtime: Python 3.11
- Entry point:
dataconnector_endpoint
In the Source Code, edit main.py with the following snippet. The implementation is explained in detail on the Data Connector Advanced Configurations page.
main.py
import os
import hashlib
import jwt
import functions_framework
# Fetch environment variable secret.
SIGNATURE_SECRET = os.environ.get('DT_SIGNATURE_SECRET')
def verify_request(body, token):
"""
Verifies that the request originated from DT and that the body
hasn't been modified since it was sent. This is done by verifying
that the checksum field of the JWT token matches the checksum of the
request body and that the JWT is signed with the signature secret.
"""
# Decode the token using signature secret.
try:
payload = jwt.decode(token, SIGNATURE_SECRET, algorithms=["HS256"])
except Exception as err:
print(err)
return False
# Verify the request body checksum.
m = hashlib.sha1()
m.update(body)
checksum = m.digest().hex()
if payload["checksum"] != checksum:
print('Checksum Mismatch')
return False
return True
@functions_framework.http
def dataconnector_endpoint(request):
# Extract the body as a byte string and the signed JWT.
# We'll use these values to verify the request.
payload = request.get_data()
token = request.headers['x-dt-signature']
# Verify request origin and content integrity.
if not verify_request(payload, token):
return ('Could not verify request.', 400)
# Decode the body as JSON
body = request.get_json(silent=True)
# Fetch some information about the event, then print.
# You can implement your own logic here as desired.
event_type: str = body['event']['eventType']
device_type: str = body['metadata']['deviceType']
device_id: str = body['metadata']['deviceId']
print(f'Got {event_type} event from {device_type} device {device_id}.')
return ('OK', 200)
Edit requirements.txt with the following entries.
requirements.txt
functions-framework==3.*
pyjwt==2.7.0
- Environment: 2nd gen
- Function name: As desired.
- Region: As desired.
- Authentication: Allow unauthenticated invocations.
Runtime, build, connections, and security settings
Add a new runtime environment variable with the following values.
- Name:
DT_SIGNATURE_SECRET
- Value: A unique password. We will need it later, so write it down.
- Runtime: Python 3.11
- Entry point:
dataconnector_endpoint
In the Source Code, edit main.py with the following snippet.
For details, read our Python API Documentation.
main.py
import os
import functions_framework
from dtintegrations import data_connector, provider
@functions_framework.http
def dataconnector_endpoint(request):
# Validate and decode the incoming request.
payload = data_connector.HttpPush.from_provider(
request=request,
provider=provider.GCLOUD,
secret=os.getenv('DT_SIGNATURE_SECRET'),
)
# Print the payload data.
print(payload)
# If all is well, return a 200 response.
return ('OK', 200)
Edit requirements.txt with the following entries.
requirements.txt
functions-framework==3.*
dtintegrations==0.5.1
- Environment: 2nd gen
- Function name: As desired.
- Region: As desired.
- Authentication: Allow unauthenticated invocations.
Runtime, build, connections, and security settings
Add a new runtime environment variable with the following values.
- Name:
DT_SIGNATURE_SECRET
- Value: A unique password. We will need it later, so write it down.
- Runtime: Node.js 20
- Entry point:
dataconnectorEndpoint
In the Source Code, edit index.js with the following snippet. The implementation is explained in detail on the Data Connector Advanced Configurations page.
index.js
const crypto = require('crypto')
const jwt = require('jsonwebtoken')
const functions = require('@google-cloud/functions-framework')
// Fetch environment variables
const signatureSecret = process.env.DT_SIGNATURE_SECRET
// Verifies that the request originated from DT, and that the body
// hasn't been modified since it was sent. This is done by verifying
// that the checksum field of the JWT token matches the checksum of the
// request body, and that the JWT is signed with the signature secret.
function verifyRequest(body, token) {
// Decode the JWT, and verify that it was
// signed using the signature secret.
let decoded
try {
decoded = jwt.verify(token, signatureSecret)
} catch(err) {
console.log(err)
return false
}
// Verify the request body checksum.
let shasum = crypto.createHash('sha1')
let checksum = shasum.update(body).digest('hex')
if (checksum !== decoded.checksum) {
console.log('Checksum Mismatch')
return false
}
return true
}
functions.http('dataconnectorEndpoint', (req, res) => {
// Extract the body as a string and the signed JWT.
// We'll use these values to verify the request.
let payload = JSON.stringify(req.body)
let token = req.get('X-Dt-Signature')
// Validate request origin and content integrity.
if (verifyRequest(payload, token) === false) {
res.sendStatus(400)
return
}
// First, check if the event type is one of the event
// types we're expecting.
// As an example, we'll check for touch events here.
switch (req.body.event.eventType) {
case 'touch':
// Now that we know this is a device event, we can
// check for the device type and device identifier
// in the event metadata.
deviceType = req.body.metadata.deviceType
deviceId = req.body.metadata.deviceId
console.log(`Received touch event from ${deviceType} sensor with id ${deviceId}`)
break
default:
break
}
res.sendStatus(200);
});
Edit package.json to contain the following dependencies field.
package.json
{
"dependencies": {
"@google-cloud/functions-framework": "^3.0.0",
"jsonwebtoken": "^9.0.1"
}
}
- Environment: 2nd gen
- Function name: As desired.
- Region: As desired.
- Authentication: Allow unauthenticated invocations.
Runtime, build, connections, and security settings
Add a new runtime environment variable with the following values.
- Name:
DT_SIGNATURE_SECRET
- Value: A unique password. We will need it later, so write it down.
- Runtime: Go 1.20
- Entry point:
DataconnectorEndpoint
In the Source Code, edit function.go with the following snippet. The implementation is explained in detail on the Data Connector Advanced Configurations page.
function.go
package dconendpoint
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"github.com/GoogleCloudPlatform/functions-framework-go/functions"
jwt "github.com/golang-jwt/jwt/v5"
)
func init() {
functions.HTTP("DataconnectorEndpoint", DataconnectorEndpoint)
}
// Environment variables.
var signatureSecret = os.Getenv("DT_SIGNATURE_SECRET")
// verifyRequest verifies that the request originated from DT and that the
// body hasn't been modified since it was sent. This is done by verifying
// that the checksum field of the JWT token matches the checksum of the
// request body, and that the JWT is signed with the signature secret.
func verifyRequest(bodyBytes []byte, tokenString string) error {
// Decode the JWT, and verify that it was signed using the signature secret.
// Also verifies the algorithm used to sign the JWT.
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Return out signature secret to verify that it was used to sign the JWT.
return []byte(signatureSecret), nil
}, jwt.WithValidMethods([]string{"HS256"}))
if err != nil {
return err
}
// Verify the request body checksum.
sha1Bytes := sha1.Sum(bodyBytes)
sha1String := hex.EncodeToString(sha1Bytes[:])
claims := token.Claims.(jwt.MapClaims)
if sha1String != claims["checksum"] {
return fmt.Errorf("Checksum mismatch.")
}
return nil
}
// DataConnectorEndpoint receives, validates, and returns a response for the forwarded event.
func DataconnectorEndpoint(w http.ResponseWriter, r *http.Request) {
// Extract the body and the signed JWT.
// We'll use these values to verify the request.
tokenString := r.Header.Get("x-dt-signature")
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}