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
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'active_support/lazy_load_hooks'
5
+
6
+ ActiveSupport.on_load :action_controller do
7
+ require "bali"
8
+ ::ActionController::Base.send :include, Bali::Statics::Authorizer
9
+ ::ActionController::Base.send :include, Bali::Statics::ScopeRuler
10
+
11
+ if defined? ::ActionController::API
12
+ ::ActionController::API.send :include, Bali::Statics::Authorizer
13
+ ::ActionController::API.send :include, Bali::Statics::ScopeRuler
14
+ end
15
+ end
16
+ rescue LoadError
17
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'active_support/lazy_load_hooks'
5
+
6
+ ActiveSupport.on_load :action_view do
7
+ require "bali"
8
+ ::ActionView::Base.send :include, Bali::Statics::Authorizer
9
+ ::ActionView::Base.send :include, Bali::Statics::ScopeRuler
10
+ end
11
+ rescue LoadError
12
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'active_support/lazy_load_hooks'
5
+
6
+ ActiveSupport.on_load :active_record do
7
+ require "bali"
8
+ ::ActiveRecord::Base.send :extend, Bali::Statics::Record
9
+ end
10
+ rescue LoadError
11
+ end
@@ -0,0 +1,13 @@
1
+ require "bali"
2
+ require "rails"
3
+
4
+ module Bali
5
+ class Railtie < ::Rails::Railtie
6
+ railtie_name :bali
7
+
8
+ rake_tasks do
9
+ path = File.expand_path(__dir__)
10
+ Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,110 @@
1
+ class Bali::Role
2
+ RIGHTS = [
3
+ INHERIT = :inherit,
4
+ DEFAULT_DENY = :default_deny,
5
+ DEFAULT_ALLOW = :default_allow
6
+ ].freeze
7
+
8
+ IDENTIFIER_CLASSES = [
9
+ String,
10
+ Symbol,
11
+ NilClass,
12
+ ].freeze
13
+
14
+ attr_accessor :name
15
+ attr_accessor :cans, :cants
16
+ attr_reader :scope
17
+
18
+ attr_accessor :can_all
19
+ alias :can_all? :can_all
20
+
21
+ attr_accessor :right_level
22
+
23
+ def self.formalize(object)
24
+ case object
25
+ when *IDENTIFIER_CLASSES then [object]
26
+ when Array then (object.count == 0 ? nil : object)
27
+ else formalize(extract_roles_from_object(object))
28
+ end
29
+ end
30
+
31
+ def self.extract_roles_from_object(object)
32
+ method_name = object.class.role_field_for_authorization
33
+
34
+ method_name ?
35
+ formalize(object.send(method_name)) :
36
+ formalize(nil)
37
+ end
38
+
39
+ def initialize(name)
40
+ @name = name.to_sym if name
41
+ @right_level = INHERIT
42
+
43
+ @cans = {}
44
+ @cants = {}
45
+ end
46
+
47
+ def can_all?
48
+ right_level == DEFAULT_ALLOW
49
+ end
50
+
51
+ def cant_all?
52
+ right_level == DEFAULT_DENY
53
+ end
54
+
55
+ ##### DSL METHODS
56
+ def can(*args, &block)
57
+ add :can, *args, block
58
+ end
59
+
60
+ def cant(*args, &block)
61
+ add :cant, *args, block
62
+ end
63
+
64
+ def can_all
65
+ @right_level = DEFAULT_ALLOW
66
+ end
67
+
68
+ def cant_all
69
+ @right_level = DEFAULT_DENY
70
+ end
71
+
72
+ def scope(&block)
73
+ return @scope unless block_given?
74
+
75
+ raise Bali::DslError, "Block can't be scoped inside a role" if name
76
+ @scope = block
77
+ end
78
+
79
+ def add(term, *operations, block)
80
+ operations.each do |operation|
81
+ rule = Bali::Rule.new(term, operation)
82
+ rule.conditional = block if block
83
+ self << rule
84
+ end
85
+ end
86
+ ##### DSL METHODS
87
+
88
+ def << rule
89
+ operation = rule.operation.to_sym
90
+
91
+ if rule.term == :cant
92
+ cants[operation] = rule
93
+ cans.delete operation
94
+ else
95
+ cans[operation] = rule
96
+ cants.delete operation
97
+ end
98
+ end
99
+
100
+ def find_rule(term, operation)
101
+ case term
102
+ when :can then cans[operation.to_sym]
103
+ when :cant then cants[operation.to_sym]
104
+ end
105
+ end
106
+
107
+ def rules
108
+ cans.values + cants.values
109
+ end
110
+ end
@@ -0,0 +1,39 @@
1
+ module RSpec
2
+ module Matchers
3
+ module BuiltIn
4
+ class AbleToMatcher < Be
5
+ def initialize(operation, class_or_record = nil)
6
+ @operation = operation
7
+ @class_or_record = class_or_record
8
+ end
9
+
10
+ def matches?(actor)
11
+ if @class_or_record
12
+ rule_class = "#{@class_or_record.class.name}#{Bali.config.suffix}".constantize
13
+ rule_class.can?(actor, @operation, @class_or_record)
14
+ else
15
+ @class_or_record = actor
16
+ rule_class = "#{@class_or_record.name}#{Bali.config.suffix}".constantize
17
+ rule_class.can?(nil, @operation, @class_or_record)
18
+ end
19
+ end
20
+
21
+ def failure_message
22
+ "expected to be able to #{@operation}, but actually cannot"
23
+ end
24
+
25
+ def failure_message_when_negated
26
+ "expected not to be able to #{@operation}, but actually can"
27
+ end
28
+
29
+ def description
30
+ "be able to #{@operation}"
31
+ end
32
+ end
33
+ end
34
+
35
+ def be_able_to(*args)
36
+ BuiltIn::AbleToMatcher.new(*args)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,17 @@
1
+ # This class represents a rule.
2
+ # can :delete
3
+ # A rule can also contains conditional part
4
+ class Bali::Rule
5
+ attr_reader :term
6
+ attr_reader :operation
7
+ attr_accessor :conditional
8
+
9
+ def initialize(term, operation)
10
+ @term = term
11
+ @operation = operation
12
+ end
13
+
14
+ def conditional?
15
+ @is_conditional ||= !!conditional
16
+ end
17
+ end
@@ -0,0 +1,38 @@
1
+ # This class represents all roles, and its rules, for a resource
2
+ class Bali::Ruler
3
+ attr_reader :model_class
4
+ attr_accessor :roles
5
+
6
+ private :model_class
7
+
8
+ def self.for(record_class)
9
+ rule_class = Bali::Rules.for(record_class)
10
+ rule_class.ruler if rule_class
11
+ end
12
+
13
+ def initialize(model_class)
14
+ @model_class = model_class
15
+ @roles = {}
16
+ @roles[nil] = Bali::Role.new(nil)
17
+ end
18
+
19
+ def << role
20
+ @roles[role.name] = role
21
+ end
22
+
23
+ def [] role
24
+ symbolized_role = role.to_sym if role
25
+ @roles[symbolized_role]
26
+ end
27
+
28
+ def find_or_create_role role_name
29
+ role = self[role_name]
30
+
31
+ if role.nil?
32
+ role = Bali::Role.new(role_name)
33
+ self << role
34
+ end
35
+
36
+ role
37
+ end
38
+ end
@@ -0,0 +1,51 @@
1
+ require "forwardable"
2
+
3
+ class Bali::Rules
4
+ extend Bali::Statics::Authorizer
5
+ extend Bali::Statics::ScopeRuler
6
+
7
+ class << self
8
+ extend Forwardable
9
+
10
+ attr_writer :current_role
11
+ attr_reader :ruler
12
+
13
+ def_delegators :inheritable_role, :scope, :scope
14
+ def_delegators :inheritable_role, :can, :can
15
+ def_delegators :inheritable_role, :cant, :cant
16
+ def_delegators :inheritable_role, :cant_all, :cant_all
17
+ def_delegators :inheritable_role, :can_all, :can_all
18
+ end
19
+
20
+ def self.for(record_class)
21
+ rule_maker_cls_str = "#{record_class}#{Bali.config.suffix}"
22
+ rule_maker_cls_str.safe_constantize
23
+ end
24
+
25
+ def self.model_class
26
+ class_name = to_s
27
+ suffix = Bali.config.suffix
28
+ rule_class_maker_str = class_name[0...class_name.length - suffix.length]
29
+ rule_class_maker_str.constantize
30
+ end
31
+
32
+ def self.role(*role_names, &block)
33
+ role_names.each do |role_name|
34
+ if Bali::Role::IDENTIFIER_CLASSES.include?(role_name.class)
35
+ role = ruler.find_or_create_role role_name
36
+ role.instance_eval(&block)
37
+ else
38
+ raise Bali::DslError, "Cannot define role using #{param.class}. " +
39
+ "Please use either a Symbol, a String or nil"
40
+ end
41
+ end
42
+ end
43
+
44
+ def self.ruler
45
+ @ruler ||= Bali::Ruler.new(model_class)
46
+ end
47
+
48
+ def self.inheritable_role
49
+ ruler[nil]
50
+ end
51
+ end
@@ -0,0 +1,2 @@
1
+ # This class is deprecated. Please use Bali::Statics::Record instead of this.
2
+ Bali::Statics::ActiveRecord = Bali::Statics::Record
@@ -0,0 +1,65 @@
1
+ module Bali::Statics::Authorizer
2
+ module HelperFunctions
3
+ extend self
4
+
5
+ def not_true_actor?(actor)
6
+ Symbol === actor || String === actor
7
+ end
8
+
9
+ def find_actor(actor, operation, record = nil)
10
+ return actor unless not_true_actor?(actor)
11
+ end
12
+
13
+ def find_operation(actor, operation, record = nil)
14
+ not_true_actor?(actor) ?
15
+ actor :
16
+ operation
17
+ end
18
+
19
+ def find_record(actor, operation, record = nil)
20
+ if not_true_actor?(actor) && record.nil?
21
+ operation
22
+ elsif actor.is_a?(ActiveRecord::Base) && record.nil?
23
+ actor.class
24
+ else
25
+ record
26
+ end
27
+ end
28
+
29
+ def determine_model_class!(obj, arg1, arg2, arg3)
30
+ if arg2.nil? && arg3.nil? && !obj.respond_to?(:model_class)
31
+ raise Bali::Error, "Cannot perform checking when the actor is not known"
32
+ end
33
+ arg3 = obj.model_class if (arg2.nil? || arg1.nil?) && arg3.nil?
34
+ arg3
35
+ end
36
+
37
+ def check(term, obj, arg1, arg2, arg3)
38
+ # try to infer current user if only passing one arg
39
+ if arg2.nil? && arg3.nil? && obj.respond_to?(:current_user)
40
+ arg2 = arg1
41
+ arg1 = obj.current_user
42
+ elsif arg3.nil? && obj.respond_to?(:current_user)
43
+ arg3 = arg2
44
+ arg2 = arg1
45
+ arg1 = obj.current_user
46
+ end
47
+
48
+ arg3 = HelperFunctions.determine_model_class! obj, arg1, arg2, arg3
49
+
50
+ actor = HelperFunctions.find_actor(arg1, arg2, arg3)
51
+ operation = HelperFunctions.find_operation(arg1, arg2, arg3)
52
+ record = HelperFunctions.find_record(arg1, arg2, arg3)
53
+
54
+ Bali::Judge.check(term, actor, operation, record)
55
+ end
56
+ end
57
+
58
+ def can?(arg1, arg2 = nil, arg3 = nil)
59
+ HelperFunctions.check(:can, self, arg1, arg2, arg3)
60
+ end
61
+
62
+ def cant?(arg1, arg2 = nil, arg3 = nil)
63
+ HelperFunctions.check(:cant, self, arg1, arg2, arg3)
64
+ end
65
+ end
@@ -0,0 +1,13 @@
1
+ module Bali::Statics::Record
2
+ def self.extended(cls)
3
+ cls.class_eval do
4
+ class << self
5
+ attr_accessor :role_field_for_authorization
6
+ end
7
+
8
+ def self.extract_roles_from method_name
9
+ @role_field_for_authorization = method_name
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,39 @@
1
+ module Bali::Statics::ScopeRuler
2
+ module HelperFunctions
3
+ extend self
4
+
5
+ def extract_data_and_actor(obj, arg1, arg2 = nil)
6
+ if arg2.nil?
7
+ data = arg1
8
+ if obj.respond_to?(:current_user)
9
+ actor = obj.current_user
10
+ end
11
+ else
12
+ data, actor = arg1, arg2
13
+ end
14
+
15
+ return data, actor
16
+ end
17
+
18
+ def scope_for(relation)
19
+ rule_class = Bali::Rules.for(relation.model)
20
+ return unless rule_class
21
+
22
+ rule_class.inheritable_role.scope
23
+ end
24
+ end
25
+
26
+ def rule_scope(arg1, arg2 = nil)
27
+ data, actor = HelperFunctions.extract_data_and_actor(self, arg1, arg2)
28
+ return unless data
29
+
30
+ scope = HelperFunctions.scope_for(data)
31
+ scoped_data = case scope.arity
32
+ when 0 then scope.call
33
+ when 1 then scope.call(data)
34
+ when 2 then scope.call(data, actor)
35
+ end
36
+
37
+ scoped_data || data
38
+ end
39
+ end
@@ -0,0 +1,9 @@
1
+ namespace :bali do
2
+ desc "Print all rules nicely"
3
+ task print_rules: :environment do
4
+ rules_path = Bali.config.rules_path
5
+ Dir.glob("#{rules_path}/**/*.rb").each { |f| load f }
6
+
7
+ $stdout.puts Bali::Printer.printable
8
+ end
9
+ end