admission 0.1.6

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.
@@ -0,0 +1,94 @@
1
+ # module Admission::Rails
2
+ #
3
+ # class ActionsAdmission
4
+ #
5
+ # attr_reader :resource_actions, :resource_loader
6
+ #
7
+ # def initialize controller_class
8
+ # @controller_class = controller_class
9
+ # end
10
+ #
11
+ # def authorize prepend:false, **options
12
+ # callback_name = prepend ? :prepend_before_action : :before_action
13
+ # options = options.slice(:only, :except, :if, :unless)
14
+ # admission = self
15
+ #
16
+ # @controller_class.send callback_name, options do |controller|
17
+ # ActionAbility.new(controller, admission).authorize!
18
+ # end
19
+ # end
20
+ #
21
+ # def resource_actions *actions, loader:nil
22
+ # @resource_actions = actions.flatten
23
+ # @resource_loader = loader || :"find_#{@controller_class.controller_name.singularize}"
24
+ # end
25
+ #
26
+ # def is_resource_action? action
27
+ # @resource_actions.present? && @resource_actions.include?(action)
28
+ # end
29
+ #
30
+ # end
31
+ #
32
+ # class ActionAbility
33
+ # attr_reader :controller
34
+ #
35
+ # def initialize controller, admission
36
+ # @controller = controller
37
+ # @admission = admission
38
+ # end
39
+ #
40
+ # def authorize!
41
+ # user.ability.can?(action_name, object) || (raise AccessDenied.new(self))
42
+ # end
43
+ #
44
+ # def user
45
+ # @controller.current_user
46
+ # end
47
+ #
48
+ # def object
49
+ # @object ||= if @admission.is_resource_action?(action_name)
50
+ # @controller.send @admission.resource_loader
51
+ # else
52
+ # @controller.class.controller_name
53
+ # end
54
+ # end
55
+ #
56
+ # def action_name
57
+ # @action_name ||= @controller.params[:action].to_sym
58
+ # end
59
+ #
60
+ # def object_to_s
61
+ # case object
62
+ # when String then ":#{object}"
63
+ # when ActiveRecord::Base
64
+ # "<#{object.class.name}>"
65
+ # else
66
+ # object.to_s
67
+ # end
68
+ # end
69
+ #
70
+ # end
71
+ #
72
+ # class AccessDenied < ::StandardError
73
+ # attr_reader :action_ability
74
+ #
75
+ # def initialize ability
76
+ # @action_ability = ability
77
+ # @message = "Nemáte oprávnění pro tuto akci. (#{ability.action_name} - #{ability.object_to_s})"
78
+ # end
79
+ #
80
+ # def to_s
81
+ # @message
82
+ # end
83
+ # end
84
+ #
85
+ # end
86
+ #
87
+ #
88
+ # ActionController::Base.instance_exec do
89
+ # def actions_admission &block
90
+ # @actions_admission ||= Admission::Rails::ActionsAdmission.new(self)
91
+ # @actions_admission.instance_exec &block if block
92
+ # @actions_admission
93
+ # end
94
+ # end
@@ -0,0 +1,112 @@
1
+ class Admission::ResourceArbitration < Admission::Arbitration
2
+
3
+ def initialize person, rules_index, request, scope_or_resource
4
+ @person = person
5
+ scope, @resource = scope_and_resource scope_or_resource
6
+ @rules_index = rules_index[scope] || {}
7
+ @request = request.to_sym
8
+ end
9
+
10
+ def make_decision from_rules, privilege
11
+ if from_rules
12
+ decision = from_rules[privilege]
13
+ if Proc === decision
14
+ if decision.instance_variable_get :@resource_arbiter
15
+ decision = @person.instance_exec @resource, *@context, &decision
16
+ else
17
+ decision = @person.instance_exec *@context, &decision
18
+ end
19
+ end
20
+
21
+ unless Admission::VALID_DECISION.include? decision
22
+ raise "invalid decision: #{decision}"
23
+ end
24
+
25
+ decision
26
+ end
27
+ end
28
+
29
+ def scope_and_resource scope_or_resource
30
+ if scope_or_resource.is_a? Symbol
31
+ [scope_or_resource]
32
+ else
33
+ [self.class.type_to_scope(scope_or_resource.class).to_sym, scope_or_resource]
34
+ end
35
+ end
36
+
37
+ def self.type_to_scope_resolution proc=nil, &block
38
+ @type_to_scope = proc || block
39
+ end
40
+
41
+ def self.type_to_scope type
42
+ scope = @type_to_scope && @type_to_scope.call(type)
43
+ scope ? scope.to_sym : :"#{type.name.downcase}s"
44
+ end
45
+
46
+ def self.nested_scope resource, scope
47
+ resource = type_to_scope resource unless resource.is_a? Symbol
48
+ "#{resource}:#{scope}".to_sym
49
+ end
50
+
51
+ class RulesBuilder < Admission::Arbitration::RulesBuilder
52
+
53
+ def allow scope, *actions, &block
54
+ raise "reserved action name #{Admission::ALL_ACTION}" if actions.include? Admission::ALL_ACTION
55
+ raise "invalid scope name" unless scope.respond_to? :to_sym
56
+ add_allowance_rule actions.flatten, (block || true), scope: scope.to_sym
57
+ end
58
+
59
+ def allow_all scope, &block
60
+ raise "invalid scope name" unless scope.respond_to? :to_sym
61
+ add_allowance_rule [Admission::ALL_ACTION], (block || true), scope: scope.to_sym
62
+ end
63
+
64
+ def forbid scope, *actions
65
+ raise "reserved action name #{Admission::ALL_ACTION}" if actions.include? Admission::ALL_ACTION
66
+ raise "invalid scope name" unless scope.respond_to? :to_sym
67
+ add_allowance_rule actions.flatten, :forbidden, scope: scope.to_sym
68
+ end
69
+
70
+ def allow_resource resource, *actions, &block
71
+ raise "reserved action name #{Admission::ALL_ACTION}" if actions.include? Admission::ALL_ACTION
72
+ raise "block not given" unless block
73
+ block.instance_variable_set :@resource_arbiter, true
74
+ scope = case resource
75
+ when Symbol then resource
76
+ when Array then nested_scope(*resource)
77
+ else type_to_scope(resource)
78
+ end
79
+ add_allowance_rule actions.flatten, block, scope: scope
80
+ end
81
+
82
+ def type_to_scope resource
83
+ Admission::ResourceArbitration.type_to_scope resource
84
+ end
85
+
86
+ def nested_scope resource, scope
87
+ Admission::ResourceArbitration.nested_scope resource, scope
88
+ end
89
+
90
+ def create_index
91
+ index_instance = @rules.reduce Hash.new do |index, allowance|
92
+ privilege = allowance[:privilege]
93
+ actions = allowance[:actions]
94
+ scope = allowance[:scope]
95
+ arbiter = allowance[:arbiter]
96
+
97
+ scope_index = (index[scope] ||= {})
98
+
99
+ actions.each do |action|
100
+ action_index = (scope_index[action] ||= {})
101
+ action_index[privilege] = arbiter
102
+ end
103
+
104
+ index
105
+ end
106
+
107
+ index_instance.freeze
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -0,0 +1,38 @@
1
+ class Admission::Status
2
+
3
+ attr_reader :person, :privileges, :rules
4
+
5
+ def initialize person, privileges, rules, arbiter
6
+ @person = person
7
+ @privileges = (privileges.nil? || privileges.empty?) ? nil : privileges
8
+ @rules = rules
9
+ @arbiter = arbiter
10
+ end
11
+
12
+ def can? *args
13
+ return false unless @privileges
14
+ process_request @arbiter.new(person, rules, *args)
15
+ end
16
+
17
+ def cannot? *args
18
+ !can?(*args)
19
+ end
20
+
21
+ def request! *args
22
+ can?(*args) || begin
23
+ exception = Admission::Denied.new self, *args
24
+ yield exception if block_given?
25
+ raise exception
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def process_request arbitration
32
+ privileges.any? do |privilege|
33
+ arbitration.prepare_sitting *privilege.context
34
+ arbitration.rule_per_privilege(privilege).eql? true
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,3 @@
1
+ module Admission
2
+ VERSION = '0.1.6'
3
+ end
@@ -0,0 +1,2 @@
1
+ require_relative '../spec_helper'
2
+ require_relative '../test_context/index'
@@ -0,0 +1,119 @@
1
+ require_relative '_helper'
2
+
3
+ RSpec.describe 'actions_arbitrating' do
4
+
5
+ def arbitration request, context=nil
6
+ person = Person.new 'person', Person::MALE, [:czech]
7
+ arbitration = Admission::Arbitration.new person, ACTIONS_RULES, request
8
+ arbitration.prepare_sitting *context
9
+ arbitration
10
+ end
11
+
12
+ def privilege *args, context: nil
13
+ p = Admission::Privilege.get_from_order PRIVILEGES_ORDER, *args
14
+ p = p.dup_with_context context if context
15
+ p
16
+ end
17
+
18
+ def rule request, privilege
19
+ arbitration(request, privilege.context).rule_per_privilege privilege
20
+ end
21
+
22
+ it 'allows human to do anything' do
23
+ expect(
24
+ rule :anything, privilege(:human)
25
+ ).to eql(true)
26
+ end
27
+
28
+ it 'disallows woman to do anything' do
29
+ person = Person.new 'person', Person::FEMALE, [:czech]
30
+ arbitration = Admission::Arbitration.new person, ACTIONS_RULES, :anything
31
+ arbitration.prepare_sitting
32
+ expect(
33
+ arbitration.rule_per_privilege privilege(:human)
34
+ ).to eql(false)
35
+ end
36
+
37
+ it 'allow woman-count to do anything in her country' do
38
+ person = Person.new 'person', Person::FEMALE, [:czech]
39
+ arbitration = Admission::Arbitration.new person, ACTIONS_RULES, :anything
40
+ arbitration.prepare_sitting :czech
41
+ expect(
42
+ arbitration.rule_per_privilege privilege(:human, :count, context: [:czech])
43
+ ).to eql(true)
44
+ end
45
+
46
+ it 'allows only king to raise taxes' do
47
+ expect(
48
+ rule :raise_taxes, privilege(:human)
49
+ ).to eql(:forbidden)
50
+
51
+ expect(
52
+ rule :raise_taxes, privilege(:human, :count)
53
+ ).to eql(:forbidden)
54
+
55
+ expect(
56
+ rule :raise_taxes, privilege(:human, :king)
57
+ ).to eql(true)
58
+ end
59
+
60
+ it 'allows count and king to impose corvee in his countries' do
61
+ expect(
62
+ rule :impose_corvee,
63
+ privilege(:human, :count, context: [:czech])
64
+ ).to eql(true)
65
+
66
+ expect(
67
+ rule :impose_corvee,
68
+ privilege(:human, :king, context: [:czech])
69
+ ).to eql(true)
70
+ end
71
+
72
+ it 'forbids count and king to impose corvee outside his countries' do
73
+ expect(
74
+ rule :impose_corvee,
75
+ privilege(:human, :count, context: [:taiwan])
76
+ ).to eql(:forbidden)
77
+
78
+ expect(
79
+ rule :impose_corvee,
80
+ privilege(:human, :king, context: [:taiwan])
81
+ ).to eql(:forbidden)
82
+ end
83
+
84
+ it 'forbids any human to impose a draft' do
85
+ expect(
86
+ rule :impose_draft, privilege(:human)
87
+ ).to eql(:forbidden)
88
+
89
+ expect(
90
+ rule :impose_draft, privilege(:human, :count)
91
+ ).to eql(:forbidden)
92
+
93
+ expect(
94
+ rule :impose_draft, privilege(:human, :king)
95
+ ).to eql(:forbidden)
96
+ end
97
+
98
+ it 'allows lord to impose draft' do
99
+ expect(
100
+ rule :impose_draft,
101
+ privilege(:vassal, :lord)
102
+ ).to eql(true)
103
+ end
104
+
105
+ it 'forbids emperor to impose draft because of inheritance' do
106
+ expect(
107
+ rule :impose_draft,
108
+ privilege(:emperor)
109
+ ).to eql(:forbidden)
110
+ end
111
+
112
+ it 'allows emperor to act as god' do
113
+ expect(
114
+ rule :act_as_god,
115
+ privilege(:emperor)
116
+ ).to eql(true)
117
+ end
118
+
119
+ end
@@ -0,0 +1,246 @@
1
+ require_relative '_helper'
2
+
3
+ RSpec.describe 'resources_arbitrating' do
4
+
5
+ let(:person){ Person.new 'person', Person::MALE, [:czech] }
6
+ let(:female){ Person.new 'female', Person::FEMALE, [:czech] }
7
+
8
+ def arbitration scope, action, context=nil
9
+ arbitration = Admission::ResourceArbitration.new person, RESOURCE_RULES, action, scope
10
+ arbitration.prepare_sitting *context
11
+ arbitration
12
+ end
13
+
14
+ def privilege *args, context: nil
15
+ p = Admission::Privilege.get_from_order PRIVILEGES_ORDER, *args
16
+ p = p.dup_with_context context if context
17
+ p
18
+ end
19
+
20
+ def actions_rule action, privilege
21
+ arbitration(:actions, action, privilege.context).rule_per_privilege privilege
22
+ end
23
+
24
+ def rule scope, action, privilege
25
+ arbitration(scope, action, privilege.context).rule_per_privilege privilege
26
+ end
27
+
28
+ describe 'actions scope' do
29
+
30
+ it 'allows human to do anything' do
31
+ expect(
32
+ actions_rule :anything, privilege(:human)
33
+ ).to eql(true)
34
+ end
35
+
36
+ it 'disallows woman to do anything' do
37
+ arbitration = Admission::ResourceArbitration.new female, RESOURCE_RULES,
38
+ :anything, :actions
39
+ arbitration.prepare_sitting
40
+ expect(
41
+ arbitration.rule_per_privilege privilege(:human)
42
+ ).to eql(false)
43
+ end
44
+
45
+ it 'allow woman-count to do anything in her country' do
46
+ arbitration = Admission::ResourceArbitration.new female, RESOURCE_RULES,
47
+ :anything, :actions
48
+ arbitration.prepare_sitting :czech
49
+ expect(
50
+ arbitration.rule_per_privilege privilege(:human, :count, context: [:czech])
51
+ ).to eql(true)
52
+ end
53
+
54
+ it 'allows only king to raise taxes' do
55
+ expect(
56
+ actions_rule :raise_taxes, privilege(:human)
57
+ ).to eql(:forbidden)
58
+
59
+ expect(
60
+ actions_rule :raise_taxes, privilege(:human, :count)
61
+ ).to eql(:forbidden)
62
+
63
+ expect(
64
+ actions_rule :raise_taxes, privilege(:human, :king)
65
+ ).to eql(true)
66
+ end
67
+
68
+ it 'allows count and king to impose corvee in his countries' do
69
+ expect(
70
+ actions_rule :impose_corvee,
71
+ privilege(:human, :count, context: [:czech])
72
+ ).to eql(true)
73
+
74
+ expect(
75
+ actions_rule :impose_corvee,
76
+ privilege(:human, :king, context: [:czech])
77
+ ).to eql(true)
78
+ end
79
+
80
+ it 'forbids count and king to impose corvee outside his countries' do
81
+ expect(
82
+ actions_rule :impose_corvee,
83
+ privilege(:human, :count, context: [:taiwan])
84
+ ).to eql(:forbidden)
85
+
86
+ expect(
87
+ actions_rule :impose_corvee,
88
+ privilege(:human, :king, context: [:taiwan])
89
+ ).to eql(:forbidden)
90
+ end
91
+
92
+ it 'forbids any human to impose a draft' do
93
+ expect(
94
+ actions_rule :impose_draft, privilege(:human)
95
+ ).to eql(:forbidden)
96
+
97
+ expect(
98
+ actions_rule :impose_draft, privilege(:human, :count)
99
+ ).to eql(:forbidden)
100
+
101
+ expect(
102
+ actions_rule :impose_draft, privilege(:human, :king)
103
+ ).to eql(:forbidden)
104
+ end
105
+
106
+ it 'allows lord to impose draft' do
107
+ expect(
108
+ actions_rule :impose_draft, privilege(:vassal, :lord)
109
+ ).to eql(true)
110
+ end
111
+
112
+ it 'forbids emperor to impose draft because of inheritance' do
113
+ expect(
114
+ actions_rule :impose_draft, privilege(:emperor)
115
+ ).to eql(:forbidden)
116
+ end
117
+
118
+ it 'allows emperor to act as god' do
119
+ expect(
120
+ actions_rule :act_as_god, privilege(:emperor)
121
+ ).to eql(true)
122
+ end
123
+
124
+ end
125
+
126
+ describe 'resources scope' do
127
+
128
+ it 'allows vassal to see only himself' do
129
+ expect(
130
+ rule person, :show, privilege(:vassal)
131
+ ).to eql(true)
132
+
133
+ person = Person.new 'person', Person::FEMALE, [:czech]
134
+ expect(
135
+ rule person, :show, privilege(:vassal)
136
+ ).to eql(false)
137
+ end
138
+
139
+ it 'passes nil as argument if resource-arbiter accessed by name-scope' do
140
+ expect{
141
+ rule :persons, :show, privilege(:vassal)
142
+ }.to raise_error('person is nil')
143
+ end
144
+
145
+ it 'allows vassal to list persons only per his countries' do
146
+ expect(
147
+ rule :persons, :index, privilege(:vassal, context: [:czech])
148
+ ).to eql(true)
149
+
150
+ expect(
151
+ rule :persons, :index, privilege(:vassal, context: [:taiwan])
152
+ ).to eql(false)
153
+ end
154
+
155
+ it 'allows access scope-arbiter by resource' do
156
+ expect(
157
+ rule person, :index, privilege(:vassal, context: [:czech])
158
+ ).to eql(true)
159
+ end
160
+
161
+ it 'allows lord to see any person' do
162
+ expect(
163
+ rule person, :show, privilege(:vassal, :lord)
164
+ ).to eql(true)
165
+
166
+ expect(
167
+ rule female, :show, privilege(:vassal, :lord)
168
+ ).to eql(true)
169
+ end
170
+
171
+ it 'allows lord to list persons from his country' do
172
+ expect(
173
+ rule person, :index, privilege(:vassal, context: [:czech])
174
+ ).to eql(true)
175
+
176
+ expect(
177
+ rule :persons, :index, privilege(:vassal, context: [:czech])
178
+ ).to eql(true)
179
+
180
+ expect(
181
+ rule person, :index, privilege(:vassal, context: [:taiwan])
182
+ ).to eql(false)
183
+ end
184
+
185
+ it 'allows lord to update person that is from his country' do
186
+ expect(
187
+ rule female, :update,
188
+ privilege(:vassal, :lord, context: [:czech])
189
+ ).to eql(true)
190
+
191
+ expect(
192
+ rule female, :update, privilege(:vassal, :lord)
193
+ ).to eql(false)
194
+ end
195
+
196
+ it 'disallows lord to update person not from his country' do
197
+ female = Person.new 'person', Person::FEMALE, [:taiwan]
198
+
199
+ expect(
200
+ rule female, :update,
201
+ privilege(:vassal, :lord, context: [:czech])
202
+ ).to eql(false)
203
+
204
+ expect(
205
+ rule female, :update,
206
+ privilege(:vassal, :lord, context: [:taiwan])
207
+ ).to eql(false)
208
+ end
209
+
210
+ it 'ensures lord cannot update person accessing him by scope-name' do
211
+ expect(
212
+ rule :persons, :update, privilege(:vassal, :lord)
213
+ ).to eql(false)
214
+ end
215
+
216
+ it 'disallows vassal to update person' do
217
+ expect(
218
+ rule person, :update, privilege(:vassal, context: [:czech])
219
+ ).to eql(false)
220
+ end
221
+
222
+ it 'allows lord to destroy person from his country' do
223
+ female = Person.new 'person', Person::FEMALE, [:taiwan]
224
+
225
+ expect(
226
+ rule person, :destroy,
227
+ privilege(:vassal, :lord, context: [:czech])
228
+ ).to eql(true)
229
+
230
+ expect(
231
+ rule female, :destroy,
232
+ privilege(:vassal, :lord, context: [:czech])
233
+ ).to eql(false)
234
+ end
235
+
236
+ it 'disallows lord to destroy apache helicopter' do
237
+ helicopter = Person.new 'person', Person::APACHE_HELICOPTER, [:czech]
238
+ expect(
239
+ rule helicopter, :destroy,
240
+ privilege(:vassal, :lord, context: [:czech])
241
+ ).to eql(false)
242
+ end
243
+
244
+ end
245
+
246
+ end