PDA

View Full Version : Best way to hide/disable GUI elements based on user's privilege?



kingcu
18 Apr 2011, 6:53 AM
I am starting a web application with client side implemented in pure ExtJS and middle tier in Grails. The application has role-based authorization, where a user can have many fine grained roles like SOME_FORM_READ, SOME_FORM_UPDATE, SOME_DATA_DELETE, SOME_DATA_READ, etc. Based on the roles of the user, certain GUI elements need to be disabled or hidden, while others need to be in a read-only mode.

I did some search on the web, but didn't find any design pattern that specifically addresses this issue, so I came up with my own design. I am sure that a lot of the web applications out there will have a similar requirement, so I'd like to post my design here and hear people's opinion on it. By no means is my design a perfect one, but I hope it can be improved with everyone's input. I also posted this on StackOverflow:


http://stackoverflow.com/questions/5700865/best-way-to-hide-disable-gui-elements-based-on-users-privilege

So, here it goes:

There are four types of code (or information) we need to deal with in the client tier regarding authorization:


GUI element manipulation code, for example:
panel.hide() form.setReadOnly(true)
GUI element permission requirement, for example:
form.requires('READ', 'FORM_READ_ROLE')
adminPanel.requires('ADMIN_ROLE')
User privilege information, which is basically a list of roles that the user has;
Authorization logic: determines which elements to hide/disable based on user privilege;

The core of the design is a singleton, named GUIPermissionManager, or GPM for short. This is a centralized design in that most of the code is in GPM, so that GUI elements are not polluted by the authorization code. This is how GPM works:


GUI elements (that need certain permission to access) register their permission information with GPM, like this:
GPM.register(this, 'DEPARTMENT_DELETE_ROLE'); // button for deleting a department
GPM maintains a list of GUI permission registration
On user login, GPM receives the list of roles the use is assigned
GPM walks through the GUI permission registration list and based on user privilege, determines which part of the GUI to hide, and in turn, calls element.hide() accordingly

Questions:


GUI elements are organized in a tree hierarchy, e.g. a panel contains a button bar and a form, so when the panel is hidden, there is no need to check further if the button bar and the form need to be hidden. Problem: how to register and maintain this hierarchical information in GPM?
Currently, I can only think of two use cases for GUI element: hide an element or set an element as read-only (such as a form). Is there any other use cases?
In ExtJS, to hide an element, we call hide(), but to set a form read-only, we have to come up with our own function, let's say it's called setReadOnly(), how to let GPM know which function to call? Passing the function as part of the registration?
What is the best way to set a form read-only? If I extend the form component with the setReadOnly() functionality, there will be a lot of code duplication and I have to do this for every form that need permission control. Is it possible to create a dynamic form transformer in GPM so that if a form is set to read-only, it automatically replaces all editable fields with display-only fields?

drian
18 Apr 2011, 9:04 AM
here's how i did it...

I've created a plugin and attached it to every component that has a certain privilege. Together with the plugin, i've added an extra attribute named resourceId. This is the unique resource ID for a component.

My user roles are dynamic - the admin can actually check/uncheck the resources(got them saved in the database) a user has access to. This will allow a user to have access to any components he wants.

NOTE : the component attribute resourceId needs to be the same as the ones the admin assigns to users.... can't get it solved without them being hard-coded.

When the user logs in, i stock the resources he has access to in an userResourses array and in the plugin i check the component attribute resourceId and see if it's inside my userResourses array. If it's not inside, i simply .hide() the component.

In your case, you need different actions for a component(hide it, or make it read-only - aka disabled). So, if you still want to keep it as dynamic as possible, you could set for each component (resourse in our case) the kind of action the user has access to.

When you get the user resources, you would also get the level of access for each resource - show/disable. In the plugin, you would check if the component resourceId is inside the userResources array, and if it is, you either .hide or .disable depending on the level of access.

As far as i know (i might be wrong as i'm not experienced with extjs) there isn't any read-only feature or components, but you can .disable() them. Moreover, if you tweak the disabled css class, you can put a more transparent & close to your needs background color so it suits your needs.

I think this solution is very vast to use as you can literally plug&play it on whatever component you want.

kingcu
18 Apr 2011, 6:18 PM
Thanks for the reply. If I remember correctly, plugins are initialized after component's initComponent(), so in a UI component tree hierarchy, if the top parent is hidden by the plugin, its children may have already been initialized; for a complex admin panel, this may waste a lot of CPU. Any way to avoid this?

ResourceId is a good idea. I'll create a hard-coded map between server side role and corresponding resource ids in my GPM. This way, the permission information is centralized in one place.

I know that there is a DisplayField, which I think is for displaying fields in read-only mode. Anyone has experience with it?

drian
18 Apr 2011, 9:22 PM
hmm... you could override the component class and do your stuff there. think that's the only place where you could alter the rendering of the component's children.

I won't go that route just for not rendering some child components. It's not worth it.

The general idea is to render the components and then alter them depending on the user permissions.

The displayfield is just for displaying some text. If you're suggesting to change the inputs into displayfields ... that's where you'd waste cpu.

kingcu
18 Apr 2011, 10:28 PM
Make sense!

One last thing: I am still trying to figure out the best way to distinguish between a .hide and a .disable using the resource id. The only thing I can think of right now is to test the resource's component type, if the component type is form, the plugin will call .disable instead of .hide if the user doesn't have privilege. Of course, the assumption is that a form will never need to be hidden, it's always enabled or disabled. I should be able to work around that.

drian
18 Apr 2011, 11:12 PM
You can do that if you want all users to see all forms disabled...or you could do this server side. When you assign the resource to a user, you specify the type of access - show/hide/disable of that resource and in javascript, when you get the user's resources, you put them into an array of objects {resourceId, resourceType}.

Now you can decide what action you need to do based on the resourceType.... and you still keep it dynamical in the way that you decide on the server the exact behavior of every resource for every user.

Depends on you how flexible you want to be.

kingcu
19 Apr 2011, 1:23 AM
I got it. Many thanks for the help!

kingcu
20 Apr 2011, 1:56 AM
After a second thought, I think I'll keep a singleton GPM in addition to the plugin. In a complex application, I may need to attach the plugin to 100 components, which means there will 100 instances of the plugin class. It's not a good idea to duplicate all the code and the access control information 100 times. So, the plugin should only have code to:

1. Look up access control information by parent component's resource id from GPM;
2. Call appropriate methods (hide/disable) on the parent component based on the look-up result;

everything else stays in the singleton GPM.

drian
20 Apr 2011, 5:39 AM
the plugin is executed exactly where it needs to. If you make a global function where you have your logic you'd have to call that function to check every component on the page, and then do you stuff.
Not to mention you'd have to call it whenever more components are created on the same page thus checking again all the components on the page and doing the necessary actions.

My plugin is incredibly simple. If the component's resourceId is not inside the user resources array, then i disable the component.

Ext.ux.Permission = function() {
this.init = function(cmp) {
if(App.user.resources.indexOf(cmp.resourceId) == -1)
cmp.disable();
};
};

Ext.preg('permission', Ext.ux.Permission);

kingcu
20 Apr 2011, 5:50 AM
I am not disagreeing with you. The App.user.resources array in your code and the logic to produce that array is what I refer to as GPM singleton in my case. In your case, the user resource array is directly loaded from server, but in my case, since I am using spring security, it's Roles on the server side, I'll need some logic to map the list of roles to the list of resources, that's exactly what I need to put in my GPM singleton.

drian
20 Apr 2011, 6:04 AM
oh, i see. My App function is actually a singleton and i load all my necessary info before i render the initial application UI.