Ozwillo Documentation v1.8

Ozwillo Documentation

v1.30

Ozwillo is a platform that distributes online services from a variety of SaaS editors. It’s underlying and strategic goal is to forge open, linked and reusable data thanks to the daily use of services.

The Ozwillo platform was formerly known as Oasis, due to the European project it was funded upon.

Ozwillo principles are to remain:

The outline of the documentation is as follows: (1) introduction to Ozwillo architecture and semantics, (2) how to play with Ozwillo APIs — prerequesites, tools and recommendations — (3) thorough description of provisioning services and then (4) enabling user authentication to access these services, (5) introduction to the Datacore API and its live Playground which documents it, (6) reference of the API that has been exhibited in the previous sections.

Contributions are welcome! If you think this documentation could be improved, please open an issue and describe the problem (about completeness, correctness, clarity…).

Follow the doc updates on twitter: @OzwilloDev

Introduction

User interface and portal features

From a user standpoint, the Ozwillo portal acts similarly to a mobile operating system, since it’s possible to:

The main user interface components are:

Except from the store, the portal pages are reserved to registered and logged users. They especially give access to:

Programming interface

From a provider standpoint, adapting to Ozwillo means:

  1. include the application deployment process within the provisioning protocol described below. This is dealt with requests exchanged between Ozwillo REST API and provider endpoints (typically over a REST API too)
  2. if user authentication is needed, delegate it to Ozwillo OpenID Connect Identity Provider
  3. create, update and consume linked and shared data through the Datacore REST API. The easiest way to try this out is to use its live Playground.

In other words, Ozwillo programming interface is made of:

As previously introduced, the accounts subdomain also serves a few web pages: single sign-in, single sign-out, forgotten password…

Ozwillo APIs access is over HTTPS, providers endpoints must be too.

Terminology

We make no asumption regarding what words final users will use between applications or services. It means that one could think “I am launching this app” when another would say “I am accessing this service”.

But when it comes to Ozwillo’s APIs and thus to this documentation, they have a different and precise meaning. From the general to the particular:

Application
an abstract application, not directly usable, declared in the catalog and visible in Ozwillo's store, that can be the object of an instantiation. It's the product you sell;
Application instance
or instance for short, a runnable copy of an application, created for a particular customer (which may be an individual or an organization) after a successful provisioning;
Service
an "endpoint" of an application instance, addressable through a URL (the service URI). Services are declared in the catalog and may or may not be visible in Ozwillo's store. An application instance is made of one or more services.
Desk shortcuts
The icons shown in a user's desk (under the Ozwillo portal) are links to services URIs.

Additional definitions to complement the picture:

(Ozwillo's) catalog
a database of all applications, instances and services. It is used internally by the portal to display a user’s desktop, to browse the app store, etc.;
(Ozwillo's) store
a system that allows users to: find services that are publicly available and add them to their desktops, or find applications that are instantiable through provisioning. The store is the visible surface of the catalog;
Store entry
A visible application or a visible service (but not an application instance, whose visible surface is always a service);
Purchase act
The act of requesting the installation of a story entry (charged or free), done by a portal end user;
Purchaser
A user that performed a purchase act;
App factory
A REST API (implemented by the provider) that Ozwillo can call to initiate the provisioning of an application instance, after a purchase act has occured.

What may be confusing at first is that both abstract — not instantiated — applications and tangible — instantiated — services can be found in the store. From a user standpoint it makes sense: one does not worry about the technical implications behind a purchase act. But since the documentation reader cares, here is the difference:

Visibility vs restricted access

If a store entry is visible, it only means it is publicly referenced and shown under Ozwillo’s store.

A visible:false (hidden) application may be under review or not be available at this time. A hidden service is typically targeted to certain users (depending on who purchased the instance): it is only shown as a desk shortcut for given authorized users, but not listed in the store.

The fact that a service is visible or not in the store is yet separated to how its access is managed, possibly being restricted to certain users. All of these scenarios are possible:

More to come: a neverVisible property for services will likely be introduced in the future. Indeed, there are valid scenarios where services could be updated from visible:false to visible:true over time, when there are others where we want to be sure it won’t happen: it would be the purpose of neverVisible:true.

The Provisioning section shows how these settings are defined.

Authorized users

app_admin vs app_user

The case for restricted:true services is that only specific users are allowed to access and interact with them. By default, the user that purchased the instance is an app_admin, meaning that s/he has the right to add new app_admin or app_user users.

Both app_admin and app_user are considered authorized users of the instance, but an app_user can not add other authorized users. These rules (who can add who) are internal to Ozwillo and frame the organization management under the portal.

As a provider, you will also get this info (is the user an app_admin or app_user) and are free to give it whatever meaning best suits your application behavior. Yet, on application instances side, it’s typical that app_admin maps the role with the highest privileges and is allowed to configure the roles of others (app_user). Then app_user may hold a variety of different user types from the application point of view. Since Ozwillo can not know all business-specific roles of all applications, you can define a finer granularity of users under the global app_user concept linked to Ozwillo.

To sum-up, it is typical (but not imposed) that on the application side:

Now, when your services are accessed by internet users, how do you know they are indeed authorized users (app_admin or app_user)? This will be answered in depth in the User Authentication section. But let’s give a glimpse of it: it will also helps introduce the scope concept.

Authorization in brief

Let’s say an internet user arrives at one of the URIs corresponding to the protected area of your service, typically the service URI entrypoint.

Without Ozwillo, you would check that you know this user (there is an existing session) and if not trigger authentication to check if s/he can identify as an authorized user.

With Ozwillo, you delegate authentication and authorization: Ozwillo is both an identity provider and an authorization server. It means that without a valid session on your server, you redirect the user to Ozwillo authentication page, passing it interesting parameters like:

Let’s focus on the client_id: thanks to it, Ozwillo knows what application instance the user is trying to access. Ozwillo then checks if s/he is indeed a valid app_admin or app_user of this instance, and now there are two cases depending on the service configuration:

There are other reasons for the authentication and authorization to fail: for instance users may refuse to accept the scopes claimed by your instance.

If authentication is successful, your service will be given at the end of the authorization code flow two tokens:

On your server, you may associate a user session with these two tokens to deal with forthcoming requests to Ozwillo APIs, but they must not be leaked client-side.

In short, you can:

Scopes

First of all, Ozwillo as an authentication and authorization server is an implementation of OpenID Connect Authorization Server, and as such uses scopes introduced by the protocol to specify access privileges.

For instance and if we focus on scopes introduced by OpenID Connect, the provider may ask for an access_token with the email scope. When authenticating, users are prompted to know if they want to share their email address with this instance. As a result, the access_token created is associated to this scope (if granted by user) on Ozwillo side.

When the instance wants to indeed access the email through one of Ozwillo API endpoints, it will send an HTTP request with this access_token, and Ozwillo is then able to decide if the operation is permitted or not.

As you will see later, you can ask for scopes at several occasions:

When a claimed scope is refused by users, the instance will be able to ask for it again later, and explain a given operation won’t be possible until they accept it.

Scopes are typically used to access private profile information (see those inherent to OpenID Connect like email, address or phone). But we will see that this mechanism is flexible enough to use additional scopes defined by application instances during provisioning.

Documentation conventions

HTTP requests and responses bodies (and data models) are described in tables. Within these tables the first column is dedicated to parameter names; when they are displayed in boldface, it means they are mandatory.

When you see curly brackets {} in a code sample, it means the content should be evaluated. Curly brackets typically contain variable names or describe an operation, for example:

POST {instantiation_uri}
X-Hub-Signature: sha1={HMAC-SHA1 digest of payload}

Experiment

Preproduction sandbox

The previous section (especially Programming interface) lists the production hosts of Ozwillo, all of which being subdomains of ozwillo.com. Ozwillo preproduction environment mirrors this setup under ozwillo-preprod.eu by providing the following hosts:

As of today, the preproduction fulfills two different objectives:

That’s why providers are asked to implement provisioning and authentication on preproduction first. Once validated, their applications are declared to production.

From now on, the documentation focuses on preproduction hosts, in particular in HTTP request samples. Hosts should be put in variables so that you can update them to the production ones when needed.

Online experimentation (Datacore)

The best and easiest way to discover, try out and learn about Datacore APIs is to use its live Playground user interface. Here is the preproduction one.

Its use is strongly advised to everybody. It is very useful to app developers before they attempt to code calls and implement authentication, in order to get an idea of their target even at the technical level thanks to the Swagger technical Playground or by debugging calls made by their browser to the server. It is mandatory for business model designers, in order to find out in the Project Portal Playground (manual) already existing models and data, and afterwards to use the Import UI documentation in order to import their own CSV models along with validating sample data. The production one may also be useful to debug data problems.

In order to use it, one must first ask administrators for his Ozwillo account to be allowed to. To this purpose, use the contact form at the bottom of a logged in portal page (preproduction). This is because the Datacore is a regular Ozwillo application that is installed in the Ozwillo organization and therefore only available to its members.

OpenID configuration

Ozwillo provides API endpoints that implement OpenID Connect. Their configuration is accessible as a well-known registry resource: here is the preproduction one.

For instance, the configuration shows the authorization_endpoint is accessible at https://accounts.ozwillo-preprod.eu/a/auth

These endpoints are also listed in the API reference section.

Authentication schemes

This paragraph provides an overview of authentication and authorization schemes that occur between Ozwillo and provider APIs.

Recognize and trust Ozwillo

As you will soon see, a purchase act on the portal causes Ozwillo to POST provisioning requests to a provider endpoint. Encoding the payload of the request and sending the resulting signature as a HTTP header allows the provider to recognize Ozwillo as the request issuer.

Knowing this request intent is to create a new application instance, it includes a client_id/client_secret pair used in the following authentication schemes.

Calling Ozwillo without an access_token

Having an access_token means an end user has successfully authenticated to Ozwillo (within a given client_id). But there is a number of cases where the provider try to reach Ozwillo before or outside an authenticated user context:

In those cases, the provider servers must issue HTTP requests to Ozwillo with basic authentication using the client_id/client_secret pair as userid and password respectively.

Calling Ozwillo with an access_token

When the provider calls Ozwillo with an access_token, it means the request is done on behalf a user. It helps Ozwillo to decide if a particular operation is allowed depending on the user identity, scopes and client_id associated with this access_token.

The corresponding requests require OAuth 2.0 Bearer authentication, meaning that the access_token is sent in an Authorization: Bearer {access_token} HTTP header.

Recommendations

HTTPS

Knowing that sensitive information may be exchanged between Ozwillo and the provider APIs (client_id, access_token), it is required that communication happens over HTTPS.

It may occur that your HTTPS configuration is not trusted by the Kernel and thus that some requests are not even sent. In this case, please use an SSL report tool to check your configuration or refer to the EFF’s guide.

Robustness

Following the robustness principle of Jon Postel, your API endpoints should be prepared to receive more data than expected and described in the documentation. It especially enables Ozwillo to add new functionality without breaking backward compatibility.

Your endpoints must be robust to new parameters or parameters not described in this document (in particular in response bodies and HTTP headers).

Monitoring

In some cases, API calls may need a manual operation on the provider side (for instance during provisioning depending on the process you put in place) or may fail. It’s good practice to be sure a real person is notified when such cases occur, and don’t let the issue sleep.

Provisioning

This section explains the provisioning protocol triggered between Ozwillo and the provider APIs when a purchase act occurs on the portal. The outcome of a successful provisioning is an application instance that is both:

  1. allocated and reachable on the provider servers
  2. declared with appropriate settings, especially regarding access rights, on Ozwillo

where (2) enables the creation of desk shortcuts so that users can access (1)

The creation of an application instance on the provider side may imply configuration changes or new resources allocation. These implementation details are left unconstrained: the protocol focuses only on API communication. It means existing deployment operations does not have to be affected and should rather be exposed through this protocol.

Prerequesite

Since provisioning starts when a user performs a purchase act, it means the purchased application is declared in the catalog and Ozwillo knows how to forward the request to the provider through its app factory.

Thus the following information is needed to have a well described and installable application in the catalog:

Commercial information
Field name Field description Field type and format
name name in the default language (try to remain below 20 characters) string
name#{l} name in the given language {l} (try to remain below 20 characters) string
description description in the default language markdown string
description#{l} description in the given language markdown string
tos_uri terms of service URI implicitly accepted on purchase, in the default language URI string
tos_uri#{l} terms of service URI implicitly accepted on purchase, in the given language URI string
policy_uri privacy policy URI implicitly accepted on purchase, in the default language URI string
policy_uri#{l} privacy policy URI implicitly accepted on purchase, in the given language URI string
icon app icon URI in the default language, a 64px x 64px png is expected URI string
icon#{l} app icon URI in the given language, a 64px x 64px png is expected URI string
screenshot_uris list of screenshot URIs, a 850px x 450px png are expected array of URI strings
contacts list of URLs to contact the provider or its support array of URI (URL or mailto) strings
supported_locales list of supported locales (end-user UI) in the application array of {l} strings
geographical_areas optional geographical areas of interest array of territory_ids
restricted_areas if the application can only be purchased by organizations in given areas array of territory_ids

A few comments on this table:

Store filters
Field name Field description Field type and format
payment_option “FREE” or “PAID” setting string
target_audience is the application intended to “CITIZENS”, “PUBLIC_BODIES” and/or “COMPANIES” array of strings
category_ids IDs of the application store categories, not supported for the moment array of strings
visible if false, the application is not visible in the application store boolean
App factory
Field name Field description Field type and format
instantiation_uri instantiation endpoint URI string
instantiation_secret secret used to compute the instantiation request signature string
cancellation_uri cancellation endpoint, to cancel pending instantiations URI string
cancellation_secret secret used to compute the cancellation request signature string

There is no official spec about secret strings, but 30 characters within a rich character set (richer than hexadecimal) can be seen as a bare minimum. Here is a tool to generate random strings.

Summary

You will likely need to read the remainder of this section to fully understand how fields are used, especially those related to the app factory. It means these setup steps are required before receiving provisioning requests from Ozwillo:

  1. gather all parameters info needed as described in the previous tables
  2. send it to providers@ozwillo.com along with your display name as an application provider (typically the name of your company)
  3. you will be notified when the application is made available on the preproduction

To ease the process, you may submit a first and simplified version of the commercial info fields, focusing on simple versions of required fields, and resubmit it later with enriched contents.

Protocol

#1 Ozwillo request

Description

When a purchase act occurs, Ozwillo creates a new application instance in its catalog, in a pending state (since it has not been provisioned for the moment). This instance is given a unique and constant instance_id and credentials (client_id and client_secret). The client_secret must remain to be known only by Ozwillo and the provider.

The current behavior of Ozwillo is that instance_id and client_id are given the same unique GUID value, but this is really an implementation detail subject to change. The spec is that instance_id is an identifier that won’t change over time, when client_id may be refreshed.

Request command

Don’t forget to read our conventions and recommendations.

The following HTTP request is sent from Ozwillo to the provider at instantiation_uri (decomposed in instantiation_path and instantiation_host below).

POST {instantiation_path} HTTP/1.1
Host: {instantiation_host}
Accept: application/json, application/*+json
X-Hub-Signature: sha1={HMAC-SHA1 digest of payload}
Content-Type: application/json;charset=UTF-8

The body of this request is encoded in JSON. A HMAC-SHA1 hash of the body is computed using the application’s instantiation_secret as key. This computed HMAC-SHA1 signature is then inserted in the X-Hub-Signature header in hexadecimal, prefixed with the string sha1= (as per PubSubHubbub Core 0.4).

As introduced in Recognize and trust Ozwillo, the provider must recompute the signature of the request body with the same secret key and method. If signatures match, this reasonably certifies the instantiation request comes from Ozwillo (which is the only one knowing the instantiation_secret) and then can be trusted. If not, you may log the request for further analysis but you must not process it. Note that the sha1= prefix must be compared case-sensitively, but the case of the hexadecimal signature value doesn’t matter.

Request body
Field name Field description Type
instance_id unique and constant identifier of the instance string
client_id used for authentication purposes string
client_secret used for authentication purposes string
user a description of purchaser, containing at least its Ozwillo user id User object
organization a description of the organization, containing at least Ozwillo organization id and name Organization object
instance_registration_uri the endpoint that must be used to acknowledge the provisioning back to Ozwillo URI string
authorization_grant an OAuth 2.0 authorization grant to be able to access the Datacore during the provisioning (e.g. to get details about the organization) AuthorizationGrant object
Embedded User object
Field name Field description Type
id Ozwillo user id of the purchaser string
name (non unique) display name of the purchaser string
Embedded Organization object
Field name Field description Type
id Ozwillo organization id of the purchasing organization string
name organization name string
type “PUBLIC_BODY” or “COMPANY” string
dc_id Datacore identifier of the organization string

It’s important to know that purchasers may install an application either on behalf an organization, or for their personal use. In the latter case, there is no organization associated to the purchase, so there is no organization field in the request body.

That said, if your application’s target_audience (as declared in store filters) does not contain CITIZENS, it means purchase acts will always be on behalf of an organization and thus the organization field will always be sent.

Embedded AuthorizationGrant object
Field name Field description or fixed value
grant_type urn:ietf:params:oauth:grant-type:jwt-bearer
assertion a JWT to be sent as-is to the Kernel’s Token Endpoint
scope scope of the access token you can obtain from the JWT, space separated (fixed to the value datacore)

You can use those field values to obtain an access token and make requests to the Datacore during the provisioning. To do that, make a POST request to the token_endpoint similar to the one made when authenticating users, using the fields values as-is.

POST /a/token HTTP/1.1
Host: accounts.ozwillo-preprod.eu
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {base64 encoding of client_id:client_secret}

grant_type={grant_type}&assertion={assertion}&scope={scope}

The authorization header needs to be set as described in Calling Ozwillo without an access_token.

You should then receive a successful response with the following fields in the JSON payload:

Field name Field description Field type and format
access_token access_token token value issued by the Authorization Server string
token_type in our case the value is always “Bearer” string
scope reminder of scopes associated with this access token strings separated by spaces
expires_in expiration time of the access token in seconds integer
Response from provider

Accepted status codes:

#2 Provider provisioning

By provider provisioning we mean the installation process needed on the provider servers. As previously explained in the introduction to this section, this step can be implemented in various ways:

Moreover this process also depends on existing software and architectural choices. That being said, we can give the following guidelines:

From a user perspective, until the step #3 is done, purchase acts are materialized as grey and disabled desktop shortcuts.

#3 Provider acknowledgement

Description

In this step, the provider declares the precise configuration of the installed instance, including in particular:

This list is not exhaustive and is detailed in the Request body paragraph below.

Request command

The following HTTP request is sent from the provider to Ozwillo at the instance_registration_uri received in step #1 (decomposed in instance_registration_path and instance_registration_host below).

POST {instance_registration_path} HTTP/1.1
Host: {instance_registration_host}
Accept: application/json, application/*+json
Authorization: Basic {base64 encoding of client_id:client_secret}
Content-Type: application/json;charset=UTF-8

The authorization header needs to be set as described in Calling Ozwillo without an access_token.

Request body
Parameter name Parameter description Type
instance_id Ozwillo application instance id string
services services (at least one) to be declared on Ozwillo, more details below array of Service objects
destruction_uri destruction endpoint of this instance URI string
destruction_secret secret used to compute the destruction request signature string
status_changed_uri status-changed endpoint of this instance URI string
status_changed_secret secret used to compute the status-changed request signature string
needed_scopes scopes needed by the instance array of NeededScope objects
scopes scopes declared by the instance array of Scope objects

Embedded Service objects

Field name Field description Type
local_id identifier that needs to be unique within the instance, for instance “front-end” string
name name in the default language (try to remain below 20 characters) string
name#{l} name in the given language (try to remain below 20 characters) string
description description in the default language markdown string
description#{l} description in the given language markdown string
tos_uri terms of service URI implicitly accepted on purchase, in the default language URI string
tos_uri#{l} terms of service URI implicitly accepted on purchase, in the given language URI string
policy_uri privacy policy URI implicitly accepted on purchase, in the default language URI string
policy_uri#{l} privacy policy URI implicitly accepted on purchase, in the given language URI string
icon service icon URI in the default language, a 64px x 64px png is expected URI string
icon#{l} service icon URI in the given language, a 64px x 64px png is expected URI string
screenshot_uris list of screenshot URIs, a 850px x 450px png are expected array of URI strings
contacts list of URLs to contact the provider or its support array of URI (URL or mailto) strings
supported_locales list of supported locales (end-user UI) by the service array of {l} strings
geographical_areas optional geographical areas of interest array of territory_ids
restricted_areas if the service can only be purchased by organizations in given areas array of territory_ids
payment_option “FREE” or “PAID” setting string
target_audience is the service intended to “CITIZENS”, “PUBLIC_BODIES” and/or “COMPANIES” array of strings
category_ids IDs of the service store categories, not supported for the moment array of strings
visibility whether the service is visible in the application store (“VISIBLE” or “HIDDEN”), and whether this can be toggled by an app_admin from the portal (“NEVER_VISIBLE”); defaults to “HIDDEN” string
access_control whether only members (app_user or app_admin) have access to the service or it’s open to anyone (“RESTRICTED” or “ANYONE”), and whether this can be toggled by an app_admin from the portal (“ALWAYS_RESTRICTED”); defaults to “RESTRICTED” string
service_uri URL entrypoint of the service URI string
notification_uri endpoint used when there are notifications for this service URI string
redirect_uris whitelist of authentication callbacks, each URI must be unique to this service within the instance array of URI strings
post_logout_redirect_uris whitelist of post-logout callbacks, each URI must be unique to this service within the instance array of URI strings

NB: redirect_uris and post_logout_redirect_uris values can’t be shared by different services within the same application instance. In some cases Ozwillo may identify a service thanks to either instance_id + redirect_uri or instance_id + post_logout_redirect_uri pairs.

A few remarks on this table:

Older versions of the Kernel used boolean properties visible and restricted in place of visibility and access_control, that only allowed to model three different states (a restricted service was necessarily hidden from the application store). For backwards compatibility, those old properties are still accepted, and will have precedence over the new ones as soon as one of them is explicitly present in the payload (this means that the absence of visible and restricted is no longer equivalent to having any one of visible: false and/or restricted: false, in case there are also a visibility or access_control property.) Similarly, the old properties will be returned in payloads if they can describe the state of the new properties’ values. This means the API should be entirely backwards compatible. You are strongly encouraged to update your app factories and applications to the new model though, as the old properties are deprecated and will eventually be removed. The table below maps between the old values and the new ones; on reading a service definition, any other combination of visibility and access_control will result in the visible and restricted properties being absent:

    visibility access_control
visible restricted    
false false HIDDEN RESTRICTED
true false VISIBLE ANYONE
false true NEVER_VISIBLE ALWAYS_RESTRICTED

NB: due to this backwards-compatibility rules, providers should remove/delete the visible and restricted properties from any payload they send to Ozwillo, as they otherwise risk to see the visibility and access_control properties being ignored.

Embedded NeededScope objects

Field name Field description Type
scope_id full identifier of the needed scope string
motivation motivation for using the scope, in the default language string
motivation#{l} motivation for using the scope, in the given language string

The motivation helps users understand why they should grant specific privileges (associated to the scope_id) to the instance, and thus help them decide if they will.

Embedded Scope objects

Field name Field description Type
local_id local identifier of this scope (within the instance) string
name name of the scope in the default language string
name#{l} name of the scope in the given language string
description description of the scope in the default language string
description#{l} description of the scope in the given language string

Scope identifiers are simple strings that correspond to the permission scopes that client applications require to access part of this API’s functionality. For instance, application X may define an API to create events within a particular instance Y; it would then declare the scope “addevent” of this instance.

When a third party instance Z wants to add an event to Y on behalf of a user, it needs to require the {Y instance_id}:addevent as part of its token negotiation (the user is asked to confirm that they want to give the “add events on Y” permission to Z).

The full identifier of an instance scope is in the form {instance_id}:{local_id}.

Response from Ozwillo

Possible status codes:

For successful 201 responses, the body response is in the form of:

{
  "back-end": "a15243e3-17a0-4511-b15c-c88e6784e287",
  "front-end": "31336385-f2ff-4488-8835-1f7da53669b9"
}

Where back-end and front-end would be the local_id of two declared services within the instance, and the corresponding GUIDs being their internal Ozwillo ids.

The 201 responses also include a Location: header with the URI of a resource about the created instance (see below).

Effects

At this stage:

#3bis Provider dismiss

If the instance creation has failed for some reason during step #2, the provider must issue a DELETE request on the instance registration URL so that Ozwillo does not show endless “pending” instance creation requests:

Request command

The following HTTP request is sent from the provider to Ozwillo.

DELETE /apps/pending-instance/{instance_id} HTTP/1.1

Instance status change

Instance status change is not part of the initial provisioning that occurs between steps #1 to #3, but will occur whenever an admin of the organization that purchased the application decides to destroy it.

As of March 10th, this is only available in preproduction.

Request command

The following HTTP request is sent from Ozwillo to the provider at the status_changed_uri defined in step #3 (decomposed in status_changed_path and status_changed_host below)

POST {status_changed_path}
Host: {status_changed_host}
X-Hub-Signature: sha1={HMAC-SHA1 digest of payload}
Content-Type: application/json;charset=UTF-8

This scheme allows sharing the same status-changed URI and secret among several instances, but Ozwillo also supports having separate status-changed URI and secret for each instance. See more on how to check if this request reliably comes from Ozwillo depending on verifying the signature.

Request body
Field name Field description Type
instance_id identifier of the instance string
status new status of the instance: either STOPPED or RUNNING string
Response from provider

The status-changed endpoint must respond with a successful status (200, 202 or 204) in a timely manner (not necessarily waiting for the underlying resources to be actually released/archived or reacquired/restored). Ozwillo will then change the instance’s state from its database and it will be impossible for users to authenticate to it, and services will disappear from the store.

If the request times out, the Kernel will change the instance’s state in its database nevertheless. Any (timely) non-successful status will abort the status change (so it can be retried later).

Note that the above does not describe the current behavior. Currently, the response is ignored, and the provider might not even be notified! This means that a STOPPED instance should continue to redirect users to Ozwillo for authentication, and should treat a successful authentication as a signal that the instance actually is now RUNNING (remember that when an instance is in a STOPPED status, it’s impossible to authenticate on it).

Instance destruction

Instance destruction happens one week after the instance status has been changed to STOPPED (and has not been changed back to status RUNNING in the mean time).

Request command

The following HTTP request is sent from Ozwillo to the provider at the destruction_uri defined in step #3 (decomposed in destruction_path and destruction_host below).

POST {destruction_path}
Host: {destruction_host}
Accept: application/json, application/*+json
X-Hub-Signature: sha1={HMAC-SHA1 digest of payload}
Content-Type: application/json;charset=UTF-8

Similarly to status changes, this scheme allows sharing the same destruction URI and secret among several instances, but Ozwillo also supports having separate destruction URI and secret for each instance. See more on how to check if this request reliably comes from Ozwillo depending on verifying the signature.

Request body
Field name Field description Type
instance_id identifier of the instance string
Response from provider

The destruction endpoint must respond with a successful status (200, 202 or 204) in a timely manner (not necessarily waiting for the underlying resources to be actually released or archived). Ozwillo will then delete the instance (and its associated resources, like services) from its database and it will be impossible to undo the change.

If the request times out, the Kernel will delete the instance from its database nevertheless. Any (timely) non-successful status will abort the destruction (so it can be retried later).

FAQ

What is the difference between cancellation and destruction URIs?

Pending instances (those for which a request has been made to the app factory, but for which no acknowledgement has been sent) can be canceled using the same mechanism as Instance destruction but at the application wide cancellation_uri.

Note that because the instance_id is sent in the request payload, the cancellation_uri and the destruction_uri for each instance (and their secrets) can technically be the same. If you choose to make them different, then the cancellation_uri should reject the destruction of provisioned instances.

TODO: use cases examples.

User Authentication

Introduction

Ozwillo authentication and authorization service implements the following international standards:

In this matter, Ozwillo acts as an OpenID Provider, which is defined as (see OpenID terminology):

OAuth 2.0 Authorization Server that is capable of Authenticating the End-User and providing Claims to a Relying Party about the Authentication event and the End-User.

In this respect, the provider acts as a Relying Party:

OAuth 2.0 Client application requiring End-User Authentication and Claims from an OpenID Provider.

You may refer to the previous Authentication in brief and scopes paragraphs for a reminder, but the outcome of a successful authentication is the creation of an access_token that links three resources (during a limited period of time):

Then, anytime the application instance tries to interact with user-related resources (typically due to user action) through Ozwillo API, this access_token is sent to Ozwillo so that it can check the operation is allowed.

That’s how Ozwillo provides both authentication and authorization features.

Prerequesite

You will soon see that you ask Ozwillo to authenticate users against a client_id. Since this in an outcome of the provisioning process, you should implement provisioning first.

In the particular case where what you offer is not an instantiable application, but a unique and singleton one, your product may be considered as a service, and you may ask for a client_id to providers@ozwillo.com.

Authorization code flow

You may refer to the official OpenID Connect authorization code flow in addition to this documentation.

Two particularities may be noted in comparison with the official spec:

Still within the context of the official spec, some optional features may not be implemented as of today, for instance passing a max_age parameter to the authentication endpoint.

#1 Authentication request

In this step, the application instance has detected an authentication of the user is needed and delegates it to Ozwillo by redirecting the end-user request to Ozwillo authentication endpoint.

HTTP/1.1 302 Found
Location: {authentication_endpoint}?{parameters}

Knowing Ozwillo OpenID configuration, the end-user navigator will issue the following request :

Request command
GET /a/auth?{parameters} HTTP/1.1
Host: accounts.ozwillo-preprod.eu
Query {parameters}
Field name Field description Field type and format
response_type determines the authorization processing flow, in our case the value is always “code” string
client_id the client_id associated to the application instance string
scope list of scopes requested by the instance, it must at least contain openid string (scopes separated by spaces)
redirect_uri the redirection URI to which the response will be sent URI string
state opaque value used to maintain state between the request and the callback string
nonce unique random string used to mitigate replay attacks string

As a result, a request could look like:

GET /a/auth?
 response_type=code
 &client_id={client_id}
 &scope=openid%20profile
 &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcb
 &state=security_token%3D{random_value}%26url%3Dhttps%3A%2F%2Fapp.example.com%home
 &nonce={another_random_value} HTTP/1.1
Host: accounts.ozwillo-preprod.eu

In this example, some characters (among space = & : /) have been URL-escaped.

#2 Ozwillo response

Several operations are then conducted on Ozwillo side:

  1. validate the incoming authentication request from step #1;
  2. authenticate the user;
  3. ask user to accept scopes claimed by the instance.

The user experience (points 2 and 3) may vary if previous interaction between the users and Ozwillo has occured or not: do they have an account (or do they have to register)? Are they currently logged to Ozwillo? Have they previously granted this instance to use those scopes? In particular, if all the answers to these questions are yes, 2 and 3 are validated silently by Ozwillo, meaning that no sign-in or grant web page is shown.

In short, the authentication endpoint implements a rich behaviour and may not display the same user interface depending on an existing history between the end user and Ozwillo through a given web client.

Success

If the previous operations succeed, and if the redirect_uri value specified in step #1 matches one of the redirect_uris specified during the provisioning acknowledgement, the following response will be sent:

HTTP/1.1 302 Found
Location: https://app.example.com/cb?state=security_token%3D{random_value}%26url%3Dhttps%3A%2F%2Fapp.example.com%2Fhome&code={code}

This redirect workflow means your server will finally receive the following request sent from the end-user navigator:

GET /cb?state=security_token%3D{random_value}%26url%3Dhttps%3A%2F%2Fapp.example.com%2Fhome&code={code} HTTP/1.1
Host: app.example.com/
Failure

The same redirect_uri callback will be notified of the authentication error according to the spec.

#3 Response validation

You should verify that the security_token (within the state) is the one you sent first to Ozwillo during step #1, to decide if you can trust the response. To do so, you should link it to the current user session. This operation ensures the user accessing Ozwillo response is the same that initiated the authentication request.

If validation passes, you finally exchange the received code for an access_token.

#4 Requesting an access token

The previous steps occured through the end-user navigator thanks to redirects. From now on, the interaction is done directly between Ozwillo and provider APIs.

Request command

Referring to the token_endpoint declared in Ozwillo OpenID configuration, you should issue the following request:

POST /a/token HTTP/1.1
Host: accounts.ozwillo-preprod.eu
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {base64 encoding of client_id:client_secret}

The authorization header needs to be set as described in Calling Ozwillo without an access_token. As shown in the Content-Type header, the following request body is sent as a serialized form.

Request body
Field name Field description Field type and format
grant_type in our case the value is always “authorization_code” string
redirect_uri the redirection URI to which the response will be sent URI string
code the code sent to you in step #1 string

#5 Ozwillo response

Similarly to step #2, Ozwillo may accept or reject the previous request (especially depending on the Authorization header and the code parameter).

Success status and headers

In case of success, the following response will be sent:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
Success body
Field name Field description Field type and format
access_token access_token token value issued by the Authorization Server string
token_type in our case the value is always “Bearer” string
scope reminder of scopes associated with this access token strings separated by spaces
expires_in expiration time of the access token in seconds integer
id_token value associated with the authenticated session JWT string
Failure

If Ozwillo rejects the previous request, a HTTP 400 error will be sent back.

#6 Token decoding and validation

The id_token sent in the previous step is a JWT (JSON Web Token). You can do some tests by using this online tool or dedicated libraries (please check these ones too).

These libraries also help you check the id_token signature knowing Ozwillo public keys. The public keys URI is named jwks_uri in the OpenID configuration.

If the signatures do not match, you can not trust the JWT. If they do match, the JWT contains the following fields:

Field name Field description Field type and format
iss issuer identifier, typically “https://accounts.ozwillo-preprod.eu” URI string
sub subject identifier, meaning the Ozwillo user id string
aud audience, in our case the client_id string
iat time at which the JWT was issued integer
exp expiration time integer
nonce used to mitigate replay attacks string
app_user Ozwillo specific, used to describe the user role (see explanation) boolean
app_admin Ozwillo specific, used to describe the user role (see explanation) boolean

The last expected validation is that you check the nonce sent back in this response is the same you sent in step 1.

If all goes well and depending on your use cases, you may now enrich and qualify your end-user session with the issued access_token and id_token. The access_token is especially useful for further interactions use Ozwillo APIs, typically the user info endpoint.

The access_token must not be leaked in a client-side cookie.

Sign-out

Signing out a user is a 3-step process.

#1 Revoke known tokens

To prevent tokens from being used maliciously would they be leaked, the first step is to use the revocation endpoint as defined by RFC 7009 to revoke all the tokens known to the application.

Authentication to the revocation endpoint is done using the same mechanism and credentials as at the token endpoint to exchange authentication codes for tokens.

Request command

Knowing Ozwillo OpenID configuration, the request looks like:

POST /a/revoke HTTP/1.1
Host: accounts.ozwillo-preprod.eu
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {base64 encoding of client_id:client_secret}

The authorization header needs to be set as described in Calling the Kernel without an access_token and the request body is sent as form serialization.

Request body
Field name Field description Field type and format
token the value of the access_token string
token_type_hint the type of the token, in this case the string “access_token” string

#2 Invalidate the application’s local session

Whichever mean the application uses to maintain the user session, it should be invalidated so that when the users come back to the application (and/or if they had the application opened in several windows or tabs), they won’t be recognized and the authentication process is triggered again.

#3 Single sign-out

If the user is still signed in on Ozwillo, going back to the service is going to transparently sign-in her/him back. Indeed the service redirects to Ozwillo authentication endpoint (see the authentication request), and knowing a session exists between the user and Ozwillo this step succeeds silently.

This would be a surprising behavior for the user so he must be given the choice to sign out from the whole Ozwillo platform. This is done using the end session endpoint as defined by RP-Initiated Logout in OpenID Connect Session Management 1.0.

According to Ozwillo OpenID configuration, you should then issue a redirect to conclude the sign-out:

HTTP/1.1 302 Found
Location: https://accounts.ozwillo-preprod.eu/a/logout?{parameters}

As a result, the end-user client will issue the following request:

Request command
GET /a/logout?{parameters} HTTP/1.1
Host: accounts.ozwillo-preprod.eu
Query {parameters}
Field name Field description Field type and format
id_token_hint the id_token JWT value that was issued at the end of authentication string
post_logout_redirect_uri the redirection URI to which the response will be sent URI string
state opaque value used to maintain state between the request and the callback string

The post_logout_redirect_uri value must match one of the post_logout_redirect_uris specified during the provisioning acknowledgement.

Datacore

As said previously, the best and easiest way to discover, try out and learn about Datacore APIs is to use its live Playground (preproduction).

In addition to going through the Playground’s live examples, be sure so read the various other documentation available on the Datacore wiki.

Using Datacore APIs requires the datacore scope.

Have a look at the Datacore’s API reference in its live Swagger technical Playground (how to get access) and try it out there.

Data workflow

Last but not least, besides the business and technical details of using the Datacore APIs, it is very important for applications consuming the Datacore API to adapt the way they work with data - their data and work flow - correspondingly. And especially so when they not only reuse but share and collaborate on data.

In short, the way to do it is to

And when interacting with data always “think Datacore”, that is at all times having the Datacore’s data in mind in addition to the application-local data :

API Reference

Endpoints description

As a reminder the APIs are served accross several hosts depending on their topic (authentication, provisioning, data…). Endpoints descriptions follow this format:

{method} {path}
host auth input
output
SCOPES {scopes}

Where:

Common behaviour

When trying to modify or delete a resource (PUT or DELETE command), it is expected that you’ve previously retrieved it (GET command) and got an associated “ETag” (entity-tags are also returned in some responses).

Then you should create your UPDATE or DELETE request with an If-Match header set with the entity-tag.

It prevents from altering resources that have been modified elsewhere since the last time you accessed it.

Datacore-specific behaviour

In addition to common behaviour:

And again, the best reference for the Datacore API is its live Swagger technical Playground (preproduction).

Keys

Retrieve the public key used for OpenID Connect

GET /a/keys
accounts basic
JWKS

Returns a JSON Web Key Set containing the public key. See JWK (RFC 7517) for more informations about JWKS.

Note that this API requires Basic authentication not for security concerns, but actually only so we can track who calls it and at which frequency. Note: this is no longer the case starting with v1.19.


Users

Return claims about the end-user

GET /a/userinfo
accounts bearer
JSON JWT
SCOPES openid and optionally any of profile email name address

See OpenID Connect 1.0, JWT (RFC 7519) and JWS (RFC 7515) for more information.

Response body
Field name Field description Type Included if scope
sub Identifier of the user string openid
name User’s full name in displayable form string profile
given_name Given name(s) or first name(s) of the user string profile
family_name Surname(s) or last name(s) of the user string profile
middle_name Middle name(s) of the user string profile
nickname Casual name of the user string profile
picture URL of the user’s profile picture (avatar) URI profile
gender User’s gender; either male or female string profile
birthdate User’s birthday, in ISO 8601 YYYY-MM-DD format string profile
zoneinfo User’s timezone, as a value from the tz database string profile
locale User’s locale, as a BCP47 language tag string profile
email User’s e-mail address string email
email_verified True if the user’s e-mail address has been verified; otherwise false. boolean email
address User’s postal address Address object address
phone_number User’s telephone number string phone
phone_number_verfied True if the user’s phone number has been verified; potherwise false. boolean phone
updated_at Time the user’s information was last updated, in seconds since Unix Epoch number openid

Return claims about the end-user

POST /a/userinfo
accounts bearer
JSON JWT
SCOPES openid profile email name address

Same as above, only the HTTP method changes.

See OpenID Connect 1.0, JWT (RFC 7519) and JWS (RFC 7515) for more information.


Authorization

Grant authorizations to the client application

GET /a/auth
accounts

See OAuth 2.0 and OpenID Connect 1.0 for more information.


Grant authorizations to the client application

POST /a/auth
accounts form

Same as above, only the HTTP method changes, and parameters are sent in the request payload rather than the query string.

See OAuth 2.0 and OpenID Connect 1.0 for more information.


Tokens

Exchange an authorization code or a refresh token for an access token

POST /a/token
accounts basic form
JSON

See OAuth 2.0 and OpenID Connect 1.0 for more information.


Get information about an access token

POST /a/tokeninfo
accounts basic form
JSON

See OAuth 2.0 Token Introspection (RFC 7662) for more information.

This endpoint is only accessible to protected resources (in OAuth 2.0 parlance) that need to validate whether a received access_token is valid and retrieve information about it. Other clients will get an { "active": false } response; even the app-instance for which the token has been issued (this might change in the future though).

Request body
Field name Field description Type
token The string value of the token to introspect. string
Response body
Field name Field description Type
active Indicator of whether or not the presented token is currently active boolean
scope Space-separated list of scopes associated with this token string
client_id Identifier of the app-instance for which the token was issued string
token_type Type of the token (e.g. Bearer) string
exp Timestamp (in seconds since Unix Epoch) indicating when the token will expire integer
iat Timestamp (in seconds since Unix Epoch) indicating when the token was originally issued integer
nbf Timestamp (in seconds since Unix Epoch) indicating when this token is not to be used before integer
sub Identifier of the user who authorized this token. string


Revoke a token

POST /a/revoke
accounts basic form

See OAuth 2.0 Token Revocation (RFC 7009) for more information.

Note: invalid tokens do not cause an error response since the client cannot handle such an error in a reasonable way. Moreover, the purpose of the revocation request, invalidating the particular token, is already achieved.

Request body
Field name Field description Type
token The string value of the token to introspect. string

Application instances

Acknowledge the provisioning of an instance

POST /apps/pending-instance/{instance_id}
kernel basic JSON

See above for details.


Notify an error while provisioning the instance

DELETE /apps/pending-instance/{instance_id}
kernel basic

See above for details.


Retrieve the services of the application instance

GET /apps/instance/{instance_id}/services
kernel bearer
JSON
Response body

The response is a JSON Array of service objects as used during provisioning.


Add a new service to the application instance

POST /apps/instance/{instance_id}/services
kernel bearer JSON
JSON
Request body

See embedded service objects used during provisioning.


Services

Retrieve information about a service

GET /apps/service/{service_id}
kernel bearer
JSON
Response body

See embedded service objects used during provisioning.


Update a service

PUT /apps/service/{service_id}
kernel bearer JSON
JSON
Request body

See embedded service objects used during provisioning.

Note that the provided description replaces the one stored in Ozwillo. This is not a patch or partial update.


Delete a service

DELETE /apps/service/{service_id}
kernel bearer

Access control

Ozwillo manages access control lists that links user to application instances.

Retrieve app_users (and app_admins) of the app instance

GET /apps/acl/instance/{instance_id}
kernel bearer
JSON

This API is only available to users who are already app_admins for the application instance.

Response body

The response is a JSON Array of access control entries, each with the following fields:

Field name Field description Type
instance_id Identifier for the application instance string
user_id Identifier for the user whom the entry is about string
user_name (non-unique) display name for the user whom the entry is about string
creator_id Identifier for the user who created the entry string
creator_name (non-unique) display name for the user who created the entry string
app_user Whether the user_id is an app_user for the instance boolean
app_admin Whether the user_id is an app_admin for the instance boolean

At least one of app_user or app_admin will be present and true. Both might be true for a given entry.


Notifications

Get all unread notifications for a defined user and a filter

GET /n/{user_id}/messages?instance={instance_id}
kernel bearer
JSON

It will give you the notifications for the given user and app-instance; note that the instance_id must be the one for which you obtained the access_token. You can also pass an additional status=READ or status=UNREAD query-string parameter to filter notifications to only those that have been read or are still unread.

Response body

The response is a JSON Array of messages, each with the following fields:

Field name Field description Type
id Identifier for the message string
user_id Identifier of the user that received the message string
instance_id Identifier of the app-instance that emitted the message string
service_id Identifier of the service that emitted the message string
message Text of the message string
message#{l} Localized text of the message string
action_uri URI for the call to action URI
action_uri#{l} Localized URI for the call to action URI
action_label Label of the call to action string
action_label#{l} Localized label of the call to action string
time Timestamp at which the message was emitted, in milliseconds since Unix Epoch number
status Read status of the message; either READ or UNREAD string

Change read status of a user’s notification messages

POST /n/{user_id}/messages
kernel bearer JSON
Request body
Field name Field description Type
message_ids Identifiers of the message to update array of strings
status New status for the identified messages; either READ or UNREAD string

Publish a notification targeted to some users

POST /n/publish
kernel bearer JSON
JSON
Request body
Field name Field description Type
user_ids Identifiers of the users to notify array of strings
service_id Identifier of the service that emits the message string
message Text of the message string
message#{l} Localized text of the message string
action_uri URI for the call to action URI
action_uri#{l} Localized URI for the call to action URI
action_label Label of the call to action string
action_label#{l} Localized label of the call to action string

Events

The Events API documentation is under work and can’t be trusted as of today.

Subscribe to a typed event from the event bus

POST /e/{instance_id}/subscriptions
kernel bearer JSON

The returned location URL get access to the subscription (delete the subscription)

Request body

Delete an event subscription

DELETE /e/subscription/{subscription_id}
kernel bearer

If-Match


Publish a typed event into the event bus

POST /e/publish
kernel bearer JSON
Request body

Data Resources

This endpoint allows to manage Datacore Resources (CRUD : Create, Read, Update, Delete) in typical RESTful fashion (GET, POST, PUT, DELETE, not respectively). Using it requires the datacore scope.

Have a look at the Datacore’s Resources API reference in its live Swagger technical Playground (how to get access) and try it out there.

The most common operations are, in typical RESTful fashion:

Also useful are:

Note that Datacore Models are themselves stored as Resources having the dcmo:model_0 (meta) model in the oasis.meta project.


Data Rights

This endpoint allows to manage rights of Datacore Resources at the Resource-level, i.e. for each one of them their owners (required to be able to use the Rights API), writers (also allows to delete), and readers beyond default owners, writers and readers specified at Model or by default Project level. Using it requires the datacore scope.

Have a look at the Datacore’s Rights API reference in its live Swagger technical Playground (how to get access) and try it out there.


Data History

This endpoint allows to retrieve older versions of Resources, since the time when historization was enabled (“dcmo:isHistorizable” : false in its model). Using it requires the datacore scope.

Have a look at the Datacore’s History API reference in its live Swagger technical Playground (how to get access) and try it out there.


Data Contributions

This endpoint allows users that don’t have write permissions to contribute new versions of existing resources to be approved by their owner. Using it requires the datacore scope.

Have a look at the Datacore’s Contributions API reference in its live Swagger technical Playground (how to get access) and try it out there.