Sunday, March 29, 2009

ActiveRecord Security: A powerful and flexible security component for activerecord

ARSecurity is a security component for Activerecord, it can manage CRUD permissions with attribute level by configuration, you can implement RBAC easily with it. It depends on the AOP framework Rinterceptor and the OO query tool Rquerypad(Optional)


Install (current version is 0.1.1)


$ sudo gem install arsecurity

Usage


initialize a rails app


$ rails testapp && cd testapp

prepare model and database

note: ARSecurity require your permission object has four methods 'target_class_name', 'operation', 'instance_condition' and 'sql_condition'.


$ ruby script/generate scaffold user username:string
$ ruby script/generate scaffold permission target_class_name:string operation:string instance_condition:string sql_condition:string
$ ruby script/generate scaffold production name:string
$ rake db:migrate

add permissions accessor of User for demo purpose, in you real application, you should persist permissions into database, maybe get permissions through roles.


#ruby code
class User < ActiveRecord::Base
attr_accessor :permissions
end

start a console of rails and add some test data


$ ./script/console
>> p = Production.new; p.name = 'Fruit'; p.save
>> p = Production.new; p.name = 'Book'; p.save
>> u = User.new; u.username = 'admin'; u.save
>> u = User.new; u.username = 'leonli'; u.save
>>exit

add a file 'arsecurity_config.rb' with following content into your RAILS_ROOT/config/initializers/




#ruby code
require 'arsecurity_default'
ActiveRecord::Base.class_eval do
include ArsecurityDefault
def self.rinter_skip?
Thread.current[:arsecurity_check] == false
end
def rinter_skip?
Thread.current[:arsecurity_check] == false
end
end
def free_access(&block)
old_security_check = Thread.current[:arsecurity_check]
Thread.current[:arsecurity_check] = false
begin
result = yield
ensure
Thread.current[:arsecurity_check] = old_security_check
end
result
end
class YourSecurityHandler < DefaultArsecurityHandler
class << self
#override
def permissions
current_user && current_user.permissions
end
private
def current_user
Thread.current[:user]
end
end
end
ArsecurityUtil.handler = YourSecurityHandler


start rails console again


$ ./script/console
>> Production.all
=> ArsecurityNotAuthorizedException
>> free_access {Production.all}
=> [#<Prodcution ...name: "Fruit", ...>, #<Production ...name: "Book", ...>]
>> #set current user with Production read permission for demo data access by user
>> u = free_access {User.find_by_username('leonli')}
>> Thread.current[:user] = u
>> p = Permission.new; p.target_class_name = 'Production'; p.operation = Arsecurity::READ; u.permissions = [p]
>> Production.all
=> [#Prodcution ...name: "Fruit"..., #Production ...name: "Book"...]

The permission's operation could be Arsecurity::READ, CREATE, UPDATE, DELETE, and nil means ALL


>> Production.new.save
=> ArsecurityNotAuthorizedException
>> u.permissions[0].operation = nil
>> Production.new.save
=> true

demo instance_condition (for demo easy, we always use one permission)


>> u.permissions[0].instance_condition = 'name != nil'
>> Production.new.save
=> ArsecurityNotAuthorizedException
>> p = Production.new; p.name = 'Computer'; p.save
=> true

demo sql_condition


>> Production.all
=> [#<Prodcution ...name: "Fruit", ...>, #<Production ...name: "Book", ...>, #<Production ...name: nil, ...>, #<Production ...name: "Computer", ...>]
>> u.permissions[0].sql_condition = 'name is null'
>> Production.all
=> [#<Prodcution ...name: nil, ...>]

You can get customization detail in ARSecurity code


--------------------------------------------------

Plus by Rquerypad(http://rquerypad.rubyforge.org/, (require 'rquerypad' before require arsecurity)

suppose your Production has many Production Item, you could even do the cool condition with any explicit join


>> u.permissions[0].sql_condition = 'name is null and items.name = "somthing"'

No comments:

Post a Comment