Ruby on Rails
SimpleAccessControlExample (Version #3)

This is an example showing simple access control in the style of the Pagination module.

The example shows it used in conjunction with the SaltedHashLoginGenerator.

A method added to user.rb returns an array of roles (permissions) for a user – the code shown simply assumes that the ‘role’ attribute contains a comma or space separated list of roles – roles could of course come from another database table.

user.rb


# return array of roles
def roles
if self.role
self.role.gsub(‘,’,’ ‘).split(’ ’)
else
[]
end
end

The rest of the code is added to user_system.rb. As standard this contains a module ‘\UserSystem’ with instance methods that are mixed in to a controller (typically a base controller inherited by application controllers).

Usage

my_controller.rb


class \MyController < \ApplicationController
# optional – redirect unless user logged in before any actions in this controller are processed
before_filter :login_required

# redirect unless user.roles includes at least one of list given
permission_required :admin, :editor, :viewer

def list
if user_permitted?(:admin, :editor)
# add edit link
end
if user_permitted?(:admin)
# add delete link
end
end

def edit
permission_required :admin, :editor
end

def delete
permission_required :admin
end

end

Code

Methods executed in the body of the controller outside a method definition, such as before_filter and permission_required (above) are controller Class methods. These can be added to a class when a module is included using the class#extend method. This is useful when you want the module to add both class and instance methods to a class.

user_system.rb


module \UserSystem

def self.included(base) #:nodoc: super base.extend(\ClassMethods) end module \ClassMethods # controller class method to add required permissions for all actions in module def permission_required(*permissions) PERMISSIONS[self] ||= Hash.new PERMISSIONS[self]‘all_actions’ = permissions before_filter :permission_required end end …

The self.included module method executes when the module is included in your controller (base) class, and adds all the methods in the nested module ‘\ClassMethods’ as controller class methods.

We now have a controller class method ‘permission_required’ which can be called from the top of the controller definition. We have two problems now – we need a method called when the controller processes an action (our class method will be called when the controller is created) and we need to make the roles/permissions available to that method.

The first problem is solved by using before_filter to call an instance method ‘permission_required’, and the second is solved by storing the class method permissions in a constant PERMISSIONS reference to a hash within the module namespace.

This means we need a little additional code to initialize the PERMISSIONS hash…

user_system.rb


module \UserSystem

unless const_defined?(:PERMISSIONS) # hash to hold permissions for controllers PERMISSIONS = Hash.new end …

end

Finally we need a controller instance method ‘permission_required’ which will be called as part of the before_filter filter chain. By testing for arguments, we can also use the same method within actions. A final ‘user_permitted?’ method encapsulates the comparison of controller and user permissions, and can be used to vary the output of an action depending on user permissions, as shown in the Usage above.

user_system.rb


module \UserSystem

# returns true if user has any of given permissions
def user_permitted?(permissions)
return false unless @session“user”
roles = @session“user”.roles
permissions.detect {|p| roles.include?(p.to_s)}
end

protected

# method/filter to check permissions for all or single action
def permission_required(
permissions)
# if no permissions passed use controller level permissions (filter)
if permissions.size > 0
required = permissions
elsif UserSystem::PERMISSIONS[self.class]
required = UserSystem::PERMISSIONS[self.class]‘all_actions’
else
required = []
end
return true if user_permitted?(required)
# redirect to desired location when user does not have permission
redirect_to :controller => “/user”, :action => “login”
false
end

end


Unit Testing gotcha

Be aware that if you are unit testing a controller and a runtime error occurs in a class method, the result is not what you would expect. Your action code is not executed, but the controller still executes using the action.rhtml template and may return a result. Typically it will fail with an unexpected error in the template evaluation because instance variables are missing.

category:Example

This is an example showing simple access control in the style of the Pagination module.

The example shows it used in conjunction with the SaltedHashLoginGenerator.

A method added to user.rb returns an array of roles (permissions) for a user – the code shown simply assumes that the ‘role’ attribute contains a comma or space separated list of roles – roles could of course come from another database table.

user.rb


# return array of roles
def roles
if self.role
self.role.gsub(‘,’,’ ‘).split(’ ’)
else
[]
end
end

The rest of the code is added to user_system.rb. As standard this contains a module ‘\UserSystem’ with instance methods that are mixed in to a controller (typically a base controller inherited by application controllers).

Usage

my_controller.rb


class \MyController < \ApplicationController
# optional – redirect unless user logged in before any actions in this controller are processed
before_filter :login_required

# redirect unless user.roles includes at least one of list given
permission_required :admin, :editor, :viewer

def list
if user_permitted?(:admin, :editor)
# add edit link
end
if user_permitted?(:admin)
# add delete link
end
end

def edit
permission_required :admin, :editor
end

def delete
permission_required :admin
end

end

Code

Methods executed in the body of the controller outside a method definition, such as before_filter and permission_required (above) are controller Class methods. These can be added to a class when a module is included using the class#extend method. This is useful when you want the module to add both class and instance methods to a class.

user_system.rb


module \UserSystem

def self.included(base) #:nodoc: super base.extend(\ClassMethods) end module \ClassMethods # controller class method to add required permissions for all actions in module def permission_required(*permissions) PERMISSIONS[self] ||= Hash.new PERMISSIONS[self]‘all_actions’ = permissions before_filter :permission_required end end …

The self.included module method executes when the module is included in your controller (base) class, and adds all the methods in the nested module ‘\ClassMethods’ as controller class methods.

We now have a controller class method ‘permission_required’ which can be called from the top of the controller definition. We have two problems now – we need a method called when the controller processes an action (our class method will be called when the controller is created) and we need to make the roles/permissions available to that method.

The first problem is solved by using before_filter to call an instance method ‘permission_required’, and the second is solved by storing the class method permissions in a constant PERMISSIONS reference to a hash within the module namespace.

This means we need a little additional code to initialize the PERMISSIONS hash…

user_system.rb


module \UserSystem

unless const_defined?(:PERMISSIONS) # hash to hold permissions for controllers PERMISSIONS = Hash.new end …

end

Finally we need a controller instance method ‘permission_required’ which will be called as part of the before_filter filter chain. By testing for arguments, we can also use the same method within actions. A final ‘user_permitted?’ method encapsulates the comparison of controller and user permissions, and can be used to vary the output of an action depending on user permissions, as shown in the Usage above.

user_system.rb


module \UserSystem

# returns true if user has any of given permissions
def user_permitted?(permissions)
return false unless @session“user”
roles = @session“user”.roles
permissions.detect {|p| roles.include?(p.to_s)}
end

protected

# method/filter to check permissions for all or single action
def permission_required(
permissions)
# if no permissions passed use controller level permissions (filter)
if permissions.size > 0
required = permissions
elsif UserSystem::PERMISSIONS[self.class]
required = UserSystem::PERMISSIONS[self.class]‘all_actions’
else
required = []
end
return true if user_permitted?(required)
# redirect to desired location when user does not have permission
redirect_to :controller => “/user”, :action => “login”
false
end

end


Unit Testing gotcha

Be aware that if you are unit testing a controller and a runtime error occurs in a class method, the result is not what you would expect. Your action code is not executed, but the controller still executes using the action.rhtml template and may return a result. Typically it will fail with an unexpected error in the template evaluation because instance variables are missing.

category:Example