PDA

View Full Version : Overriding Component to apply permissions, is it a good thing?



Grolubao
21 Feb 2011, 2:40 AM
Hi to all,
I'm interested in implementing a behavior in all Components that would make them hidden if they didn't had all the permissions I would provide as an array. Something like this:



var newPanel = new Ext.Panel({
renderTo: Ext.getBody(),
width: 200,
height: 200,
permissions: ['PERM_USER','PERM_ADMIN']
]
});
Then I would have a generic permissions array that would contains all the permissions this user had. My idea was override Component onRender, compare the permissions passed as a config with the ones he had, if he missed one it wouldn't render.

What's your opinion on the best way to do it?

Thanks a lot!

Condor
21 Feb 2011, 4:48 AM
You would have to override 'initComponent' or 'render', but not 'doRender'.

ps. This solution wouldn't support role changes (logout, additional login etc.). Is that OK?

Grolubao
21 Feb 2011, 4:56 AM
You're correct Condor, I meant render...

Why do you say it doesn't support logout, etc? As long as I refresh the page, the permissions will be evaluated again (if I include sort of a permissions jsp that contains a permissions array) and then will be passed again to the components to be rendered all over again, so once again it will be evaluated.

From an architecture point of view, would it be the correct way to do it?

Condor
21 Feb 2011, 5:04 AM
You need a complete page reload. Is that really what you want or should all components update themselves after a login or logout.

Grolubao
21 Feb 2011, 5:10 AM
We are currently using sitemesh in our applications. My idea was to create a jsp that just creates a javascript array that contains all the permissions the current logged in user has. Then that page would be included to decorate each other page, thus when creating the components they could access this array and evaluate if they should be shown or not.

It's hard to read more about architecture of complex applications. I'm starting with a new project and I would like to do it in a proper way.

It's always a pain, because one could use the acegi tags to evaluate permissions:



<security:authorize ifAllGranted="PERMISSION_CALCULATION_MENU">
menuBar.add({
id: 'calculationsMenu',
text: 'Calculations',
tooltip: 'Perform Calculations',
toggleGroup: 'menuToolbar',
handler: function() {
document.location.href = '<c:url value="/calculations/calculations.html"/>';
}
});
</security:authorize>


But this creates a tie between the jsp and javascript and I wanted to avoid that at all costs.

Condor
21 Feb 2011, 5:40 AM
I use a slightly different approach.

All my restriction-affected components are hidden or readOnly by default and I add an 'applyPermissions' method to each component that updates the state based on the passed permissions.

That way if a user logs in or out I can just loop through all components and call applyPermissions (if the method is defined for the component).

drian
21 Feb 2011, 10:21 AM
I use a slightly different approach.

All my restriction-affected components are hidden or readOnly by default and I add an 'applyPermissions' method to each component that updates the state based on the passed permissions.

That way if a user logs in or out I can just loop through all components and call applyPermissions (if the method is defined for the component).

Hmm, i might be missing something, but what about components that are not rendered when the user first logs in?

Wouldn't it be better to override the component class and add another method "applyPermissions(componentResourceId)". This method changes the hidden state of the component if it finds the "componentResourceId" inside a global array which has all the componentsResourcesIds that the user has access to.

opinions?

Condor
21 Feb 2011, 10:49 AM
That's what I meant (Ext.ComponentMgr.all.each).

drian
21 Feb 2011, 11:18 AM
That's what I meant (Ext.ComponentMgr.all.each).

What about components that are not registered in component manager? Aren't components registered only when they are rendered? So, for example, if i have a button that is shown/not shown depending on the user's permission, and that button is situated inside a window that is only rendered when a certain event is fired, i won't have the button component rendered and also won't have it registered in the component manager.

Isn't it better if i do something like this?

var userResources = new Array('button1', 'button2'); // the IDs that the user has access to

Ext.override(Ext.Component, {
resourceId: '',
render : function(){
if(this.resourceId && (userResources.indexOf(this.resourceId) < 0))
{
this.disable();
}

// forgot how to call the component original render code which should go here :)

}
});

Now, when i add a new component, i pass in the config the resourceId for that component and it will enable/disable the component when it's rendered on the page.

Condor
21 Feb 2011, 12:30 PM
Components are registered when created, not when rendered.

Just in case you could call applyPermissions from the Ext.Component initComponent too.

Grolubao
21 Feb 2011, 2:30 PM
I ended up developing a plugin that would check the permissions and if he didn't had it would set hidden to true.

It's good to discuss this advanced concepts :)

Grolubao
22 Feb 2011, 2:15 AM
Here is my solution:


//Abstract
Plugins.AbstractPermissionsChecker = Ext.extend(Object, {
/**
* Apply the permissions configuration
*/
constructor: function(config) {
config = config || {};
Ext.apply(this, config);

},
isHiddenAccordingToPermissions: function(){
throw 'Should implement isHiddenAccordingToPermissions method in the subclass';
},
/**
* Plugin init function will look for each permission, if it doesn't find one it renders it hidden
*/
init: function(component){
if(Ext.isDefined(this.permissions) && this.isHiddenAccordingToPermissions()){
Ext.apply(component, {
hidden : true
});
}
}
});\

Plugins.AllPermissionsChecker = Ext.extend(Plugins.AbstractPermissionsChecker,{
isHiddenAccordingToPermissions: function(){
var hide = false;
Ext.each(this.permissions,function(item){
if(!permission[item]){
hide = true;
return;
}
});
return hide;
}
});

Plugins.AtLeastOnePermissionsChecker = Ext.extend(Plugins.AbstractPermissionsChecker,{
isHiddenAccordingToPermissions: function(){
var hide = true;
Ext.each(this.permissions,function(item){
if(permission[item]){
hide = false;
return;
}
});
return hide;
}
});


Usage:



plugins: [new Plugins.AllPermissionsChecker({
permissions: ['PERM_USER','PERM_ADMIN']
})],

drian
22 Feb 2011, 3:57 AM
I also went with a plugin, but i also took into consideration that a certain component might have its permissions changed from one user to the other, while your's is only available for 2 hard-coded permissions.

Ok, so each of my components that have permissions i add a config option of "permissionId", which is, in fact, a key for that permission(just like an ID, a link to this component). For example, a button will have permissionID: 'saveButton'.

Now, when my user first logs in, i create a global array with all the permissions that the user has. Example: var userPermissions = new Array('saveButton', 'addButton', 'delButton');

Now, i create a plugin which I attach to every component that has permissions. This addon checks if the component permissionId is inside the global userPermissions array. If the component has set permissionId and the permissionId is not found in the userPermission array, the component is disabled/hidden/ or whatever you want to do with it :).

Component sample:


new Ext.Button({
text: 'my save button'
permissionId: 'saveButton',
plugins: [new Ext.applyPermissions()]
})


Plugin sample:

Ext.extend(Ext.applyPermissions, Ext.util.Observable, {
init:function(comp) {
if(comp.permissionId && (userPermissions.indexOf(comp.permissionId) <=0 )){
comp.disable(); // or whatever you want to do with the components that the user doesn't have access to
}
}
});


That's how simple I have my code and it works.

Grolubao
22 Feb 2011, 4:02 AM
Yes, but I'm not seeing the permissions changing any time soon.

I'm not seeing the added value with your implementation, because you will need to know that a specific user has the 'saveButton', 'addButton', 'delButton' visibility, while in my case I delegate that to spring security (acegi). My global array consists of spring security permissions translated into a javascript array.

My permissions are hard-coded, but in your case it's also hardcoded that a certain user has the savebutton, addbutton, delbutton visibility.

Can you explain a bit more about it?

drian
22 Feb 2011, 4:09 AM
I only hardcode the permissionIds for each component. My global userPermissions array is created when the user logs in. It's basically created as an ajax request to the server, where it fetches the permissions, and creates the userPermisions array.

In my database i have a table with all the permissionsId in the application, and i can dinamically set for each user or user group the permissions he has access to.

Grolubao
22 Feb 2011, 4:22 AM
I only hardcode the permissionIds for each component. My global userPermissions array is created when the user logs in. It's basically created as an ajax request to the server, where it fetches the permissions, and creates the userPermisions array.

In my database i have a table with all the permissionsId in the application, and i can dinamically set for each user or user group the permissions he has access to.

Ok, but then you tie a dependency between the visibility of your components, and the persistent layer. Your DB shouldn't know anything about what permissions should show or not something, that's web logic in the end. Your DB should only store what permissions each user has.

drian
22 Feb 2011, 4:30 AM
that is true in your case where your permissions do not change, however i need to be able to dinamically switch permissions between users and i can't find another alternative other than keeping a reference of the permissions.

Grolubao
23 Feb 2011, 2:12 AM
drian, one question, that in this case also applies to my case: Isn't this a security hole, in a sense that one could hack the global javascript array and mess with the permissions?

drian
23 Feb 2011, 9:36 AM
this is true Grolubao, however, javascript is a client-side technology and the js code can be accessed and read by any person. So, if you think it this way, anyone can have access to your code.

Solution1:
Rendering the components server-side. This would mean loading an application page from the server and letting the server render the component IF the user has access to it.

This would mean having php code inside js files, which i personally don't like.

Solution2:
Use a permission plugin.

Case1
Load the global userPermission 1 time via an ajax call.

Case2
Inside applyPermissions plugin, you make an ajax request to the server and you check if the component's "permissionId" belongs to the registred user. - this means that everytime you render a component that has he permission plugin, it will fire an ajax request - might not be that good to send that many ajax calls.

However, if someone breaks your code and removes the plugin? In this case, i think a better solution would be to override the component class so he needs to look deeper in your code.

Solution3: (Client-side + server-side validation) - safest & prolly the best way
Have both a client-side validation and a server-side validation as well. Besides implemention a permission plugin(and using the global userPermissions), every time you make an ajax request to the server, check if the logged user has the permission to do that action.


Let's face it, as long as someone has access to the code, which is the problem with any client-side technology, and as long as the decision the show/hide a component is done client-side, there's always going to be a way to crack it.

Grolubao
23 Feb 2011, 12:24 PM
Exactly!
I thought of exactly your solutions, but none seemed good enough. Probably by insuring the client side is hidden plus wrapping the server side service layer with security annotations seems to be the best oen.

The way we were doing it before was using Spring Security (Acegi) tags directly in the js files not even creating the variables if one didn't had the permission, but obviously created a tie between the js and jsps.

Now that you put it that way, having this validation in the client side plus the one in the server side makes me feel more confortable.

Thanks for your answer, it's nice to discuss concepts like this :)