The aim of this document is to explain how to configure and deploy OperatorFabric.

1. Deployment

For now OperatorFabric consist of Docker images available either by compiling the project or by using images releases from Dockerhub

Service images are all based on openjdk:8-jre-alpine.

For simple one instance per service deployment, you can find a sample deployment as a docker-compose file here

To run OperatorFabric in development mode, see the development environment documentation .

2. User computer

From the user perspective, OperatorFabric is compatible with recent versions of chrome, edge and firefox and has no specific hardware performance constraints.

Be aware that if you use OperatorFabric in realtime you should prevent user’s computers to go in "sleep mode".

In particular, if you are using edge have a look to github.com/opfab/operatorfabric-core/issues/2754

3. Configuration

OperatorFabric has multiple services to configure.

See the architecture documentation for more information on the different services.

All services are SpringBoot applications and use jetty as an embedded servlet container. As such, they share some common configuration which is described in the following documentation:

Configuration is centralized in the config directory, the dev sub-directory is specific to development environments while the docker sub-directory is a specific configuration meant for use in a full docker environment.

3.1. Business service configuration

3.1.1. Specify an external configuration

When starting docker in the full docker environment an external environment file could be provided like:

cd ./config/docker
./docker-compose.sh ~/config/local.env

In the provided environment file the Spring active profiles can be set.

SPRING_PROFILES_ACTIVE=docker,local

This way the configuration file 'cards-publication-local.yml' can be provided in the same configuration directory and can be read by SpringBoot

3.1.2. Shared service configuration

The configuration shared by all services is in a yaml file, you can find an example with the file /config/docker/common-docker.yml. In this file you will find, among others, the parameters below :

name default mandatory? Description

operatorfabric.servicesUrls.users

yes

Indicates where the Users service can we reached (to get information about the current user).

3.1.3. Service specific configurations

Each service has a specific yaml configuration file. It should a least contain the name of the service:

spring:
  application:
    name: businessconfig

Examples of configuration of each service can be found either under config/docker or config/dev depending on the type of deployment you’re looking for.

3.1.3.1. Businessconfig service

The businessconfig service has this specific property :

name default mandatory? Description

operatorfabric.businessconfig.storage.path

null

no

File path to data storage folder

3.1.3.2. Users service

The user service has these specific properties :

name default mandatory? Description

operatorfabric.users.default.users

null

no

Array of user objects to create upon startup if they don’t exist

operatorfabric.users.default.user-settings

null

no

Array of user settings objects to create upon startup if they don’t exist

operatorfabric.users.default.groups

null

no

Array of group objects to create upon startup if they don’t exist

operatorfabric.users.default.entities

null

no

Array of entity objects to create upon startup if they don’t exist

3.1.3.3. Cards-publication service

The cards-publication service has these specific properties :

name default mandatory? Description

checkAuthenticationForCardSending

true

no

If false, OperatorFabric will not require user authentication to send or delete a card via endpoint /cards (it does not concern user cards which always need authentication). Be careful when setting the value to false, nginx conf must be adapted for security reasons (see security warning in the reference nginx.conf)

spring.kafka.consumer.group-id

null

no

If set, support for receiving cards via Kafka is enabled

spring.deserializer.value.delegate.class

io.confluent.kafka.serializers. KafkaAvroDeserializer

yes

Deserializer used to convert the received bytes into objects

spring.serializer.value.delegate.class

io.confluent.kafka.serializers. KafkaAvroSerializer

yes

Serializer used to convert cards to bytes

spring.kafka.producer.bootstrap-servers

localhost:9092

no

comma separated list of URL(s) of the broker(s) / bootstrap server(s)

opfab.kafka.topics.card.topicname

opfab

no

Name of the topic to read the messages from

opfab.kafka.topics.response-card.topicname

opfab

no

Name of the topic to place the response cards to

opfab.kafka.schema.registry.url

localhost:8081

yes

URL of the schema registry. Can be set to the empty string "" is no registry is used

3.1.3.4. Cards-consultation service

The cards-consultation service has these specific properties :

name default mandatory? Description

checkIfUserIsAlreadyConnected

true

no

If false, OperatorFabric will allow a user to have several sessions opened at the same time. However, it may cause synchronization problems between the sessions using the same login, so it is recommended to let it to true, its default value.

3.2. Web UI Configuration

OperatorFabric Web UI service is built on top of a NGINX server. It serves the Angular SPA to browsers and act as a reverse proxy for other services.

3.2.1. NGINX configuration

An external nginx.conf file configures the OperatorFabric Nginx instance named web-ui service. Those files are mounted as docker volumes. There are two of them in OperatorFabric, one in config/dev and one in config/docker.

The one in config/dev is set with permissive CORS rules to enable web development using ng serve within the ui/main project.It’s possible to use ng serve with the one in config/docker version also. To do so use the conf file named nginx-cors-permissive.conf by configuring the /docker-compose.yml with the following line:

- "./nginx-cors-permissive.conf:/etc/nginx/conf.d/default.conf"

instead of:

- "./nginx.conf:/etc/nginx/conf.d/default.conf"

The line customized in the nginx configuration file must end with à semicolon (';') otherwise the Nginx server will stop immediately

3.2.2. UI properties

The properties lie in the web-ui.json.The following table describes their meaning and how to use them. An example file can be found in the config/docker directory.

name default mandatory? Description

security.provider-realm

yes

The realm name in keycloak server settings page. This is used for the log out process to know which realm should be affected.

security.provider-url

yes

The keycloak server instance

security.logout-url

yes

The keycloak logout URL. Is a composition of: - Your keycloak instance and the auth keyword (ex: www.keycloakurl.com/auth), but we also support domains without auth (ex: www.keycloakurl.com/customPath) - The realm name (Ex: dev) - The redirect URL (redirect_uri): The redirect URL after success authentication

security.changePasswordUrl

no

URL to change the user password (if the top-right menu item "Change password" is visible)

security.oauth2.flow.mode

PASSWORD

no

authentication mode, available options:

  • CODE: Authorization Code Flow;

  • PASSWORD: Direct Password Flow (fallback);

  • IMPLICIT: Implicit Flow.

security.oauth2.flow.provider

null

no

provider name to display on log in button

security.oauth2.flow.delegate-url

null

no

Url to redirect the browser to for authentication. Mandatory with:

  • CODE flow: must be the url with protocol choice and version as query parameters;

  • IMPLICIT flow: must be the url part before .well-known/openid-configuration (for example in dev configuration it’s localhost:89/auth/realms/dev).

feed.card.time.display

BUSINESS

no

card time display mode in the feed. Values :

  • BUSINESS: displays card with entire business period. It the fallback if the set value is none of the values listed here;

  • BUSINESS_START: displays card with business start date;

  • PUBLICATION: displays card with publication date;

  • LTTD: displays card with lttd date;

  • NONE: nothing displayed.

feed.card.hideTimeFilter

false

no

Control if you want to show or hide the time filter in the feed page

feed.card.hideAckFilter

false

no

Control if you want to show or hide the acknowledgement filter in the feed page

feed.card.hideResponseFilter

false

no

Control if you want to show or hide the response filter in the feed page

feed.card.hideApplyFiltersToTimeLineChoice

false

no

Control if you want to show or hide the option of applying filters or not to timeline in the feed page

feed.card.hideReadSort

false

no

Control if you want to show or hide the option to sort cards by read status in the feed page

feed.card.hideSeveritySort

false

no

Control if you want to show or hide the option to sort cards by severity in the feed page

feed.card.hideAckAllCardsFeature

true

no

Control if you want to show or hide the option for acknowledging all the visible cards of the feed

feed.card.secondsBeforeLttdForClockDisplay

180

no

Number of seconds before lttd when a clock is activated in cards on the feed

feed.card.maxNbOfCardsToDisplay

100

no

Max number of card visible in feed (This limit is used for performance reasons, setting the value too high can have consequences on browser response times)

feed.timeline.domains

["TR", "J", "7D", "W", "M", "Y"]

no

List of domains to show on the timeline, possible domains are : "TR", "J", "7D", "W", "M", "Y".

feed.enableGroupedCards

false

no

(Experimental feature): If true cards with the same tags are grouped together. Clicking on the grouped card will show the other cards with the same tags in the feed.

feed.geomap.enable

false

no

(Experimental feature): If true a geographical map will be shown and cards that have geographical coordinates will be drawn on the map.

feed.geomap.defaultDataProjection

EPSG:4326

no

The default data projection of card.wktGeometry to use when no wktProjection is embedded in the card.

feed.geomap.initialLongitude

0

no

The initial longitude of the map when no cards with geographical coordinates are present.

feed.geomap.initialLatitude

0

no

The initial latitude of the map when no cards with geographical coordinates are present.

feed.geomap.initialZoom

1

no

Initial zoom level of the map.

feed.geomap.maxZoom

11

no

Max zoom level, to prevent zooming in too much when only one card is shown (or multiple cards in close proximity).

feed.geomap.zoomDuration

500

no

Time in milliseconds it takes to zoom the map to the specific location. Set to 0 to disable the zoom animation.

i18n.supported.locales

no

List of supported locales (Only fr and en so far) Values should be taken from the TZ database.

archive.filters.page.size

10

no

The page size of archive filters

archive.history.size

100

no

The maximum size of card history visible

archive.filters.tags.list

no

List of tags to choose from in the corresponding filter in archives page

archive.filters.publishDate.days

10

no

The default search period (days) for publish date filter in archives page

logging.filters.tags.list

no

List of tags to choose from in the corresponding filter in logging page

logging.filters.publishDate.days

10

no

The default search period (days) for publish date filter in logging page

settings.styleWhenNightDayModeDesactivated

no

style to apply if not using day night mode, possible value are DAY or NIGHT

settings.dateFormat

Value from the browser configuration

no

Format for date rendering (example : DD/MM/YYYY )

settings.timeFormat

Value from the browser configuration

no

Format for time rendering (example : HH:mm )

settings.dateTimeFormat

Value from the browser configuration

no

Format for date and time rendering (example : HH:mm DD/MM/YYYY )

settings.infos.hide.description

false

no

Control if we want to hide(true) or display(false or not specified) the user description in the settings page

settings.infos.hide.language

false

no

Control if we want to hide(true) or display(false or not specified) the language in the settings page

settings.infos.hide.sounds

false

no

Control if we want to hide(true) or display(false or not specified) the checkboxes for sound notifications in the settings page

settings.infos.hide.remoteLoggingEnabled

false

no

Control if we want to hide(true) or display(false or not specified) the checkbox to activate remote logging

settings.locale

en

no

Default user locale (use en if not set)

settings.playSoundForAlarm

false

no

If set to true, a sound is played when Alarm cards are added or updated in the feed

settings.playSoundForAction

false

no

If set to true, a sound is played when Action cards are added or updated in the feed

settings.playSoundForCompliant

false

no

If set to true, a sound is played when Compliant cards are added or updated in the feed

settings.playSoundForInformation

false

no

If set to true, a sound is played when Information cards are added or updated in the feed

settings.playSoundOnExternalDevice

false

no

If set to true (and externalDevicesEnabled is set to true as well) and the user has an external device configured, sounds will be played on this device rather than in the browser

settings.replayEnabled

false

no

If set to true, sounds are replayed every settings.replayInterval seconds until the user interacts with the application

settings.replayInterval

5

no

Interval between sound replays (see settings.replayEnabled)

settings.remoteLoggingEnabled

false

no

If set to true, some logs form the UI are sent to the back and write in the log file of the cards-consultation service

settings.about

none

no

Declares application names and their version into web-ui about section.
Each entry is a free key value followed by its name (a string of characters), its version (a string of characters) and its facultative rank of declaration (a number).
For OperatorFabric value, with 'OperatorFabric' as name and 0 as rank, the value of ${currentVersion} is the version of the current release, 1.3.0.RELEASE for example.
It should look like:

"operatorfabric": {
 "name":  "OperatorFabric",
 "version":  "1.3.0.RElEASE",
 "rank": 0
}

logo.base64

medium OperatorFabric icon

no

The encoding result of converting the svg logo to Base64, use this online tool to encode your svg. If it is not set, a medium (32px) OperatorFabric icon is displayed.

logo.height

40

no

The height of the logo (in px) (only taken into account if logo.base64 is set). The value cannot be more than 48px (if it is set to more than 48px, it will be ignored and set to 48px).

logo.width

40

no

The width of the logo (in px) (only taken into account if logo.base64 is set).

title

OperatorFabric

no

Title of the application, displayed on the browser

environmentName

no

Name of the environment to display in the top-right corner (examples: PROD , TEST .. ), if the value not set the environment name is not shown .

environmentColor

blue

no

Color of the background of the environment name. The format of color is css, for example : red , #4052FF

showUserEntitiesOnTopRightOfTheScreen

false

no

if set to true the users entities will be displayed under the login on top right of the screen

usercard.useDescriptionFieldForEntityList

false

no

If true, show entity description field instead of name in user card page

externalDevicesEnabled

false

no

If true, users have the opportunity to play sounds on external devices rather than in the browser. See settings.playSoundOnExternalDevice

secondsToCloseSession

60

no

Number of seconds between logout and token expiration

selectActivityAreaOnLogin

false

no

if set to true the users belonging to multiple Entities will be required to configure activity area on login

checkIfUrlIsLocked

true

no

if set to false, an OperatorFabric url can be used by several tabs in the same browser. Note that there can only be one token per browser for a given OperatorFabric url, so the first session will be replaced by the second one

IMPORTANT:

To declare settings parameters, you now need to group all fields under settings: { } For example:

Replace the following invalid settings config

  "settings.replayInterval": 10,
  "settings.replayEnabled": true,
  "settings": {
    "about": {
      "keycloack": {
        "name": "Keycloak",
        "rank": 2,
        "version": "6.0.1"
      },
    }
    "locale": "en",
    "dateTimeFormat": "HH:mm DD/MM/YYYY",
    "dateFormat": "DD/MM/YYYY",
    "styleWhenNightDayModeDesactivated": "NIGHT"
  },

By this valid one :

  "settings": {
    "replayInterval": 10,
    "replayEnabled": true,
    "about": {
      "keycloack": {
        "name": "Keycloak",
        "rank": 2,
        "version": "6.0.1"
      },
    }
    "locale": "en",
    "dateTimeFormat": "HH:mm DD/MM/YYYY",
    "dateFormat": "DD/MM/YYYY",
    "styleWhenNightDayModeDesactivated": "NIGHT"
  },
3.2.2.1. External Devices Service

The external devices service can be configured with the following properties:

name default mandatory? Description

operatorfabric.externaldevices.watchdog.enabled

false

no

If true, watchdog signals will be sent to external devices to show that the OperatorFabric is running and connected.

operatorfabric.externaldevices.watchdog.cron

*/5 * * * * *

no

CRON expression determining when watchdog signals should be sent to external devices.

operatorfabric.externaldevices.watchdog.signalId

0

no

Id the signal the external devices are expecting as watchdog

3.3. Security Configuration

Configure the security concern throughout several files:

  • nginx.conf of the nginx server

  • config/dev/common-dev.yml or config/docker/common-docker.yml, called common.yml in the following chapters

  • web-ui.json served by the web-ui service;

For each user it is possible to configure a list of authorized source ip addresses by setting the authorizedIPAddresses field in User object.

3.3.1. Authentication configuration

There are 3 OAuth2 Authentication flows available into OperatorFabric UI:

  • password grant: referred as PASSWORD mode flow;

  • code flow : referred as CODE mode flow;

  • implicit flow: referred as IMPLICIT mode flow.

Alternatively there is also another flow available:

  • none flow: referred as NONE mode flow.

The NONE flow assumes that the application is behind a secured proxy wich handles login and token generation. Calls to backend services will get a valid token added to the headers and the token will not be visible for the (web)client.

3.3.1.1. Nginx Configuration

The UI calls need some mapping to reach the Authentication Provider. In the default OperatorFabric configuration it’s a docker keycloak instance, called keycloak in the project docker-compose.yml files.

There are 3 properties to configure within nginx.conf file:

  • $KeycloakBaseUrl: the base url of keycloak;

  • $OperatorFabricRealm: the realm configure within keycloak instance to provide authentication to OperatorFabric;

  • $ClientPairOFAuthentication: base64 encoded string of the pair of client authentication used by OperatorFabric to log to the Authentication Provider (keycloak). The cient-id and the client-secret are separated by a colon(':').

Example of the docker configuration

# Url of the Authentication provider
    set $KeycloakBaseUrl "http://keycloak:8080";
# Realm associated to OperatorFabric within the Authentication provider
    set $OperatorFabricRealm "dev";

# base64 encoded pair of authentication in the form of 'client-id:secret-id'
    set $ClientPairOFAuthentication "b3BmYWItY2xpZW50Om9wZmFiLWtleWNsb2FrLXNlY3JldA==" ;

where b3BmYWItY2xpZW50Om9wZmFiLWtleWNsb2FrLXNlY3JldA== is the base64 encoded string of opfab-client:opfab-keycloak-secret with opfab-client as client-id and opfab-keycloak-secret its client secret.

3.3.1.2. Configuration file common.yml
name default mandatory? Description

spring.security.provider-url

null

no

The keycloak instance url.

spring.security.provider-realm

null

no

The realm name within the keycloak instance.

spring.security.oauth2.resourceserver.jwt.jwk-set-uri

null

yes

The url providing the certificat used to verify the jwt signature

example of common.yml

spring:
  security:
    provider-url: http://localhost:89
    provider-realm: dev
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: ${spring.security.provider-url}/auth/realms/${spring.security.provider-realm}/protocol/openid-connect/certs

where jwt-set-uri reuses provider-url and provider-realm properties.

3.3.1.3. Configuration file web-ui.json

Nginx web server serves this file. OperatorFabric creates and uses a custom Docker image containing an Nginx server with a docker volume containing this file. The two docker-compose environments contain an example of it. The path in the image to it is /usr/share/nginx/html/opfab/web-ui.json.

For OAuth2 security concerns into this file, there are two ways to configure it, based on the Oauth2 chosen flow. There are several common properties:

  • security.provider-realm: OAuth2 provider realm under which the OpertaroFabric client is declared;

  • security.provider-url: url of the keycloak server instance.

  • security.logout-url: url used when a user is logged out of the UI;

  • security.oauth2.flow.provider: name of the OAuth2 provider;

  • security.oauth2.flow.delegate-url: url used to connect to the Authentication provider;

  • security.oauth2.flow.mode: technical way to be authenticated by the Autentication provider.

3.3.1.4. OAuth2 PASSWORD or CODE Flows

These two modes share the same way of declaring the delegate URL. CODE is the default mode of authentication for deploy docker-compose environment.

  • security.oauth2.flow.mode to PASSWORD or CODE;

  • security.oauth2.flow.delegate-url with the URL of the OAuth2 leading to the protocol used for authentication.

Example of Configuration For CODE Flow
{
    "security": {
      "oauth2": {
        "flow": {
          "mode": "CODE",
          "provider": "Opfab Keycloak",
          "delegate-url": "http://localhost:89/auth/realms/dev/protocol/openid-connect/auth?response_type=code&client_id=opfab-client"
      },
      "logout-url":"http://localhost:89/auth/realms/dev/protocol/openid-connect/logout?redirect_uri=http://localhost:2002/",
    "provider-realm": "dev",
    "provider-url": "http://localhost:89"
    }
  }
}

Within the delegate-url property dev is the keycloak client realm of OperatorFabric. For keycloak instance used for development purposes, this delegate-url correspond to the realm under which the client opfab-client is registred. Here, the client-id value is opfab-client which is define as client under the realm named dev on the dev keycloak instance.

3.3.1.5. OAuth2 IMPLICIT Flow

It had its own way of configuration. To enable IMPLICIT Flow authentication the following properties need to be set:

  • security.oauth2.flow.mode to IMPLICIT;

  • security.oauth2.flow.delegate-url with the URL of the OAuth2 leading to the .well-known/openid-configuration end-point used for authentication configuration.

Example of configuration for IMPLICIT Flow
{
  "operatorfabric": {
    "security": {
      "oauth2": {
        "flow": {
          "mode": "IMPLICIT",
          "provider": "Opfab Keycloak",
          "delegate-url": "http://localhost:89/auth/realms/dev"
      },
      "logout-url":"http://localhost:89/auth/realms/dev/protocol/openid-connect/logout?redirect_uri=http://localhost:2002/",
    "provider-realm": "dev",
    "provider-url": "http://localhost:89"
      }
    }
  }
}

Within the delegate-url property dev is the keycloak client realm of OperatorFabric. For keycloak instance used for development purposes, this delegate-url correspond to the realm under which the client opfab-client is registred. The url look up by the implicit ui mechanism is localhost:89/auth/realms/dev/.well-known/openid-configuration.

3.3.1.6. NONE Flow

The configuration for the NONE flow is a bit different because the token isn’t handled/visible in the front-end.

Nginx

The following variables can be removed:

  • $KeycloakBaseUrl

  • $OperatorFabricRealm

  • $ClientPairOFAuthentication

The locations for handling tokens can be edited to return a 401 by default. If one of these locations is called, the token generated by the secured proxy has expired.

location /auth/check_token {
  return 401;
}
location /auth/token {
  return 401;
}
location /auth/code/ {
  return 401;
}
Web-ui.json

Set the security.oauth2.flow.mode to NONE;

Set the security.oauth2.client-id to Your Oauth client ID;

Use the security.jwt.login-claim to select value from the token will be used to identify your account. In this example preferred_username is used;

Settings that are not required are:

  • delegate-url

  • provider-realm

Example of configuration
{
  "operatorfabric": {
    "security": {
      "jwt": {
        "expire-claim": "exp",
        "login-claim": "preferred_username"
      },
      "oauth2": {
        "client-id": "OAUTHCLIENTID",
        "flow": {
          "mode": "NONE",
          "provider": "My Secured Proxy"
      },
      "logout-url":"http://my-secured-proxy/OAUTHTENANTID/oauth2/logout?client_id=OAUTHCLIENTID&post_logout_redirect_uri=https%3A%2F%2Flocalhost:2002%2Fui%2Fui/",
      "provider-url": "http://my-secured-proxy/"
      }
    }
  }
}

3.3.2. User creation

Setting automated user creation==. Creation user requires a user id. Given name and family name are optional.

name default mandatory? Description

operatorfabric.security.jwt.login-claim

sub

no

Jwt claim is used as a user login or id

operatorfabric.security.jwt.given-name-claim

given-name

no

Jwt claim is used to set the user’s given name

operatorfabric.security.jwt.family-name-claim

family-name

no

Jwt claim is used to set the user’s family name

3.3.3. Alternative way to manage groups (and/or entities)

By default, OperatorFabric manages groups (and/or entities) through the user's collection in the database. Another mode can be defined, the JWT mode. The groups (and/or entities) come from the authentication token. The administrator of the authentication service has to set what claims define a group (and/or entity). In the Operator-Fabric configuration, the opfab administrator has to set properties to retrieve those groups (and/or entities).

name default mandatory? Description

operatorfabric.security.jwt.groups.mode

OPERATOR_FABRIC

no

Set the group mode, possible values JWT or OPERATOR_FABRIC

operatorfabric.security.jwt.groups.rolesClaim.rolesClaimStandard.path

no

path in the JWT to retrieve the claim that defines a group

operatorfabric.security.jwt.groups.rolesClaim.rolesClaimStandardArray.path

no

path in the JWT to retrieve the claim that defines an array of groups

operatorfabric.security.jwt.groups.rolesClaim.rolesClaimStandardList.path

no

path in the JWT to retrieve the claim that defines a list of group

operatorfabric.security.jwt.groups.rolesClaim.rolesClaimStandardList.separator

no

set the separator value of the list of group

operatorfabric.security.jwt.groups.rolesClaim.rolesClaimCheckExistPath.path

no

path in the JWT to check if that path does exist, if it does, use the roleValue as a group

operatorfabric.security.jwt.groups.rolesClaim.rolesClaimCheckExistPath.roleValue

no

set the value of the group if the path exists

operatorfabric.security.jwt.entitiesIdClaim

no

set the name of the field in the token

operatorfabric.security.jwt.gettingEntitiesFromToken

no

boolean indicating if you want the entities of the user to come from the token and not mongoDB (possible values : true/false)

application.yml

operatorfabric:
  security:
    jwt:
      entitiesIdClaim: entitiesId
      gettingEntitiesFromToken: true
      groups:
        mode: JWT # value possible JWT | OPERATOR_FABRIC
        rolesClaim:
          rolesClaimStandard:
            - path: "ATTR1"
            - path: "ATTR2"
          rolesClaimStandardArray:
            - path: "resource_access/opfab-client/roles"
          rolesClaimStandardList:
            - path: "roleFieldList"
              separator: ";"
          rolesClaimCheckExistPath:
            - path: "resource_access/AAA"
              roleValue: "roleAAA"
            - path: "resource_access/BBB"
              roleValue: "roleBBB"

JWT example

{
  "jti": "5ff87583-10bd-4946-8753-9d58171c8b7f",
  "exp": 1572979628,
  "nbf": 0,
  "iat": 1572961628,
  "iss": "http://localhost:89/auth/realms/dev",
  "aud": [
    "AAA",
    "BBB",
    "account"
  ],
  "sub": "example_user",
  "typ": "Bearer",
  "azp": "opfab-client",
  "auth_time": 0,
  "session_state": "960cbec4-fcb2-47f2-a155-975832e61300",
  "acr": "1",
  "realm_access": {
    "roles": [
      "offline_access",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "AAA": {
      "roles": [
        "role_AAA"
      ]
    },
    "BBB": {
      "roles": [
        "role_BBB"
      ]
    },
    "opfab-client": {
      "roles": [
        "USER"
      ]
    },
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "openid ATTR2 email ATTR1 profile roleFieldList",
  "email_verified": false,
  "name": "example_firtstname example_lastname",
  "ATTR2": "roleATTR2",
  "ATTR1": "roleATTR1",
  "preferred_username": "example_user",
  "given_name": "example_firtstname",
  "entitiesId": "ENTITY1",
  "family_name": "example_lastname",
  "email": "example_user@mail.com",
  "roleFieldList": "roleA;roleB;roleC"
}

As the result, the group will be [ATTR1, ATTR2, roleA, roleB, roleC, USER, roleBBB, roleAAA]

3.3.4. Adding certification authorities or certificates to the Java keystore

If you’re using certificates (for example for Keycloak) that are not from a certification authority trusted by the JVM, this will cause errors such as this one:

Missing certificate error message
Caused by: sun.security.validator. ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to
requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:397)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:302)
at sun.security.validator.Validator.validate(Validator.java:262)
at sun.security.ssl.X509TrustManager Impl.validate(x509TrustManagerImpl.java:330)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(x509TrustManagerImpl.java:237)
at sun.security.ssl.X509TrustManager Impl.checkServerTrusted(x509TrustManager Impl.java:132)
at sun.security.ssl.clientHandshaker.serverCertificate(ClientHandshaker.java:1621)
94 common frames omitted
Caused by: sun.security.provider.certpath. SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath. SunCertPathBuilder.build(SunCertPathBuilder.java:141)
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:392)
... 100 common frames omitted

If that is the case, you can pass the additional authorities or certificates that you use to the containers at runtime.

To do so, put the relevant files (*.der files for example) under src/main/docker/certificates.

  1. This directory should only contain the files to be added to the keystore.

  2. The files can be nested inside directories.

  3. Each certificate will be added with its filename as alias. For example, the certificate in file mycertificate.der will be added under alias mycertificate. As a consequence, filenames should be unique or it will cause an error.

  4. If you need to add or remove certificates while the container is already running, the container will have to be restarted for the changes to be taken into account.

If you would like certificates to be sourced from a different location, replace the volumes declarations in the deploy docker-compose.yml file with the selected location:

volumes:
 - "path/to/my/selected/location:/certificates_to_add"

instead of

volumes:
 - "../../../../src/main/docker/certificates:/certificates_to_add"
The steps described here assume you’re running OperatorFabric in docker mode using the deploy docker-compose, but they can be adapted for single container deployments and development mode.

If you want to check that the certificates were correctly added, you can do so with the following steps:

  1. Open a bash shell in the container you want to check

    docker exec -it deploy_businessconfig_1 bash
  2. Run the following command

    $JAVA_HOME/bin/keytool -list -v -keystore /tmp/cacerts -storepass changeit

You can also look at the default list of authorities and certificates trusted by the JVM with this command:

$JAVA_HOME/bin/keytool -list -v -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit

3.4. OperatorFabric Mongo configuration

We only use URI configuration for mongo through the usage of the spring.data.mongodb.uris, it allows us to share the same configuration behavior for simple or cluster configuration and with both spring classic and reactive mongo configuration. See mongo connection string for the complete URI syntax.

3.4.1. Define time to live for archived cards

By default, archived cards will remain stored in the database forever. It is possible to have them automatically removed after a specified duration by using the TTL index feature of mongoDB on their publishDate field.

For example, to have cards expire after 10 days (864000s), enter the following commands in the mongo shell:

use operator-fabric
db.archivedCards.createIndex( { "publishDate": 1 }, { expireAfterSeconds: 864000 } )
You cannot use createIndex() to change the value of expireAfterSeconds of an existing index. Instead use the collMod database command in conjunction with the index collection flag. Otherwise, to change the value of the option of an existing index, you must drop the index first and recreate.

3.5. OperatorFabric Kafka configuration

Next to publishing cards to OperatorFabric using the REST API, OperatorFabric also supports publishing cards via a Kafka Topic. In the default configuration Kafka is disabled. To enable Kafka you need to set the consumer group to the consumer group you assign to the OpFab Kafka consumer. This can be any group-id, as long as it isn’t used by other consumers (unless you explicitly want multiple consumers for the same group).

You can set the group_id by uncommenting the kafka.consumer.group_id in the cards-publication.yml

  kafka:
    consumer:
      group-id: opfab-command

By default, the consumer will consume messages from the opfab topic. See Spring for Apache Kafka for more information on the Spring Kafka implementation.

With the default settings, the Kafka consumer expects a broker running on http//127.0.0.1:9092 and a schema registry on 127.0.0.1:8081.

Operator Fabric is also able to publish response cards to a Kafka topic. The default topic name opfab-response. You can specify which response cards are to be returned via Kafka by setting the external-recipients in the cards-publication yaml file. Instead of setting http:// URL you should set it to kafka:

external-recipients:
  recipients:
    - id: "processAction"
      url: "http://localhost:8090/test"
      propagateUserToken: true
    - id: "mykafka"
      url: "kafka:topicname"
      propagateUserToken: false

Note that topicname is a placeholder for now. All response cards are returned via the same Kafka response topic, as specified in the opfab.kafka.topics.response-card field.

Also note enabling Kafka does not disable the REST interface.

Example Kafka configuration plain:

spring:
  application:
    name: cards-publication
  deserializer:
    value:
      delegate:
        class: org.opfab.cards.publication.kafka.consumer.KafkaAvroWithoutRegistryDeserializer
  serializer:
    value:
      delegate:
        class: org.opfab.cards.publication.kafka.producer.KafkaAvroWithoutRegistrySerializer
  kafka:
    consumer:
      group-id: OPFAB
      properties:
        specific:
          avro:
            reader: true
    producer:
      client-id: operatorfabric-producer
    bootstrap-servers: kafka-server:9092
opfab:
  kafka:
    topics:
      card:
        topicname: m_opfab-card-commands_dev
      response-card:
        topicname: m_opfab-card-response_dev

Example Kafka configuration SASL:

spring:
  application:
    name: cards-publication
  deserializer:
    value:
      delegate:
        class: org.opfab.cards.publication.kafka.consumer.KafkaAvroWithoutRegistryDeserializer
  serializer:
    value:
      delegate:
        class: org.opfab.cards.publication.kafka.producer.KafkaAvroWithoutRegistrySerializer
  kafka:
    consumer:
      group-id: OPFAB
      security:
        protocol: SASL_SSL
      properties:
        specific:
          avro:
            reader: true
        sasl:
          mechanism: SCRAM-SHA-256
          jaas:
            config: org.apache.kafka.common.security.scram.ScramLoginModule required username="kafkaUsername" password="kafkaPassword";
    producer:
      client-id: operatorfabric-producer
      security:
        protocol: SASL_SSL
      properties:
        sasl:
          mechanism: SCRAM-SHA-256
          jaas:
            config: org.apache.kafka.common.security.scram.ScramLoginModule required username="kafkaUsername" password="kafkaPassword";
    bootstrap-servers: kafka-server:9094
    ssl:
      trust-store-type: PKCS12
      trust-store-password: truststorePassword
      trust-store-location: file:///etc/truststore.pkcs
    properties:
      ssl:
        endpoint:
          identification:
            algorithm: ""
opfab:
  kafka:
    topics:
      card:
        topicname: opfab-card-commands
      response-card:
        topicname: opfab-card-response

Example Kafka configuration Kerberos:

spring:
  application:
    name: cards-publication
  deserializer:
    key:
      delegate:
        class: org.apache.kafka.common.serialization.StringDeserializer
    value:
      delegate:
        class: org.opfab.cards.publication.kafka.consumer.KafkaAvroWithoutRegistryDeserializer
  serializer:
    value:
      delegate:
        class: org.opfab.cards.publication.kafka.producer.KafkaAvroWithoutRegistrySerializer
  kafka:
    security:
      protocol: SASL_SSL
    properties:
      sasl.mechanism: GSSAPI
      sasl:
        jaas:
          config: com.sun.security.auth.module.Krb5LoginModule required useKeyTab=true keyTab="/etc/kafkaUsername.keytab" storeKey=true useTicketCache=false serviceName="kafka" principal="kafkaUsername@DOMAIN";
    bootstrap-servers: kafka-server:9094
    ssl:
      trust-store-type: pkcs12
      trust-store-password: truststorePassword
      trust-store-location: file:///etc/truststore.pkcs12
    consumer:
      group-id: OPFAB
      key-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
      value-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
      properties:
        spring:
          deserializer:
            key:
              delegate:
                class: org.apache.kafka.common.serialization.StringDeserializer
            value:
              delegate:
                class: org.opfab.cards.publication.kafka.consumer.KafkaAvroWithoutRegistryDeserializer
    producer:
      client-id: OPFAB
      value-serializer: org.opfab.cards.publication.kafka.producer.KafkaAvroWithoutRegistrySerializer
opfab:
  kafka:
    topics:
      card:
        topicname: opfab-card-commands
      response-card:
        topicname: opfab-card-response

4. RabbitMQ

4.1. Docker container

In development mode, the simplest way to deploy a RabbitMQ server is to create a RabbitMQ docker container. A docker-compose file is provided to allow quick setup of a convenient RabbitMQ server.

4.2. Server installation

This section is dedicated to production deployment of RabbitMQ. It is not complete and needs to be tailored to any specific production environment.

4.2.1. Download & Installation

Download and install RabbitMQ following the official procedure for the target environment

4.2.2. Used ports

If RabbitMQ may not bind to the following ports, it won’t start :

  • 4369: epmd, a peer discovery service used by RabbitMQ nodes and CLI tools

  • 5672, 5671: used by AMQP 0-9-1 and 1.0 clients without and with TLS

  • 25672: used for inter-node and CLI tools communication (Erlang distribution server port) and is allocated from a dynamic range (limited to a single port by default, computed as AMQP port + 20000). Unless external connections on these ports are really necessary (e.g. the cluster uses federation or CLI tools are used on machines outside the subnet), these ports should not be publicly exposed. See networking guide for details.

  • 35672-35682: used by CLI tools (Erlang distribution client ports) for communication with nodes and is allocated from a dynamic range (computed as server distribution port + 10000 through server distribution port + 10010). See networking guide for details.

  • 15672: HTTP API clients, management UI and rabbitmqadmin (only if the management plugin is enabled)

  • 61613, 61614: STOMP clients without and with TLS (only if the STOMP plugin is enabled)

  • 1883, 8883: (MQTT clients without and with TLS, if the MQTT plugin is enabled)

  • 15674: STOMP-over-WebSockets clients (only if the Web STOMP plugin is enabled)

  • 15675: MQTT-over-WebSockets clients (only if the Web MQTT plugin is enabled)

4.2.3. Production configuration

See the guide for production configuration guidelines

5. Monitoring

Operator Fabric provides end points for monitoring via prometheus. The monitoring is available for the four following services: user, businessconfig, cards-consultation, cards-publication. You can start a test prometheus instance via config/monitoring/startPrometheus.sh , the monitoring will be accessible on localhost:9090/

6. Logging Administration

Operator Fabric includes the ability to view and configure the log levels at runtime through APIs. It is possible to configure and view an individual logger configuration, which is made up of both the explicitly configured logging level as well as the effective logging level given to it by the logging framework. These levels can be one of:

  • TRACE

  • DEBUG

  • INFO

  • WARN

  • ERROR

  • FATAL

  • OFF

  • null

null indicates that there is no explicit configuration.

Querying and setting logging levels is restricted to administrators.

To view the configured logging level for a given logger it is possible to send a GET request to the '/actuator/logger' URI as follows:

curl http://<server>:<port>/actuator/loggers/${logger} -H "Authorization: Bearer ${token}" -H "Content-type:application/json"

where ${token} is a valid OAuth2 JWT for a user with administration privileges and ${logger} is the logger (ex: org.opfab)

The response will be a json object like the following:

{
  "configuredLevel" : "INFO",
  "effectiveLevel" : "INFO"
}

To configure a given logger, POST a json entity to the '/actuator/logger' URI, as follows:

curl -i -X POST http://<server>:<port>/actuator/loggers/${logger} -H "Authorization: Bearer ${token}" -H 'Content-Type: application/json' -d '{"configuredLevel": "DEBUG"}'

To “reset” the specific level of the logger (and use the default configuration instead) it is possible to pass a value of null as the configuredLevel.

7. Users, Groups and Entities Administration

A new operator call John Doe, who has OAuth granted right to connect ot current OperatorFabric instance, need to receive cards within current OperatorFabric instance. As a user of OperatorFabric, he needs to be added to the system with a login (john-doe-operator), his firstName (John) and his lastName (Doe). As there is no Administration GUI for the moment, it must be performed through command line, as detailed in the Users API.

7.1. Users

7.1.1. List all users

First of all, list the users (who are the recipients in OperatorFabric) of the system with the following commands:

Httpie

http http://localhost:2103/users "Authorization:Bearer $token" "Content-Type:application/type"

cURL

curl -v http://localhost:2103/users -H "Authorization:Bearer $token" -H "Content-Type:application/type"

response

HTTP/1.1 200 OK

[
    {
        "firstName": null,
        "groups": [
            "ADMIN"
        ],
        "entities": [
            "ENTITY1_FR",
            "ENTITY2_FR"
        ],
        "lastName": null,
        "login": "admin"
    },
    {
        "firstName": null,
        "groups": [
            "RTE",
            "ADMIN",
            "CORESO",
            "ReadOnly",
            "TEST"
        ],
        "lastName": null,
        "login": "operator3_fr"
    },
    {
        "firstName": null,
        "groups": [
            "ELIA"
        ],
        "lastName": null,
        "login": "elia-operator"
    },
    {
        "firstName": null,
        "groups": [
            "CORESO"
        ],
        "lastName": null,
        "login": "coreso-operator"
    },
    {
        "firstName": null,
        "groups": [
            "Dispatcher",
            "ReadOnly",
            "TEST"
        ],
        "entities": [
            "ENTITY1_FR"
        ],
        "lastName": null,
        "login": "operator1_fr"
    },
    {
        "firstName": null,
        "groups": [
            "Planner",
            "ReadOnly"
        ],
        "entities": [
            "ENTITY2_FR"
        ],
        "lastName": null,
        "login": "operator2_fr"
    },
]

7.1.2. Create a new User

We are sure that no John-doe-operator exists in our OperatorFabric instance. We can add him in our OperatorFabric instance using the following command use httpie:

echo '{"login":"john-doe-operator","firstName":"Jahne","lastName":"Doe"}' | http POST http://localhost:2103/users "Authorization:Bearer $token" "Content-Type:application/json"

Or here cURL:

curl -X POST http://localhost:2103/users -H "Authorization:Bearer $token" -H "Content-Type:application/json" --data '{"login":"john-doe-operator","firstName":"Jahne","lastName":"Doe"}'

response

HTTP/1.1 200 OK

{
    "firstName": "Jahne",
    "lastName": "Doe",
    "login": "john-doe-operator"
}

7.1.3. Fetch user details

It’s always a good thing to verify if all the information has been correctly recorded in the system:

with httpie:

http -b http://localhost:2103/users/john-doe-operator "Authorization:Bearer $token" "Content-Type:application/json"

or with cURL:

curl http://localhost:2103/users/john-doe-operator -H "Authorization:Bearer $token" -H "Content-Type:application/json"

response

HTTP/1.1 200 OK

{
    "firstName": "Jahne",
    "groups": [],
    "entities": [],
    "lastName": "Doe",
    "login": "john-doe-operator"
}

7.1.4. Update user details

As shown by this result, the firstName of the new operator has been misspelled. We need to update the existing user with john-doe-operator login. To correct this mistake, the following commands can be used:

with httpie:

echo '{"login":"john-doe-operator","lastName":"Doe","firstName":"John"}' | http PUT http://localhost:2103/users/john-doe-operator "Authorization:Bearer $token" "Content-Type:application/json"

or with cURL:

curl -X PUT http://localhost:2103/users/john-doe-operator -H "Authorization:Bearer $token" -H "Content-Type:application/json" --data '{"login":"john-doe-operator","firstName":"John","lastName":"Doe"}'

response

HTTP/1.1 200 OK

{
    "firstName": "John",
    "lastName": "Doe",
    "login": "john-doe-operator"
}

7.2. Groups/Entities

All the commands below :

  • List groups

  • Create a new group

  • Fetch details of a given group

  • Update details of a group

  • Add a user to a group

  • Remove a user from a group

are available for both groups and entities. In order not to overload the documentation, we will only detail group endpoints.

7.2.1. List groups (or entities)

This operator is the first member of a new group operator called the OPERATORS, which doesn’t exist for the moment in the system. As shown when we list the groups existing in the server.

Httpie

http http://localhost:2103/groups "Authorization:Bearer $token" "Content-Type:application/type"

cURL

curl http://localhost:2103/groups -H "Authorization:Bearer $token" -H "Content-Type:application/json"

response

HTTP/1.1 200 OK

[
    {
        "description": "The admin group",
        "name": "ADMIN group",
        "id": "ADMIN"
    },
    {
        "description": "RTE TSO Group",
        "name": "RTE group",
        "id": "RTE"
    },
    {
        "description": "ELIA TSO group",
        "name": "ELIA group",
        "id": "ELIA"
    },
    {
        "description": "CORESO Group",
        "name": "CORESO group",
        "id": "CORESO"
    },
    {
        "description": "Dispatcher Group",
        "name": "Dispatcher",
        "id": "Dispatcher"
    },
    {
        "description": "Planner Group",
        "name": "Planner",
        "id": "Planner"
    },
    {
        "description": "ReadOnly Group",
        "name": "ReadOnly",
        "id": "ReadOnly"
    }
]

7.2.2. Create a new group (or entity)

Firstly, the group called OPERATORS has to be added to the system using the following command:

using httpie:

echo '{"id":"OPERATORS","decription":"This is the brand new group of operator"}' | http POST http://localhost:2103/groups "Authorization:Bearer $token" "Content-Type:application/json"

using cURL:

curl -X POST http://localhost:2103/groups -H "Authorization:Bearer $token" -H "Content-Type:application/json" --data '{"id":"OPERATORS","decription":"This is the brand new group of operator"}'

response

HTTP/1.1 200 OK

{
    "perimeters": [],
    "description": null,
    "name": null,
    "id": "OPERATORS"
}

7.2.3. Fetch details of a given group (or entity)

The result returned seems strange, to verify if it’s the correct answer by displaying the details of the group called OPERATORS, use the following command:

using httpie:

http http://localhost:2103/groups/OPERATORS "Authorization:Bearer $token" "Content-Type:application/json"

using cURL:

curl http://localhost:2103/groups/OPERATORS -H "Authorization:Bearer $token" -H "Content-Type:application/json"

response

HTTP/1.1 200 OK

{
    "perimeters": [],
    "description": null,
    "name": null,
    "id": "OPERATORS"
}

7.2.4. Update details of a group (or entity)

The description is really null. After verification, in our first command used to create the group, the attribute for the description is misspelled. Using the following command to update the group with the correct spelling, the new group of operator gets a proper description:

with httpie:

echo '{"id":"OPERATORS","description":"This is the brand-new group of operator"}' | http -b PUT http://localhost:2103/groups/OPERATORS "Authorization:Bearer $token" "Content-Type:application/json"

with cURL:

curl -X PUT http://localhost:2103/groups/OPERATORS -H "Authorization:Bearer $token" -H "Content-Type:application/json" --data '{"id":"OPERATORS","description":"This is the brand-new group of operator"}'

response

{
    "perimeters": []
    "description": "This is the brand-new group of operator",
    "name": null,
    "id": "OPERATORS"
}

7.2.5. Add a user to a group (or entity)

As both new group and new user are correct it’s time to make the user a member of the group . To achieve this, use the following command:

with httpie:

echo '["john-doe-operator"]' | http PATCH http://localhost:2103/groups/OPERATORS/users "Authorization:Bearer $token" "Content-Type:application/json"

with cURL:

curl -X PATCH http://localhost:2103/groups/OPERATORS/users -H "Authorization:Bearer $token" -H "Content-Type:application/json" --data '["john-doe-operator"]'

response

HTTP/1.1 200 OK

Let’s verify that the changes are correctly recorded by fetching the :

http http://localhost:2103/users/john-doe-operator "Authorization:Bearer $token" "Content-Type:application/json"

with cURL

curl http://localhost:2103/users/john-doe-operator -H "Authorization:Bearer $token" -H "Content-Type:application/json"

response

HTTP/1.1 200 OK

{
    "firstName": "John",
    "groups": ["OPERATORS"],
    "entities": [],
    "lastName": "Doe",
    "login": "john-doe-operator"
}

It’s now possible to send cards either specifically to john-doe-operator or more generally to the OPERATORS group.

7.2.6. Remove a user from a group (or entity)

When John Doe is no longer in charge of hypervising cards for OPERATORS group, this group has to be removed from his login by using the following command:

with httpie:

http DELETE http://localhost:2103/groups/OPERATORS/users/john-doe-operator "Authorization:Bearer $token"

with cURL:

curl -X DELETE -H "Authorization:Bearer $token" http://localhost:2103/groups/OPERATORS/users/john-doe-operator

response

HTTP/1.1 200 OK

{
	"login":"john-doe-operator","
	firstName":"John",
	"lastName":"Doe",
	"groups":[],
    "entities":[]
}

A last command to verify that OPERATORS is no longer linked to john-doe-operator:

with httpie:

http http://localhost:2103/users/john-doe-operator "Authorization:Bearer $token" "Content-Type:application/json"

with cURL:

curl http://localhost:2103/users/john-doe-operator -H "Authorization:Bearer $token" -H "Content-Type:application/json"

response

HTTP/1.1 200 OK

{
    "firstName": "John",
    "groups": [],
    "entities": [],
    "lastName": "Doe",
    "login": "john-doe-operator"

}

7.2.7. Entity parents

Entities have a 'parents' attribute instead of a 'perimeters' one. This attribute is a string array. Each element of the array is the id of another Entity. When adding or patching an Entity into the system, operatorFabric performs a cycle detection. On a positive cycle detection cancels the addition or the patch.

7.2.7.1. Add a new Entity without a cycle in the parent declaration

using httpie:

echo '{"id":"NEW_ENTITY","name":"Last New Entity","description":"This is the last new entity","parents": ["ENTITY1_FR"]}' \
| http POST http://localhost:2103/entities "Authorization:Bearer $token" "Content-Type:application/json"

using cURL:

curl http://localhost:2103/entities -H "Authorization:Bearer $token" -H "Content-Type:application/json" \
--data '{"id":"NEW_ENTITY","name":"Last New Entity","description":"This is the last new entity","parents": ["ENTITY1_FR"]}'

response

HTTP/1.1 200 OK

{
    "id": "NEW_ENTITY",
    "parents": ["ENTITY1_FR"],
    "name": "Last New Entity",
    "description":"This is the last new entity",
    "labels":[]
}
7.2.7.2. Add a new Entity with a cycle

For simplicity, in this example the new entity will declare itself as a parent. This auto-referencing triggers a cycle detection.

using httpie:

echo '{"id":"NEW_ENTITY","name":"Last New Entity","description":"This is the last new entity","parents": ["NEW_ENTITY"]}' | http POST http://localhost:2103/entities "Authorization:Bearer $token" "Content-Type:application/json"

using cURL:

curl http://localhost:2103/entities -H "Authorization:Bearer $token" -H "Content-Type:application/json" --data '{"id":"NEW_ENTITY","name":"Last New Entity","description":"This is the last new entity","parents": ["NEW_ENTITY"]}'

response

{
    "status":"BAD_REQUEST",
    "message":"A cycle has been detected: NEW_ENTITY->NEW_ENTITY"
}

with a 400 as http status return.

8. Service port table

By default all service built artifacts are configured with server.port set to 8080

If you run the services using bootRun Gradle task, the run_all.sh script or the full docker docker-compose (found under config/docker), the used ports are:

Port Service Forwards to Description

89

KeyCloak

89

KeyCloak api port

2002

web-ui

8080

Web ui and gateway (Nginx server)

2100

businessconfig

8080

Businessconfig management service http (REST)

2102

cards-publication

8080

Cards publication service http (REST)

2103

users

8080

Users management service http (REST)

2104

cards-consultation

8080

Cards consultation service http (REST)

2105

external-devices

8080

External devices management service http (REST)

4100

businessconfig

5005

java debug port

4102

cards-publication

5005

java debug port

4103

users

5005

java debug port

4104

cards-consultation

5005

java debug port

4105

external-devices

5005

java debug port

27017

mongo

27017

mongo api port

5672

rabbitmq

5672

amqp api port

15672

rabbitmq

15672

rabbitmq api port

9. Restricted operations (administration)

Some operations are restricted to users with the ADMIN role, either because they are administration operations with the potential to impact the OperatorFabric instance as a whole, or because they give access to information that should be private to a user.

Below is a quick recap of these restricted operations.

Users Service

Any action (read, create/update or delete) regarding a single user’s data (their personal info such as their first and last name, as well as their settings) can be performed either by the user in question or by a user with the ADMIN role.

Any action on a list of users or on the groups (or entities) (if authorization is managed in OperatorFabric) can only be performed by a user with the ADMIN role.

Businessconfig Service

Any write (create, update or delete) action on bundles can only be performed by a user with the ADMIN role. As such, administrators are responsible for the quality and security of the provided bundles. In particular, as it is possible to use scripts in templates, they should perform a security check to make sure that there is no XSS risk.

The ADMIN role doesn’t grant any special privileges when it comes to card consultation (be they current or archived), so a user with the ADMIN role will only see cards that have been addressed to them (or to one of their groups (or entities)), just like any other user.