2007-06-19

Using activities for user management

One common requirement from almost all RCP based applications is the ability to restrict parts of the user interface based on the identity of the current user: when the user logs in as the application is started, the current capabilities of the user is typically downloaded from a sign-on server, and now we want to restrict the user interface of the application based on these capabilities.

This blog entry will show how to implement this using activities in Eclipse RCP. A demonstration plug-in is found here.

There seems to be at least a couple of possible solutions based on plug-ins: only install the plug-ins relevant for the user or only load the plug-ins that are allowed for the user. Although this is not all that difficult to implement, it is a rather coarse solution. It is not always the case that the functionality of plug-ins can be divided along the same lines as wanted from the security system. It is similar to using a canon to shoot a mosquito...

A much more elegant solution is to use activities - also known as capabilities - for the job as these can be used completely orthogonal to plug-ins: once the activities can be defined, these can be used to restrict just about any contribution of the interface in any plug-in.

Another very nice property of activities, is the fact the almost all of the implementation is confined to the various plugin.xml files. Only a very small part of the functionality must go into the Java code itself and even that can be limited to the WorkbenchAdvisor or a similar place...

To use activities you must use the following model:
  • Activity: A single activity (or capability)
  • Category: An arbitrary set of activities - these are only relevant if you expect to expose the Capabilities preference page to the users, which is very unlikely when activities are used in this use case...
  • Pattern: A regular expression for the contributions that are managed via an activity
  • Manager: The activity manager
  • Identifier: A single ID from a specific plug-in
It is possible to assign many different types of contributions to activities, so the contributions are only enabled if the activity is enabled: perspectives, views, editors, wizards, preference pages, menus, toolbars, commands, and actions.

You must go through the following steps to use the activities in your application.

Defining a new activity


To define a new activity you use the following org.eclipse.ui.activities/activity extension:

<extension
point="org.eclipse.ui.activities">
<activity
description="The Support Role"
id="com.rcpcompany.demo.activities.support"
name="Sample action"/>
</extension>

The ID is normally chosen to make it easy to map the capabilities returned by the sign-on server to the internally used IDs. In this case, we assume that the returned token is "support" so it is just a matter of concatenating two strings :-)

Assigning contributions to activities


This is the difficult part, until you find out how to map the different contributions to the ID scheme used in the activity patterns.

Basically you must add a org.eclipse.ui.activities/activityPatternBinding extension for each item you want to handle. Although it is possible to use regular expressions to match many IDs at once, this can hardly be recommended as this makes it rather hard to assign IDs to the different contributions used in an RCP application.

Given the following definition for a view:

<extension
point="org.eclipse.ui.views">
<view
class="com.rcpcompany.demo.ui.views.History"
id="com.rcpcompany.demo.ui.views.History"
name="History"/>
</extension>

the following definition is used to restrict access to the view based on whether the activity is enabled:

<extension
point="org.eclipse.ui.activities">
<activityPatternBinding
activityId="com.rcpcompany.demo.activities.support"
pattern=".*/com\.rcpcompany\.demo\.ui\.views\.History"/>
</activityPatternBinding>
</extension>

The IDs to use for different contributions is taken from the id attribute used in the definition elements.

I use one conversion to help avoid some of the work: when a command is used in a org.eclipse.ui.menus/menuContribution/command the id is the same as the command with suffix .mc.something. As in the following example:

<extension
point="org.eclipse.ui.commands">
<command
description="Shows the name of the current resource"
id="com.rcpcompany.demo.ui.commands.showName"
name="&Show Name">
</command>
</extension>

<extension
point="org.eclipse.ui.menus">
<menuContribution
locationURI="menu:file">
<command
commandId="com.rcpcompany.demo.ui.commands.showName"
id="com.rcpcompany.demo.ui.commands.showName.mc.file">
<visibleWhen>
...
</visibleWhen>
</command>
<command
commandId="org.eclipse.ui.edit.rename"
id="org.eclipse.ui.edit.rename.mc.file">
</command>
</menuContribution>
<menuContribution
locationURI="toolbar:org.eclipse.ui.main.toolbar">
<toolbar
id="com.rcpcompany.demo.ui.toolbars.main.show">
<command
commandId="com.rcpcompany.demo.ui.commands.showName"
id="com.rcpcompany.demo.ui.commands.showName.mc.toolbar.main.show">
</command>
</toolbar>
</menuContribution>
</extension>

<extension
point="org.eclipse.ui.activities">
<activityPatternBinding
activityId="com.rcpcompany.demo.activities.support"
pattern=".*/com\.rcpcompany\.demo\.ui\.commands\.showName(\.mc\..*)?"/>
</activityPatternBinding>
</extension>

Enabling activities


To enable the needed features when the user is logged in, the following code can be used in WorkbenchAdvitor:

@Override
public void initialize(IWorkbenchConfigurer configurer) {
super.initialize(configurer);

IWorkbenchActivitySupport activitySupport = configurer.getWorkbench()
.getActivitySupport();
IActivityManager activityManager = activitySupport.getActivityManager();

Set<String> enabledActivities = new HashSet<String>();
String id = "com.rcpcompany.training.demo.activities.activities." + myRole;
if (activityManager.getActivity(id).isDefined()) {
enabledActivities.add(id);
}

activitySupport.setEnabledActivityIds(enabledActivities);
}

Please notice the the code tests whether the role exists before using it. If this is not done, an undeclared activity will be enabled and no parts of the interface will be affected.

And the rest...

There are a number of other things to be done as well depending on the actual application:
  • If the roles can be updated while the application is running, you must repeat the code above in your listener. Unfortunately, you must also clean up the existing perspectives, views and editors in the workbench. This is particular difficult for editors as this can be dirty or in an inconsistent state where they cannot just be saved...
  • If you want to debug what contributions that are visible and not, then use the Capability preference page:

    <extension
    point="org.eclipse.ui.preferencePages">
    <page
    category="org.eclipse.ui.preferencePages.Workbench"
    name="Capabilities"
    id="org.eclipse.sdk.capabilities"
    class="org.eclipse.ui.activities.ActivitiesPreferencePage"/>
    </extension>
    Please note that you also need an activity category in this case...
  • There can be any number of roles enabled at the same time.
  • Roles can depend on other roles, so you can have "support manager" that depends on "support".
  • A demonstration plug-in is found here.

12 comments:

pookzilla said...

I think it's only fair to mention that activities don't actually restrict anything from running - the disabled functionality is still there and if your user is clever enough (or simply one of those users who always finds your bugs) it can be made visible.

Unknown said...

I understand that the functionality is still present in the application - after all the class files are removed, but as far as I can see the contributions are effectively removed from plugin.xml, so the functionality will be unavailable in the workbench.

Also all active perspectives, views, editors and wizards will still be active.. This is what the ActivityManagerListener can be used for.

Can you describe how to get access to circumlate the activities stuff?

Patrick Roumanoff said...

It's kind of easy to circumvent activities hiding UI component. All you have to do is add a plugin which defines the Capabilities preference page (as per your example), and then you are only one click away from enabling all activities/categories.

Activities are still a very nice feature to hide UI components to which the current user role shouldn't have access, it doesn't stop you from implementing proper security on the remote server.

When the remote server implements security, even if the hidden UI is displayed, then any call to unauthorized action fail and security is preserved.

Unknown said...

Patrick,

You are of cause correct in your comments. But most of the RCP applications, I have been working on has had proper back-end authentication and authorization via ESB, J2EE or whatever... The primary problem has just been to mirrow the allowed functionality in the client interface. And for this activities has been fine (as long as you don't use the open perspective action :-()

Unknown said...

I have just found the following description from IBM on how to extend my method: Creating a declarative security model for RCP applications.

Maralc said...

Hi Tonny,

Thanks for the good article.

I am using the activities mechanism, but there is a point killing me right now.

How to filter key binding shortcuts?

It is ok that my buttons disappear but the shortcuts assigned are still working.

Thanks for any ideias.

Marcelo

Unknown said...

Hi Tonny,

thanxs for that good article.

I have a problem with enabling / disabling menu bound commands - the activity says it is enabled , a second disabled - but both produce a reaction ...

I fired fireSourceChanged(16, stateMap), but with no reaction...

Did i forgot something ?

NJ

Mark said...

Currently I use activities to hide elements from the user. One problem I encountered are e.g. the perspective extensions. I couldn't hide view shortcuts?

Anonymous said...

Hmm, I seem to have the same problem... I'll look into the alternative with the new security framework some time soon...

Unknown said...

Hi Thanks's for the article, I used this concept for my application. I used the activities to restrict the acces to certain perspectives. When I open the show perspective Dialog all the Perspectives that i protected were not visible. The problem is that the show Perspective Dialog has a Checkbox 'show all'. When I select this my hidden perspectives appear. When I select one og the 'hidden' Pespectives I get a dialog that allows me to turn on all the activities that i want.
Is there an elegant way to prevent eclipse from doing that. My current solution is, that I override the Handler for show perspective.

Anonymous said...

As noted elsewhere, the show view and show perspective commands both allows you to circumvent to activities. The only known solution is to implement you own versions of these. Not very difficult, but a bit annoying.

Mahesh said...

Excellent Post !! Thank you very much ! Unfortunately not much resources on net on activities part of RCP. Thanks again !