This blog is about a custom library that can be used to adapt an SAP / Open UI5 app at run-time, based on the user’s roles. The library is open source and can be found here on GitHub.
Even though authorization must be enforced at backend level, in practice we frequently get requirements along the lines: “Hide this button if the user does not have XYZ privileges”. Of course, this does not mean that we don’t do the authorization checks in the backend as well; it is basically the same situation as with UI-based validations: nice to have them, but the validations must exist in the service layer as well.
How is this done now?
Well, if you wake a look in some of SAP’s own UI5 apps (e.g. the HANA cockpit apps, XS Admin, etc), it is done with the following steps:
- [Backend] Make a specialized service / endpoint / whatever to expose the authorization information.
- [Frontend] Make a request to this service and save the results in a model (generally a JSONModel, although I have seen an ODataModel being used for this as well).
- [Frontend] Use bindings throughout the views to hide / disable controls based on the roles or privileges the user has.
There might be also other approaches, but this is the most common from my experience.
So what’s the problem?
If you take a look into the UI5 SDK, you will see the following bit in the “Security” chapter:
Moreover, common security mechanisms, which are usually taken for granted, like user authentication, session handling, authorization handling, or encryption are not part of SAPUI5 and need to be handled by the server-side framework and/or custom code of the application.
So we won’t get anything standard soon. But it would be nice to have something reusable to do this kind of things.
Using the approach presented above, you have one big “issue”: your application is tightly coupled with the authorization scheme of the backend. If something changes (e.g. a role is split into two), then you have to go through all your views and change a lot of small places. This can be painful and very error prone (you could very easily overlook one place or another).
To put it in another way: the authorization-based adaptation is polluting your application.
My solution: a custom library
Why wouldn’t we be able to make a custom library which handles this for us?
To avoid the “issue” that we discovered before, it should do it’s magic without polluting the application’s views or controllers (or at least to minimize this effect).
We will still need the backend service for retrieving the authorization information, but at least the UI will be “clean”.
We have to decide what kind of features this library would have and what concepts it will use. The following list could be a good starting point:
- Declarative specification of roles / privileges that the user interface application knows of. This shouldn’t be the same as the backend roles. Normally, it should be a list of conceptual actions that the user can do (with a right granularity; the definition of “right” depends on the application itself).
- Definition of relationships between roles. Example: if you are a “super user”, then you have all the other possible privileges.
- Declarative specification of what should be done (how the UI should be adapted) based on the run-time values of these roles. Let’s name these operations “actions“:
- Static actions: which are applied once (preferably as soon as we know the roles of the user).
- Dynamic actions: which are applied depending on the data displayed in the UI. This is the UI version of HANA analytic privileges.
It would also be nice if we can extend this library easily.
Spoiler alert: I already implemented something which does the stuff from above. It’s still a rough version, but I feel that it is ready for the world to see it.
It is open source and I would be glad if I get some feedback (feature requests, bug reports, etc.) or maybe even some help (pull requests, forks). You can find it here on GitHub and read more about how you can use it, what is already available and even see a very small sample.
I will only write a small sneak-peak in the rest of the blog (maybe I will write a further blog posts if there is some interest in the library).
At the center of the solution, there is a role specification. Each component can have its own role specification. These specifications contain:
- The list of “known” roles.
- A map of implications: defines what roles are implied by each super-ordinate role.
- A map of role computation expressions.
- A list of selection rule – action pairs.
The approach I have used is fairly simple and is centered around XML Preprocessing:
- Each component can be registered against the library with a given role specification (a JSON object). At this moment (or later, when the role specification or role assignments are obtained), the static roles are computed based on the basic roles that the user has and the relationships between roles.
- Whenever a view of a registered component is instantiated, the preprocessor is called.
- The preprocessor iterates over each control of the view using a “ControlIterator”. The control tree is traversed in a breadth-first order.
- Each control is checked against the selection rules (selectors). If a control matches, then the corresponding action is executed on the control.
The actions that you can apply to the controls are the “juice” of the library. They can either be “static” (their effects are applied once) or “dynamic” (their effects depend on the data displayed).
Dynamic actions are implemented through a helper element. This helper element is added to the control tree during preprocessing and is paired with its target control (the control that it must affect). The purpose of this element is to access the binding context of the view or target element and apply some action at run-time.
All the actions and selectors are instantiated through a factory class. The type of the selector or action is retrieved from the role specification. Additional types may be easily registered to the factory (basically allowing client code to add custom actions or selectors to be used by the library).
There are a few limitations which come along with my approach:
- It only works on async XML views.
- Whenever fragments are lazily loaded (by a controller), the library code will have to be called manually.
Apart from improving on the existing code base, I already have some other features in mind:
- Adding some adapters to obtain the current user’s role list (OData, JSON, etc).
- Enhancing the selectors and actions to allow composition.
- Improve the build and versioning (only relevant if it starts being used).