Grails, Acegi and the authority ROLE_ prefix

After spending way too much time figuring this one out, I am not sure if it’s an RTFM issue or if the Grails part of the documentation is incomplete/unclear about it.

The story goes like this: I was trying to set up a simple Grails application that should include user authentication via LDAP against a number of roles stored in the application database. Fortunately, there is a convenient plugin available that makes Acegi Security available for Grails.

I had the business objects and logic pretty much done before I started with the authentication part – this is a small internal project, so it’s rather simple stuff – and as this was a quick rewrite of a legacy application, the users and roles already existed in the database. So I followed the appropriate tutorial in the beginning and everything worked fine. The users and roles were mapped correctly and the LDAP connection worked out of the box as I was able to log in with an existing user (I haven’t been able to get the authentication through bind working yet, but that’s a separate issue).

I had defined two roles, i.e., DEVELOPER and MANAGER:

Roles in the database

The configuration was straight forward as well, here is just the part of the SecurityConfig.groovy that affects the user and role mappings:

1
2
3
4
5
6
7
8
active = true
loginUserDomainClass = "User"
authorityDomainClass = "Role"
//... some more stuff
useLdap = true
useControllerAnnotations = true
ldapRetrieveGroupRoles = false
ldapRetrieveDatabaseRoles = true

For testing purposes, one of the business object controllers was annotated to only accept the MANAGER role.

1
2
3
4
5
6
7
8
import org.codehaus.groovy.grails.plugins.springsecurity.Secured
 
@Secured(['MANAGER']) // THIS DOES NOT WORK
class MyController {
   def index = {
      // do something useful
   }
}

But even though the user authentication worked fine, I was not able to access the secured controller after login but would get the “Sorry, you’re not authorized to view this page” error message, even though the log showed that the user was granted the correct authorities: Granted Authorities: MANAGER.

I obviously noticed that in the examples all roles had the format ROLE_XYZ and it was mentioned that the roles retrieved through LDAP would be converted into that format automatically, but it didn’t seem to be a requirement. However, after fixing the setup accordingly, everything worked great:

Fixed roles in database

1
2
3
4
5
6
7
8
import org.codehaus.groovy.grails.plugins.springsecurity.Secured
 
@Secured(['ROLE_MANAGER']) // the ROLE_ prefix is a must
class MyController {
   def index = {
      // do something useful
   }
}

Behind the scenes

The requirement for the prefix originates from the default implementation of the AccessDecisionVoter which is RoleVoter inside the Spring Security code.

That implementation will compare the ConfigAttributes in the session against the granted authorities. However, there might be attributes that are not roles and should not be considered for comparison – as that might introduce security vulnerabilities – so the voter is only taking the attributes into consideration that start with a certain prefix. The corresponding snippet from RoleVoter.java makes that pretty obvious:

56
private String rolePrefix = "ROLE_";
75
76
77
if ((attribute.getAttribute() != null) && attribute.getAttribute().startsWith(getRolePrefix())) {
    return true;
}

It is of course possible to provide a different implementation for AccessDecisionVoter. That class would not have to rely on a prefix, could use a completely different comparison or you could simply pass a different prefix, e.g., an empty String, to the RoleVoter, but I guess unless you have special requirements or cannot prefix your authority names for some odd reason, that shouldn’t be necessary. Looking at that implementation a couple of lines further down also makes it apparent that the authority names comparisons are case sensitive, another little gotcha to look out for. Some more details about the internals of how Acegi Security handles the authorization is covered in this JavaWorld article.

I guess I would have been looking in the right direction earlier, had I created the roles through the generated interfaces instead of using a prepopulated database, as the forms generated by the Grails plugin don’t accept role names that do not contain "ROLE" in the authority name. Here is a snippet of the UserController template in the Acegi Security plugin 0.5.2:

137
138
139
140
141
142
143
 private void addRoles(person) {
    for (String key in params.keySet()) {
        if (key.contains('ROLE') && 'on' == params.get(key)) {
            ${authorityClassName}.findByAuthority(key).addToPeople(person)
        }
    }
}

Even though this code was probably added with good intentions, it has several major flaws:

  1. Any authority name not matching the requirement is silently dropped without any feedback
  2. The constraint is not obvious, documented or explained in the UI
  3. The check is semantically wrong, as the role name "PAROLEE" would be accepted as well – which would give developers working for the judicial system some headaches, I guess. Here, key.startsWith('ROLE_') would be an improvement

Running into this issue would have been at least an indicator that the name can not be chosen arbitrarily. The documentation of the Spring Security classes involved is pretty straight forward, but the tutorials and manuals of the Grails plugin could be a little bit more obvious in that respect. I guess this is just another case of leaky abstractions.

0 Responses to “Grails, Acegi and the authority ROLE_ prefix”


Comments are currently closed.