kantox-roles 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,138 @@
1
+ module Kantox
2
+ module Helpers
3
+ # Fakes hash values
4
+ # @param [Hash] hash to fake
5
+ # @return [Hash] faked hash
6
+ def fake_data hash_or_array_or_json, deep = true
7
+ (hash_or_array_or_json.is_a?(String) ? JSON.parse(hash_or_array_or_json) : hash_or_array_or_json).each_with_index.map do |kv, index|
8
+ case kv
9
+ when Array # we iterate hash
10
+ [ kv.first.respond_to?(:to_sym) ? kv.first.to_sym : kv.first,
11
+ deep ? case kv.last
12
+ when Array then fake_data(kv.last).values
13
+ when Hash then fake_hash kv.last
14
+ else Kantox::LOCK_EMOJI
15
+ end
16
+ : Kantox::LOCK_EMOJI
17
+ ]
18
+ else [index, Kantox::LOCK_EMOJI]
19
+ end
20
+ end.to_h
21
+ end
22
+ def fake_hash hash_or_array_or_json, deep = true
23
+ fake_data(hash_or_array_or_json, deep).merge({ Kantox::LOCK_EMOJI => true })
24
+ end
25
+ def fake_array array
26
+ array.zip(fake_data(array, false).values).to_h
27
+ end
28
+ module_function :fake_data, :fake_hash, :fake_array
29
+ private :fake_data
30
+
31
+ # Returns class, method and parameters as s string.
32
+ # @return [Hash|nil]
33
+ def get_instance_method name
34
+ k, m = name.split('#')
35
+
36
+ unless Kernel.const_defined? k
37
+ Kantox::Helpers.debug "Bad handler: «#{k}##{m}» not found. Error message: “#{e}”.\nUsually it’s OK meaning that the respective class is not yet initialized."
38
+ return nil
39
+ end
40
+
41
+ klazz = Kernel.const_get(k)
42
+
43
+ meth = (
44
+ klazz.private_method_defined?(m) ||
45
+ klazz.protected_method_defined?(m) ||
46
+ klazz.public_method_defined?(m)
47
+ ) && klazz.instance_method(m)
48
+
49
+ unless meth
50
+ Kantox::Helpers.err "Class [#{klazz}] has no instance method [#{m}]."
51
+ return nil
52
+ end
53
+
54
+ params_arr = meth.parameters
55
+ params_str = [
56
+ params_arr.select { |tv| tv.first == :req }.map(&:last).map(&:to_s),
57
+ params_arr.select { |tv| tv.first == :rest }.map(&:last).map { |m| "*#{m}"},
58
+ params_arr.select { |tv| tv.first == :block }.map(&:last).map { |m| "&#{m}"}
59
+ ].flatten.join(', ')
60
+
61
+ Hashie::Mash.new(
62
+ class: klazz,
63
+ method: { name: m, instance: meth },
64
+ params: { string: params_str }
65
+ )
66
+ end
67
+
68
+ # @param mash [Hash|Hashie::Mash|NilClass] hash to merge `hos` into
69
+ # @param hos [Hash|Hashie::Mash|String] the new values taken from hash,
70
+ # mash or string (when string, should be either valid YAML file name or
71
+ # string with valid YAML)
72
+ def merge_hash_or_string mash, hos
73
+ case mash
74
+ when Hashie::Mash then mash
75
+ when Hash then Hashie::Mash.new(mash)
76
+ when NilClass then Hashie::Mash.new
77
+ else
78
+ fail ArgumentError.new "#{__callee__} expects [Hash|Hashie::Mash|NilClass] as first parameter"
79
+ end.deep_merge case hos
80
+ when NilClass then {} # aka skip
81
+ when Hash then hos
82
+ when String
83
+ begin
84
+ File.exists?(hos) ? Hashie::Mash.load(hos) : Hashie::Mash.new(YAML.load(hos))
85
+ rescue ArgumentError => ae
86
+ fail ArgumentError.new "#{__callee__} expects valid YAML configuration file. [#{hos}] contains invalid syntax."
87
+ rescue Psych::SyntaxError => pse
88
+ fail ArgumentError.new "#{__callee__} expects valid YAML configuration string. Got:\n#{hos}"
89
+ end
90
+ else
91
+ Kantox::Helpers.warn 'Kantox::Roles#configure accepts either String or Hash as parameter.'
92
+ end
93
+ end
94
+
95
+ # @return [Method|nil]
96
+ def get_runner_strategy name
97
+ im = get_instance_method(name)
98
+ im.nil? ? im : im[:method][:instance].bind(im[:class])
99
+ end
100
+
101
+ # @return [Method|nil]
102
+ def get_simple_strategy name
103
+ get_runner_strategy("Kantox::Strategies##{name}")
104
+ end
105
+
106
+ # @return [Proc]
107
+ def get_lambda_strategy lmbd
108
+ begin
109
+ eval(lmbd)
110
+ rescue LocalJumpError => lje
111
+ Kantox::Helpers.err "Lambda strategy failed: «return» is not allowed from this lambda.\n#{lmbd}\nOriginal error: #{lje}"
112
+ rescue NameError => ne
113
+ Kantox::Helpers.err "Lambda strategy failed: syntax error in lambda.\n#{lmbd}\nOriginal error: #{ne}"
114
+ rescue SyntaxError => se
115
+ Kantox::Helpers.err "Lambda strategy failed: syntax error in lambda.\n#{lmbd}\nOriginal error: #{se}"
116
+ rescue RuntimeError => re
117
+ Kantox::Helpers.err "Lambda strategy failed: undetermined error in lambda.\n#{lmbd}\nOriginal error: #{re}"
118
+ end
119
+ end
120
+
121
+ # @return [Object]
122
+ def get_object_strategy name, mash
123
+ klazz = name.split('_').map { |n| n.capitalize }.join('::')
124
+ return nil unless Module.const_defined?(klazz)
125
+ klazz = Module.const_get(klazz)
126
+ return nil unless klazz.is_a?(Class) && klazz.instance_methods(false).include?(:to_proc)
127
+ klazz.new(mash)
128
+ end
129
+
130
+ module_function :get_instance_method, :merge_hash_or_string
131
+
132
+ Helpers.instance_methods.select do |m|
133
+ m.to_s =~ /\Aget_(.*)_strategy\z/
134
+ end.each do |m|
135
+ module_function m
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,102 @@
1
+ require 'logger'
2
+ require 'json'
3
+
4
+ module Kantox
5
+ module Helpers
6
+ BACKTRACE_LENGTH = 12
7
+ SEV_COLORS = {
8
+ 'INFO' => ['01;38;05;21', '00;38;05;152'],
9
+ 'WARN' => ['01;38;05;226', '00;38;05;222'],
10
+ 'ERROR' => ['01;38;05;196', '01;38;05;174'],
11
+ 'DEBUG' => ['01;38;05;242', '00;38;05;246'],
12
+ 'ANY' => ['01;38;05;222;48;05;238', '01;38;05;253;48;05;238']
13
+ }
14
+ SEV_SYMBOLS = {
15
+ 'INFO' => '✔',
16
+ 'WARN' => '✗',
17
+ 'ERROR' => '✘',
18
+ 'DEBUG' => '✓',
19
+ 'ANY' => '▶'
20
+ }
21
+
22
+ @stopwords = []
23
+ def logger_stopwords file
24
+ if File.exist?(file)
25
+ @stopwords += File.read(file).split($/).map { |l| Regexp.new l }
26
+ else
27
+ Kantox::Helpers.warn "Bad stopwords file: [#{file}]."
28
+ end
29
+ end
30
+ module_function :logger_stopwords
31
+
32
+ def self.clrz txt, clr
33
+ return txt unless @tty
34
+
35
+ "\e[#{clr}m#{txt.gsub(/«(.*?)»/, "\e[01;38;05;51m\\1\e[#{clr}m")}\e[0m"
36
+ end
37
+
38
+ def logger
39
+ unless @logger
40
+ @logger, @logdevice = if Kernel.const_defined? 'Rails'
41
+ [
42
+ ::Rails.logger,
43
+ ::Rails.logger
44
+ .instance_variable_get(:@logger)
45
+ .instance_variable_get(:@log)
46
+ ]
47
+ else
48
+ l = Logger.new($stdout)
49
+ [l, l]
50
+ end
51
+ @tty = @logdevice.instance_variable_get(:@logdev).instance_variable_get(:@dev).tty? ||
52
+ Kernel.const_defined?('Rails') && ::Rails.env.development?
53
+
54
+ unless Kernel.const_defined?('Rails') && ::Rails.env.production? || ENV['RAILS_PRETTY_LOG'] != '42'
55
+ @logdevice.formatter = proc { |severity, datetime, progname, message|
56
+ message = message.join($/) if message[0] == '[' && message[-1] == ']' && (arr_msg = JSON.parse(message)).is_a?(Array) rescue message
57
+
58
+ message.strip! # strip
59
+ message.gsub! "\n", "\n⮩#{' ' * 29}" # align
60
+ if message.empty? || @stopwords.any? { |sw| sw =~ message }
61
+ nil
62
+ else
63
+ '' << clrz("#{SEV_SYMBOLS[severity]} ", SEV_COLORS[severity].first)\
64
+ << clrz(severity[0..2], SEV_COLORS[severity].first) \
65
+ << ' | ' \
66
+ << clrz(datetime.strftime('%Y%m%d-%H%M%S.%3N'), '01;38;05;238') \
67
+ << ' | ' \
68
+ << clrz(message, SEV_COLORS[severity].last) \
69
+ << "\n"
70
+ end
71
+ }
72
+ end
73
+ end
74
+ @logger
75
+ end
76
+ module_function :logger
77
+ def log message
78
+ logger.unknown { message }
79
+ nil
80
+ end
81
+
82
+ def catched message, e
83
+ bt = e.backtrace.is_a?(Array) ? e.backtrace : caller(1)
84
+ msg = "#{message}\nOriginal exception «#{e.class}»: #{e.message}\n" \
85
+ "Stacktrace: #{bt.take(BACKTRACE_LENGTH).join($/)}\n" \
86
+ "[...#{bt.length - BACKTRACE_LENGTH} more]"
87
+ warn msg
88
+ end
89
+ module_function :log, :catched
90
+ %i(warn info error debug).each do |m|
91
+ class_eval "
92
+ def #{m} message
93
+ logger.#{m}(message)
94
+ nil
95
+ end
96
+ module_function :#{m}
97
+ "
98
+ end
99
+ alias_method :err, :error
100
+ module_function :err
101
+ end
102
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'strategy_error'
2
+
3
+ module Kantox
4
+ module Strategies
5
+ class AspectError < Kantox::Strategies::StrategyError ; end
6
+ def aspect context, im, *args
7
+ # fail AspectError.new(context, im)
8
+ Kantox::Helpers.info "Aspect strategy applyed to #{context}"
9
+ Kantox::Helpers.warn 'Aspect strategy does not expect block passed.' if block_given?
10
+ end
11
+ module_function :aspect
12
+ end
13
+ end
@@ -0,0 +1,48 @@
1
+ require_relative 'strategy_error'
2
+
3
+ module Kantox
4
+ module Strategies
5
+ class CanCanCanError < Kantox::Strategies::StrategyError
6
+ attr_reader :ability
7
+ def initialize context, im, policy = nil
8
+ super(context, im)
9
+ @policy = policy
10
+ end
11
+ end
12
+
13
+ # Ability factory for cancancan.
14
+ module AbilityFactory
15
+ def lookup klz, mth
16
+ meth = mth.capitalize.gsub(/_./) { |m|
17
+ m[-1].capitalize
18
+ }
19
+ klazz = "#{klz.capitalize}#{meth}Ability"
20
+ if Kantox::Abilities.const_defined? klazz
21
+ Kantox::Abilities.const_get klazz
22
+ else
23
+ Kantox::Helpers.error "Missing ability for «#{self.class.name}##{m}». You should define it explicitly."
24
+ end
25
+ end
26
+ module_function :lookup
27
+ end
28
+
29
+ def cancancan context, im, *args
30
+ Kantox::Helpers.debug "CanCanCaning object: [#{context}], «#{im}»"
31
+ begin # Whoever does not reply on classify would be punished :)
32
+ model = const_defined?('Rails') ?
33
+ context.instance_eval('controller_path.classify.singularize') :
34
+ context.class.name
35
+ ability = AbilityFactory.lookup model.split('::').last, im.split('#').last
36
+ user = Kernel.const_defined?('User') && User.respond_to?(:current) ?
37
+ User.current :
38
+ (context.respond_to?(:current_user) ? context.current_user : '@fixme')
39
+ fail CanCanCanError.new(context, im, ability) unless ability.new(user, '@todo').can *im.split('#')
40
+ rescue NameError => e
41
+ Kantox::Helpers.err "Error cancancaning «#{context}». Will reject request.\nOriginal error: #{e}"
42
+ throw CanCanCanError.new(context, im)
43
+ end
44
+ Kantox::Helpers.warn "CanCanCan strategy does not expect block passed.\nFailed: «#{ability}»" if block_given?
45
+ end
46
+ module_function :cancancan
47
+ end
48
+ end
@@ -0,0 +1,70 @@
1
+ require_relative 'strategy_error'
2
+
3
+ module Kantox
4
+ module Strategies
5
+ class PunditError < Kantox::Strategies::StrategyError
6
+ attr_reader :policy, :user
7
+ def initialize context, im, policy = nil, user = nil
8
+ super(context, im)
9
+ @policy = policy
10
+ @user = user
11
+ end
12
+
13
+ def to_s
14
+ ctx = @context ? ", context: #{@context}" : ''
15
+ usr = @user ? ", user: #{@user.login rescue nil} #{@user.class}" : ''
16
+ "(strategy: «pundit»#{usr}, policy: #{@policy}#{ctx})"
17
+ end
18
+ end
19
+
20
+ # Policy factory for pundit.
21
+ module PolicyFactory
22
+ def lookup name
23
+ klazz = "#{name}Policy"
24
+ if Kantox::Policies.const_defined? klazz
25
+ Kantox::Policies.const_get klazz
26
+ else
27
+ c = Kantox::Policies.const_set(klazz, Class.new(ApplicationPolicy) {
28
+ def method_missing m, *args, &cb
29
+ if m.to_s[-1] == '?'
30
+ Kantox::Helpers.error "Missing policy for «#{self.class.name}##{m}». You should define it explicitly."
31
+ nil
32
+ else
33
+ super
34
+ end
35
+ end
36
+ })
37
+ c.const_set('Scope', Class.new(ApplicationPolicy::Scope) {
38
+ def resolve
39
+ scope
40
+ end
41
+ })
42
+ c
43
+ end
44
+ end
45
+ module_function :lookup
46
+ end
47
+
48
+ def pundit context, im, *args
49
+ context_as_string = "#{context}"
50
+ context_as_string = "#{context_as_string[0..58]} [...]" if context_as_string.length > 65
51
+ Kantox::Helpers.debug "Punditing object: [#{context_as_string}], «#{im}»"
52
+ begin # Whoever does not reply on classify would be punished :)
53
+ model = const_defined?('Rails') && context.respond_to?(:controller_path) ?
54
+ context.instance_eval('controller_path.classify.singularize') :
55
+ context.class.name
56
+ model << 's' if model[-5..-1] == 'Statu' # Fucking Rails
57
+ policy = PolicyFactory.lookup model.split('::')[2..-1].join('::')
58
+ user = Kernel.const_defined?('User') && User.respond_to?(:current) ?
59
+ User.current :
60
+ (context.respond_to?(:current_user) ? context.current_user : nil)
61
+ fail PunditError.new(context, im, policy, user) unless policy.new(user, context).send "#{im.split('#').last}?"
62
+ rescue NameError => e
63
+ Kantox::Helpers.err "Error punditing «#{context.class}». Will reject request.\nOriginal error: #{e}"
64
+ throw PunditError.new(context, im)
65
+ end
66
+ Kantox::Helpers.warn "Pundit strategy does not expect block passed.\nFailed: «#{policy}»" if block_given?
67
+ end
68
+ module_function :pundit
69
+ end
70
+ end
@@ -0,0 +1,12 @@
1
+ module Kantox
2
+ module Strategies
3
+ class StrategyError < RuntimeError
4
+ def initialize context, im
5
+ # FIXME Patch the stacktrace
6
+ # failed = caller(4).first[/`(\w+)'/, 1]
7
+ meth = Kantox::Helpers.get_instance_method(im.sub('#', '#∃'))[:method][:instance]
8
+ super("StrategyError @ #{meth.source_location.join(':')}:in: `#{im}'\n ==> induced by #{self.to_s}")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'strategy_error'
2
+
3
+ module Kantox
4
+ module Strategies
5
+ module Wrapper
6
+ def wrap context, *params
7
+ Kantox::Helpers.info "Wrapped. Context: #{context}. Params: #{params.inspect}"
8
+ # Kantox::Helpers.debug "Caller: #{caller(0)[0..5]}"
9
+ # fail Kantox::Strategies::StrategyError, "@ #{context}"
10
+ yield *params if block_given?
11
+ end
12
+ module_function :wrap
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ module Kantox
2
+ module Roles
3
+ VERSION = "1.2.1"
4
+ end
5
+ end
@@ -0,0 +1,35 @@
1
+ ENV['RAILS_ENV'] ||= 'test'
2
+
3
+ require 'pundit/rspec'
4
+
5
+ RSpec.configure do |c|
6
+ c.treat_symbols_as_metadata_keys_with_true_values = true
7
+ end
8
+
9
+ UserTypes = ApplicationPolicy::UserTypes.call
10
+
11
+ def examples_for_policy klazz
12
+ describe klazz do
13
+ extend Pundit::RSpec::DSL
14
+ subject { klazz }
15
+ [:read, :write].each do |access|
16
+ klazz::METHODS[access].each do |meth|
17
+ permissions(meth) do
18
+ it "denies access if user is not allowed", :roles do
19
+ extend Pundit::RSpec::Matchers
20
+ (UserTypes - klazz::ALLOWED[access]).each do |user|
21
+ expect(subject).not_to permit(user.send(:new), controller)
22
+ end
23
+ end
24
+
25
+ it "grants access if user is allowed", :roles do
26
+ extend Pundit::RSpec::Matchers
27
+ klazz::ALLOWED[access].each do |user|
28
+ expect(subject).to permit(user.send(:new), controller)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end