Setup translator

What is a translator and why do I need it?

A translator acts as a standardization component between Platform of Trust and applications using the platform to fetch information from data sources. The translator MUST validate the signature coming from the Platform of Trust Data Broker, as well as the headers.

Below is a Python example on verifying the headers and the signature:

Please note that if you're using a framework, such as Bottle, the headers work using whichever case you want, but if you're not using a framework, you need to lowercase the header name in the request, e.g. 'X-Pot-Signature'.lower() to make the validation pass.

import base64
import json
import settings
from jwt.algorithms import RSAAlgorithm

# List of supported headers, and if they're required or not.
SUPPORTED_HEADERS = {
    'X-Pot-Signature': {
        'required': True
    },
    'X-Pot-Token': {
        'required': False
    },
    'X-Pot-App': {
        'required': True
    }
}

# Get the request headers from the request object.
headers = dict()
# Get the body from the request json body.
body = dict()

# Keep the Platform of Trust public key e.g. in the settings.
public_pem = settings.POT_PUBLIC_KEY

# Loop through the request headers and validate them.
for header, rules in SUPPORTED_HEADERS.items():
    header = header.lower()
    if rules['required'] and header not in headers:
        raise Exception(
            f'Missing required header "{header}"'
        )

# Convert the request body to a string, sorting the keys, without 
# indentation and using separators comma (,) and colon (:)
payload_string = json.dumps(
    body,
    sort_keys=True,
    indent=None,
    separators=(',', ': ')
).strip()

alg_obj = RSAAlgorithm(RSAAlgorithm.SHA256)  # RSA SHA256 signature
key = alg_obj.prepare_key(public_pem)
# Verify the payload and signature against the Platform of Trust public 
# key
is_valid = alg_obj.verify(
    payload_string.encode('utf-8'), 
    key, 
    base64.b64decode(headers['X-Pot-Signature'].encode('utf-8'))
)
if not is_valid:
    raise Exception('Signature validation failed')

When creating a translator, the standardization of the response is done using JSON-LD contexts. Please define the contexts with the help of our data modeling experts.

Two contexts MUST be defined:

  • Parameter context
  • Data context

The parameter context defines the parameters that are passed to the translator. In the below example is a JSON payload that is sent to the Platform of Trust Data Broker. The parameter context MUST define what the keys in the parameters object means, and what their type is.

{
  "timestamp": "2019-04-10T14:36:25+00:00",
  "productCode": "unique-product-code",
  "parameters": {
    "apartmentId": 123456
  }
}

The data context MUST define the actual data response that the translator returns.

In the below example the translator returns the actual data in the data object. The data context is the @context within the object. The data context SHOULD define the keys within the data object, e.g. apartmentId, name, rooms, balcony etc.

{
  "@context": "https://standards.oftrust.net/contexts/identity-data_product.jsonld",
  "data": {
    "@context": "https://standards.oftrust.net/contexts/data_product-apartment_data.jsonld",
    "apartmentId": 123456,
    "name": "A 18",
    "rooms": "4",
    "balcony": true
  },
  "signature": {
    "type": "RsaSignature2018",
    "created": "2019-04-10T14:36:40+00:00",
    "creator": "https://example.com/public-key",
    "signatureValue": "eyXDfjefphhfwe...gwrg+wgr0=="
  }
}

The signature information MUST include the signature type, RsaSignature2018, the created timestamp when the data was created, who the creator is (the public key URL used to validate the signature) and the signature value.

The signature is generated by adding a key __signed__ in the data object and using the corresponding private key to generate the signature.

Python example of how to sign the data:

import base64
import json
import settings
from copy import copy
from datetime import datetime, timezone
from jwt.algorithms import RSAAlgorithm

# This is the private key matching the `creator` public key.
private_pem = settings.PRIVATE_KEY

# Get the created_at timestamp in RFC3339 format
created_at = str(datetime.now(timezone.utc).isoformat(timespec='seconds'))

# The data contains the actual data to return to the application.
data = {
    "@context": "https://standards.oftrust.net/contexts/data_product-apartment_data.jsonld",
    "apartmentId": 123456,
    "name": "A 18",
    "rooms": "4",
    "balcony": True
}

# Make a copy of the data, and add the __signed__ key to it
sign_data = copy(data)
sign_data['__signed__'] = created_at

# Create a string of the data, sort the keys, without indentation and 
# separators comma (,) and colon (:)
payload_hash = json.dumps(
    sign_data,
    sort_keys=True,
    indent=None,
    separators=(',', ': ')
).strip()

alg_obj = RSAAlgorithm(RSAAlgorithm.SHA256)  # RSA SHA256 signature
key = alg_obj.prepare_key(private_pem)
signature = alg_obj.sign(payload_hash.encode('utf-8'), key)

# Base64 encode the signature value and use it in the response
# `signatureValue`
signature_value = base64.b64encode(signature).decode("utf-8")

When the translator is implemented, using any of the below mentioned methods, you need to register the data product in Platform of Trust, see the Data Product Guide.

Use Python skeleton code as base

The first option is to build and setup a translator component yourself. You can use our Python based translator to start from, see our GitHub for the repository at https://github.com/PlatformOfTrust/translator-skeleton-python

The translator skeleton code should be pretty straight forward. It's a Python Bottle application that you can host anywhere you want. The most important parts to check are:

  • routes.py -- Includes the routes for the API, default route is POST /fetch
  • controllers.py -- Includes the controller actions for the endpoints. The Translator.fetch() endpoint defines the arguments to be sent to the translator, e.g. timestamp, productCode, parameters. The parameters should be updated to match the parameters defined in the parameter context.
  • services.py -- Includes the actual fetching of the data, mainly the function get_data() MUST be implemented. You can use any other means to populate the data, e.g. request data from another data source. Manipulate the response data to comply with the data context.

Please read the comments carefully when using the skeleton code as base.

Buy as a service

The second option is to buy the translator as a service from IT provider such as Tunninen.

Create an account and get started!

If you are an application developer, it might be a good idea to read the Application Development Guide first.

If you are integrating data and creating data products, take a look at the Data Product Guide.

The open sandbox is your friend! It's an isolated environment for testing applications and data product integrations. Read more from Sandbox Guide.