Skip to main content

Authorization

Overview

Authorization, sometimes referred to as permissions or rights, is the concept of ensuring users can only access features and data from the application that they are authorized to access, for example ensure only permitted users may view and update (create, edit, delete) data in an application.

The Genesis Application Platform makes it simple to set up powerful authorization models based on business requirements.

There are three levels that can be configured:

  1. Feature access : Accessing a UI component (for example a grid, chart, form, ...) or server component (for example a query or event)
  2. Entity access : Accessing particular entities which make up the application data. For example users being permitted to submit events or read data for a particular counterparty, book, or some other application entity.
  3. Attribute access : Accessing specific fields / column values. For example restricting access to view or update a particular field in a table.

All levels can be configured in applications to ensure complete security.

This model enables full control of data within an application at the most granular level.

Typically levels 1 and 3 are controlled by permission codes, and level 2 is controlled by entity access, however you can set up the application as per your business requirements. The following sections give an overview of the 2 layers and example use-cases.

Beyond this page, there is a how to guide on authorization available with a project with code examples you can clone and run, which explains further the practical implementation of authorization.

Configuration options

Permission (Right) codes

Permission (right) codes need to be inserted into the RIGHT table in the server. Each entry has:

  • A CODE : The code which will be set in various components (server, client)
  • A DESCRIPTION : A description of the type of application access the code will enable for the user.

See assigning users permissions for details on assigning them to users.

Entity access

There are two methods for configuring entity access. The first is simple and quite a common use case, the second allows full configuration to meet more complex requirements. Both methods look to achieve the same thing, which is "is a given user authorized to access this entity"

A services called GENESIS_AUTH_PERMS is responsible for building maps around this data, which are in turn accessed by any other components.

Simple configuration

A common requirement is to restrict access to rows of data based on a given entity contained within them. For example if we had a COUNTERPARTY table, which represents a client, it is common in financial applications that only certain sets of users see rows of data relating to a given set of clients.

In this pattern, it is most common to have a simple USER_<ENTITY>_MAP table, in this example USER_COUNTERPARTY_MAP, in which presence of a USER_NAME + COUNTERPARTY combination in a row means that user has access to that counterparty, and should be able to view and update data. If no such row exists, they should not be able to.

To set this up, you can configure the following system definition items in your application's genesis-system-definition.kts.

systemDefinition {
...
global {
...
item(name = "ADMIN_PERMISSION_ENTITY_TABLE", value = "COUNTERPARTY")
item(name = "ADMIN_PERMISSION_ENTITY_FIELD", value = "COUNTERPARTY_ID")
...
}
...
}

In this example we use COUNTERPARTY table and it's ID field COUNTERPARTY_ID, however you can set any entity in your data model and it's common ID field as required.

Setting these items and installing your application will create the USER_COUNTERPARTY_MAP table which can be populated. You can then set "ENTITY_VISIBILITY" in auth blocks to restrict entity access according to that table in any client facing components as required.

Full configuration

In addition to the simple entity mapping configuration, you can create your own entity mapName values to use in auth per the examples listed in that section.

To do this, we need to add configuration to a *-permissions.kts file which should be located in your application's scripts folder.

Examples

In this example, user authorization is based on an ACCOUNT entity.

A table called TAG which stores USER_NAME and a PERSON_TYPE in rows is read to see the type of user.

The user will only have access to an ACCOUNT if:

  1. The user has a PERSON_TYPE of SALES_OFFICE and the ACCOUNT OFFICE_ID is set to the user's username.
  2. The user has a PERSON_TYPE of ASSET_MANAGER and the ACCOUNT ASSET_MANAGER_ID is set to the user's username.

If neither of those things are true for a given USER + ACCOUNT, then the user cannot access the ACCOUNT entity.

dynamicPermissions {
entity(ACCOUNT) {
maxEntries = 10000
batchingPeriod = 15
expression {
var allowed = false
val username = user.userName

val tagRec = entityDb.get(Tag.byCodeEntityId("PERSON_TYPE", username))
val entityType = tagRec?.tagValue

if (entityType == "SALES_OFFICE" && entity.officeId == username) {
allowed = true
}

if (entityType == "ASSET_MANAGER" && entity.assetManagerId == username) {
allowed = true
}

allowed
}
}
}

Further examples

dynamicPermissions {
entity(POSITION_VIEW) {
averageEntityChars = 15
averageUserNameChars = 10
averageUsers = 10000
maxEntries = 10000
batchingPeriod = 15
idField = idField = listOf(POSITION_VIEW.INSTRUMENT_ID) //Specifying custom field to be used as cache key instead of using the fields from the primary key
backwardsJoin = true
expression {
entity.companyId == user.companyId
}
}
entity(TRADE) {
name = "INNER_TRADES" //Specifying custom entity name instead of using the name of the TRADE_TABLE
averageEntityChars = 150
averageUserNameChars = 100
averageUsers = 100000
maxEntries = 100000
batchingPeriod = 150
expression {
entity.allowedTraders.contains(user.userName) // User's name is in a list of allowedTraders defined on the TRADE
}
}
}
entity

The entity block will need an entity, which can be a table or a view from your application's data model.

An entity can have the following attributes:

NameDescriptionMandatory
nameThe name of the entity you want to authorize, which you will refer to in auth blocksNo. Default value is the name of the Table or the View
maxEntriesThe maximum number of entries that the authorization map will contain.Yes
batchingPeriodPeriod in seconds to batch records before processingYes
idFieldField(s) to use for keying internal collection (should be unique)No. Default value is the primary key for the entity
averageUserNameCharsAverage number of characters in each username. This setting defines backing data structure for the authorization map.No. Default = 7
averageEntityCharsAverage number of characters of each entity. This setting defines the backing data structure for the authorization map.No. Default = 20
averageUsersAverage number of users on the system. This setting defines the backing data structure for the authorization map.No. Default = 1,000
expressionFunction that calculates whether an entity can be accessed by a user. The result is either true (user has access) or false (user has no access)Yes

Each expression has the following properties and should return either true or false based on whether the user can access the entity:

NameDescription
entityDbRead only access to the database if additional data query is required. Note: The expression is called on each data update, and querying the database each time will result in performance hit. A better approach would be to define a view that joins all the necessary tables
entityThe entity to be evaluated for access
userThe user to be evaluated for access to the entity
entityIdThe value of the idField for this `entity

Component configuration options

Server component configuration

The permissioning block allows application developers to set all the described levels of authorization, an overview of setting them up is included below:

permissioning

The permissioning block is used to implement access control measures on the data being returned to a user.

tip

With the exception of the inner auth block which relies on inbound data, the permissioning block and its contents can also be placed in the outer block and apply to all query/requestServer/eventHandler blocks named in the file.

permissionCodes

permissionCodes takes a list of permission codes. The client user accessing the query must have access to at least one of the permission codes in the list to be able to access any query data.

    permissioning {
permissionCodes = listOf("TradeView", "TradeUpdate")
}
customPermissions

The customPermissions block takes boolean logic. If this function returns true, the user will be able to access the resource; otherwise, the request will be rejected.
It is useful, for example, in the case you have an external API to check against. In this example an entitlementUtils object wraps an external API we can call to check the user's name has access.

  customPermissions { message ->
entitlementUtils.userIsEntitled(message.userName)
}

The customPermissions block also has access to entityDb so that you can query any data in the database. You can add complex logic in here as needed, and have access to the full inbound message.

  customPermissions { message ->
val userAttributes = entityDb.get(UserAttributes.byUserName(message.userName))
userAttributes?.accessType == AccessType.ALL
}

Where utilizing customPermissions you should also consider configuring a customLoginAck so that the front end of the application can also permission its components in a similar way.

auth

auth is used to restrict rows (queries) or events with a particular entity based on a user's entity access, also known as row-level permissions.

permissioning {
auth(mapName = "ENTITY_VISIBILITY") {
authKey {
key(data.counterpartyId)
}
}

Details on restricting query entity access here

Auth definitions can be grouped with “and” or “or” operators.

  • You could have two simple permission maps, for example: one by counterparty and another one for forbidden symbols. If the user wants to see a specific row, they need to have both permissions at once.
  • You could have two permission maps: one for buyer and one for seller. A user would be allowed to see a row if they have a seller or buyer profile, but users without one of the those profiles would be denied access.

This example shows an AND grouping:

permissioning {
auth(mapName = "ENTITY_VISIBILITY") {
authKey {
key(data.counterpartyId)
}
} and auth(mapName = "SYMBOL_RESTRICTED") {
authKey {
key(data.symbol)
}
}
}

This example shows OR grouping:

permissioning {
auth(mapName = "ENTITY_VISIBILITY") {
authKey {
key(data.buyerId)
}
} or auth(mapName = "ENTITY_VISIBILITY") {
authKey {
key(data.sellerId)
}
}
}
userHasRight

userHasRight is a helpful function which can be called within the auth block, or anywhere else in client facing server components which have a user accessing them, to determine if a user has a given right.

if (!userHasRight(userName, "TradeViewFull")) { ... }
hideFields

For queries, the auth block also allows a hideFields which can be used to restrict attribute/field/column values being returned to users.

For example, you can hide a column (or set of columns) based on boolean logic, for example 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 has the Right Code TradeViewFull. If this code is not set for the user, then the column CUSTOMER_NAME will not be returned to the user:

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

RightSummaryCache

GPAL server components have the permissionCodes block mentioned above for convenience. If you are writing custom code, or event need more complex boolean logic to determine access within a GPAL server component, the RightSummaryCache is injectable into any java or kotlin code and can be used for efficient lookups base on user and permission codes.

See the example below where we first want to check a trade is not on a restricted stock, and where it is not ensure the user has a TRADE_INSERT permission.

import global.genesis.session.RightSummaryCache
...
val rightSummaryCache = inject<RightSummaryCache>()
...
if (stockInRestrictedList != null) {
false
} else {
rightSummaryCache.userHasRight(userName, "TRADE_INSERT")
}

AuthCacheFactory

GPAL server components have the auth block mentioned above for convenience. If you are writing custom code, or event need more complex boolean logic to determine entity access within a GPAL server component, the AuthCacheFactory is injectable into any java or kotlin code and can be used for efficient lookups base on user and an entity.

See the example below for creating a ReadOnlyAuthCache from the AuthCacheFactory onto a particular permissioned entity, and example boolean logic to see if the user has access

import global.genesis.session.AuthCacheFactory
...
val authFactory = inject<AuthCacheFactory>()
val myMap = authFactory.newReader("ENTITY_VISIBILITY")
...
myMap.isAuthorised(companyId, userName)

Client component configuration

You can apply authorization 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 permissioning 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/route):

    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')}"

See the relevant client capabilities pages for more details on how to secure them with permissions.

Assigning users permissions

Most applications will include the User management component for easily assigning rights and users to profiles. Users gain the superset of permissions that are assigned to the profiles they are a part of.

The following section provides details as to how user permissions assignment works, and the table records which make it work.

How it works

Genesis has the concept of users, profiles and right codes. For each one, there is a table to store the related entity data:

  • USER
  • PROFILE
  • RIGHT

Users gain rights via profiles. So we have tables to determine which users and rights belong to each given profile. Note that you cannot allocate right codes directly to a specific user. However, a user can have multiple profiles.

A profile can have zero or more rights and zero or more users.

These relationships are held in the following tables:

  • PROFILE_RIGHT
  • PROFILE_USER

Related to these tables, we have theRIGHT_SUMMARY table, which contains the superset of rights any given user has. These are based on the profiles assigned to them. This is the key table used when checking rights, and it exists to allow the efficient checking of a user's rights.

Using GENESIS_AUTH_MANAGER

You can use the GENESIS_AUTH_MANAGER process to add users and maintain their rights. As long as you use this process, then the entries in the RIGHT_SUMMARY table are maintained automatically by the system in real time, so the rights are easily accessible at speed.

For example, if you add a new user or you update a profile with new rights, the RIGHT_SUMMARY table is updated immediately and all the users in that profile receive the new right automatically.

Updating directly in the database

Note that you can also maintain the following tables manually using DbMon or SendIt:

  • USER
  • PROFILE
  • RIGHT
  • PROFILE_USER
  • PROFILE_RIGHT

This is an easy way to bulk-load permissions data in a brand new environment, for example.

However, when you change any of the permissions tables in this way, the RIGHT_SUMMARY table will not be maintained automatically. To update the the RIGHT_SUMMARY so that the changes take effect, run ConsolidateRights.

warning

If you update any of the permission tables manually, the changes won't take effect until you run ConsolidateRights.

Sample explanation

See the following simple system set-up. We have a set of entities (our user, rights and profiles), a set of profile mappings (to users and rights) and, finally, the resultant set of right entries we would see in RIGHT_SUMMARY:

The above image shows:

  • 3 profiles, each with particular rights assigned
  • 4 users, three of which have one profile assigned and one of which, Jenny.Super, is assigned to have all rights.

Another way of achieving this set-up would be to have a fourth profile, say SUPER, as per below, and to have all rights assigned to it, and Jenny.Super assigned just to the one profile:

Note how we now have an extra profile, and edits to the PROFILE_USER and PROFILE_RIGHT entries, but the resulting rights are the same.

As you can tell, this enables you to build powerful combinations, and since Users, Profiles, Profile_Users and Profile_Rights are all editable by system administrators, they can build their own set-up that makes sense for their organization.

Good practice

Having profiles as an intermediary between users and rights enables admin users of the system to create complex permission models with no code change. Rights codes generally need to be added to the code. Although this is simple to do, it requires a code change. Our advice is to design applications with enough granularity in the rights to ensure that code changes aren't required.