Permission Service
The Permission Integration Adapter on the server side configures all of the actions allowed for any single user or group of users. Although it has no control over the permissions that will be enforced on the server side, there is a client side API that allow the client to retrieve the permissions that apply to the logged in user. This allows the front end developer to provide a better user experience by, for example, greying out or hiding UI elements that relate to actions the user does not have permission to perform.
Objectives
In this tutorial you will use the PermissionService to query user’s permissions and adjust the trade tile accordingly.
In the Permissions Integration Adapter tutorial you created permissions for demouser such that this user is allowed to view some subjects and perform FX ESP trades trade on others:
-
The GBPUSD tile has both view and trade permissions, hence you are able to see the prices being streamed to the tile and to perform a trade.
-
The GBPCHF currency pair is set up to have VIEW permission but no trading permission.
-
The GBPAUD currency pair has neither.
If we extend our application so that rather than create one tile it creates a tile for each of these three currency pairs, then we can see how they react to the differing permissions.
Adding more tiles
Open your main JavaScript class CaplinTrader/apps/TutorialApp/default-aspect/src/mycompany/App.js.
At the bottom of the start() method you will see a call to this._createTile()
. it doesn’t take any arguments at the moment. Let’s change that method call so that we pass in the currency pair we want to create a tile for, and create three tiles.
App.prototype.start = function() {
this._initialiseConfigService();
this._initialiseStreamLink();
this._startStreamLinkConnection();
this._initialiseTradeMessageService();
this._createTradeFactory();
this._initialiseDelayedReadinessServices();
this.createTile('GBPUSD');
this.createTile('GBPCHF');
this.createTile('GBPAUD');
};
We’ll need to update our createTile() method so that it expects to be given a currency pair and passes it straight into the TilePresentationModel that it creates.
App.prototype.createTile = function(currencyPair) {
var templateID = 'mycompany.tutorial.tile.tile-template';
var presentationModel = new TilePresentationModel(currencyPair);
var presenterComponent = new PresenterComponent(templateID, presentationModel);
document.getElementById('body').appendChild(presenterComponent.getElement());
};
Lastly, we must update our TilePresentationModel so that it expects the currency pair as a constructor argument and creates a tile for that currency pair, rather than a hard-coded GBPUSD tile every time. We will assume that the first currency in the pair is the dealt currency and pull it out using substring.
The TilePresentationModel class can be found at CaplinTrader/apps/TutorialApp/tutorial-bladeset/blades/tile/src/mycompany/tutorial/tile/TilePresentationModel.js.
function TilePresentationModel(currencyPair) {
this.currencyPair = new Property(currencyPair);
this.dealtCurrency = new Property(currencyPair.substring(0,3));
...
}
Now we’re done. Refresh the application and you will see three tiles, one for each currency pair.
As you will see:
-
The GBPUSD tile behaves as expected
-
The GBPCHF tile shows the prices but does not receive a trade response from the server, therefore no confirmation message when the buy or sell buttons are clicked
-
The GBPAUD tile neither shows prices nor receives a trade response from the server
-
The StreamLink logs (?debug-finer) show a write access denied error when you try to trade with either of the latter 2 tiles:
<timestamp> - INFO : StreamLink publish to subject /PRIVATE/TRADE/FX called. <timestamp> - FINE : Out - Contrib [subject=/PRIVATE/TRADE/FX, fields=[MsgType=Open,RequestID=1362226304188,TradingProtocol=ESP,Instrument=/FX/GBPCHF,BuySell=SELL,Price=1.72373,DealtCurrency=GBP,BaseCurrency=GBP,TermCurrency=CHF,Amount=50000,SettlementDate=20130302,eventSource=client]] <timestamp> - FINER : > 0yYBrvSrcYXiPqOT8U-D7I+CONTRIB+%252FPRIVATE%252FTRADE%252FFX+MsgType%3DOpen+RequestID%3D1362226304188+TradingProtocol%3DESP+Instrument%3D%252FFX%252FGBPCHF+BuySell%3DSELL+Price%3D1.72373+DealtCurrency%3DGBP+BaseCurrency%3DGBP+TermCurrency%3DCHF+Amount%3D50000+SettlementDate%3D20130302+eventSource%3Dclient <timestamp> - FINER : < 9i /PRIVATE/TRADE/FX WRITE+ACCESS+DENIED <timestamp> - FINE : In - ActionFailResponseEvent [subject=/PRIVATE/TRADE/FX]
The Permission Integration Adapter will not allow the client to perform actions for which it is not permissioned. However, it is somewhat misleading not to be shown any response on the trade tile when you click to trade! The PermissionService on the client can be used to subscribe to the permissions on the server and customise the UI appropriately. |
Setting up the services
In order to read the permissions, which in our case are sent into Liberator by the Permission Integration Adapter, it is necessary to use a new service - the PermissionService. Since we are going to use StreamLink to request these from Liberator, we use the StreamLink implementation of this service, which is called the StreamLinkPermissionService.
The container to be requested is configured in the file: default-aspect\unbundled-resources\permissionDatasourceDefinitions.xml which indicates that the MASTER container is to be used. So the StreamLinkPermissionService will request /PERMISSIONS/MASTER/CONTAINER/<username>
.
The StreamLinkPermissionService therefore requires another new service in order to work - the UserService. This service tells you (or it) the username of the currently logged in user.
These two services cannot be initialised before a connection with the server is established. Such services are termed "Delayed Readiness services". When using these services, we must do a two-step initialisation process. Where previously we simply registered the services and then started using them, here we must register the service and then explicitly tell them to initialize before we can use them. We can’t start using them until the Service Registry informs us that they are ready.
Follow the steps below:
-
The default PermissionService implementation is the OpenPermissionService, which is essentially a stub that tells you everything is allowed, while the UserService has no default implementation set.
The implementation for both of these services can be set via configuration. Open CaplinTrader/apps/TutorialApp/default-aspect/resources/aliases.xml and add aliases for the PermissionService and the UserService:
<aliases xmlns="http://schema.caplin.com/CaplinTrader/aliases"> <!-- you can add any overrides you need here --> <alias name="br.user-prompt-service" \ class="caplin.webcentric.alert.WebcentricAlertDispatcher"/> <alias name="caplin.preference-service" \ class="caplin.webcentric.providers.WebcentricPreferencesService"/> <alias name="caplin.frame-manager-service" \ class="caplin.webcentric.frame.WebcentricFrameManager"/> <alias name="caplin.message-service" \ class="caplin.sljsadapter.providers.StreamLinkMessageService"/> <alias name="caplin.permission-service" \ class="caplin.sljsadapter.providers.StreamLinkPermissionService"/>(1) <alias name="caplin.user-service" \ class="caplin.sljsadapter.providers.StreamLinkUserService"/>(2) </aliases>
1 Alias for caplin.permission-service
2 Alias for caplin.user-service
-
The StreamLinkPermissionService, being a Delayed Readiness service, can only be used by the tile after the connection to the server has been established, the Permissions container has been requested, and the permissions have been received in response. Therefore it is necessary to be notified when the service’s initialisation is complete.
Open CaplinTrader/apps/TutorialApp/default-aspect/src/mycompany/App.js. Add the lines below to the currently empty method _initialisedDelayedReadinessServices(). This line tells the Service Registry to initialize the Delayed Readiness services, and registers two function that can receive callbacks when it’s done - one for a successful initialization and one for a failure. Note that the two functions we are referencing already exist in App.js so you don’t have to add them yourself.
If you’re not sure what the bind() method does, don’t worry about it - it’s just a built-in JavaScript convenience function that tells the runtime what the "this" object should be bound to when it calls your function.
App.prototype._initialiseDelayedReadinessServices = function() { var successCallback = this._onServicesInitialisedSuccess.bind(this); var failureCallback = this._onServicesInitialisedError; ServiceRegistry.initializeServices(successCallback, failureCallback); };
-
The last thing we will do is defer the creation of our three tiles until after the services have initialised. Remove the three this.createTile() calls from the constructor of the class. Paste them into your service initialisation success callback instead:
App.prototype._onServicesInitialisedSuccess = function() { this.createTile('GBPUSD'); this.createTile('GBPCHF'); this.createTile('GBPAUD'); };
If you’re not quite sure what’s going on, it’s important to read back through this section and understand what we have done:
-
We want to use the PermissionService, which depends on the UserService. So we configured our application to use the StreamLink implementations of these services.
-
At the initialisation stage of our application, we told the Service Registry to initialize these two services and inform us when it has finished by calling one of our methods.
-
When our callback method is invoked, we create our tiles. By deferring the creation of our tiles until this time, we can be guaranteed that the tile can start using the services straight away.
Subscribing to view permissions
Now that we have access to the permission service, let’s fix up our GBPAUD tile. The user does not have access to streaming prices for this currency pair, so the tiles are currently blank. We can register a listener for VIEW permissions, and if we don’t have the required permission we can display a helpful message to the user.
We could also disable the buy and sell buttons, since you can’t trade unless you are receiving streaming quotes.
Follow the steps below:
-
When the tile is created it can start to listen for permission updates from the Permission Service. Firstly we will query whether a user has permission to view prices for a particular currency pair. This translates to: "Does demouser have permission to VIEW (action) /FX/<currencypair> (product)?" The second parameter is the namespace, which we are not using - this should be passed as null.
Add the two lines below to the end of the constructor (don’t replace the whole constructor with these two lines):
function TilePresentationModel(currencyPair) { ... var product = '/FX/' + this.currencyPair.getValue(); ServiceRegistry.getService('caplin.permission-service').addPermissionListener(product, null, 'VIEW', this); };
-
You will notice that the last parameter for
addPermissionListener()
is the listener for permission updates. As usual we are passing this, so we must once again declare that our class implements the relevant listener. The relevant listener this time is caplin.services.security.PermissionServiceListener, so let’s add that to our block of implemented interfaces just under the constructor:var PermissionServiceListener = require('caplin/services/security/PermissionServiceListener'); ... topiarist.extend(TilePresentationModel, PresentationModel); topiarist.inherit(TilePresentationModel, SubscriptionListener); topiarist.inherit(TilePresentationModel, ConnectionServiceListener); topiarist.inherit(TilePresentationModel, StateChangedListener); topiarist.inherit(TilePresentationModel, PermissionServiceListener);
-
It would be good if our tile only tries to subscribe to streaming prices if it has the permission to view them. Let’s delete these two lines from our Tile constructor:
var messageService = ServiceRegistry.getService('caplin.message-service'); this._subscription = messageService.subscribe('/FX/' + this.currencyPair.getValue(), this);
-
Now we must implement one of the methods from that PermissionServiceListener interface. Incidentally, it’s worth noting that the PermissionServiceListener interface is a forgiving one - unlike some of the other interfaces it does not throw errors if you don’t implement all of the methods. In this sense it is not a "true" interface, but it allows you the flexibility of only implementing the methods you care about. We are able to do this because interfaces are not a concept that is supported natively in JavaScript, we have our own mechanism to simulate them.
Add this permission callback method to your class. When it receives the callback, it will subscribe to streaming prices if it has permission to view them. Otherwise it will display an error message and disable the buttons.
TilePresentationModel.prototype.onSinglePermissionChanged = function(isAuthorized) { if (isAuthorized) { var messageService = ServiceRegistry.getService('caplin.message-service'); this._subscription = messageService.subscribe('/FX/' + this.currencyPair.getValue(), this); this.message.setValue(''); } else { this.message.setValue(i18n('mycompany.tutorial.tile.no_view_message') + this.currencyPair.getValue()); } };
-
Refresh the application. Everything almost works, except that you have an untranslated internationalisation message in the GBPAUD tile. The other two tiles are working as normal. As a final exercise, try fixing this yourself so that the tile says "You do not have the necessary permission to see rates for GBPAUD". You will need to add a new i18n token to the language files in the resources directory of the tile blade.
-
As a further task, try updating your onSinglePermissionChanged()callback method so that it disables the buy and sell buttons if the user does not have the permission, and enables them if the user does have the permission. You will need to add lines like this to the relevant places in the if/else block:
this.buyRate.enabled.setValue(false);
Now the tile should alert the user that he does not have the necessary permission to see the GBPAUD currency pair:
Stretch Task - querying a user’s permissions
You can retreive permissions in two ways; by registering a listener, as we did in the example above, or by doing a one-off query.
In general it is almost always better to register a listener rather than do a one-off query. We are only demonstrating how to do a query for completeness. This is because you want your blade to react to real time permissions, so that if the user’s permission is revoked the tile disables itself immediately. This will happen if you have registered a listener. If you just do a one-off query when the tile is created, then you will not know if the permission is revoked later on. This means the tile will not show the correct state until the user logs out and logs back in, or closes and re-opens the tile. |
Follow the steps below:
-
You can query individual or group permissions using the methods on the Permission Service such as canUserPerformAction().
Try updating your _executeTrade() method so that it checks whether the user has the relevant trading permission before attempting to trade. You can do this by executing the permission query first, and moving all of the existing logic in the method into an if block that only executes if the user has the permission.
TilePresentationModel.prototype._executeTrade = function(buySell) { var product = '/FX/' + this.currencyPair.getValue(); var canTrade = ServiceRegistry.getService('caplin.permission-service').canUserPerformAction(product, null, 'FX-ESP-TRADE'); if (canTrade) { // ... existing logic goes here } else { this.message.setValue(i18n('mycompany.tutorial.tile.no_esp_message') + this.currencyPair.getValue()); } };
-
Note that we are using another new i18n token, mycompany.tutorial.tile.no_esp_message. This does not exist, so you’ll have to add it to your language file(s) as you did in the example above.
When this is done try refreshing the application and click buy or sell in the GBPCHF tile. The user has permission to view streaming rates for this currency pair but not trade, so you should get an error message as shown below.
Review
Having completed this tutorial you should be able to use the PermissionService to listen for permission changes on the server and also to query individual permissions on the fly.
More information about using permissions in the client can be found on the website.