cannie 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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