Fully qualified link

Introduction

This documentation will walk you through the creation of fully qualified links, allowing you to grant access to the contract.fit web application by sending a "fully qualified link".

The goal of the following example will be to generate a "fully qualified link" to a specific document review page.

This doc is also available as an interactive Jupyter notebook:

Technical description

The fully-qualified link embeds authentication and authorization in a randomly generated API-Key
that you can then share to end users who don't have an account on the contract.fit web application.
They will be granted temporary access to a given resource, scoped by the roles you have defined.

Their session is short-lived and derived from the API-Key; when their session expires
they can reopen the fully qualified link to obtain a new short-lived session.

API-Key

To make this flow possible, every time you need to create a fully qualified link you must call
the contract.fit API with the roles (permissions) you want. In return,
you get back the matching API-Key for this scope that you then can embed in a link.

An API-Key is revocable at any time using the Admin API, can be created with a baked-in
expiration date and must be bound to a dedicated User in our database.

For security reasons and consistency you cannot alter the roles of an API-Key after its creation:
you need to create a new one and delete the previous one.

WARNING: the API-Key will allow the end user to perform ANY action authorized by the roles you
set in the API-Key: be careful about the roles and permissions you set!

User

The API-Key User will be used in all User references (for instance lock info) and
must not be deleted: It is a placeholder user for all the end users who don't have an
account on our dashboard. The password authentication of this user - even if possible - is never used.

Roles

To access the review page in the contract.fit web application you need to grant 2 different roles to the API-Key so that the user has access to all required functions:

  1. One Role which grants access to view_list but no access to review nor submit

  2. One Role which grants access to review and submit

The first role will be used to access inbox related settings and data, and the second one will restrict the
user to a single document if its scope is a document. By using inbox as scope, the user will be granted the
review role on all doc in the inbox.

If you grant review and submit to the first role, the user will have access to all docs in the inbox

 

 

In this diagram:

  • User john is a regular User with an account

  • User guest is the placeholder User for the API-Keys

The permissions will be the following:

  • Since John has a regular account, his user roles allow him to access inbox A & B

  • Using hPym5P API-Key, only inbox A and the document 1 will be accessible - not document 2

  • Using doCWk4 API-Key, only inbox A and the document 2 will be accessible - not document 1

  • Using RAck9j API-Key, only inbox B but no document will be accessible

The placeholder user must also have the permissions set in the API-Keys: adding the same 2 roles is enough

Flow example

  • on your dashboard, add a button "Go to document review" on the doc page

  • on click, a call is made to your back-end

  • your back-end calls contract.fit API and obtains a new API-Key scoped to the specific document

  • your back-end generates the fully qualified link to the doc

  • your back-end redirects the user to the contract.fit web application using the link

  • your end user obtains a session derived from the API-Key roles and is logged in

  • after review, your end user returns to your dashboard and mark the doc as done

  • your back-end revokes the API-Key to avoid abuse

  • the API-Key cannot be used anymore

Note

Although currently incomplete, you can check out the OpenAPI documentation about API-Key endpoints.

Initial setup

You will need the following:

  • a User on your tenant with enough permissions to set up the authentication

    • permissions: edit_backend_settings and view_api_keys (global, not inbox scoped)

    • this user is only used to make the setup calls, you must not use it as the base user for the API-Key!

  • a dedicated User on your tenant who will be bound to your API-Key

  • at least 2 roles to grant to the end user

    • one Role for the inbox related permissions

    • one Role for the review related permissions

import requests from secrets import token_urlsafe from getpass import getpass tenant = "https://alfredo.contract-q.fit" admin = "timothe@contract.fit" password = getpass(f"User {admin} password: ") sess = requests.Session() res = requests.post(f"{tenant}/admin/auth", json={ "username": admin, "password": password }) res.raise_for_status() jwt = res.json()["authentication_token"] sess.headers = dict(Authorization=f"Bearer {jwt}")

Get inbox

You need to have the inbox ID, used in the roles declaration

res = sess.get(f"{tenant}/admin/inboxes") res.raise_for_status() inboxes = res.json() invoice_inbox = next(filter(lambda x: x["name"] == "invoice", inboxes))

Create roles

One time only: use / create roles with as little permissions as possible.

We'll create 2 roles:

  1. InboxGuest granting only the view_list read_thresholds_settings and edit_format_settings permissions

  2. ReviewGuest granting review & submit.

You can grant additional permissions, but be careful not to give too broad permissions

from functools import partial def create_or_update_role(name, perms): """ Create the role named *name* or update its permissions with the ones provided as *perms* """ r = sess.get(f"{tenant}/admin/roles") r.raise_for_status() try: role = next(filter(lambda x: x["name"] == name, r.json())) except StopIteration: func = partial(sess.post,f"{tenant}/admin/roles") else: func = partial(sess.patch, f"{tenant}/admin/roles/{role['id']}") r = func(json={"name": name, "permissions": perms }) r.raise_for_status() return r.json() permissions = { "view_list": True, "read_thresholds_settings": True, "edit_format_settings": True } rolename = "InboxGuest" inbox_role = create_or_update_role(rolename, permissions) permissions = { "review": True, "submit": True, "escalate_document": True, } rolename = "ReviewGuest" review_role = create_or_update_role(rolename, permissions)

Create dedicated user

One time only: create the guest placeholder user. All end users using the API-Keys will be seen as this user.

The User must have equal or broader role and permissions than the API-Keys will require.

Create API-Key

Each time you need to create a fully qualified link with a new scope (ie. new doc),
you need to create a new key with the new scopes.

Each API-Key has:

  • an expire_at attribute indicating the expiration date of the Key

  • a roles list of role scope

  • an active flag

Roles

Important: you cannot change the roles after the API-Key creation!

If you want to limit the API-Key to a single doc, you can specify the document scope but you also need to specify the
inbox scope, so that your enduser has access to all required features for the review page.

If you want to change the roles, just create a new API-Key and delete the old one.

To give access to a specific document you must also specify the inbox in another role definition; you can add multiple independent roles, each one giving access to a single inbox or a document.

Now you can share a fully qualified link to your end users: they will be granted temporary access to
the review page for the given document.

To pass the API-Key to the contract.fit web application, you need to pass it in the
querystring parameter named nonce :

Based on my example variables, the link will thus look like:


https://alfredo.contract-q.fit/#/view/60ddb937b260064252bd5a98?nonce=5a1e2351e492f360fac51d98985b64281ad52f3661776cbb6dacd28982f318d8