Permissioning Integration Adapter

A permissioning integration adapter sits between a backend permissioning server and the Caplin Platform. Its role is to receive permission information for users of the system and propagate this to the Caplin Platform. Liberator's Permissioning Auth Module (PAM) uses this information to vet any data or trade requests made by client applications. Permission information can be updated dynamically on the server and the adapter must propagate these updates to the Auth module immediately.

Permission data on the Caplin components is classified as follows:

  • Can be ALLOW or DENY permissions
  • Are defined for a user or group of users
  • Can apply to a specific action (e.g. VIEW or TRADE)
  • Can apply to a specific product or group of products (e.g. /FX/GBPUSD or /FX/*)
  • Related permissions can be grouped under a single namespace (e.g. FX-TRADING)

Objectives

In this tutorial you will create a permissioning integration adapter that defines a number of users. Each of the users will have a different set of permissions – starting with the admin user who has permission to request anything, and leading on to other users who have specifically organised permissions for subscribing to certain sets of data and being able to trade on others. 

You will also create groups of users so that multiple users can share the same permissions (which are associated with the group).

Deploying the Permissioning Service

The default auth module for Liberator is openauth, packaged in the OpenPermissioning blade. The openauth module accepts all requests and does not subscribe to data provided by permissioning integration adapters.

The Permissioning Auth Module (PAM), which works with permissioning integration adapters, is packaged in the Permissioning Service blade, available as a separate download to Liberator.

To implement permissioning using permissioning integration adapters, you will need to deactivate the OpenPermissioning blade and deploy the PermissioningService blade.

After enabling the PermissioningService blade, all requests will be rejected by Liberator until you write and deploy a permissioning integration adapter.

Follow the steps below to deploy the Permissioning Service:

  1. Download CPB_PermissioningService<version>.zip to the Deployment Framework’s kits directory and deploy the blade, just like you did for the RefinerService blade.
  2. You will see a warning telling you that it's not valid to have both the OpenPermissioning and PermissioningService blades active at the same time. Run ./dfw deactivate OpenPermissioning.
  3. Restart the Deployment Framework and try logging in to Liberator Explorer (un:admin/pw:admin, as before). You will notice from the “Log” panel that the login fails. This is because the Auth module, which is now active, does not know about this user. The Liberator status page too does not work.

Create a new permissioning adapter project

This section assumes that you have already downloaded the Caplin Project Templates from GitHub, as described in Pricing Integration Adapter (1).

To create the project, follow the steps below:

  1. Run the command below to copy the Permissioning Adapter Template directory to your src directory and rename it to PermissioningAdapter:

    cp -r ~/project-templates-training/permissioning-template ~/src/PermissioningAdapter
  2. Edit the file ~/src/PermissioningAdapter/settings.gradle. Change the value of the variable rootProject.name to 'PermissioningAdapter':

    rootProject.name = "PermissioningAdapter"
  3. Choose one of the methods below to resolve project dependencies:
    • Manual download: download the following files from the Caplin Download site and copy them to the libs folder in your project.

      • DataSource for Java: datasource-java-version-jar-with-dependencies.jar

      • Permissioning Integration API for Java: permissioning-datasource-version.jar

    • Caplin Software Repository: create a gradle.properties file in the root directory of your project and assign your Caplin username and password to the variables below:

      caplinNexusUser=username
      caplinNexusSecret=password

After you have created the project, perform the following tasks:

  1. Import the project into your IDE
  2. Enable your adapter's JMX interface. Use different port numbers to the JMX ports used for the Pricing Adapter and the Trading Adapter.
  3. Export the adapter's configuration as a config-only blade and deploy it to your Deployment Framework
  4. Run the adapter from your IDE
  5. Add the adapter to the CMC panel

Overview of the permissioning project template

The new PermissioningAdapter project contains the entry point class PermissioningAdapter and a PermissioningProvider class that does the work. A PermissioningDataSource object is created in the constructor of the PermissioningProvider. This object is the one which you use to manage all the users and their permissions. All permission updates are propagated to the Auth module by means of a “transaction”. The PermissioningDataSource object can be used to start a new transaction, adds permissions to the transaction, then commits the transaction so that all these permissions are sent to the Auth module. It is only when you commit the transaction that the permissions are sent to Liberator.

There are two types of transactions for sending permissions to the Auth module. An image transaction (startImageTransaction() method) will clean any permissions you have previously set and add the new ones which are described inside the transaction. If you don’t want to clear everything, you can use an update transaction (startUpdateTransaction() method). Never forget the commitTransaction() method. You can also use the rollbackTransaction() method if you catch an exception while executing the transaction.

After starting a transaction, you can use the PermissioningDataSource methods createUser() or createGroup() to create a new user or group respectively. Then use user/group.applyPermission() to define permissions for that user or group before committing the transaction.

Create a user

The first user you will configure is the administrator who should have permission to see all data and trade on any data.

Follow the steps below:

  1. Create a new user. Specify a hard-coded username and password.
    public PermissioningAdapterPermissioningProvider(DataSource dataSource) 
    {
        PermissioningDataSource permissioningDataSource = new PermissioningDataSource(dataSource, Role.MASTER);
    
        permissioningDataSource.startImageTransaction();
        /* Permissioning data should initially be added as part of an image transaction. This
         * can be done before the Permissioning DataSource starts or after it has started.
         * Subsequent changes in permissioning data should be done as an update transaction.
         */
    
        User adminUser = permissioningDataSource.createUser("admin", "admin");
    
        permissioningDataSource.commitTransaction();
    }
  2. Restart the adapter and try logging in to Liberator Explorer again. Notice that now you are able to log in with u:admin/pw:admin. However if you try subscribing to any currency pair (e.g. /FX/GBPUSD) you get a READ_DENIED error because the admin user does not yet have the permission to view any data.

Assign view permissions

Now that the admin user exists and is able to login to Liberator, we will give that user permission to subscribe to streaming data.

Follow the steps below:

  1. Allow the admin user to subscribe to (VIEW) all subjects as shown below. With this statement you are creating a new “ALLOW” permission and classifying it under the default namespace. The permission is to subscribe to any subject. When you restart the permissioning adapter and retry subscribing to a currency pair you should notice that the subscription is successful and prices are streamed to the client.

    public PermissioningAdapterPermissioningProvider(DataSource dataSource) 
    { 
         PermissioningDataSource permissioningDataSource = new PermissioningDataSource(dataSource, Role.MASTER); 
    
         permissioningDataSource.startImageTransaction();
         /* Permissioning data should initially be added as part of an image transaction. This 
          * can be done before the Permissioning DataSource starts or after it has started. 
          * Subsequent changes in permissioning data should be done as an update transaction. 
          */
    
         User adminUser = permissioningDataSource.createUser("admin", "admin");
         adminUser.applyPermission(Collections.singleton("/.*"), Constants.DEFAULT_PERMISSION_NAMESPACE, "VIEW", Authorization.ALLOW);
    
         permissioningDataSource.commitTransaction(); 
    }
    
  2. Open the trading test page (http://localhost:18080/testpage/), open the Chrome console panel (F12), log in and click the “trade” button. This time a trade does not succeed and instead there is a WRITE_DENIED error. This is because we have only given permission for admin to subscribe to any subject, not to publish messages on any subject.

View permissions are the simplest permissions you can create. They are simply defined by the action "VIEW" and a regular expression that must match the requested subject. The namespace is not important.

In this case we have provided the regular expression "/.*", which will allow the user to subscribe to any subject that begins with a forward slash character. Later in the tutorial we will see what happens if we provide a more restrictive pattern, so that users can subscribe to some subjects but not others.

Assign trading permissions

Now we are going to create some trading permissions for the admin user. This is a bit more complicated than simple view permissions. First we have to create a rule which tells Liberator how to identify which incoming trade messages represent an ESP trade. Then we must create a permission for the user that allows them to perform ESP trades on certain currency pairs.

Follow the steps below:

  1. Define a new rule specifically for messages published on our trade channel /PRIVATE/TRADE/FX (the %U in the channel subject, below, is the username placeholder which is added by default by Liberator).

    public PermissioningAdapterPermissioningProvider(DataSource dataSource) 
    { 
         PermissioningDataSource permissioningDataSource = new PermissioningDataSource(dataSource, Role.MASTER); 
    
         permissioningDataSource.startImageTransaction();
         /* Permissioning data should initially be added as part of an image transaction. This
          * can be done before the Permissioning DataSource starts or after it has started. 
          * Subsequent changes in permissioning data should be done as an update transaction. 
          */ 
    
         User adminUser = permissioningDataSource.createUser("admin", "admin"); 
         adminUser.applyPermission(Collections.singleton("/.*"), Constants.DEFAULT_PERMISSION_NAMESPACE, "VIEW", Authorization.ALLOW); 
    
         Map<String, String> espTradeFields = Collections.singletonMap("TradingProtocol", "ESP"); 
         permissioningDataSource.createActionRule("/PRIVATE/%U/TRADE/FX", espTradeFields, Constants.DEFAULT_PERMISSION_NAMESPACE, "FX-ESP-TRADE", "Instrument"); 
    
         permissioningDataSource.commitTransaction(); 
    }

    This rule tells Liberator:

    "When you receive a message on the subject /PRIVATE/%U/TRADE/FX, if it contains the field TradingProtocol=ESP then this message is an FX-ESP-TRADE. You must check that the user has an allowed value in the Instrument field of this message."

  2. Now that the rule exists to define what an FX-ESP-TRADE message looks like, we will give our admin user permission to make those trades.

    public PermissioningAdapterPermissioningProvider(DataSource dataSource) 
    { 
         PermissioningDataSource permissioningDataSource = new PermissioningDataSource(dataSource, Role.MASTER); 
    
         permissioningDataSource.startImageTransaction();
         /* Permissioning data should initially be added as part of an image transaction. This
          * can be done before the Permissioning DataSource starts or after it has started. 
          * Subsequent changes in permissioning data should be done as an update transaction. 
          */ 
    
         User adminUser = permissioningDataSource.createUser("admin", "admin"); 
         adminUser.applyPermission(Collections.singleton("/.*"), Constants.DEFAULT_PERMISSION_NAMESPACE, "VIEW", Authorization.ALLOW); 
    
         Map<String, String> espTradeFields = Collections.singletonMap("TradingProtocol", "ESP"); 
         permissioningDataSource.createActionRule("/PRIVATE/%U/TRADE/FX", espTradeFields, Constants.DEFAULT_PERMISSION_NAMESPACE, "FX-ESP-TRADE", "Instrument");
    
         adminUser.applyPermission(Collections.singleton("/.*"), Constants.DEFAULT_PERMISSION_NAMESPACE, "FX-ESP-TRADE", Authorization.ALLOW);
    
         permissioningDataSource.commitTransaction(); 
    }

    This permission tells Liberator:

    "When adminUser sends an FX-ESP-TRADE message, allow it if the value in the field specified by the rule (which is Instrument) matches the pattern /.*"

Our test page sends the Open trade message with several fields, among them are:

  • TradingProtocol=ESP
  • Instrument= <whatever currency pair the user wants to trade>

Every time Liberator receives a message from a client, it checks all of the rules to determine what type of action this is. It does this based on the subject and fields in the message. In this example, we have defined a rule that says if the message is on the trade channel subject and contains the field TradingProtocol=ESP, the action is FX-ESP-TRADE. The final part of the rule definition is the field on the message to inspect in order to determine if the message should be allowed. In this case it is the Instrument field.

Having found the rule(s) that match the message, Liberator then checks that the user who sent the message has a valid permission for this action. In this example, the admin user has a permission that allows him to perform the FX-ESP-TRADE action with a pattern of "/.*". Since the value in the Instrument field will always match the pattern "/.*", Liberator will allow this trade message to go through to the adapter.

If you restart the adapter and try to trade again from the test page, your trade should now be successful.

User1: A user in a group (simple inheritance)

Now that we’ve got the basics set up, let’s create some more fine-grained permissions. The aim in this section is to define a new user, user1, who will be allowed to view all the GBP currency pairs available on the Pricing Integration Adapter (e.g. /FX/GBPUSD) but not any other currency pairs (e.g. /FX/EURUSD). Additionally, we will demonstrate simple inheritance by applying these permissions to a group and adding user1 to the group so that it will inherit the group’s permissions.

Follow the steps below:

  1. Start a new update transaction after committing the admin’s transaction. Create a new user, u:user1/pw:password1. (Don’t give the user any specific permission.) Create a group called “GBPGroup”. Add the permissions to the group to view all the GBP currencies. Finally, add the user1 to the GBPGroup.
    public PermissioningAdapterPermissioningProvider(DataSource dataSource) 
    { 
         PermissioningDataSource permissioningDataSource = new PermissioningDataSource(dataSource, Role.MASTER); 
         
         permissioningDataSource.startImageTransaction();
         /* Permissioning data should initially be added as part of an image transaction. This
          * can be done before the Permissioning DataSource starts or after it has started. 
          * Subsequent changes in permissioning data should be done as an update transaction. 
          */ 
    
         User adminUser = permissioningDataSource.createUser("admin", "admin"); 
         adminUser.applyPermission(Collections.singleton("/.*"), Constants.DEFAULT_PERMISSION_NAMESPACE, "VIEW", Authorization.ALLOW); 
    
         Map<String, String> espTradeFields = Collections.singletonMap("TradingProtocol", "ESP"); 
         permissioningDataSource.createActionRule("/PRIVATE/%U/TRADE/FX", espTradeFields, Constants.DEFAULT_PERMISSION_NAMESPACE, "FX-ESP-TRADE", "Instrument");
    
         adminUser.applyPermission(Collections.singleton("/.*"), Constants.DEFAULT_PERMISSION_NAMESPACE, "FX-ESP-TRADE", Authorization.ALLOW);
    
         permissioningDataSource.commitTransaction(); 
    
    
         permissioningDataSource.startUpdateTransaction(); 
         User user1 = permissioningDataSource.createUser("user1", "password1"); 
         Group gbpGroup = permissioningDataSource.createGroup("GbpGroup"); 
         gbpGroup.applyPermission(Collections.singleton("/FX/GBP.*"), Constants.DEFAULT_PERMISSION_NAMESPACE, "VIEW", Authorization.ALLOW); 
         gbpGroup.addMember(user1); 
         permissioningDataSource.commitTransaction();
    }
    
  2. Reload the test page, open the Chrome console panel (F12), and log in as user1.

    Be careful from this point on! Every time you reload the test page, the username and password in the text fields will switch back to admin/admin. It's easy to forget to change these fields and think your new permissions are not working.
  3. Enter /FX/GBPUSD, or another GBP based currency pair, in the subscribe input box and press the “subscribe” button. You will notice (in the Chrome console panel) a number of updates coming in for that currency pair.
  4. Try subscribing to /FX/EURUSD, or another non-GBP based currency pair. You will notice (in the Chrome console panel) that you cannot subscribe to this currency pair: Error: Subject /FX/EURUSD is READ_DENIED

User2: A user in a group with some permission exceptions (masking)

Sometimes you may wish a user to inherit permissions from a group, but wish to override just a few of those permissions just for this user. For example, you may want all users to be able to trade on any instrument, but forbid a specific group of users from trading on more volatile currencies. You can do this by “masking” the permissions of the group by applying specific override permissions directly to the user or group of users who require restricted permissions.

You are going to add a new user, user2, to the GBPGroup group which you just created. This user however, will not have permission to view one specific currency pair: GBPUSD.

Follow the steps below:

  1. Create user2 and override the GBPUSD permission in the same update transaction which you used for creating user1 and GBPGroup:
    public PermissioningAdapterPermissioningProvider(DataSource dataSource) 
    { 
         PermissioningDataSource permissioningDataSource = new PermissioningDataSource(dataSource, Role.MASTER); 
    
         permissioningDataSource.startImageTransaction();
         /* Permissioning data should initially be added as part of an image transaction. This
          * can be done before the Permissioning DataSource starts or after it has started. 
          * Subsequent changes in permissioning data should be done as an update transaction. 
          */ 
    
         User adminUser = permissioningDataSource.createUser("admin", "admin"); 
         adminUser.applyPermission(Collections.singleton("/.*"), Constants.DEFAULT_PERMISSION_NAMESPACE, "VIEW", Authorization.ALLOW); 
    
         Map<String, String> espTradeFields = Collections.singletonMap("TradingProtocol", "ESP"); 
         permissioningDataSource.createActionRule("/PRIVATE/%U/TRADE/FX", espTradeFields, Constants.DEFAULT_PERMISSION_NAMESPACE, "FX-ESP-TRADE", "Instrument");
    
         adminUser.applyPermission(Collections.singleton("/.*"), Constants.DEFAULT_PERMISSION_NAMESPACE, "FX-ESP-TRADE", Authorization.ALLOW);
    
         permissioningDataSource.commitTransaction(); 
    
    
         permissioningDataSource.startUpdateTransaction(); 
         User user1 = permissioningDataSource.createUser("user1", "password1"); 
         Group gbpGroup = permissioningDataSource.createGroup("GbpGroup"); 
         gbpGroup.applyPermission(Collections.singleton("/FX/GBP.*"), Constants.DEFAULT_PERMISSION_NAMESPACE, "VIEW", Authorization.ALLOW); 
         gbpGroup.addMember(user1); 
    
         User user2 = permissioningDataSource.createUser("user2", "password2"); 
         gbpGroup.addMember(user2); 
         user2.applyPermission(Collections.singleton("/FX/GBPUSD"), Constants.DEFAULT_PERMISSION_NAMESPACE, "VIEW", Authorization.DENY);
    
         permissioningDataSource.commitTransaction();
    }
    
  2. Use the trading test page to login as user2 and subscribe to /FX/GBPUSD. You should see a READ_DENIED error in the Chrome console panel because this user has been specifically denied access to that currency pair. The other GBP based currency pairs (e.g. /FX/GBPAUD) will still return price updates successfully, because they are allowed by the "/FX/GBP.*" permission inherited from the user's group.

User3: A user in multiple groups (multiple inheritance)

A user can be a part of more than one group. When this is the case it inherits the permissions associated with both groups. In this scenario there may be clashing permissions i.e. ALLOW and DENY permissions on the same instruments. If this is the case, the DENY permission is always the one that is applied.

You are going to create two new groups, EURGroup1 and EURGroup2, and a new user, user3, who is a member of both groups. Some of the permissions associated with the groups will clash. No code example this time, but you should be able to complete the task based on the examples above.

Follow the steps below:

  1. Create a new update transaction beneath the two existing transactions.

  2. Within the new transaction, perform the following actions:

    1. Create a group called EURGroup1 with permissions defined to view EURCHF and deny EURUSD subscriptions.
    2. Create a group called EURGroup2 with permissions defined to view both EURCHF and EURUSD subscriptions.
    3. Create user3 and add it to both groups.
  3. In the test page, login as user3 and subscribe to /FX/EURCHF. What happens?
  4. In the test page, login as user3 and subscribe to /FX/EURUSD. What happens?

Demouser

Finally, we are going to create a new user which will be used in the Caplin Trader tutorials to follow (where you will create a simple trading application, including a trade tile, which connects to the backend which you’ve created in this first set of tutorials). These permissions will be used to demonstrate the frontend permission API.

The user, called 'demouser', can:

  • Subscribe to the FX Major (/CONTAINER/FX/MAJOR) container and the trade channel (/PRIVATE/TRADE/FX);
  • Subscribe to and trade on all FX currency pairs (with the exceptions below);
  • Subscribe to but not trade on /FX/GBPCHF;
  • Not subscribe to or trade on /FX/GBPAUD.
public PermissioningAdapterPermissioningProvider(DataSource dataSource) 
{ 
     PermissioningDataSource permissioningDataSource = new PermissioningDataSource(dataSource, Role.MASTER); 

     permissioningDataSource.startImageTransaction();
     /* Permissioning data should initially be added as part of an image transaction. This
      * can be done before the Permissioning DataSource starts or after it has started. 
      * Subsequent changes in permissioning data should be done as an update transaction. 
      */ 

     User adminUser = permissioningDataSource.createUser("admin", "admin"); 
     adminUser.applyPermission(Collections.singleton("/.*"), Constants.DEFAULT_PERMISSION_NAMESPACE, "VIEW", Authorization.ALLOW); 

     Map<String, String> espTradeFields = Collections.singletonMap("TradingProtocol", "ESP"); 
     permissioningDataSource.createActionRule("/PRIVATE/%U/TRADE/FX", espTradeFields, Constants.DEFAULT_PERMISSION_NAMESPACE, "FX-ESP-TRADE", "Instrument");

     adminUser.applyPermission(Collections.singleton("/.*"), Constants.DEFAULT_PERMISSION_NAMESPACE, "FX-ESP-TRADE", Authorization.ALLOW);

     permissioningDataSource.commitTransaction(); 


     permissioningDataSource.startUpdateTransaction(); 
     User user1 = permissioningDataSource.createUser("user1", "password1"); 
     Group gbpGroup = permissioningDataSource.createGroup("GbpGroup"); 
     gbpGroup.applyPermission(Collections.singleton("/FX/GBP.*"), Constants.DEFAULT_PERMISSION_NAMESPACE, "VIEW", Authorization.ALLOW); 
     gbpGroup.addMember(user1); 

     User user2 = permissioningDataSource.createUser("user2", "password2"); 
     gbpGroup.addMember(user2); 
     user2.applyPermission(Collections.singleton("/FX/GBPUSD"), Constants.DEFAULT_PERMISSION_NAMESPACE, "VIEW", Authorization.DENY);

     permissioningDataSource.commitTransaction();

     // your code to implement the multiple inheritence task

     permissioningDataSource.startUpdateTransaction(); 

     User demouser = permissioningDataSource.createUser("demouser","demopass"); 
    
     HashSet<String> allowedSubscriptions = new HashSet<String>(); 
     allowedSubscriptions.add("/FX/.*"); 
     allowedSubscriptions.add("/PRIVATE/%U/TRADE/FX"); 
     allowedSubscriptions.add("/CONTAINER/FX/MAJOR"); 
     demouser.applyPermission(allowedSubscriptions, Constants.DEFAULT_PERMISSION_NAMESPACE, "VIEW", Authorization.ALLOW);

     HashSet<String> allowedTrading = new HashSet<String>(); 
     allowedTrading.add("/FX/.*"); 
     demouser.applyPermission(allowedTrading, Constants.DEFAULT_PERMISSION_NAMESPACE, "FX-ESP-TRADE", Authorization.ALLOW);

     HashSet<String> deniedSubscriptions = new HashSet<String>(); 
     deniedSubscriptions.add("/FX/GBPAUD"); 
     demouser.applyPermission(deniedSubscriptions, Constants.DEFAULT_PERMISSION_NAMESPACE, "VIEW", Authorization.DENY);

     HashSet<String> deniedTrading = new HashSet<String>(); 
     deniedTrading.add("/FX/GBPCHF"); 
     deniedTrading.add("/FX/GBPAUD"); 
     demouser.applyPermission(deniedTrading, Constants.DEFAULT_PERMISSION_NAMESPACE, "FX-ESP-TRADE", Authorization.DENY);

     permissioningDataSource.commitTransaction();
}

FAQs

Some questions you may have are:

  1. Why are we supplying a plain text password?

    This is only for the tutorial. In real life users would be authenticated using the Caplin KeyMaster SSO integration product. This is why there is also a username-only method in the API:

    permissioningDataSource.createUser(“username”);
  2. Why am I always setting the DEFAULT_NAMESPACE? What does it do?

    Namespaces are not actually used by Liberator to enforce permissions. They are only used when you want the client to retrieve a list of permissions (such as the allowed tenors for forward trades), for displaying in a drop-down menu for example.

  3. Isn’t this supposed to be real-time?

    Yes. Permissioning adapters behave in broadcast mode, so they send all of their permissions into Liberator when they start up rather than waiting for Liberator to subscribe to specific subjects as you have seen in the adapters so far (active mode). However you can send “update” transactions at any time. The typical workflow for a permissioning adapter is to read the permissions on start-up and broadcast them into Liberator, then listen to real-time permission updates from a bank system. When it receives an update, the permissioning adapter can send an update transaction. These updates take effect immediately, for example if a user is currently subscribed to a subject and the permission is removed, Liberator will terminate the subscription. If an update transaction removes a user who is currently logged in, Liberator will kick the user out.

Review

Having completed this tutorial you should now have understood how to use the PermissioningDataSource to create users and groups and how to assign permissions to the group. Also remember that you can define your own actions like you did for the FX-ESP-TRADE action. In reality it may be necessary to connect to a server providing permission data, interpret this data, and translate it into permission transactions.