¶ ↑
JustRightsA simple way to manage an arbitrary set of permissions on a ActiveRecord or other resources.
¶ ↑
ExampleCapabilities are arbitrary (read, comment, close) and will be saved as a bitmask in a magic column called rights
in the database.
class Post < ActiveRecord::Base permissions :read, :comment, :close end
¶ ↑
MethodsThis will add the following methods on your class:
p = Post.new p.set_permissions = :read, :comment
You can compare permissions:
p.permission.capabilities # => [:read, :comment] p.permission.can? :read # => true p.permission.can? :close # => false
You can see if a capability exists:
p.has_capability? :close # => true p.has_capability? :foobar # => false
You can transfer permissions:
p2 = Post.new p2.permission = p.permission
¶ ↑
Admin Rights (Sticky Bit)Sometimes you want admins to be able to do everything, you can create or alias a method on Post called sticky
which will override all permissions:
class Post def sticky creator.admin? end end
Now no matter what the permissions are set in the database they can do anything:
p.sticky # => true p.set_permissions = :read, :comment p.permission.capabilities = [:read, :comment, :close] p.permission.can? :read # => true p.permission.can? :comment # => true p.permission.can? :close # => true
¶ ↑
Helpers Methods for Nested AttributesSaving permissions from views, there are additional method helpers to aid in the creation of permissions. These are array indices or attributes:
p.permission[:read] # => false p.permission[:read] = true # => true p.permission[:read] # => true p.permission.read # => true
There is a simulation of the nested attributes:
p.permission_attributes = { :read => '0', :close => '1' } p.permission[:read] # => false p.permission[:close] # => true
So in the view you can do the following and it will update as expected:
<%= f.fields_for :permission do |p| %> <%= p.check_box :read %> <%= p.label :read %> <%= p.check_box :comment %> <%= p.label :comment %> <%= p.check_box :close %> <%= p.label :close %> <%- end %>
¶ ↑
Multiple ResourcesYou may also specify different resources that may have different permissions or capabilities. You don’t have to create a model for each one, you may collect them on a single model which may makes sense:
class Project permissions :attach, :modify, :delete, :on => :files permissions :create, :order, :complete, :on => :todos end
Here, these will be stored as todo_rights
and file_rights
in the database.
p = Project.new p.set_file_permissions :attach, :modify p.set_todo_permissions :create p.file_permission.capabilities # => [:attach, :modify] p.file_permission.can? :attach # => true p.file_permission.can? :finish # => false p.todo_permission.capabilities # => [:create] p.todo_permission.can? :append # => true p.todo_permission.can? :finish # => false p.todo_permission.can? :attach # => false
As you can see, the capabilities are separated, and do not crossover. The exception is if sticky is set on the project, it will be set for all underlying permissions.
p.sticky # => true p.file_permission.capabilities # => [:attach, :modify, :delete] p.todo_permission.capabilities # => [:create, :order, :complete]
Behind the scenes, JustRights creates new classes within the resource that they are defined:
Post::Permission Project::FilePermission Project::TodoPermission
In this way, all permissions classes should be name-spaced and not cause constant collisions unless you have a previously defined class of the same name. Currently there is no option to define the name of the column the bitmask is saved, you may alias_method or contribute :D
¶ ↑
Permission SystemIf you include the PermssionSystem in your controllers you will gain View Helpers, prebuilt before_filters, and rights management variables.
class ApplicationHelper include PermissionSystem end
Once you have done this you can setup a filter or another mechanism to setup the current user’s rights. For one right just do:
class ApplicationControlller before_filter do |controller| self.rights = current_user.permission end end
Or in the example above with multiple resources:
class ProjectController before_filter do |controller| project = Project.find params[:id] grant_rights_for :files => project.file_permission, :todos => project.todo_permissions end end
Which ever works for your application. Once you have rights set from your controller or view you can:
<%- if rights.can?(:read) %> Priviledged info here <%- end %>
Or again, with multiple resources:
<%- if rights_for(:files).can? :modify %> render :partial => 'modify_files', :collection => @project.files <%- end %>
¶ ↑
Verify Access FilterTo ensure that the correct rights are secured before executing an action there are before_filters that you can setup with verify_access
:
class SomeController verify_access :can? => :read, :deny => 'You do not have permission to read this file', :only => :show end
This will throw a PermissionExtension::ForbiddenAccess exception with the deny messaging. The exception is setup so that it will generate a 403 HTTP error, which can be targetted by placing a file in public/403.html or using rescue_for
. And you can pass any of the options you normally pass to filters.
You can also use multiple resource form (and if you add multiples it will match on any of them):
class ProjectController verify_access :can? => {:project => :modify}, :deny => 'You do not have permission to read this file' end
¶ ↑
View HelpersThere are also two helpers that can aid in creating rights-based links and submit buttons. These are added for you when you include PermissionSystem:
<%= link_by_rights 'Read', read_path, :can? => :read, :deny => 'You do not have permission to read this resource' %> <%= link_by_rights 'Attach', attach_file_path, :can? => {:file => :attach}, :deny => 'You do not have permission to attach files' %>
This will generate a link to the read path if the User can read, or a link that will show an alert with the :deny message if they cannot.
Another is the submit button submit_by_rights
which take the same options as link_by_rights
.
¶ ↑
Notes¶ ↑
OrderingOrdering of the capability is significant, always append if you can. If you change the order of the rights, their values do not move with them. In this case, you may need to write migration code or take care of how you order your capabilities.
¶ ↑
Memory or Data CapacityEach capability is exponential (adds a power of 2), so make sure that your columns are large enough to handle all the different capabilities.
Copyright © 2009 Reid MacDonald <reid@laruby.com>, released under the MIT license