Skip to main content
Version: Current

How to set up authorisation

For any application that has more than minimal complexity, you will want to give some users access to specific information and functions, and you will want to deny that access to others. For example, you might want to give an admin user the ability to delete an order, but you don't want anyone else to be able to do that.

The Genesis Platform enables you to do this in extremely precise ways to match your business requirements.

What you can control, and where

Until you start to specify any permissions, everything is accessible by any authenticated user. But you can insert permmission statements in the relevant places to control access to data and the ability to change it.

You can also control how users are able to change data. For example, one user might be allowed to create and edit records in a table, but not delete them. Another user might be allowed to create, edit and delete records in that table.

All you need to do is specify the permissions at each point. The Genesis Platform ensures that only users with appropriate permissions are able to act at that point.

Permissions for the back end

Broadly speaking, you can insert permission statements on the back end at three levels:

  • tables (data entities)
  • records within a table (rows)
  • fields within a table (attributes/columns)

Permissions for the front end

You can also add authorisation at the front end. For example, if a user does not have permission to insert or update records on a grid of data, you can set the front end so that the user does not see the relevant buttons or commands on that display.

So, you have full control of data within your application at the most granular level.

Check our example application, which shows the permission features that are available.

Permissions for tables (data entities)

Applying permissions at the back end

At this level, you can authorise users to have access to records within a table. Let us consider the example of a TRADE table.

You can set up the following permissions, for example:

Useris permitted tois not permitted to
User Aview the grid of tradescreate, edit or delete trades
User Bview the grid of trades, and create and edit themdelete trades
User Cview the grid of trades, and create, edit and delete* them
User Dview the grid of trades, or create, edit or delete trades

So, as a result of these permissions:

  • User A can view trades, but cannot create, edit or delete trades in the table.
  • User B can view, create and edit trades, but cannot delete trades in the table.
  • User C can view, create, edit, and delete trades in the table.
  • User D cannot view, create, edit or delete trades in the table.

You can also directly apply permissions to other actions that users can perform on a table, such as “assign a trade to a book”. Authorisation for any finer-grained events can also easily be controlled with minimal code.

Applying permissions at the front end

So users are limited in what information they have access to and in what actions they can perform by the permissions you add to the back end.

But you can also add permissions at the front end. Here, you can control what is displayed for each user. For example, you can permission a User interface component that displays a grid of data from the TRADE table so that different users have a different experience:

Useris permitted tois not permitted to
User Aview the grid of tradesview any buttons to create, edit or delete trades
User Bview the grid of trades, and view buttons to create and edit tradesview the button to delete trades
User Cview the grid of trades, and view buttons to create, edit and delete trades
User Dview the grid of trades, or any buttons to create, edit or delete trades

So, as a result of these permissions:

  • User A can see the grid of trades, but cannot see any buttons to create, edit or delete trades.
  • User B can see the grid of trades, and sees buttons to create and edit trades. This user does not see a button to delete records in the table.
  • User C can see the grid of trades, and sees buttons to creat*, edit and delete trades.
  • User D cannot see the grid of trades or any buttons.
caution

It's important to note that your front-end permissioning should add to the controls that you have created on the back end. If you don't display a button to delete a record, for example, but the user has no restrictions on deleting at the back end, then your security is at risk.

Permissions for records (rows)

At the next level down, you can apply conditions for users who are authorised to view, create, edit and delete. A user's permission can depend on the content of the specific record (or row).

For example, continue looking at our TRADE table and consider the following permissioning requirements. We want different users to see different information depending on the country of the trade; and we want to control which users can create, edit and delete records (trades) in the table:

Useris permitted tois not permitted to
User AAview trades (rows) only where the country is GBcreate, edit or delete trades in GB or CA, or view, create, edit or delete trades for CA
User BBview trades (rows) only where the country is GB, and to create and edit trades for GB onlydelete trades in GB, or view, create, edit or delete trades for CA
User CCview trades (rows) where the country is CA, and create, edit and delete them in CAview, create, edit or delete trades for GB
User DDview, create, edit or delete trades (rows) in all countries

As a result of these permissions:

  • User AA can only see trades in Britain (GB), but cannot create, edit or delete those trades. The user cannot see or change trades in Canada (CA).
  • User BB can only see trades in Britain, and can create or edit trades for Britain, but cannot delete those trades. The user cannot see or change trades in Canada.
  • User CC can see, create, edit and delete trades in Canada, but cannot see or change trades for Britain.
  • User DD can see, create, edit and delete trades for both countries.

Permissions for fields (attributes/columns)

At the same level, you can configure access to each field in the table. You can do this in conjunction with any authorisation you set up at the record level. Again, consider our example of the TRADE table:

  • User A (for example, a member of the support team) is not permitted to view the counterparty for any trade (row).
  • User B is permitted to view the counterparty but is not permitted to change (edit) the counterparty to a counterparty based in country GB

All configuration can be applied with simple statements in the relevant part of the code. So, however precise your business requirements are, you can achieve them quickly without undue difficulty.

How does it work?

Authorisation for data is based on Right Codes that you define for your application.

All users can have one or more User Profiles. Each User Profile has one or more Right Codes.

Once you have set up the Right Codes for your application, you can add the relevant Profiles through the front end:

  1. Create your Profiles: for example, Trader, Senior Trader or Operations.
  2. Assign one or more Right Codes to each Profile. When a user is given a Profile, she or he has all the rights belonging to that Profile.
  3. A user can be assigned more than one Profile.

For example, you could set up the following user profiles:

  • Trader - with the Right Codes InsertTrade and AmendTrade
  • Senior Trader - with only one Right Code: CancelTrade

You could then give all the traders on the team the User Profile Trader. This includes the head trader in the team, but you then also give that user the User Profile Senior Trader. This gives your head trader a superset of rights.

Let's look at how these Right Codes work at different levels.

Restricting access to tables (entities)

First, let us look at how you control access to specific tables.

The example below restricts who can view a Data Server query called ALL_TRADES. Only users who have a User Profile containing the Right Code TradeView can view the data in this table. Users without this Right Code are prevented from calling this query - so the data is fully protected:

dataServer {
query("ALL_TRADES", TRADE) {
permissioning {
permissionCodes = listOf("TradeView")
}
fields {
...
}
}
}

Events are the mechanism for changing the database. You can control access to these in the same way.

In the example below, only users with the Right Code TradeInsert in their profile are able to insert trades. Attempts to insert by all other users will be rejected.

eventHandler<Trade>("TRADE_INSERT", transactional = true) {
permissioning {
permissionCodes = listOf("TradeInsert")
}
...
}

Restricting access to rows and columns within a table

This is where you use Right Codes to control access to rows and columns.

For example, you can hide a column if a user does not have a specific Right Code in their profile.

In the example below, the code uses auth and hideFields to check if the user's Profile has the Right Code TradeViewFull. If this code is not in the Profile, then the column is not returned by the query:

  query("ALL_TRADES", TRADE) {
permissioning {
auth {
hideFields { userName, rowData ->
if (!userHasRight(userName, "TradeViewFull")) listOf(CUSTOMER_NAME)
else emptyList()
}
}
}
}

The example above is for a Data Server query; you can permission a requestReply in your request Server the same way.

To perform similar column-level authorisation for an event in an Event Handler, you need to decide how you handle edits by a user who is not authorised to see (or therefore change) a column. For example, on modify you could force the unseen column to remain constant.

The code below looks up the value on the prior version and sets it on the proposed modification before saving, based on the Right Code TradeViewFull.

  eventHandler<Trade>("TRADE_MODIFY", transactional = true) {
...
if (!userHasRight(event.userName, "TradeViewFull")) {
val trade = entityDb.get(Trade.ById(event.details.tradeId))!!
details.customerName = trade.customerName
}
entityDb.modify(details)
...
}

You can set up row-level authorisation through a further table where (continuing the example above) Countries are mapped to Profiles. This could be any reference field as per the authorisation requirements.

Row-level permissioning is then performed via a look-up on an in-memory map (which is created and managed by the auth process):

  query("ALL_TRADES", TRADE) {
permissioning {
auth(mapName = "COUNTRY_VISIBILITY") {
authKeyWithUserName {
key(data.countryName, userName)
}
}
}
}

You can specify row-level authorisation for each event in your Event Handler in a similar way:

  eventHandler<Trade>("TRADE_MODIFY", transactional = true) {
permissioning {
auth(mapName = "COUNTRY_VISIBILITY") {
authKeyWithUserName {
key(data.countryName, userName)
}
}
}
}

You can combine table-, row- and column-level authorisation in a single data server.

Multiple authorisation

It is also possible to apply authorisation for all queries in a Data Server or all events in an Event Handler file.

When you do this, all queries or events within the .kts (file) receive these permissions. But you can override them within any specific query or event. So, effectively, you are providing default permission for all the queries or events in the file.

This is useful, for example, where a project has collections of functions; you can create separate files of Data Servers or Event Handlers and simply apply the authorisation once in each file.

Here is an example of a Data Server file, where the authorisation applies to every query in the file:

dataServer {
permissioning {
permissionCodes = listOf("StaticUpdate")
}
query(...)
query(...)
}

This works exactly the same way for an Event Handler file.

All above patterns are the same for Request Replies (reqreps) within the Genesis Platform - this can be seen in the example application referenced below.

Front-end authorisation

It is possible to apply authorisation at the front end to remove grids and buttons where there is no appropriate permission. If you don't want to do this, the system is still protected by the premissioning you have set on the back end. Users will receive error messages explaining their lack of permissions.

Here is an example of restricting tab visibility (based on the Right Code TradeView that provides access to the tab):

    navItems: [
{
title: 'Trades',
permission: 'TradeView',
},
],

Here is an example of restricting button visibility (based on the Right Code TradeUpdate, which provides access to the button):

      createEvent="${(x) => getViewUpdateRightComponent(x.user, 'TradeUpdate', 'EVENT_TRADE_INSERT')}"

Code examples

Take a look at the code

Check our example application, which shows the permission features that are available.

The example application has a single data table called Trade. After a remap, this will be populated with 10 entries - 5 in each of 2 Countries. Two further tables are added:

  • Countries (single column)
  • Mapping (which maps between User and Country to enable row authorisation based upon permitted Countries)

The front-end grid demonstrates different authorisations in each quadrant - these map directly to four different Data Server queries, which you can see in the file Permissions-dataserver.kts.

Only Users assigned to the Profile Full_Access can insert or modify trades. Currently, this is the user AmyAccess, but more Profiles and Users can be added to the application once it is installed and running.

For example, you can edit the User Country table on the Static tab to remove Canada from AmyAccess. Once you have done that, AmyAccess will only be able to enter trades against the UK. You can see the code in the file Permissions-eventhandler.kts.

Note that only the user admin can modify Profiles and add users. The password for all users who are set up in advance is genesis.

The application also demonstrates authorisation at a dataserver and eventhandler level.

info

The ReadMe file in the project folder provides further information on the project.

A more complex scenario

Consider how authorisation can manage more complex situations.

For example, consider the lifecycle of a trade. Perhaps you want to ensure that only super users can modify trades that have settled.

To achieve this, we are going to modify an existing TRADE_MODIFY event and then create a new modify event for super users only.

The following code modifies the earlier TRADE_MODIFY event to prevent users from changing the transaction if it has a status of Settled. This is defined in an onValidate codeblock:

  eventHandler<Trade>("TRADE_MODIFY", transactional = true) {
permissioning {
permissionCodes = listOf("TradeUpdate")
}
onValidate { event ->
val trade = entityDb.get(Trade.ById(event.details.tradeId))!!
require(trade != null) { "Trade Id does not exist" }
require(trade.status != status.Settled) { "Trade is already settled" }
ack()
}
onCommit { event ->
...
}
}

You can then easily create a separate event so that super users can modify any trade. In this event, you don't need to check the status - you simply make sure that it is only available to super users.

The example code below enables any user with the Right Code SuperUser to perform any edit on a trade. Users without this Profile cannot gain access to this event. They can only use the TRADE_MODIFY event above, which, as you saw, prevents them from editing settled trades.

  eventHandler<Trade>("TRADE_MODIFY_SUPERUSER", transactional = true) {
permissioning {
permissionCodes = listOf("SuperUser")
}
onCommit { event ->
...
}
}

Testing

info

Go to our Testing page for details of our testing tools and the dependencies that you need to declare.

For testing the auth permissions we have described here, we use common automation frameworks: cucumber and playwright.

Our example tests are able to test authorisation via both the user interface and the API.

There are three types of file.

  • The feature file contains the test cases, which are written in the Gherkin language. Tags are used to differentiate between the UI (@UI) and the API (@API).
  • There are separate step definitions (written in Java) for testing the user interface and the API in the step definitions folder.
  • You can find the expected results files (.json) in the result folder. The actual results are written to this folder after the tests have been executed.

The E2E tests use two different users to request data from the Data Server. Each user should (of course) see all the data that they are permissioned to view and none of the data that they are not:

  • AmyAccess (full access)
  • RogerRestricted (limited access)

Preparation

  • For testing the API, the back-end processes must be running and healthy.
  • For testing the UI, the both the back end and the front end must be running.

Before starting the servers:

  1. Run remap (which imports the data).
  2. Run consolidate rights.

Both tasks should execute without any errors.

Genesis has tested the examples successfully on the H2 and Postgres databases.

Full technical details

Find more details in our in our reference documentation: