Technosorcery

Ramblings of Jacob Helwig.

Unhappy With the Standard Rails Authorization Plugins

| Comments

Recently, I’ve decided to start learning Ruby on Rails (2.3.4). Things have been going along more-or-less smoothly (I’m still not sure whether or not I hate ActiveRecord, or can tolerate it, but that’s a post for another time.). That is, until I started looking into the various plugins/frameworks for doing Authorization in Rails.

After searching around for a bit, the two main contenders I found were rails-authorization-plugin, and acl9. acl9 seemed to be, by far, the more commonly recommended of the two.

I wasn’t really impressed with the Apache style allow/deny used by acl9. Just wasn’t what I was looking for with my project, so I started looking at rails-authorization-plugin. This is where I ran into trouble.

rails-authorization-plugin allows you to assign roles for objects, and have an optional scope. For example:

Assign permissions
1
2
3
4
5
6
# Assign user the "global" role 'administrator'
user.has_role 'administrator'
# Assign user the role "moderator" for the class Group
user.has_role 'moderator', Group
# Assigns user the role "member" for the instance (of class Group)
user.has_role 'member', club

So far, so good.

Now, let’s check what roles this user has:

Check the permissions
1
2
3
4
5
6
7
8
9
10
11
user.has_role? 'administrator'        # => true
user.has_role? 'administrator', Group # => false
user.has_role? 'administrator', club  # => false

user.has_role? 'moderator'        # => true
user.has_role? 'moderator', Group # => true
user.has_role? 'moderator', club  # => false

user.has_role? 'member'        # => true
user.has_role? 'member', Group # => true
user.has_role? 'member', club  # => true

Wait? What? That’s right: Inheritance of roles flows from instance, to class, to global. If you have a role for any instance, you also have it globally. This is completely backwards from what I would have expected, and from what I wanted.

Ok, time to go back to looking at acl9. Guess what? It does the exact same thing. Given these findings, I did what any developer, with access to Git would do. I forked the code to make it do what I want. Thus my fork of rails-authorization-plugin (and it’s tests) was born. I decided to base my changes off of rails-authroization plugin, simply because I didn’t like the Apache style allow/deny syntax of acl9.

Now, given the initial role assignments above, we get the following:

Check the permissions again
1
2
3
4
5
6
7
8
9
10
11
user.has_role? 'administrator'        # => true
user.has_role? 'administrator', Group # => true
user.has_role? 'administrator', club  # => true

user.has_role? 'moderator'        # => false
user.has_role? 'moderator', Group # => true
user.has_role? 'moderator', club  # => true

user.has_role? 'member'        # => false
user.has_role? 'member', Group # => false
user.has_role? 'member', club  # => true

Inheritance is handled as follows: Global -> Class -> Instance. If a role is assigned at a “higher” level (further left), then it applies at all levels “lower” than it (further right).

While I was in there monkeying around, I decided that I didn’t like strings as role names. They seemed a little more special to me than just plain old strings.

Now we can use symbols as role names:

Symbol role names
1
2
user.has_role :administrator
user.has_role? :administrator, Group

Things still didn’t quite seem right though. The whole role to scope mapping seemed like it deserved to be emphasized a little more, and assigning multiple roles at once was a little clunky.

Now we can use hashes for assignment, and lookup:

Multiple role assignment and checking
1
2
3
4
5
6
user.has_role :administrator => nil,
  :moderator => Group,
  :member    => club

user.has_role? :administrator => Group, :moderator => club # => true
user.has_role? :administrator => nil, :moderator => nil    # => false

This actually demonstrates two things. First using a scope of nil, is the same as saying that the scope is global. Secondly, when doing has_role? with a hash, all role requirements must be met for has_role? to be true.

Have an idea for a post you'd like to see? Spot something that needs correcting? Feel free to contact me, or open an issue on the GitHub repository.

Comments