bali 2.4.0 → 6.0.1

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.
Files changed (54) hide show
  1. checksums.yaml +5 -13
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +44 -2
  5. data/gemfiles/Gemfile-rails.5.0.x +7 -0
  6. data/gemfiles/Gemfile-rails.5.1.x +6 -0
  7. data/gemfiles/Gemfile-rails.5.2.x +6 -0
  8. data/gemfiles/Gemfile-rails.6.0.x +5 -0
  9. data/gemfiles/Gemfile-rails.edge +14 -0
  10. data/lib/bali.rb +33 -22
  11. data/lib/bali/config.rb +12 -0
  12. data/lib/bali/dsl_error.rb +3 -0
  13. data/lib/bali/{foundations/exceptions/bali_error.rb → error.rb} +0 -0
  14. data/lib/bali/judge.rb +221 -0
  15. data/lib/bali/printer.rb +42 -31
  16. data/lib/bali/rails/action_controller.rb +17 -0
  17. data/lib/bali/rails/action_view.rb +12 -0
  18. data/lib/bali/rails/active_record.rb +11 -0
  19. data/lib/bali/railtie.rb +13 -0
  20. data/lib/bali/role.rb +110 -0
  21. data/lib/bali/rspec/able_to_matcher.rb +39 -0
  22. data/lib/bali/rule.rb +17 -0
  23. data/lib/bali/ruler.rb +38 -0
  24. data/lib/bali/rules.rb +51 -0
  25. data/lib/bali/statics/active_record.rb +2 -0
  26. data/lib/bali/statics/authorizer.rb +65 -0
  27. data/lib/bali/statics/record.rb +13 -0
  28. data/lib/bali/statics/scope_ruler.rb +39 -0
  29. data/lib/bali/tasks/bali/print_rules.rake +9 -0
  30. data/lib/bali/version.rb +1 -1
  31. data/lib/generators/rails/USAGE +8 -0
  32. data/lib/generators/rails/rules_generator.rb +17 -0
  33. data/lib/generators/rails/templates/rules.rb +4 -0
  34. data/lib/generators/rspec/rules_generator.rb +12 -0
  35. data/lib/generators/rspec/templates/rules_spec.rb +7 -0
  36. metadata +131 -49
  37. data/lib/bali/dsl/map_rules_dsl.rb +0 -75
  38. data/lib/bali/dsl/rules_for_dsl.rb +0 -130
  39. data/lib/bali/foundations/all_foundations.rb +0 -17
  40. data/lib/bali/foundations/exceptions/authorization_error.rb +0 -38
  41. data/lib/bali/foundations/exceptions/dsl_error.rb +0 -3
  42. data/lib/bali/foundations/exceptions/objection_error.rb +0 -3
  43. data/lib/bali/foundations/judger/judge.rb +0 -329
  44. data/lib/bali/foundations/judger/negative_judge.rb +0 -40
  45. data/lib/bali/foundations/judger/positive_judge.rb +0 -41
  46. data/lib/bali/foundations/role_extractor.rb +0 -61
  47. data/lib/bali/foundations/rule/rule.rb +0 -55
  48. data/lib/bali/foundations/rule/rule_class.rb +0 -54
  49. data/lib/bali/foundations/rule/rule_group.rb +0 -91
  50. data/lib/bali/integrators/all_integrators.rb +0 -8
  51. data/lib/bali/integrators/rule_class_integrator.rb +0 -27
  52. data/lib/bali/integrators/rule_group_integrator.rb +0 -29
  53. data/lib/bali/integrators/rule_integrator.rb +0 -56
  54. data/lib/bali/objector.rb +0 -173
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- M2I3NWNjNmVlNWM4YzQyMGU0YzAyNzBmZGJmNTBjMTUxMGYxOWMxYg==
5
- data.tar.gz: !binary |-
6
- MzhkMjlhYzA4NDU3M2M4ODE1ODkxZTU0ZDA3NDJjZGUwODNmOTZhYg==
2
+ SHA256:
3
+ metadata.gz: 5f2c3170fb8da9fc0a76becddae855901f97cef723cd9db86073e0e2c15d177c
4
+ data.tar.gz: e9555504164bfb3155d281cfbf4d40629c347d178b76b7eaed2c83caacdcfc4a
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- MTJhOTRkODJhMWM5OTIzNjA0NTQ0OWVjMTMzOTgwZDFlZDBhZjQzNjQ0N2Y2
10
- YWViMDExYzMyZjI5Y2YyOTYxNDdhZWJmYWU5YTVlNDYwODgwZDA1NDhhZWRm
11
- NTliMTI1ZGEyMjNjY2QyNGEwNTM5YjNjNDc5OGIxYjk2MTYyNTI=
12
- data.tar.gz: !binary |-
13
- NTMzMzBjNTczZmM2MDE1YmI0NjA0ZjY1MDRhZGZmNWRiNTRjZGYzMDRmMWI4
14
- Zjk4ZTEzNGZkZDg0NzFmZjg0YjYwNDQ2OGVlYjgwNzkwNTY4MGFlZDE1NjI3
15
- ZDI1ZjlhNmFhODA4OGQzZDFhMjBkNjdjMjk1ZTI0YTkzZmQ4MTY=
6
+ metadata.gz: d2241cfc888b7b1c3ea4210c6a0dfc26ba790e8c190631b86d616000fbe4e927bea425f73b1547025c849f9ffc6b01c9f5389129ba9707258c79bf6a31c2c248
7
+ data.tar.gz: 472f04be142d2e7c42549f2927218c3ca57e880c11c86f2a6d409d0682ea76303299a33f065e070fa8ab38821a408a5ad37507f5d7189210d5baf68c231d4be4
data/.gitignore CHANGED
@@ -7,10 +7,12 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ /spec/test_app/tmp
10
11
 
11
12
  *.swp
12
13
  *.swo
13
14
  *.gemspec
14
15
  *.gem
16
+ *.log
15
17
 
16
18
  plugins
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --format documentation
2
2
  --color
3
+ --require spec_helper
@@ -1,4 +1,46 @@
1
1
  language: ruby
2
+
3
+ env:
4
+ global:
5
+ - CC_TEST_REPORTER_ID=76cedf4d3aa437009b269ff68b901102ca24baeb46245eb94fca42e21bba20a7
6
+
2
7
  rvm:
3
- - 2.2.2
4
- before_install: gem install bundler -v 1.10.6
8
+ - 2.6.5
9
+
10
+ before_install:
11
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
12
+ - chmod +x ./cc-test-reporter
13
+ - ./cc-test-reporter before-build
14
+ - sudo apt update -qq
15
+ - gem i bundler -v '<2'
16
+
17
+ cache: bundler
18
+
19
+ script:
20
+ - bundle exec rspec
21
+
22
+ after_script:
23
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
24
+
25
+ matrix:
26
+ include:
27
+ - rvm: 2.7.0
28
+ gemfile: gemfiles/Gemfile-rails.6.0.x
29
+ - rvm: 2.6.3
30
+ gemfile: gemfiles/Gemfile-rails.6.0.x
31
+ - rvm: 2.6.3
32
+ gemfile: gemfiles/Gemfile-rails.5.2.x
33
+ - rvm: 2.4.4
34
+ gemfile: gemfiles/Gemfile-rails.5.2.x
35
+ - rvm: 2.6.3
36
+ gemfile: gemfiles/Gemfile-rails.5.1.x
37
+ - rvm: 2.6.3
38
+ gemfile: gemfiles/Gemfile-rails.5.0.x
39
+ - rvm: 2.4.4
40
+ gemfile: gemfiles/Gemfile-rails.5.0.x
41
+ - rvm: ruby-head
42
+ gemfile: gemfiles/Gemfile-rails.edge
43
+
44
+ allow_failures:
45
+ - rvm: ruby-head
46
+ - gemfile: gemfiles/Gemfile-rails.edge
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '..'
4
+
5
+ gem 'rails', '~> 5.0.0'
6
+ gem 'rspec-rails', '~> 3.9'
7
+ gem 'sqlite3', '< 1.4'
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '..'
4
+
5
+ gem 'rails', '~> 5.1.0'
6
+ gem 'rspec-rails', '~> 3.9'
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '..'
4
+
5
+ gem 'rails', git: 'https://github.com/rails/rails', branch: '5-2-stable'
6
+ gem 'rspec-rails', '~> 3.9'
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '..'
4
+
5
+ gem 'rails', '~> 6.0.0'
@@ -0,0 +1,14 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '..'
4
+
5
+ git_source(:github) do |repo_name|
6
+ repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
7
+ "https://github.com/#{repo_name}.git"
8
+ end
9
+
10
+ github 'rails/rails' do
11
+ gem 'rails'
12
+ end
13
+
14
+ gem 'rspec-rails', '~> 4.0'
@@ -1,33 +1,44 @@
1
1
  require_relative "bali/version"
2
2
 
3
- # load the DSL syntax maker definition, ordered by proper order of invocation
4
- require_relative "bali/dsl/map_rules_dsl"
5
- require_relative "bali/dsl/rules_for_dsl"
6
-
7
- require_relative "bali/objector"
8
- require_relative "bali/printer"
3
+ begin
4
+ require "rails"
5
+ require "rails/generators"
6
+ rescue LoadError => e
7
+ # ignores
8
+ end
9
9
 
10
- require_relative "bali/foundations/all_foundations"
11
- require_relative "bali/integrators/all_integrators"
10
+ require "zeitwerk"
11
+ loader = Zeitwerk::Loader.for_gem
12
+ loader.ignore("#{__dir__}/generators")
13
+ loader.ignore("#{__dir__}/bali/rails")
14
+ loader.ignore("#{__dir__}/bali/rspec")
15
+ loader.setup
12
16
 
13
17
  module Bali
14
- # mapping class to a RuleClass
15
- RULE_CLASS_MAP = {}
18
+ extend self
19
+
20
+ def config
21
+ @config ||= Bali::Config.new
22
+ end
16
23
 
17
- # {
18
- # User: :roles,
19
- # AdminUser: :admin_roles
20
- # }
21
- TRANSLATED_SUBTARGET_ROLES = {}
24
+ def configure
25
+ yield config
26
+ end
22
27
 
23
- extend self
24
- def map_rules(&block)
25
- dsl_map_rules = Bali::MapRulesDsl.new
26
- dsl_map_rules.instance_eval(&block)
28
+ if defined? Rails
29
+ require "bali/railtie"
30
+ require "bali/rails/action_controller"
31
+ require "bali/rails/action_view"
32
+ require "bali/rails/active_record"
27
33
  end
28
34
 
29
- def clear_rules
30
- Bali::RULE_CLASS_MAP.clear
31
- true
35
+ if defined? RSpec
36
+ begin
37
+ require "rspec/matchers"
38
+ require "bali/rspec/able_to_matcher"
39
+ rescue LoadError => e
40
+ end
32
41
  end
33
42
  end
43
+
44
+ loader.eager_load
@@ -0,0 +1,12 @@
1
+ class Bali::Config
2
+ attr_accessor :rules_path
3
+ attr_accessor :suffix
4
+
5
+ def initialize
6
+ if Rails.respond_to?(:root) && Rails.root
7
+ @rules_path = Rails.root.join("app", "rules")
8
+ end
9
+
10
+ @suffix = "Rules"
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ # Error class to represent incorrect usage of syntax
2
+ class Bali::DslError < Bali::Error
3
+ end
@@ -0,0 +1,221 @@
1
+ class Bali::Judge
2
+ # Fuzy value is possible when the evaluation is not yet clear cut, for example in this case:
3
+ #
4
+ # role :finance do
5
+ # cant :view
6
+ # end
7
+ #
8
+ # others do
9
+ # can :view
10
+ # can :index
11
+ # end
12
+ #
13
+ # Checking cant view for finance role results in a definite false, but
14
+ # checking on index for the same role results in FUZY_TRUE. Eventually, all FUZY value will be
15
+ # normal TRUE or FALSE if no definite counterpart is found.
16
+
17
+ FUZY_FALSE = -2
18
+ FUZY_TRUE = 2
19
+ DEFINITE_FALSE = -1
20
+ DEFINITE_TRUE = 1
21
+
22
+ attr_accessor :term,
23
+ :actor,
24
+ :role,
25
+ :operation,
26
+ :record,
27
+ :should_cross_check
28
+
29
+ class << self
30
+ def default_judgement_value(term)
31
+ case term
32
+ when :can then false
33
+ when :cant then true
34
+ end
35
+ end
36
+
37
+ def check(term, actor_or_roles, operation, record)
38
+ if operation.nil?
39
+ # eg: user.can? :sign_in
40
+ operation = actor_or_roles
41
+ actor_or_roles = nil
42
+ end
43
+
44
+ judgement_value = default_value = default_judgement_value(term)
45
+ roles = Bali::Role.formalize actor_or_roles
46
+
47
+ roles.each do |role|
48
+ judge = Bali::Judge.new(
49
+ term: term,
50
+ role: role,
51
+ actor: actor_or_roles,
52
+ operation: operation,
53
+ record: record
54
+ )
55
+
56
+ judgement_value = judge.judgement
57
+ break if judgement_value != default_value
58
+ end
59
+
60
+ judgement_value
61
+ end
62
+ end
63
+
64
+ def initialize(term:,
65
+ actor:,
66
+ role:,
67
+ operation:,
68
+ record:,
69
+ should_cross_check: true)
70
+
71
+ @term = term
72
+ @role = role
73
+ @actor = actor
74
+ @operation = operation
75
+ @record = record
76
+ @should_cross_check = should_cross_check
77
+ end
78
+
79
+ def judgement
80
+ judgement = natural_value if no_rule_group?
81
+
82
+ if judgement.nil? && rule.nil?
83
+ cross_check_value = nil
84
+ # default if can? for undefined rule is false, after related clause
85
+ # cant be found in cant?
86
+ cross_check_value = cross_check_judge.judgement if should_cross_check
87
+
88
+ # if cross check value nil, then the reverse rule is not defined,
89
+ # let's determine whether they can do anything or not
90
+ if cross_check_value.nil?
91
+ judgement = deduce_from_defined_disposition
92
+ else
93
+ # process value from cross checking
94
+ if otherly_rule && (cross_check_value == FUZY_FALSE || cross_check_value == FUZY_TRUE)
95
+ # give chance to check at others block
96
+ @rule = otherly_rule
97
+ else
98
+ judgement = reverse_value(cross_check_value)
99
+ end
100
+ end
101
+ end
102
+
103
+ judgement ||= deduce_by_evaluation ||
104
+ deduce_from_fuzy_rules ||
105
+ natural_value
106
+
107
+ return !should_cross_check ?
108
+ judgement :
109
+
110
+ # translate response for value above to traditional true/false
111
+ # holy judgement refer to non-standard true/false being used inside Bali
112
+ # which need to be translated from other beings to know
113
+ judgement > 0
114
+ end
115
+
116
+ private
117
+
118
+ def record_class
119
+ record.is_a?(Class) ? record : record.class
120
+ end
121
+
122
+ def ruler
123
+ @ruler ||= Bali::Ruler.for record_class
124
+ end
125
+
126
+ def rule_group_for(role)
127
+ ruler.nil? ? nil : ruler[role]
128
+ end
129
+
130
+ def rule_group
131
+ @rule_group ||= rule_group_for role
132
+ end
133
+
134
+ def other_rule_group
135
+ @other_rule_group ||= rule_group_for nil
136
+ end
137
+
138
+ def no_rule_group?
139
+ rule_group.nil? && other_rule_group.nil?
140
+ end
141
+
142
+ def rule
143
+ # rule group may be nil, for when user checking for undefined rule group
144
+ @rule ||= rule_group ? rule_group.find_rule(term, operation) : nil
145
+ end
146
+
147
+ def otherly_rule
148
+ @otherly_rule ||= other_rule_group ? other_rule_group.find_rule(term, operation) : nil
149
+ end
150
+
151
+ def cross_check_judge
152
+ @cross_check_judge ||= begin
153
+ Bali::Judge.new(
154
+ term: reversed_term,
155
+ role: role,
156
+ operation: operation,
157
+ record: record,
158
+ should_cross_check: false,
159
+ actor: actor
160
+ )
161
+ end
162
+ end
163
+
164
+ def reversed_term
165
+ case term
166
+ when :can then :cant
167
+ when :cant then :can
168
+ end
169
+ end
170
+
171
+ def natural_value
172
+ term == :cant ? DEFINITE_TRUE : DEFINITE_FALSE
173
+ end
174
+
175
+ def reverse_value(value)
176
+ case value
177
+ when DEFINITE_TRUE then DEFINITE_FALSE
178
+ when DEFINITE_FALSE then DEFINITE_TRUE
179
+ when FUZY_FALSE then FUZY_TRUE
180
+ when FUZY_TRUE then FUZY_FALSE
181
+ end
182
+ end
183
+
184
+ def evaluate(rule, actor, record)
185
+ conditional = rule.conditional
186
+ evaluation = case conditional.arity
187
+ when 0 then conditional.()
188
+ when 1 then conditional.(record)
189
+ when 2 then conditional.(record, actor)
190
+ end
191
+
192
+ evaluation ? DEFINITE_TRUE : DEFINITE_FALSE
193
+ end
194
+
195
+ def deduce_by_evaluation
196
+ return unless rule
197
+
198
+ rule.conditional? ?
199
+ evaluate(rule, actor, record) :
200
+ judgement = DEFINITE_TRUE
201
+ end
202
+
203
+ def deduce_from_defined_disposition
204
+ return unless rule_group
205
+
206
+ if rule_group.can_all?
207
+ reverse_value(natural_value)
208
+ elsif rule_group.cant_all?
209
+ natural_value
210
+ end
211
+ end
212
+
213
+ def deduce_from_fuzy_rules
214
+ reversed_otherly_rule = other_rule_group.find_rule(reversed_term, operation)
215
+
216
+ if reversed_otherly_rule
217
+ term == :cant ? FUZY_TRUE : FUZY_FALSE
218
+ end
219
+ end
220
+
221
+ end
@@ -2,62 +2,73 @@ require "stringio"
2
2
  require "date"
3
3
 
4
4
  # module that would allow all defined rules to be printed for check
5
- module Bali::Printer
6
- module_function
7
-
5
+ class Bali::Printer
6
+ include Singleton
7
+
8
8
  SEPARATOR = " " * 6
9
9
  SUBTARGET_TITLE_SEPARATOR = SEPARATOR + ("-" * 80) + "\n"
10
10
 
11
- def pretty_print
11
+ def self.printable
12
+ instance.printable
13
+ end
14
+
15
+ def self.pretty_print
16
+ printable
17
+ end
18
+
19
+ def load_rule_classes
20
+ return unless Bali.config.rules_path.present?
21
+
22
+ Dir["#{Bali.config.rules_path}/**/*.rb"].each do |rule_class_path|
23
+ require rule_class_path
24
+ end
25
+ rescue LoadError
26
+ # ignore
27
+ end
28
+
29
+ def printable
30
+ load_rule_classes
12
31
  output = StringIO.new
13
32
 
14
33
  # build up the string for pretty printing rules
15
- Bali::RULE_CLASS_MAP.each do |klass, rule_class|
16
- output << "===== #{klass.to_s} =====\n\n"
17
-
18
- rule_class.rule_groups.each do |subtarget, rule_group|
19
- print_rule_group(rule_group, output)
20
- end
34
+ rule_classes = ObjectSpace.each_object(Class).select { |cls| cls < Bali::Rules }
35
+ rule_classes.sort! { |a, b| a.to_s <=> b.to_s }
36
+ rule_classes.each do |rule_class|
37
+ output << "===== #{rule_class.model_class} =====\n\n"
21
38
 
22
- if rule_class.others_rule_group.rules.any?
23
- print_rule_group(rule_class.others_rule_group, output)
39
+ rule_class.ruler.roles.each do |subtarget, role|
40
+ print_role role, output
24
41
  end
42
+
25
43
  output << "\n\n"
26
44
  end
27
45
 
28
- output << "\n\n"
29
- output << DateTime.now.strftime("Printed at %d-%m-%Y %I:%M%p %Z")
46
+ output << DateTime.now.strftime("Printed at %Y-%m-%d %I:%M%p %Z")
30
47
 
31
48
  output.string
32
49
  end
33
50
 
34
- def print_rule_group(rule_group, target_io)
35
- target = rule_group.target.to_s
36
- subtarget = rule_group.subtarget.to_s.capitalize
37
- subtarget = "Others" if subtarget == "__*__"
38
- is_zeus = rule_group.zeus?
39
- is_plant = rule_group.plant?
51
+ def print_role role, target_io
52
+ subtarget = role.name.to_s.capitalize
53
+ subtarget = "By default" if subtarget.blank?
54
+ can_all = role.can_all?
40
55
  counter = 0
41
56
 
42
- target_io << "#{SEPARATOR}#{subtarget}, can all: #{is_zeus}, cannot all: #{is_plant}\n"
57
+ target_io << "#{SEPARATOR}#{subtarget}\n"
43
58
  target_io << SUBTARGET_TITLE_SEPARATOR
44
-
45
- if is_zeus
59
+
60
+ if can_all
46
61
  target_io << "#{SEPARATOR} #{counter+=1}. #{subtarget} can do anything except if explicitly stated otherwise\n"
47
- elsif is_plant
48
- target_io << "#{SEPARATOR} #{counter+=1}. #{subtarget} cannot do anything except if explicitly stated otherwise\n"
49
62
  end
50
-
51
- rule_group.rules.each do |rule|
63
+
64
+ role.rules.each do |rule|
52
65
  written_rule = StringIO.new
53
- written_rule << "#{SEPARATOR} #{counter+=1}. #{subtarget} #{rule.auth_val} #{rule.operation} #{target}"
54
- if rule.has_decider?
66
+ written_rule << "#{SEPARATOR} #{counter+=1}. #{subtarget} #{rule.term} #{rule.operation}"
67
+ if rule.conditional?
55
68
  written_rule << ", with condition"
56
69
  end
57
70
  written_rule << "\n"
58
71
  target_io << written_rule.string
59
72
  end
60
-
61
- target_io << "\n"
62
73
  end
63
74
  end