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.
Post a Comment