due to some recent dicussions about authorization I feel obliged to show how to guard your controller actions with a simple Hash per controller and a single before fitler. the whole will be orthogonal to the application, i.e. no coding required beside setting up this Hash.
the only assumption is that the permissions are organized with user/group model or user/role model, similar what you find on linux/unix systems or with PosixAccounts/PosixGroups on LDAP systems.
in case you need a full fledged authroization framework with DSL and protecting the models instead of the controllers probably
github.com/brayn/cancan
setup the permissions per controller
let's have a scaffolded
account resource (
rails g scaffold accounts name:string) and for each action for the acounts controller you configure a list of groups which are allowed to perform that action.
PERMISSIONS = {
:accounts => {
:index => [:root, :supervisor, :accountant],
:show =>[:root, :supervisor, :accountant],
:new =>[:root, :accountant],
:create =>[:root, :accountant],
:update =>[:root, :accountant],
:edit =>[:root, :accountant],
:destroy =>[:root]
}
}
to check the permissions you need some method like this
def allowed?(action = param[:action], controller = params[:controller])
perm = PERMISSIONS[controller.to_sym] || {}
allowed = perm[action.to_sym]
if allowed
found = allowed & (current_user_groups || [])
found.size > 0
end
end
now the guard is almost in done. just add a before filter and a method which returns the an array of groups (here these are group-names) of the current-user.
before_fitler :guard
def guard
if allowed?
raise "permission denied"
else
raise "no permission for #{params[:action]}"
end
end
def current_user_groups
[:root] if current_user # change this to something real !!
end
the
allowed? method from above can be put into the
application_helper.rb to have it available in the view templates.
def allowed?(action, controller = params[:controller])
controller.allowed?(action, controller)
end
the controller defaults to the current controller but the action is compulsory for the view.
add some default permissions
with this
allowed? method
def allowed?(action = param[:action], controller = params[:controller])
perm = PERMISSIONS[controller.to_sym] || {}
allowed = perm[action.to_sym] || perm[:defaults]
if allowed
allowed & (current_user_groups || [])
end
end
the config is reduced to
PERMISSIONS = {
:accounts => {
:defaults =>[:root, :accountant],
:index => [:root, :supervisor, :accountant],
:show =>[:root, :supervisor, :accountant],
:destroy =>[:root]
}
}
add a superuser
the superuser group is
:root so we can change the
allowed? method to
def allowed?(action = param[:action], controller = params[:controller])
perm = PERMISSIONS[controller.to_sym] || {}
allowed = perm[action.to_sym] || perm[:defaults]
if allowed
found = (allowed | [:root]) & (current_user_groups || [])
found.size > 0
end
end
this reduces the
PERMISSIONS setup to this
PERMISSIONS = {
:accounts_with_defaults_and_root => {
:defaults =>[:accountant],
:index => [:supervisor, :accountant],
:show =>[:supervisor, :accountant]
}
}
this is not perfect but for most cases sufficiently compact and readable.
groups
looking at the code then the groups do pop up at two places
- the
PERMISSIONS
- the
current_user_groups method
with such a setup you can make the users and groups totally orthogonal to the application. for example they can be stored as PosixAccounts/PosixGroups in an LDAP system. the allowed? method uses only the controller and action name as parameter. the
current_user_groups method can be generic depending on your user/group model and the
PERMISSIONS could be a database table as well, allowing to configure permissions on the fly without changing the code !
one advanced example
now we assume a group can be associated with an organization (which is part of domain model).
first we change the
allowed? method to check the found allowed groups for agiven controller-action-pair if any if it is allowed to access the
current organization. this check is done by a given block.
def allowed?(action = param[:action], controller = params[:controller], &block)
perm = PERMISSIONS[controller.to_sym] || {}
allowed = perm[action.to_sym] || perm[:defaults]
if allowed
found = (allowed | [:root]) & (current_user_groups || [])
if block
permitted = found.detect do |group|
block.call(group)
end
permitted.size > 0
else
found.size > 0
end
end
end
now we add some extra methods to the
application_controller.rb
def guard_organization
guard do
GroupsOrganziationsUser.first(:conditions => ["group_name = ? or org_id = ? and user_id = ?", group, current_organization.id, current_user.id]).size > 0
end
end
def current_organization
@org ||= Organization.find(params[:org_id])
end
to change the
account_controller.rb to use the restricted group the before filters need to reflect this
skip_before_filter :guard
before_filter :guard_organization
more
there is also a gem available which wraps this approach and adds some yaml configuration for the permissions:
ixtlan-guard