Wednesday, July 11, 2007

Dynamic roles management in acegi security: Part 1

Acegi security is a great powerful and flexible security solution. What I don't like about it is the lack of documentation and samples provided, and this is why I thought of writing about this subject today.

The example that comes with acegi security, and the one that comes with appfuse define secure URL patterns, and the roles that can access them in the XML configuration file, which is not a very flexible solution. It is a good approach if you have predefined roles and access rights , that are never going to change (or the changes are very rare). In case you have a security module, where you define which role can access which URL patterns, this method is the worst thing you could do.

The solution to this problem is to define your own data source, which loads the secure URL patterns and the corresponding roles that can access it from somewhere (database for example). A simple way to do that is by extending PathBasedFilterInvocationDefinitionMap and loading the secure URL patterns and the roles that can access them in an initialization method or in the constructor. Here's an example:

public class UrlPatternRolesDefinitionSource extends PathBasedFilterInvocationDefinitionMap {

public void init(){
Session session = sessionFactory.openSession();
try{

List urlPatternsList = urlPatternDao.listUrlPatterns(session);

for (UrlPattern pattern : urlPatternsList) {

ConfigAttributeDefinition configDefinition = new ConfigAttributeDefinition();

for(Role role: pattern.getRoles()){

ConfigAttribute config = new SecurityConfig(role.getAuthority());

configDefinition.addConfigAttribute(config);
}

addSecureUrl(pattern.getUrlPattern(), configDefinition);

}
}
catch (FindException e) {
// Handle exception
}
finally {
session.close();
}
}
}


In this example I used a Hibernate DAO I created (urlPatternDao) to retrieve the secure URL patterns which is defined and initialized with the sessionFactory somewhere else in the code (using spring's dependency injection). You can implement it anyway you like (JDBC, with any other ORM framework, or any other method you like).

In this example, each URL pattern can has a set roles that can access it, so I loop over this set to create the ConfigAttributeDefinition. I prefer if you have your own implementation of the ConfigAttributeDefinition, but this is going to be discussed in part 2 of this article. Of course the Role class used in this code implements the GrantedAuthority interface.

As for the XML configuration of this entry, it's shown below:
<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web. FilterSecurityInterceptor">

<property name="authenticationManager" ref="authenticationManager"/>

<property name="accessDecisionManager" ref="accessDecisionManager"/>

<property name="objectDefinitionSource" ref="objectDefinitionSource"/>

</bean>


<bean id="objectDefinitionSource" class= "net.jnassef.security.UrlPatternRolesDefinitionSource" init-method="init">

<property name="convertUrlToLowercaseBeforeComparison">
<value type="boolean">false</value>
</property>

<property name="urlPatternDao" ref="urlPatternDao"/>
<property name="sessionFactory" ref="sessionFactory"/>
</bean>


As for the first bean in the previous snippet, this definition is the default that comes with the sample acegi application and with appfuse, except to the reference to the objectDefinitionSource bean which was added by us, to allow dynamic role management. The second bean maps to the class we created, and defines the initialization method, that will load the secured URL patterns, and the roles associated with them. This definition also sets the DAO and the hibernate session factory used by the class. Of course you don't need those if you want to have any other implementation.

The previous implementation seems good as a solution for dynamic role management, but if we look more closely, it's not a very good solution (or to be more accurate, it's not a complete solution). The problem that is going to face us here is that secure URL patterns are loaded only ONCE at the application start up, and any change done to the role access rights will not effective until the application is started, and this is not dynamic at all. In fact, there is no difference between this solution, and having the access rights in the XML configuration file, except that you will not need to redeploy your application, but you will need to restart it. There are several solutions to this problem, and this will be the main focus of the next part of this article.

8 comments:

Anonymous said...

Can you please explain briefly how to update the role lsit in the application after you update the database? I am new to Acegi and trying to figure out how to use for our project. Thx a lot for your help.

Alaa Nassef said...

I again repeat my apology for delaying part two which was supposed to speak about this part. I'm currently working on it, and it should be posted soon. I'll give you a brief description here, and will elaborate in the second part of the article.

All you have to do is is to get the ConfigAttributeDefinition from the definition source instance that is used in your application by ysing the lookupAttributes method. You can then remove the old patterns that were unassigned from the role, and add the new ones that were assigned to the role. Since the implementation of ConfigAttributeDefinition supplied by acegi uses a vector for the configAttributes, it does not have a remove method. This is why I suggest that you override this implementation with your own that uses a map instead of a list, so that you can have a remove method.

Anonymous said...

I'm new to Acegi too. I have users. They have permissions depends on group. Users can choose group in my project and then do something in it (e.g. permission=A) Later they can choose another group and then they (e.g. permission=B, but no A) haven't such a permission like in previous group. This groups and permissions I have in another table ("perm", I have user_role too. (because I don't know how to change it)
Thanks for your help

Alaa Nassef said...

Well, I'm sorry majaa, but I don't understand your question. Can you make it more clear please? Thanks

Anonymous said...

Three tables (user, user_role and permission (id_user, id_group, perm). When user log in to application, acegi read all from user_role (user_id, role_id) and ok . But I would like to change it. User can access to many group after login. There is one page with groups and then they have others permissions. In one group they have only readonly access, and in other they can e.g delete something. How can I do it?
Sorry for my english. I hope it is better now als earlier;)

Anonymous said...

How to modify acegi permission for my own (I want to change permissions after user choose one group, when he changes group, I want change this permissions.

Alaa Nassef said...

Hello majaa. Did you read part two of the article? If not, please read it. If it does'nt help, please let me know, and I'll post a reply to you ASAP. I have to leave now. I'll be replying to you soon in case you still have problems after reading part two of the article.

Anonymous said...

Your article is very usefull! Sorry, I couldn't find earlier. I thought there was only first part. But! I have another situation.
tables :user, group (id_group, name_group) and role (id_role, name) and finally table perm(with colummns id, user_id, role_id, group_id)
After user logged in, he sees groups. After he chooses one group. I have to modify him permissions. He can back to the groups and choose other and then i want to update his permissions.
I can choose user's urls, but I want to change roles! not urls. So e.g. An userX has permissionA in group1 but in group2 he has permissionB, not permissionA etc. So for url /components.html* = permissionA
and i want to have it always. But I want to change permissions for theese users, when they change the group.
or second solution is:
url A: ROLE_permissionA_group1, ROLE_permissionA_group2 (for each group) and later when user changes one group, take only this permission with selected group. Can I access to httpRequest (to know which group is selected)
Or I think in wrong direction? I hope, you understand what i mean
thx for your help