Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

We’re showing branches in this repository, but you can also compare across forks.

base fork: tulp/access_schema
base: 137f5f0e4b
...
head fork: tulp/access_schema
compare: 6d6b4fe6a1
  • 5 commits
  • 11 files changed
  • 0 commit comments
  • 1 contributor
1  .gitignore
View
@@ -5,3 +5,4 @@ pkg/*
*.swp
*.swo
.rvmrc
+coverage
1  Gemfile
View
@@ -7,6 +7,7 @@ group :development, :test do
gem "rake"
gem "rspec"
+ gem "simplecov", :require => false
gem "rack-test"
gem "guard"
gem "guard-rspec"
248 README.md
View
@@ -1,7 +1,6 @@
-# AccessSchema gem - ACL/plans for your app
+# AccessSchema gem - ACL and domain policies for your app
-AccessSchema provides decoupled from Rails and ORM agnostic tool
-to define ACL schemas with realy simple DSL.
+AccessSchema is tool to add ACL and domain policy rules to an application. It is framework/ORM agnostic and provides declarative DSL.
Inspired by [ya_acl](https://github.com/kaize/ya_acl)
@@ -9,104 +8,23 @@ Inspired by [ya_acl](https://github.com/kaize/ya_acl)
gem install access_schema
```
-## An example of use
-
-
-### Accessing from application code
-
-In Rails controllers we usualy have a current_user and we can
-add some default options in helpers:
-
-```ruby
- #access_schema_helper.rb
-
- class AccessSchemaHelper
-
- def plan
- AccessSchema.schema(:plans).with_options({
- :plan => Rails.development? && params[:debug_plan] || current_user.try(:plan) || :none
- })
- end
-
- def acl
- AccessSchema.schema(:acl).with_options({
- :role => current_user.try(:role) || :none,
- :user_id => current_user.try(:id)
- })
- end
-
- end
-
-```
-
-So at may be used in controllers:
-
-```ruby
- acl.require! review, :edit
- plan.require! review, :mark_featured
-
-```
-
-Or views:
-
-```ruby
- - if plan.allow? review, :add_photo
- = render :partial => "add_photo"
-```
-
-
-On the ather side there are no any current_user accessible. In a Service Layer for
-example. So we have to pass extra options:
-
-
-```ruby
- #./app/services/review_service.rb
-
- class ReviewService < BaseSevice
-
- def mark_featured(review_id, options)
-
- review = Review.find(review_id)
-
- acl = AccessSchema.schema(:acl).with_options(:roles => options[:actor].roles)
- acl.require! review, :mark_featured
-
- plans = AccessSchema.schema(:plans).with_options(:plans => options[:actor].plans)
- plans.require! review, :mark_featured
-
- review.featured = true
- review.save!
-
- end
-
- def update(review_id, attrs)
-
- review = Review.find(review_id)
-
- acl = AccessSchema.schema(:acl).with_options(:roles => options[:actor].roles)
- acl.require! review, :edit
-
- plans = AccessSchema.schema(:plan).with_options(:plan => options[:actor].plan)
- plans.require! review, :edit, :new_attrs => attrs
-
- review.update_attributes(attrs)
-
- end
-
- end
-
-```
+## An example of use with Rails
### Definition
```ruby
- # config/plans.rb
+ # config/policy.rb
roles do
+
+ # Tariff plans
role :none
role :bulb
role :flower
role :bouquet
+
+ # To allow admin violate tariff plan rules
+ role :admin
end
asserts do
@@ -125,7 +43,8 @@ example. So we have to pass extra options:
privilege :mark_featured, [:flower, :bouquet]
- privilege :add_photo, [:bouquet] do
+ # Admin is able to add over limit
+ privilege :add_photo, [:bouquet, :admin] do
assert :photo_limit, [:none], :limit => 1
assert :photo_limit, [:bulb], :limit => 5
assert :photo_limit, [:flower], :limit => 10
@@ -154,27 +73,30 @@ example. So we have to pass extra options:
end
- resource "Review" do
+ resource "ReviewsController" do
+
+ privilege :index
+ privilege :show
privilege :edit, [:admin] do
assert :owner, [:none]
end
+ privilege :update, [:admin] do
+ assert :owner, [:none]
+ end
+
end
```
-## Configuration
-
-Configured schema can be accessed with AccessSchema.schema(name)
-anywhere in app. Alternatively it can be assempled with ServiceLocator.
-
+### Configuration
```ruby
#config/initializers/access_schema.rb
AccessSchema.configure do
- schema :plans, AccessSchema.build_file('config/plans.rb')
+ schema :policy, AccessSchema.build_file('config/policy.rb')
schema :acl, AccessSchema.build_file('config/acl.rb')
logger Rails.logger
@@ -183,4 +105,132 @@ anywhere in app. Alternatively it can be assempled with ServiceLocator.
```
+### Accessing from Rails application code
+
+Define a helper:
+
+```ruby
+ #access_schema_helper.rb
+
+ class AccessSchemaHelper
+
+ # Use ACL in controllers:
+ #
+ # before_filter { required! :reviews, :delete }
+ #
+ # and views
+ #
+ # - if can? :reviews, :delete, :subject => review
+ # = link_to "Delete", review_path(review)
+ #
+
+ def required!(route_method, action = nil, options = {})
+
+ url_options = send "hash_for_#{route_method}_path"
+ resource = "#{url_options[:controller].to_s.camelize}Controller"
+
+ privilege = action || url_options[:action]
+ acl.require! resource, privilege, options
+
+ end
+
+ def can?(*args)
+ required!(*args)
+ rescue AccessSchema::NotAllowed => e
+ false
+ else
+ true
+ end
+
+ def acl
+
+ AccessSchema.schema(:acl).with_options({
+ roles: current_roles,
+ user_id: current_user.try(:id)
+ })
+
+ end
+
+ # Use in controllers and views
+ # tarifF plans or other domain logic policies
+ #
+ # policy.allow? review, :add_photo
+ #
+
+
+ def policy
+
+ # Policy have to check actor roles and subject owner state (tariff plans for example)
+ # to evaluate permission. So we pass proc and deal with particular subject to
+ # calculate roles.
+ #
+ roles_calculator = proc do |options|
+
+ plan = options[:subject].try(:owner).try(:plan)
+ plan ||= [ current_user.try(:plan) || :none ]
+ current_roles | plan
+
+ end
+
+ AccessSchema.schema(:policy).with_options({
+ roles: roles_calculator,
+ user_id: current_user.try(:id)
+ })
+
+ end
+
+ end
+
+```
+
+But there are no current_user method in a Service Layer! So pass an extra option - actor:
+
+```ruby
+ #./app/services/base_service.rb
+ class BaseService
+
+ def policy(actor)
+
+ roles_calculator = proc do |options|
+
+ plan = options[:subject].try(:owner).try(:plan)
+ plan ||= [ actor.try(:plan) || :none ]
+ current_roles | plan
+
+ end
+
+ AccessSchema.schema(:policy).with_options({
+ roles: roles_calculator,
+ user_id: actor.try(:id)
+ })
+ end
+
+ end
+
+ #./app/services/review_service.rb
+
+ class ReviewService < BaseSevice
+
+ def mark_featured(review_id, actor)
+
+ review = Review.find(review_id)
+ policy(actor).require! review, :mark_featured
+
+ review.featured = true
+ review.save!
+
+ end
+
+ def update(review_id, attrs, actor)
+
+ review = Review.find(review_id)
+ policy(actor).require! review, :edit, :attrs => attrs
+
+ review.update_attributes(attrs)
+
+ end
+
+ end
+
+```
2  lib/access_schema/exceptions.rb
View
@@ -9,7 +9,7 @@ class AccessError < CheckError; end
class NotAllowedError < AccessError; end
- class NoRoleError < CheckError; end
+ class InvalidRolesError < CheckError; end
class NoResourceError < CheckError; end
class NoPrivilegeError < CheckError; end
9 lib/access_schema/proxy.rb
View
@@ -10,10 +10,6 @@ def roles
@schema.roles
end
- def plans
- @schema.plans
- end
-
def allow?(*args)
@schema.allow?(*normalize_args(args))
end
@@ -34,14 +30,13 @@ def normalize_args(args)
roles, options = case args[2]
when Hash, nil
- [@options[:role] || @options[:plan], args[2] || {}]
+ [@options[:roles], args[2] || {}]
else
[args[2], args[3] || {}]
end
options_to_pass = @options.dup
- options_to_pass.delete :plan
- options_to_pass.delete :role
+ options_to_pass.delete :roles
[resource, privilege, roles, options_to_pass.merge(options)]
end
41 lib/access_schema/schema.rb
View
@@ -2,7 +2,6 @@ module AccessSchema
class Schema
attr_reader :roles
- alias :plans :roles
def initialize
@roles = []
@@ -40,26 +39,46 @@ def normalize_args(args)
options = args.last.is_a?(Hash) ? args.pop : {}
privilege = args[1].to_s
-
roles = args[2]
- roles = roles.respond_to?(:map) ? roles.map(&:to_s) : [roles && roles.to_s]
-
- raise NoRoleError.new if (self.roles & roles).empty?
-
- roles = normalize_roles_order(roles)
case args[0]
when String, Symbol
resource = args[0].to_s
- [resource, privilege, roles, options]
else
resource = args[0].class.name.to_s
- [resource, privilege, roles, options.merge(:subject => args[0])]
+ options.merge!(:subject => args[0])
+ end
+
+ roles = calculate_roles(roles, options)
+
+ if (self.roles & roles).empty?
+ raise InvalidRolesError.new(:roles => roles)
+ end
+
+ roles = sort_roles(roles)
+
+ [resource, privilege, roles, options]
+ end
+
+ def calculate_roles(roles, check_options)
+
+ roles = if roles.respond_to?(:call)
+ roles.call(check_options.dup)
+ elsif !roles.respond_to?(:map)
+ [ roles ]
+ else
+ roles
+ end
+
+ unless roles.respond_to?(:map)
+ raise InvalidRolesError.new(:result => roles)
end
+ roles.map(&:to_s)
+
end
- def normalize_roles_order(roles)
+ def sort_roles(roles)
@roles.select do |role|
roles.include? role
end
@@ -110,8 +129,6 @@ def check!(resource_name, privilege_name, roles, options)
end
- private
-
def logger
AccessSchema.config.logger
end
6 spec/access_schema/proxy_spec.rb
View
@@ -13,14 +13,14 @@
describe "#with_options" do
before do
- @schema = @proxy.with_options(:plan => [:flower], :user_id => 1)
+ @schema = @proxy.with_options(:roles => [:flower], :user_id => 1)
end
- it "allows to not specify plan for schema calls" do
+ it "allows to not specify roles for schema calls" do
@schema.allow?("Review", :mark_featured).should be_true
end
- it "but it accepts plan too" do
+ it "but it accepts roles too" do
@schema.allow?("Review", :mark_featured, :flower).should be_true
@schema.allow?("Review", :mark_featured, :none).should be_false
end
11 spec/access_schema/schema_builder_spec.rb
View
@@ -25,9 +25,6 @@
end
-
-class Review; end
-
describe AccessSchema::SchemaBuilder, "produced schema example" do
before do
@@ -40,7 +37,7 @@ class Review; end
@schema.roles.should ==%w(none bulb flower bouquet admin user)
end
- context "when checking against plan 'none'" do
+ context "when checking against role 'none'" do
it "does not allows to mark featured" do
@schema.allow?(@review, :mark_featured, :none).should be_false
@@ -58,7 +55,7 @@ class Review; end
end
- context "when checking against plan 'bulb'" do
+ context "when checking against role 'bulb'" do
it "does not allow to mark featured" do
@schema.allow?(@review, :mark_featured, :bulb).should be_false
@@ -76,7 +73,7 @@ class Review; end
end
- context "when checking against plan 'flower'" do
+ context "when checking against role 'flower'" do
it "allows to mark featured" do
@schema.allow?(@review, :mark_featured, :flower).should be_true
@@ -94,7 +91,7 @@ class Review; end
end
- context "when checking against plan 'bouquet'" do
+ context "when checking against role 'bouquet'" do
it "allows to mark featured" do
@schema.allow?(@review, :mark_featured, :bouquet).should be_true
59 spec/access_schema/schema_spec.rb
View
@@ -6,7 +6,7 @@
@schema = AccessSchema::SchemaBuilder.build_file('spec/schema_example.rb')
end
- describe "#add_plan" do
+ describe "#add_role" do
it "raises error if duplicate"
@@ -21,7 +21,7 @@
describe "#add_feature" do
it "raises error if duplicate"
- it "raises error for invalid plan"
+ it "raises error for invalid role"
it "raises error for invalid assert"
end
@@ -37,11 +37,11 @@
it "raises exception on invalid role" do
lambda {
@schema.allow? "Review", :mark_featured, :invalid
- }.should raise_error(AccessSchema::NoRoleError)
+ }.should raise_error(AccessSchema::InvalidRolesError)
lambda {
@schema.allow? "Review", :mark_featured
- }.should raise_error(AccessSchema::NoRoleError)
+ }.should raise_error(AccessSchema::InvalidRolesError)
end
it "raises exception on invalid feature"
@@ -68,6 +68,57 @@
end
+ describe "dynamic roles calculation" do
+
+ it "accepts proc as roles" do
+
+ lambda {
+ roles_calculator = proc { [:admin] }
+ @schema.allow? "Review", :update, roles_calculator
+ }.should_not raise_error
+
+ end
+
+ it "passes options hash with subject into proc" do
+
+ @passed_options = nil
+ roles_calculator = proc do |options|
+ @passed_options = options
+ [:admin]
+ end
+ subject = Review.new
+ @schema.allow? subject, :update, roles_calculator, :option1 => :value1
+ @passed_options.should be
+ @passed_options[:subject].should == subject
+ @passed_options[:option1].should == :value1
+
+ end
+
+ it "passes a copy of options hash" do
+
+ @passed_options = {:option1 => :value1}
+ roles_calculator = proc do |options|
+ options[:option1] = :changed_value
+ [:admin]
+ end
+
+ @schema.allow? "Review", :update, roles_calculator
+
+ @passed_options[:option1].should == :value1
+
+ end
+
+ it "raises error if none array returned from proc" do
+
+ lambda {
+ roles_calculator = proc { :admin }
+ @schema.allow? "Review", :update, roles_calculator
+ }.should raise_error(AccessSchema::InvalidRolesError)
+
+ end
+
+ end
+
describe "#require!" do
it "raises en error is feature is nt allowed"
7 spec/spec_helper.rb
View
@@ -1,9 +1,14 @@
require 'rspec'
+Dir[File.expand_path('../support/**/*', __FILE__)].each { |f| require f }
+
+require 'simplecov'
+
+SimpleCov.start
+
require 'access_schema'
require 'access_schema/loggers/test_logger'
-Dir[File.expand_path('../support/**/*', __FILE__)].each { |f| require f }
RSpec.configure do |config|
end
3  spec/support/review.rb
View
@@ -0,0 +1,3 @@
+class Review
+end
+

No commit comments for this range

Something went wrong with that request. Please try again.