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:
- provider agnostic, meaning that any editor providing valuable and ethical services is welcome on ozwillo.com;
- business agnostic, meaning that no specific business concern will curve Ozwillo platform implementation.
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:
- find and install applications from Ozwillo’s store
- launch installed services (see the difference between applications and services below)
- manage one’s personal and application settings
The main user interface components are:
- information pages served under
www.ozwillo.com
- the portal (as defined above) and its store, served under
portal.ozwillo.com
- authentication-related pages served under
accounts.ozwillo.com
Except from the store, the portal pages are reserved to registered and logged users. They especially give access to:
- one’s desk, made of shortcut icons to launch services;
- one’s network, to create and manage organizations and links within organizations. Thanks to this, it is possible for someone to install applications on behalf organizations, in addition to a personal use, if one is an admin of this organization;
- one’s application settings, where one can give others access to a given service, typically an organization admin authorizing other organization members.
Programming interface
From a provider standpoint, adapting to Ozwillo means:
- 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)
- if user authentication is needed, delegate it to Ozwillo OpenID Connect Identity Provider
- 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:
- the API surface dedicated to authentication and authorization available under
accounts.ozwillo.com
- the Datacore API available (along with its live Playground) under
data.ozwillo.com
- other APIs (for instance for provisioning) available under
kernel.ozwillo.com
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:
- installing an application triggers the provisioning protocol, leading to software deployment on the provider side, and then to a declaration of the created instance to Ozwillo. It requires communication between Ozwillo and the provider servers;
- installing a service is simply the process of bookmarking the service URI as a user desk shortcut. This is instantaneous and does not require communication between Ozwillo and the provider servers.
Visibility and access control
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 visibility:"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. Visibility of a service can be toggled from the portal by the service owner, unless the provider declared it visibility:"NEVER_VISIBLE"
.
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:
- a visible service whose access is restricted to given users (for example if the service owner wants to explicitely grant access to requesting users)
- a hidden service with public access (for example if the service owner does not want to reference it on Ozwillo’s store)
- more typically services that are either visible and public, or hidden and restricted
Similarly to the visibility of a service, the access control can also take 3 values: two that can be toggled from the portal by the service owner, unless the provider declared the service as access_control:"ALWAYS_RESTRICTED"
.
The Provisioning section shows how these settings are defined.
Authorized users
app_admin
vs app_user
The case for restricted 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:
app_admin
characterises users with the highest privilegesapp_user
is generic and may apply to different business roles (for instance: moderator, editor…)
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:
- a
client_id
that helps identify the application instance in which the service resides - a
scope
list that will be explained in the next paragraph - and others that will be detailed in the User Authentication section
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:
- if the service is
restricted:true
Ozwillo authentication will only succeed for anapp_admin
orapp_user
(you can rely on it) - if the service is
restricted:false
Ozwillo authentication will succeed even if the user is neither anapp_admin
nor aapp_user
, but the service will be notified of it
There are other reasons for the authentication and authorization to fail: for instance users may refuse to accept the scopes or claims requested by your instance.
If authentication is successful, your service will be given at the end of the authorization code flow two tokens:
- an
id_token
assessing authentication is successful and giving information about the user identity - an
access_token
which authorizes you to access and interact with Ozwillo APIs (depending on granted scopes) on behalf this user, during a limited period
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:
- restrict the access to certain web pages depending on the ability to retrieve an
id_token
- possibly refine capabilities offered to the user depending on
app_admin
andapp_user
properties (which are wrapped in theid_token
) - ask to access Ozwillo API resources on behalf the user thanks to the
access_token
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:
- during the application instance installation (users will globally accept or deny it for their future usage of the application instance)
- when accessing a service (prompt during authentication) and for the lifetime of the
access_token
- on a per action basis
When a requested 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.
Despite the example above about accessing private profile information (the email address), scopes are typically used to access the Datacore, and are a flexible enough mechanism that allows application instances to declare their own during provisioning to authorize inter-application requests.
When it comes to private profile information, there’s actually a better mechanism:
Claims
Claims represent information about the user. OpenID Connect defines a list of standard claims (most of which are implemented by Ozwillo), and a number of scopes (see also above) that grant access to groups of claims; but those permissions can also be requested on a per-claim basis.
For instance, instead of asking for the profile
scope (which grants access to the user’s names, locale, date of birth, gender, etc.), the provider can individually ask for the claims it really needs; for example nickname
and locale
.
This feature is particularly important in face of the GDPR (and other similar laws), as it helps with data minimization: only collecting that personal data which is needed to fulfill the service, and no more.
Additionally, some claims can be requested as being essential to service, and the user will not be able to access it until he fills in his profile. This feature should only be used if your application would fail in the absence of value for those claims, as could be the case when adapting existing applications to Ozwillo; otherwise you’re strongly encouraged to make your applications resilient to the absence of value for given claims, either by using default values instead, or by asking the user. As terminology goes, a claim that is not requested as essential is said to be voluntary.
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:
accounts.ozwillo-preprod.eu
for authentication pages and the accounts API rootdata.ozwillo-preprod.eu
as the the Datacore API root (which also hosts its live Playground)kernel.ozwillo-preprod.eu
as the root of other APIsportal.ozwillo-preprod.eu
being the portal for logged userswww.ozwillo-preprod.eu
for information pages
As of today, the preproduction fulfills two different objectives:
- as its name implies, the preproduction may serve a different (more recent) software version than production, showcasing its evolution in a future update
- it’s also the right place for providers to safely test and discover the APIs
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:
- precisely when asking for an
access_token
during the authentication flow - when answering the provisioning request described in the previous paragraph
- when posting notifications or events
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:
- allocated and reachable on the provider servers
- 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:
- by default the store will only present the applications that support (
supported_locales
) the user preferred language (the user may disable this restriction); {l}
identifies a language following BCP 47. Some examples regarding the fields introduced above:name#es
,description#fr-FR
,description#fr-BE
. When looking up values, matching is currently done following the Java algorithm for finding resource bundles; it might change in the future to follow the Unicode Consortium rules for language matching.- the default language information is the fallback in case the user preferred language information is not available (no matching custom
{l}
version). Following BCP 18 advices, it should “be understandable by an English-speaking person”; this also matches the CLDR Likely Subtags for when the locale cannot be determined; - here is a description of the markdown syntax. In particular, you may include raw text separated by two line breaks to shape paragraphs.
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:
- gather all parameters info needed as described in the previous tables
- send it to providers@ozwillo.com along with your display name as an application provider (typically the name of your company)
- 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:
- 2xx: request acknowledged, the app factory will do the job
- 4xx: request cannot be honoured (for instance, the organization already owns an instance of this application and it is limited to one)
- any other non-2xx will be declared a failure too (maybe we’ll follow redirect, but let’s say for now that we won’t)
#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:
- fully automatic (for instance - but not restricted to - a multitenant architecture where a single software instance manages several application instances)
- half automatic (requires a manual validation)
- fully manual (requires a manual installation)
Moreover this process also depends on existing software and architectural choices. That being said, we can give the following guidelines:
- there should be an internal process that stores step #1 request properties and link them to services declared in step #3. This configuration will be useful during authentication (for instance, you will soon see that a service accessible at a given
service_uri
has to know to whatinstance_id
it belongs); - you should be able to treat both preproduction and production provisioning requests, and identify them as such. Knowing you will start implementing provisioning against Ozwillo preproduction, and then move it to production while keeping the preproduction tests up, you need to know to what API hosts your servers communicate with.
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:
- services composing the instance (the user endpoints)
- scopes needed for the instance to work (for instance the OpenID Connect
email
scope if you need to know the user email to configure the instance) - scopes declared by the instance, so that other instances may require them as needed if they want to use this very instance exposed API
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:
- you may compare it with the declaration of applications in the catalog, and find many common features due to the fact both applications and services are store entries;
redirect_uris
andpost_logout_redirect_uris
are related to User authentication.
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:
- 201: means the acknowledgement is valid and processed;
- 422: means there is a problem with the acknowledgement request, additional information about the particular error is given in the response body.
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:
- the application instance is created;
- its services are declared in the catalog;
- its scopes are declared and available for use by other applications;
- the purchaser has desktop shortcuts for the created services;
visible: true
services are listed in store.
#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:
- OpenID Connect Core 1.0 (using the Authorization Code flow only)
- OpenID Connect Discovery 1.0 (provider configuration only)
- OpenID Connect Session Management 1.0
- OAuth 2.0 Token Introspection (RFC 7662)
- OAuth Token Revocation (RFC 7009)
- JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants (RFC 7523) (using a JWT as an authorization grant only, used during provisioning)
- Proof Key for Code Exchange (PKCE) by OAuth Public Clients (RFC 7636) (S256 method only)
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):
- a user
- an application instance identified by a
client_id
- scopes
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:
-
if there is a one-to-one mapping between
client_id
s and application instances, in Ozwillo we also add a one-to-many relation between applications instances and services. It means that several services (end-user endpoints) may exist within the sameclient_id
; -
the
id_token
sent by Ozwillo as an outcome of step #5 contains the official required fields, plus Ozwillo specific properties (app_user
andapp_admin
boolean values).
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 |
response_mode | determines the mechanism used to send the response, in our case the value is always “query” | 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 |
code_challenge | code challenge for PKCE | string |
code_challenge_method | if code_challenge is present, value must be “S256” | string |
prompt | whether to prompt the user for authentication and/or consent; value can be “none”, “login”, and/or “consent” (space separated) | string |
id_token_hint | ID Token previously issued by Ozwillo being passed as a hint about the expected user | JWT string |
max_age | allowable elapsed time in seconds since the last time the user was actively authenticated, failing that he’ll be prompted to reauthenticate | integer |
claims | request that specific claims about the user be returned (only userinfo requested claims are handled, id_token is ignored) | JSON string |
ui_locales | user’s preferred languages for the user interface (only used when not already authenticated) | string (BCP47 codes separated by spaces) |
The claims
parameter value is a URL-escaped JSON string following a precise structure. It should have a userinfo
property (any other property than userinfo
will be ignored), whose value is another JSON object. Properties of that other object are claim names (unrecognized names will be ignored, so beware of typos!), and values can either be null
or a JSON object with an essential
boolean property. A null
value is equivalent to an {"essential":false}
object, and the essential
property tells Ozwillo that the claim is essential for the application (see the introduction for what it means for a claim to be essential.)
As a result, a request could look like:
GET /a/auth? response_type=code &client_id={client_id} &scope=openid &claims=%7B%22userinfo%22%3A%7B%22nickname%22%3Anull%2C%22locale%22%3Anull%7D%7D &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcb &state=security_token%3D{random_value}%26url%3Dhttps%3A%2F%2Fapp.example.com%2Fhome &nonce={another_random_value} HTTP/1.1 Host: accounts.ozwillo-preprod.eu
In this example, some characters (among which { “ : , } / = &) have been URL-escaped.
For instance, the claims
value, URL-unescaped, reads {"userinfo":{"nickname":null,"locale":null}}
, which requests the nickname
and locale
claims as voluntary claims.
#2 Ozwillo response
Several operations are then conducted on Ozwillo side:
- validate the incoming authentication request from step #1;
- authenticate the user;
- ask user to consent to the scopes and claims requested 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 and/or claims? 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 consent 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}&session_state={session_state}
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}&session_state={session_state} 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
.
The session_state
is an opaque value sent by Ozwillo that can be used with OpenID Connect Session Management 1.0.
#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 |
code_verifier | code verifier for PKCE | 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
- design a collaborative data workflow that not only minds other users but also other apps,
- design the rights policy that goes with it and setting it up.
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 :
- taking in to account the facts that current, local data might not be up-to-date or in sync with the Datacore’s,
- and that there may be more data in the Datacore.
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:
{scopes}
Where:
- host value is shortened to the host subdomain among
accounts
,kernel
ordata
(read more) - auth value is:
- input describes a specific required input content type:
JSON
forContent-Type: application/json;charset=UTF-8
form
forContent-Type: application/x-www-form-urlencoded
- output describes available output content types (
JWT
implying signedJWT
) - SCOPES is filled when the endpoint response depends on these scopes being previously granted by the end-user and thus associated to an
access_token
(it means this scopes option will only appear forbearer
auth)
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:
- when trying to get a resource (
GET
command), in order to benefit from your client-side cache if any, provide an If-None-Match header set with the version you have in said cache as entity-tag. - when attempting any change (
POST
,PUT
orDELETE
command), it is mandatory to provide the project they (should) reside in using the X-Datacore-Project HTTP header. Otherwise, the current project will be the defaultoasis.main
one. Therefore it is also mandatory if resources reside in a project that is not among oasis.main’s visible projects, such asoasis.sandbox
or forks. So it is an overall good practice. - providing the
X-Datacore-View
header allows to get back fewer data, in order to optimize bandwidth use.
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
Returns a JSON Web Key Set containing the public key. See JWK (RFC 7517) for more informations about JWKS.
Users
Return claims about the end-user
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 |
gender | User’s gender; either male or female |
string | profile |
birthdate | User’s birthday, in ISO 8601 YYYY-MM-DD format |
string | profile |
locale | User’s locale, as a BCP47 language tag | string | profile |
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_verified | 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 |
Please note that the “included if scope” column is only indicative, in case you used to use scopes to access private profile information or you do not have the choice. You’re strongly encouraged to request access to individual claims through the claims
request parameter to the authentication endpoint (see the authentication request) instead.
Return claims about the end-user
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
See OAuth 2.0 and OpenID Connect 1.0 for more information.
Grant authorizations to the client application
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
See OAuth 2.0 and OpenID Connect 1.0 for more information.
Get information about an access token
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
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
See above for details.
Notify an error while provisioning the instance
See above for details.
Retrieve the services of the application instance
Response body
The response is a JSON Array of service objects as used during provisioning.
Add a new service to the application instance
Request body
See embedded service objects used during provisioning.
Services
Retrieve information about a service
Response body
See embedded service objects used during provisioning.
Update a service
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
Access control
Ozwillo manages access control lists that links user to application instances.
Retrieve app_users (and app_admins) of the app instance
This API is only available to users who are already app_admin
s 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
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
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
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
The returned location URL get access to the subscription (delete the subscription)
Request body
Delete an event subscription
If-Match
Publish a typed event into the event bus
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:
- GET /dc/type/{type} for typed queries (criteria including $fulltext, sort, ranged query- or iterator-based pagination, debug mode, view),
- POST /dc/type/{type} for typed creation.
Also useful are:
- its companion PUT /dc/type/{type} for updates (mind the version),
- GET /dc/type/{type} (mind the version to benefit from your client-side cache if any),
- and DELETE /dc/type/{type} (mind the version).
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.