bali 2.4.0 → 6.0.1

Sign up to get free protection for your applications and to get access to all the features.
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