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 +4 -4
- data/README.md +34 -62
- data/lib/cannie/controller_extensions.rb +16 -62
- data/lib/cannie/exceptions.rb +0 -2
- data/lib/cannie/permissions.rb +55 -54
- data/lib/cannie/rule.rb +16 -11
- data/lib/cannie/version.rb +1 -1
- data/lib/generators/cannie/permissions/templates/permissions.rb +15 -11
- data/spec/cannie/controller_extensions_spec.rb +29 -92
- data/spec/cannie/permissions_spec.rb +91 -48
- data/spec/cannie/rule_spec.rb +43 -22
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd4ec59a6c5ecf6c0e7307ea41c381cdca880956
|
4
|
+
data.tar.gz: 666cb9f5b763b66abd5ba9e8e8c5404933e4a940
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
27
|
+
Than you can define all the permissions you want:
|
28
28
|
|
29
29
|
class Permissions
|
30
30
|
include Cannie::Permissions
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
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
|
33
|
-
next if
|
34
|
-
|
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
|
-
|
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? :
|
45
|
+
# can? :index, on: :entries
|
72
46
|
#
|
73
47
|
# or
|
74
48
|
#
|
75
|
-
# can? :
|
76
|
-
#
|
77
|
-
# @param [Symbol] action
|
78
|
-
# @param [Object] subject
|
79
|
-
# @return [Boolean] result of checking permission
|
49
|
+
# can? :index, on: EntriesController
|
80
50
|
#
|
81
|
-
|
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
|
-
#
|
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]
|
56
|
+
# @param [Object] controller class or controller_path as a string or symbol
|
57
|
+
# @return [Boolean] result of checking permission
|
95
58
|
#
|
96
|
-
def
|
59
|
+
def can?(action, on: nil)
|
97
60
|
raise Cannie::SubjectNotSetError, 'Subject should be specified' unless on
|
98
|
-
current_permissions.
|
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
|
data/lib/cannie/exceptions.rb
CHANGED
data/lib/cannie/permissions.rb
CHANGED
@@ -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
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
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 :
|
3
|
+
attr_reader :action, :subject
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
@
|
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
|
12
|
-
if
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
data/lib/cannie/version.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
1
|
class Permissions
|
2
2
|
include Cannie::Permissions
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
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
|
-
|
13
|
-
|
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
|
-
|
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
|
35
|
+
before do
|
36
|
+
klass.check_permissions
|
37
|
+
end
|
49
38
|
|
50
|
-
it 'raises exception if
|
51
|
-
expect { subject.
|
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
|
55
|
-
subject.
|
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.
|
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.
|
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.
|
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.
|
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? :
|
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? :
|
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
|
-
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
18
|
-
expect(rules.size).to eq(1)
|
13
|
+
allow :new, on: :all
|
19
14
|
end
|
15
|
+
subject.new('user')
|
16
|
+
end
|
20
17
|
|
21
|
-
|
22
|
-
|
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
|
-
|
26
|
-
|
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 '
|
30
|
-
expect(
|
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 '
|
35
|
-
|
36
|
-
subject.
|
37
|
-
|
38
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
expect(
|
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 '
|
55
|
-
|
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 '
|
59
|
-
|
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 '#
|
64
|
-
|
65
|
-
subject
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
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
|
-
|
77
|
-
|
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
|
81
|
-
expect { permissions.permit!
|
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
|
|
data/spec/cannie/rule_spec.rb
CHANGED
@@ -2,41 +2,62 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Cannie::Rule do
|
4
4
|
describe '#initialize' do
|
5
|
-
it 'stores passed
|
6
|
-
|
7
|
-
rule
|
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 :
|
13
|
-
expect(rule.subject).to eq(
|
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 '
|
23
|
-
let(:
|
24
|
-
|
25
|
-
|
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
|
30
|
-
|
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
|
34
|
-
|
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
|
38
|
-
rule = described_class.new(:
|
39
|
-
expect(rule.
|
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.
|
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-
|
11
|
+
date: 2013-09-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|