Just ran into an oddity that I didn’t expect (due to a false assumptions) when testing permissions and wanted to send it out to the world.
I have an app with two types of roles; :admin or :user. I am using Rolify in combination with Pundit to perform the role based authorization of REST actions.
I mixin a module containing general purpose authorization classes. I do it this way because permissions schemes tend to change alot and centralization makes it easier to change. You could easily just add these to your pundit policy classes.
[ruby]
def is_admin?
user.has_role?(:admin)
end
def is_user?
user.has_role?(:user)
end
def is_allowed?
user.has_role?(:user, record)
end
[/ruby]
Pretty straight-forward. An administrator has the role of :admin, a user has a role of :user and a :user is only allowed to access records that they are explicitly granted permission on.
My error was the assumption that user.has_role?(:user) would return true if a user had a role of :user explicitly set on any object in the system. In a pundit action it would look something like this.
[ruby]
def index?
is_admin? || is_user?
end
[/ruby]
The problem is that this was returning false unless someone had a :user role set in general, not on a specific instance of a record.
I think code clears this up further, here is the error.
[ruby]
#Assign the user role of :user on a specific object instance
User.first.add_role(:user, SomeObject.first)
User.first.has_role?(:user) #= false
#Assign the user a role of :user
User.first.add_role(:user)
User.first.has_role?(:user) #= true
[/ruby]
So i had to alter this to use see if the User had the role of :user on any object in the system.
[ruby]
#Old definition, broken
def is_user?
user.has_role?(:user)
end
#New definition, fixed!
def is_user?
SomeObject.find_roles(:user, user).any?
end
[/ruby]
The new is_user? returns true when the user has a role of :user on any SomeObject in the system. Which is exactly what we need.
Lesson learned, I assumed that the method worked a certain way and lost several hours of debugging in my unit tests. I normally will go straight to IRB when i run into anomalies but because i strongly help my assumption I didn’t.