cannie 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9fbbd16e6ac148de444fe153765a6dc094dc24e9
4
- data.tar.gz: cb1002198639bb6ae623277915b5cc903147c88d
3
+ metadata.gz: bd4ec59a6c5ecf6c0e7307ea41c381cdca880956
4
+ data.tar.gz: 666cb9f5b763b66abd5ba9e8e8c5404933e4a940
5
5
  SHA512:
6
- metadata.gz: 87b47746d61b5604032cc51f2f11720893628a1b6019274d9ab870f41d875d76bd13119105fdfecc154020ecae50d2ada46792853aa4366f147903de97c53445
7
- data.tar.gz: ae87022d66e48972f60f9fa31adef1a5e00e4e7324ab312fe32162375051f6ab8a6f78441a77251676740d78a2d3629ab47ee44b945fed372a7963beeb88dd2c
6
+ metadata.gz: bb8863ea60b52ac207da478b9f890001f3d3525620a0f46971f21135d66fbef5182a315b73ea5ec66554f06a6ebb1aac8aeeebf4c9878e806f62aa9f62533e3f
7
+ data.tar.gz: 4832f762eaa2a38592721dce60f27390464055ac5b5e8a5a8123fc4f9cd3407249aa526e90d03c6f9380aeeb2f521b4ba9aa0c72b21e0f2c5519242f7e1810b8
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Cannie
2
2
 
3
- Cannie is a gem for authorization/permissions checking.
3
+ Cannie is a gem for authorization/permissions checking on per-controller/per-action basis.
4
4
 
5
5
  ## Installation
6
6
 
@@ -24,27 +24,47 @@ Permissions are defined in Permissions class, which could be generated by Rails
24
24
 
25
25
  rails g cannie:permissions
26
26
 
27
- Than you can define all the permissions you want inside ::initialize method of Permissions class:
27
+ Than you can define all the permissions you want:
28
28
 
29
29
  class Permissions
30
30
  include Cannie::Permissions
31
31
 
32
- def initialize(user)
33
- if user.admin?
34
- allow :manage, on: :all
35
- else
36
- allow :read, on: Post
37
- allow :read, on: Comment
38
- allow :create, on: Comment
39
-
40
- # allow delete comments, that were created only if user has posted those comments
41
- allow :delete, on: Comment do |*comments|
42
- comments.all?{|c| c.user_id == user.id}
43
- end
32
+ # allow action on controller
33
+ allow :index, on: :posts
34
+
35
+ # or if controller is namespaced
36
+ allow :index, on: 'namespace/controller'
37
+
38
+ # few actions for controller
39
+ allow [:index, :show], on: :posts
40
+
41
+ # many actions for many controllers
42
+ allow [:index, :show], on: [:posts, :comments]
43
+
44
+ # few rules inside controller scope
45
+ controller :posts do
46
+ allow :show
47
+ allow :new
48
+ end
49
+
50
+ # namespaced controllers
51
+ namespace :admin do
52
+ controller :users do
53
+ allow [:index, :show]
44
54
  end
45
55
  end
46
56
  end
47
57
 
58
+ Also its possible to pass conditions for `allow` calls:
59
+
60
+ allow :index, on: :posts, if: ->{ user.admin? }
61
+
62
+ or
63
+
64
+ allow :index, on: :posts, unless: ->{ user.guest? }
65
+
66
+ These conditions are executed in context of `Permissions` object and its possible to use `user` method to access user that was passed to `Permissions::initialize`.
67
+
48
68
  ### Checking permissions
49
69
 
50
70
  To be sure that permissions checking is handled in each action of your controller, add `check_permissions` method call to your controllers:
@@ -95,54 +115,6 @@ To skip checking permissions for controller, add `skip_check_permissions` method
95
115
  #...
96
116
  end
97
117
 
98
- Checking of permissions on per-action basis is done by calling `permit!` method inside of controller's actions:
99
-
100
- class PostsController < ApplicationController
101
- check_permissions
102
-
103
- def index
104
- @posts = Posts.all
105
- permit! :read, on: posts # checks whether user able to read fetched posts
106
- end
107
- end
108
-
109
- It is possible to check permissions for resource actions. Currently collection and entry name is taken from resource controller name (some_namespace/entries_controller => @entries/@entry instance variables).
110
- To add this behavior add `permit_resource_actions` instead of `check_permissions` with no need to write custom `permit!` calls in each resource action:
111
-
112
- class EntriesController < ApplicationController
113
- permit_resource_actions
114
-
115
- def index
116
- @entries = Entry.all
117
- end
118
-
119
- def show
120
- @entry = Entry.find(params[:id])
121
- end
122
- end
123
-
124
- For now only standard resource actions supported: `index`, `show`, `new`, `create`, `edit`, `update`, `destroy`.
125
- To define `Permissions` properly, use this mapping of action names to permissions:
126
- - :index => :list
127
- - :show => :read
128
- - :new or :create => :create
129
- - :edit or :update => :update
130
- - :destroy => :destroy
131
-
132
- Sample `Permissions` class to deal with all resource actions for `EntriesController`:
133
-
134
- class Permissions
135
- include Cannie::Permissions
136
-
137
- def initialize(user)
138
- allow :list, on: Entry # index action
139
- allow :read, on: Entry # show action
140
- allow :create, on: Entry # new & create actions
141
- allow :update, on: Entry # edit & update actions
142
- allow :destroy, on: Entry # destroy action
143
- end
144
- end
145
-
146
118
  ### Handling of unpermitted access
147
119
 
148
120
  If user is not permitted for appropriate action, `Cannie::ActionForbidden` exception will be raised.
@@ -7,31 +7,22 @@ module Cannie
7
7
  helper_method :can?, :current_permissions
8
8
  end
9
9
 
10
- RESOURCE_ACTIONS = {
11
- index: :list,
12
- show: :read,
13
- new: :create,
14
- create: :create,
15
- edit: :update,
16
- update: :update,
17
- destroy: :destroy
18
- }
19
-
20
10
  module ClassMethods
21
11
  # Method is used to be sure, that permissions checking is handled for each action inside controller.
22
12
  #
23
13
  # class PostsController < ApplicationController
24
14
  # check_permissions
25
15
  #
26
- # #...
16
+ # # ...
27
17
  # end
28
18
  #
29
19
  def check_permissions(options={})
30
- after_action(options.slice(:only, :except)) do |controller|
20
+ _if, _unless = options.values_at(:if, :unless)
21
+ before_action(options.slice(:only, :except)) do |controller|
31
22
  next if controller.permitted?
32
- next if options[:if] && !controller.instance_eval(&options[:if])
33
- next if options[:unless] && controller.instance_eval(&options[:unless])
34
- raise CheckPermissionsNotPerformed, 'Action failed the check_permissions because it does not calls permit! method. Add skip_check_permissions to bypass this check.'
23
+ next if _if && !controller.instance_eval(&_if)
24
+ next if _unless && controller.instance_eval(&_unless)
25
+ current_permissions.permit!(controller.action_name, controller)
35
26
  end
36
27
  end
37
28
 
@@ -40,63 +31,34 @@ module Cannie
40
31
  # class PostsController < ApplicationController
41
32
  # skip_check_permissions
42
33
  #
43
- # #...
34
+ # # ...
44
35
  # end
45
36
  def skip_check_permissions(*args)
46
- before_action(*args) do |controller|
37
+ prepend_before_action(*args) do |controller|
47
38
  controller.instance_variable_set(:@_permitted, true)
48
39
  end
49
40
  end
50
-
51
- # Permit resource actions [index, show, new, create, edit, update, destroy] in controller
52
- #
53
- #
54
- def permit_resource_actions(options={})
55
- after_action(options.slice(:only, :except)) do |controller|
56
- begin
57
- next if controller.permitted?
58
- next if options[:if] && !controller.instance_eval(&options[:if])
59
- next if options[:unless] && controller.instance_eval(&options[:unless])
60
- controller.permit! RESOURCE_ACTIONS.with_indifferent_access[action_name], on: controller.subject_for_action
61
- rescue Cannie::ActionForbidden
62
- self.response_body = nil
63
- raise
64
- end
65
- end
66
- end
67
41
  end
68
42
 
69
43
  # Checks whether passed action is permitted for passed subject
70
44
  #
71
- # can? :read, on: @posts
45
+ # can? :index, on: :entries
72
46
  #
73
47
  # or
74
48
  #
75
- # can? :read, on: Post
76
- #
77
- # @param [Symbol] action
78
- # @param [Object] subject
79
- # @return [Boolean] result of checking permission
49
+ # can? :index, on: EntriesController
80
50
  #
81
- def can?(action, on: nil)
82
- raise Cannie::SubjectNotSetError, 'Subject should be specified' unless on
83
- current_permissions.can?(action, on: on)
84
- end
85
-
86
- # Define permissions, that should be checked inside controller's action
51
+ # or
87
52
  #
88
- # def index
89
- # permit! :read, on: Post
90
- # @posts = Post.all
91
- # end
53
+ # can? :index, on: 'admin/entries'
92
54
  #
93
55
  # @param [Symbol] action
94
- # @param [Object] subject
56
+ # @param [Object] controller class or controller_path as a string or symbol
57
+ # @return [Boolean] result of checking permission
95
58
  #
96
- def permit!(action, on: nil)
59
+ def can?(action, on: nil)
97
60
  raise Cannie::SubjectNotSetError, 'Subject should be specified' unless on
98
- current_permissions.permit!(action, on: on)
99
- @_permitted = true
61
+ current_permissions.can?(action, on)
100
62
  end
101
63
 
102
64
  def permitted?
@@ -107,14 +69,6 @@ module Cannie
107
69
  @current_permissions ||= ::Permissions.new(current_user)
108
70
  end
109
71
 
110
- def subject_for_action
111
- return unless RESOURCE_ACTIONS.with_indifferent_access.keys.include?(action_name)
112
- entry_name = controller_name.classify.demodulize.downcase
113
- collection_name = entry_name.pluralize
114
- variable_name = action_name == 'index' ? collection_name : entry_name
115
- instance_variable_get(:"@#{variable_name}")
116
- end
117
-
118
72
  private
119
73
  attr_reader :_permitted
120
74
  end
@@ -1,7 +1,5 @@
1
1
  module Cannie
2
2
  class SubjectNotSetError < StandardError; end
3
3
 
4
- class CheckPermissionsNotPerformed < StandardError; end
5
-
6
4
  class ActionForbidden < StandardError; end
7
5
  end
@@ -1,71 +1,72 @@
1
1
  module Cannie
2
- # This module provides possibility to define permissions using "allow" method
3
- #
4
- # class Permissions
5
- # include Cannie::Permissions
6
- #
7
- # def initialize(user)
8
- # allow :read, on: Model
9
- # allow :manage, on: Model do |*entries|
10
- # entries.all?{|v| v.user == user}
11
- # end
12
- # end
13
- # end
14
- #
15
2
  module Permissions
16
- # Define rules for further permissions checking
17
- #
18
- # allow :read, on: Model
19
- #
20
- # @param actions
21
- # @param on
22
- # @return array of rules
23
- def allow(*actions, on: nil, &block)
24
- rules << Rule.new(*actions, on, &block)
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ extend ClassMethods
25
7
  end
26
8
 
27
- # Check permission by given action on subject, that is passed in 'on' parameter
28
- #
29
- # can? :read, on: Model
30
- #
31
- # or
32
- #
33
- # can? :read, on: models
34
- #
35
- # @param action
36
- # @param on
37
- # @return result of permissions check
38
- #
39
- def can?(action, on: nil)
40
- rules = rules_for(action, on)
41
- rules.present? && rules.all? do |rule|
42
- rule.permits?(*on)
9
+ module ClassMethods
10
+ def namespace(name, &block)
11
+ orig_scope = @scope
12
+ @scope = [orig_scope, name].compact.join('/')
13
+ instance_exec(&block)
14
+ ensure
15
+ @scope = orig_scope
16
+ end
17
+
18
+ def controller(name, &block)
19
+ @controller = name
20
+ instance_exec(&block)
21
+ ensure
22
+ @controller = nil
23
+ end
24
+
25
+ def allow(action, options={})
26
+ opts = options.slice(:if, :unless)
27
+ subjects = Array(@controller || options[:on]).map { |v| subject(v) }
28
+
29
+ Array(action).each do |action_name|
30
+ subjects.each do |subj|
31
+ rules << Rule.new(action_name, subj, opts)
32
+ end
33
+ end
34
+ end
35
+
36
+ def rules
37
+ @rules ||= []
43
38
  end
39
+
40
+ private
41
+ def subject(name)
42
+ (name == :all && name) || ([@scope, name].compact.join('/'))
43
+ end
44
+ end
45
+
46
+ attr_reader :user
47
+
48
+ def initialize(user)
49
+ @user = user
50
+ end
51
+
52
+ def can?(action, subject)
53
+ rules_for(action, subject).present?
44
54
  end
45
55
 
46
- # Permit access for action on a subject
47
- #
48
- # permit! :read, on: Model
49
- #
50
- # or
51
- #
52
- # permit! :manage, on: models
53
- #
54
- # @param [Symbol] Action
55
- #
56
- def permit!(action, on: nil)
57
- raise Cannie::ActionForbidden unless can?(action, on: on)
56
+ def permit!(action, subject)
57
+ raise Cannie::ActionForbidden unless can?(action, subject)
58
58
  end
59
59
 
60
60
  private
61
61
  def rules
62
- @rules ||= []
62
+ @rules ||= self.class.rules.select { |rule| rule.applies_to?(self) }
63
63
  end
64
64
 
65
65
  def rules_for(action, subject)
66
- klass = subject.is_a?(Class) ? subject : subject.class
67
- rules.select do |r|
68
- r.actions.include?(action) && (subject == :all || klass <= r.subject)
66
+ subject = subject.respond_to?(:controller_path) ? subject.controller_path : subject.to_s
67
+
68
+ rules.select do |rule|
69
+ rule.action.to_sym == action.to_sym && (rule.subject == :all || rule.subject == subject)
69
70
  end
70
71
  end
71
72
  end
data/lib/cannie/rule.rb CHANGED
@@ -1,19 +1,24 @@
1
1
  module Cannie
2
2
  class Rule
3
- attr_reader :actions, :subject, :condition
3
+ attr_reader :action, :subject
4
4
 
5
- def initialize(*actions, subject, &block)
6
- @actions = actions
7
- @subject = subject
8
- @condition = block
5
+ def initialize(action, subject, options={})
6
+ @action, @subject, @_if, @_unless = action, subject, *options.values_at(:if, :unless)
9
7
  end
10
8
 
11
- def permits?(*args)
12
- if condition
13
- !!condition.call(*args)
14
- else
15
- true
16
- end
9
+ def applies_to?(permissions)
10
+ if?(permissions) && unless?(permissions)
11
+ end
12
+
13
+ private
14
+ attr_reader :_if, :_unless
15
+
16
+ def if?(permissions)
17
+ _if ? permissions.instance_exec(&_if) : true
18
+ end
19
+
20
+ def unless?(permissions)
21
+ _unless ? !permissions.instance_exec(&_unless) : true
17
22
  end
18
23
  end
19
24
  end
@@ -1,3 +1,3 @@
1
1
  module Cannie
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -1,15 +1,19 @@
1
1
  class Permissions
2
2
  include Cannie::Permissions
3
3
 
4
- def initialize(user)
5
- # Define abilities for the passed user:
6
- #
7
- # user ||= User.new
8
- # if user.admin?
9
- # allow :manage, on: Model
10
- # else
11
- # can :read, on: Model
12
- # end
13
- #
14
- end
4
+ # namespace :admin do
5
+ # allow :index, on: :users, if: ->{ user.admin? }
6
+ # allow :show, on: :users, if: :admin?
7
+ # allow :new, on: :users, unless: :member?
8
+ #
9
+ # controller :posts do
10
+ # allow :index
11
+ # end
12
+ # end
13
+ #
14
+ # controller :users do
15
+ # allow [:sign_in, :sign_up]
16
+ # end
17
+ #
18
+ # allow [:index, :show, :new, :create, :edit, :update, :destroy], on: [:posts, :comments]
15
19
  end
@@ -1,22 +1,22 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Cannie::ControllerExtensions do
4
- let(:klass) {
4
+ let(:klass) do
5
5
  Class.new(ActionController::Base) do
6
- def action; end
6
+ def action
7
+ render nothing: true
8
+ end
9
+
7
10
  def index
8
11
  @entries = [1,2,3,4,5]
9
12
  render text: @entries.to_s
10
13
  end
11
14
 
12
- %i(show new create edit update destroy).each do |action|
13
- define_method action do
14
- @entry = 123
15
- render text: @entry.to_s
16
- end
15
+ def self.controller_path
16
+ 'entries'
17
17
  end
18
18
  end
19
- }
19
+ end
20
20
 
21
21
  subject { klass.new }
22
22
 
@@ -24,50 +24,38 @@ describe Cannie::ControllerExtensions do
24
24
  Class.new do
25
25
  include Cannie::Permissions
26
26
 
27
- def initialize
28
- allow :update, on: Array do |*attrs|
29
- attrs.all?{|v| v % 3 == 0}
30
- end
31
- end
32
- end
33
- end
34
-
35
- let(:resource_permissions) do
36
- Class.new do
37
- include Cannie::Permissions
38
-
39
- def initialize
40
- allow :list, on: Array
41
- %i(read create update destroy).each{ |v| allow v, on: Fixnum }
42
- end
27
+ allow :index, on: :entries
43
28
  end
44
29
  end
45
30
 
46
31
  describe '.check_permissions' do
32
+ before { subject.stub(:current_permissions).and_return(permissions.new('User')) }
33
+
47
34
  describe 'without conditions' do
48
- before { klass.check_permissions }
35
+ before do
36
+ klass.check_permissions
37
+ end
49
38
 
50
- it 'raises exception if controller.permitted? evaluates to false' do
51
- expect { subject.run_callbacks(:process_action) }.to raise_error(Cannie::CheckPermissionsNotPerformed)
39
+ it 'raises exception if no rules for action & subject exist' do
40
+ expect { subject.dispatch(:action, ActionDispatch::TestRequest.new) }.to raise_error(Cannie::ActionForbidden)
52
41
  end
53
42
 
54
- it 'does not raise exception if controller.permitted? evaluates to true' do
55
- subject.stub(:permitted?).and_return(true)
56
- expect { subject.run_callbacks(:process_action) }.not_to raise_error
43
+ it 'does not raise exception rules match action & subject' do
44
+ expect { subject.dispatch(:index, ActionDispatch::TestRequest.new) }.not_to raise_error
57
45
  end
58
46
  end
59
47
 
60
48
  describe 'with if condition' do
61
49
  before { klass.check_permissions if: :condition? }
62
50
 
63
- it 'raises exception if :if block executed in controller scope returns true' do
51
+ it 'raises exception if :if block executed in controller scope returns true and no rules for action/subject' do
64
52
  subject.stub(:condition?).and_return(true)
65
- expect { subject.run_callbacks(:process_action) }.to raise_error(Cannie::CheckPermissionsNotPerformed)
53
+ expect { subject.dispatch(:action, ActionDispatch::TestRequest.new) }.to raise_error(Cannie::ActionForbidden)
66
54
  end
67
55
 
68
56
  it 'does not raise exception if :if block executed in controller scope returns false' do
69
57
  subject.stub(:condition?).and_return(false)
70
- expect { subject.run_callbacks(:process_action) }.not_to raise_error
58
+ expect { subject.dispatch(:action, ActionDispatch::TestRequest.new) }.not_to raise_error
71
59
  end
72
60
  end
73
61
 
@@ -76,12 +64,12 @@ describe Cannie::ControllerExtensions do
76
64
 
77
65
  it 'raises exception if :unless block executed in controller scope returns false' do
78
66
  subject.stub(:condition?).and_return(false)
79
- expect { subject.run_callbacks(:process_action) }.to raise_error(Cannie::CheckPermissionsNotPerformed)
67
+ expect { subject.dispatch(:action, ActionDispatch::TestRequest.new) }.to raise_error(Cannie::ActionForbidden)
80
68
  end
81
69
 
82
70
  it 'does not raise exception if :unless block executed in controller scope returns false' do
83
71
  subject.stub(:condition?).and_return(true)
84
- expect { subject.run_callbacks(:process_action) }.not_to raise_error
72
+ expect { subject.dispatch(:action, ActionDispatch::TestRequest.new) }.not_to raise_error
85
73
  end
86
74
  end
87
75
  end
@@ -94,77 +82,26 @@ describe Cannie::ControllerExtensions do
94
82
  end
95
83
  end
96
84
 
97
- describe 'permit_resource_actions' do
98
- before do
99
- klass.permit_resource_actions
100
- subject.stub(:controller_name).and_return('test/entries')
101
- subject.stub(:current_permissions).and_return resource_permissions.new
102
- end
103
-
104
- it 'calls permit! for index with valid params' do
105
- expect(subject).to receive(:permit!).with(Cannie::ControllerExtensions::RESOURCE_ACTIONS[:index], on: [1,2,3,4,5])
106
- subject.dispatch(:index, ActionDispatch::TestRequest.new)
107
- end
108
-
109
- it 'calls permit! for show with valid params' do
110
- expect(subject).to receive(:permit!).with(Cannie::ControllerExtensions::RESOURCE_ACTIONS[:show], on: 123)
111
- subject.dispatch(:show, ActionDispatch::TestRequest.new)
112
- end
113
-
114
- %i(index show).each do |action|
115
- it "permits #{action} action" do
116
- subject.dispatch(action, ActionDispatch::TestRequest.new)
117
- expect(subject.permitted?).to be_true
118
- end
119
- end
120
-
121
- it 'raises Cannie::ActionForbidden error up the stack' do
122
- expect(subject).to receive(:index).and_raise(Cannie::ActionForbidden)
123
- expect { subject.dispatch(:index, ActionDispatch::TestRequest.new) }.to raise_error(Cannie::ActionForbidden)
124
- expect(subject.response_body).to be_nil
125
- end
126
- end
127
-
128
85
  describe '#can?' do
129
86
  it 'raises SubjectNotSetError if value of :on param is nil' do
130
- expect { subject.can? :action }.to raise_error(Cannie::SubjectNotSetError)
87
+ expect { subject.can? :action, on: nil }.to raise_error(Cannie::SubjectNotSetError)
131
88
  end
132
89
 
133
90
  it 'returns true if action allowed on subject' do
134
- subject.stub(:current_permissions).and_return permissions.new
135
- expect(subject.can? :update, on: [3,6,9]).to be_true
91
+ subject.stub(:current_permissions).and_return permissions.new('user')
92
+ expect(subject.can? :index, on: klass).to be_true
136
93
  end
137
94
 
138
95
  it 'returns false if action not allowed on subject' do
139
- subject.stub(:current_permissions).and_return permissions.new
140
- expect(subject.can? :update, on: [3,7,9]).to be_false
141
- end
142
- end
143
-
144
- describe '#permit!' do
145
- it 'raises SubjectNotSetError if value of :on param is nil' do
146
- expect { subject.permit! :action }.to raise_error(Cannie::SubjectNotSetError)
147
- end
148
-
149
- it 'assigns @_permitted to true if action is allowed on subject' do
150
- subject.stub(:current_permissions).and_return permissions.new
151
- subject.permit! :update, on: [3,6,9]
152
- expect(subject.permitted?).to be_true
153
- end
154
-
155
- it 'raises AccessDenied error if action is not allowed on subject' do
156
- subject.stub(:current_permissions).and_return permissions.new
157
- expect { subject.permit! :update, on: [3,6,11] }.to raise_error(Cannie::ActionForbidden)
96
+ subject.stub(:current_permissions).and_return permissions.new('user')
97
+ expect(subject.can? :action, on: klass).to be_false
158
98
  end
159
99
  end
160
100
 
161
101
  describe '#current_permissions' do
162
102
  before(:all) do
163
103
  Permissions = Class.new do
164
- attr_reader :user
165
- def initialize(user)
166
- @user = user
167
- end
104
+ include Cannie::Permissions
168
105
  end
169
106
  end
170
107
 
@@ -3,82 +3,125 @@ require 'spec_helper'
3
3
  describe Cannie::Permissions do
4
4
  subject { Class.new { include Cannie::Permissions } }
5
5
 
6
- describe '#allow' do
7
- before do
8
- subject.class_eval do
9
- def initialize
10
- allow :read, :update, on: Array
11
- end
6
+ let(:permissions) do
7
+ subject.class_exec do
8
+ controller :entries do
9
+ allow :index
10
+ allow :show
12
11
  end
13
- end
14
-
15
- let(:rules) { subject.new.send(:rules) }
16
12
 
17
- it 'creates only one rule for each call of allow method' do
18
- expect(rules.size).to eq(1)
13
+ allow :new, on: :all
19
14
  end
15
+ subject.new('user')
16
+ end
20
17
 
21
- it 'creates and stores Rule object with passed actions' do
22
- expect(rules.first.actions).to eq([:read, :update])
18
+ let(:klass) do
19
+ Class.new(ActionController::Base) do
20
+ def index; end
21
+ def self.controller_path
22
+ 'entries'
23
+ end
23
24
  end
25
+ end
24
26
 
25
- it 'creates and stores Rule object with passed subject' do
26
- expect(rules.first.subject).to eq(Array)
27
+ describe '.namespace' do
28
+ it 'executes block with changed scope' do
29
+ expect(subject.namespace('test_namespace') { subject('entries') }).to eq('test_namespace/entries')
27
30
  end
28
31
 
29
- it 'creates and stores Rule object with passed condition block' do
30
- expect(rules.first.condition).to be_nil
32
+ it 'allows nesting of namespaces' do
33
+ expect(
34
+ subject.namespace('test_namespace') do
35
+ namespace 'namespace_2' do
36
+ subject('entries')
37
+ end
38
+ end
39
+ ).to eq('test_namespace/namespace_2/entries')
31
40
  end
32
41
  end
33
42
 
34
- describe '#can?' do
35
- before do
36
- subject.class_eval do
37
- def initialize
38
- allow :read, on: Array
43
+ describe '.controller' do
44
+ it 'executes block with changed controller' do
45
+ subject.controller('entries') { allow :index }
46
+ expect(subject.rules.map(&:subject)).to eq(['entries'])
47
+ end
39
48
 
40
- allow(:read, on: Array) do |*args|
41
- args.all?{|v| v % 2 == 0}
42
- end
43
- end
49
+ it 'allows nesting into namespaces' do
50
+ subject.namespace(:namespace) do
51
+ controller(:entries) { allow :index }
44
52
  end
53
+ expect(subject.rules.map(&:subject)).to eq(['namespace/entries'])
45
54
  end
55
+ end
46
56
 
47
- let(:permissions) { subject.new }
48
-
49
- it 'returns false if no rules for action exists' do
50
- permissions.stub(:rules).and_return([])
51
- expect(permissions.can? :read, on: [2, 4, 8]).to be_false
57
+ describe '.allow' do
58
+ it 'creates Rule object for specified controller and action' do
59
+ subject.allow :index, on: :entries
60
+ rule = subject.rules.last
61
+ expect(rule).to be_instance_of(Cannie::Rule)
62
+ expect(rule.action).to eq(:index)
63
+ expect(rule.subject).to eq('entries')
52
64
  end
53
65
 
54
- it 'returns true if all rules for action & subject are permitted' do
55
- expect(permissions.can? :read, on: [2, 4, 8]).to be_true
66
+ it 'creates Rule object for each of specified actions and controllers' do
67
+ subject.allow [:index, :show], on: [:entries, :comments]
68
+ expected = [[:index, 'entries'], [:index, 'comments'], [:show, 'entries'], [:show, 'comments']]
69
+ expect(subject.rules.map { |rule| [rule.action, rule.subject] }).to eq(expected)
56
70
  end
57
71
 
58
- it 'returns false if not all rules for action & subject are permitted' do
59
- expect(permissions.can? :read, on: [1, 2, 3]).to be_false
72
+ it 'allows nesting into controllers' do
73
+ subject.class_exec do
74
+ allow :index, on: :entries
75
+
76
+ controller :entries do
77
+ allow :show
78
+ end
79
+
80
+ allow :show, on: :comments
81
+ end
82
+
83
+ expected = [[:index, 'entries'], [:show, 'entries'], [:show, 'comments']]
84
+ expect(subject.rules.map { |rule| [rule.action, rule.subject] }).to eq(expected)
60
85
  end
61
86
  end
62
87
 
63
- describe '#permit!' do
64
- before do
65
- subject.class_eval do
66
- def initialize
67
- allow :read, on: Array do |*args|
68
- args.all?{|v| v % 2 == 0}
69
- end
70
- end
88
+ describe '#can?' do
89
+ describe 'when passed as class' do
90
+ it 'returns true if it has at least one rule for corresponding action & subject' do
91
+ expect(permissions.can?(:index, klass)).to be_true
92
+ end
93
+
94
+ it 'returns true for any subject if rule subject set to :all' do
95
+ expect(permissions.can?(:new, klass)).to be_true
96
+ end
97
+
98
+ it 'returns false if no rules found for corresponding action & subject' do
99
+ expect(permissions.can?(:edit, klass)).to be_false
71
100
  end
72
101
  end
73
102
 
74
- let(:permissions) { subject.new }
103
+ describe 'when passed as string' do
104
+ it 'returns true if it has at least one rule for corresponding action & subject' do
105
+ expect(permissions.can?(:index, klass.controller_path)).to be_true
106
+ end
107
+
108
+ it 'returns true for any subject if rule subject set to :all' do
109
+ expect(permissions.can?(:new, klass.controller_path)).to be_true
110
+ end
111
+
112
+ it 'returns false if no rules found for corresponding action & subject' do
113
+ expect(permissions.can?(:edit, klass.controller_path)).to be_false
114
+ end
115
+ end
116
+ end
75
117
 
76
- it 'raises AccessDenied error if permission checking failed' do
77
- expect { permissions.permit! :read, on: [1, 2, 3] }.to raise_error
118
+ describe '#permit!' do
119
+ it 'raises ActionForbidden error if can? returns false' do
120
+ expect { permissions.permit!(:edit, klass) }.to raise_error(Cannie::ActionForbidden)
78
121
  end
79
122
 
80
- it 'does not raise AccessDenied error if permission checking was successfull' do
81
- expect { permissions.permit! :read, on: [2, 4, 6] }.not_to raise_error
123
+ it 'does not raise ActionForbidden error if can? returns true' do
124
+ expect { permissions.permit!(:index, klass) }.not_to raise_error
82
125
  end
83
126
  end
84
127
 
@@ -2,41 +2,62 @@ require 'spec_helper'
2
2
 
3
3
  describe Cannie::Rule do
4
4
  describe '#initialize' do
5
- it 'stores passed actions' do
6
- actions = %i(read create update delete)
7
- rule = described_class.new *actions, Array
8
- expect(rule.actions).to eq(actions)
5
+ it 'stores passed action' do
6
+ rule = described_class.new :index, 'entries'
7
+ expect(rule.action).to eq(:index)
9
8
  end
10
9
 
11
10
  it 'stores passed subject' do
12
- rule = described_class.new :read, Array
13
- expect(rule.subject).to eq(Array)
14
- end
15
-
16
- it 'scores passed block' do
17
- rule = described_class.new(:read, Array){ |*attrs| attrs.all?{ |v| v % 2 == 0 } }
18
- expect(rule.condition.call(2,4,8)).to be_true
11
+ rule = described_class.new :index, 'entries'
12
+ expect(rule.subject).to eq('entries')
19
13
  end
20
14
  end
21
15
 
22
- describe '#permits?' do
23
- let(:rule) do
24
- described_class.new(:read, Array) do |*attrs|
25
- attrs.all?{ |v| v % 2 == 0 }
16
+ describe 'applies_to?' do
17
+ let(:permissions) do
18
+ Class.new do
19
+ def initialize(is_admin=false, is_guest=false)
20
+ @is_admin, @is_guest = is_admin, is_guest
21
+ end
22
+
23
+ def admin?
24
+ !!@is_admin
25
+ end
26
+
27
+ def guest?
28
+ !!@is_guest
29
+ end
26
30
  end
27
31
  end
28
32
 
29
- it 'returns true if result of executing condition is true' do
30
- expect(rule.permits?(2,4,8)).to be_true
33
+ it 'returns true if no conditions passed in initialize' do
34
+ rule = described_class.new(:index, 'entries')
35
+ expect(rule.applies_to?(Array)).to be_true
36
+ end
37
+
38
+ it 'returns true if passed if-condition evaluated in scope of passed argument return true' do
39
+ rule = described_class.new(:index, 'entries', if: -> { admin? })
40
+ expect(rule.applies_to?(permissions.new(true))).to be_true
41
+ end
42
+
43
+ it 'returns false if passed if-condition evaluated in scope of passed argument return false' do
44
+ rule = described_class.new(:index, 'entries', if: -> { admin? })
45
+ expect(rule.applies_to?(permissions.new())).to be_false
46
+ end
47
+
48
+ it 'returns true if passed unless-condition evaluated in scope of passed argument return false' do
49
+ rule = described_class.new(:index, 'entries', unless: -> { admin? })
50
+ expect(rule.applies_to?(permissions.new)).to be_true
31
51
  end
32
52
 
33
- it 'returns false if result of executing condition is false' do
34
- expect(rule.permits?(1,4,8)).to be_false
53
+ it 'returns false if passed unless-condition evaluated in scope of passed argument return true' do
54
+ rule = described_class.new(:index, 'entries', unless: -> { admin? })
55
+ expect(rule.applies_to?(permissions.new(true))).to be_false
35
56
  end
36
57
 
37
- it 'returns true for any subject if rule subject is :all' do
38
- rule = described_class.new(:read, :all)
39
- expect(rule.permits?(1,2,3)).to be_true
58
+ it 'returns true if all conditions returned true' do
59
+ rule = described_class.new(:index, 'entries', if: -> { admin? }, unless: -> { guest? })
60
+ expect(rule.applies_to?(permissions.new(true))).to be_true
40
61
  end
41
62
  end
42
63
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cannie
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - hck
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-17 00:00:00.000000000 Z
11
+ date: 2013-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler