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,2 @@
1
+ write-me
2
+ (sorry, work in progress)
@@ -0,0 +1,18 @@
1
+ require_relative 'lib/admission/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+
5
+ spec.name = 'admission'
6
+ spec.version = Admission::VERSION
7
+ spec.summary = 'admission library'
8
+ spec.license = 'GPL-3.0'
9
+
10
+ spec.homepage = 'https://github.com/doooby/admission'
11
+ spec.author = 'doooby'
12
+ spec.description = 'update-me'
13
+ spec.email = 'zelazk.o@email.cz'
14
+
15
+ library_files = (`git ls-files -z`).split "\x0"
16
+ spec.files = library_files
17
+
18
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ # Set up gems listed in the Gemfile.
5
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
6
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
7
+
8
+ load Gem.bin_path('rspec-core', 'rspec')
@@ -0,0 +1,9 @@
1
+ require_relative 'admission/admission'
2
+ require_relative 'admission/version'
3
+
4
+ require_relative 'admission/privilege'
5
+ require_relative 'admission/status'
6
+ require_relative 'admission/arbitration'
7
+ require_relative 'admission/resource_arbitration'
8
+
9
+ require_relative 'admission/denied'
@@ -0,0 +1,140 @@
1
+ # class Admission::Ability
2
+ #
3
+ # attr_reader :status
4
+ #
5
+ # def initialize status, arbiter=Admission::RequestArbitration
6
+ # @status = status
7
+ # @no_privileges = @status.privileges.nil? || @status.privileges.empty?
8
+ # @arbiter = arbiter
9
+ # end
10
+ #
11
+ # # def can? *agrs
12
+ # # return false if @no_privileges
13
+ # # # subject, object = subject_and_object subject
14
+ # # process @arbiter.new(status)
15
+ # # process Arbitration.new(status.user, action), subject, object
16
+ # # end
17
+ #
18
+ # def cannot? *args
19
+ # !can?(*args)
20
+ # end
21
+ #
22
+ # class << self
23
+ #
24
+ # # attr_reader :rules_index
25
+ #
26
+ # # def define_for_privileges privileges, &block
27
+ # # end
28
+ #
29
+ # # def set_up_rules &block
30
+ # # @rules_index = []
31
+ # # self.instance_exec &block
32
+ # # remove_instance_variable :@privilege
33
+ # #
34
+ # # @rules_index = @rules_index.reduce Hash.new do |index, allowance|
35
+ # # privilege = allowance[:privilege]
36
+ # #
37
+ # # actions = allowance[:actions]
38
+ # # *actions = actions unless Array === actions
39
+ # # actions = %i[all] if actions.include? :all
40
+ # #
41
+ # # subject_index = (index[allowance[:subject]] ||= {})
42
+ # # resource_index = (index[allowance[:resource_type]] ||= {} if allowance[:resource_type])
43
+ # #
44
+ # # actions.each do |action|
45
+ # # subject_arbiter = allowance[:arbiter]
46
+ # #
47
+ # # if resource_index
48
+ # # action_index = (resource_index[action] ||= {})
49
+ # # action_index[privilege] = allowance[:arbiter]
50
+ # #
51
+ # # subject_arbiter = true
52
+ # # end
53
+ # #
54
+ # # action_index = (subject_index[action] ||= {})
55
+ # # action_index[privilege] = subject_arbiter
56
+ # # end
57
+ # #
58
+ # # index
59
+ # # end
60
+ # # end
61
+ #
62
+ # # def privilege name, level=nil, &block
63
+ # # @rules_index || raise('must be called within `set_rules` block')
64
+ # # @privilege = Admission.get_privilege(name, level) || raise("no such privilege: #{name}-#{level}")
65
+ # # self.instance_exec &block
66
+ # # @privilege = nil
67
+ # # end
68
+ # #
69
+ # # def allow subject, actions=:all
70
+ # # raise 'must be called within `privilege` block' unless @privilege
71
+ # # @rules_index << {
72
+ # # privilege: @privilege,
73
+ # # subject: subject,
74
+ # # actions: actions,
75
+ # # arbiter: true
76
+ # # }
77
+ # # end
78
+ # #
79
+ # # def forbid subject, actions=:all
80
+ # # raise 'must be called within `privilege` block' unless @privilege
81
+ # # @rules_index << {
82
+ # # privilege: @privilege,
83
+ # # subject: subject,
84
+ # # actions: actions,
85
+ # # arbiter: :forbid
86
+ # # }
87
+ # # end
88
+ # #
89
+ # # def allow_resource type, actions=:all, lambda=nil, &block
90
+ # # raise 'must be called within `privilege` block' unless @privilege
91
+ # # @rules_index << {
92
+ # # privilege: @privilege,
93
+ # # subject: resource_type_to_subject(type),
94
+ # # resource_type: type,
95
+ # # actions: actions,
96
+ # # arbiter: lambda || block || true
97
+ # # }
98
+ # # end
99
+ #
100
+ # private
101
+ #
102
+ # # def resource_type_to_subject type
103
+ # # raise "bad resource type, must respond to `#name` (should be class)" unless type.respond_to? :name
104
+ # # name = type.name.downcase
105
+ # # if name.respond_to? :pluralize
106
+ # # name.pluralize
107
+ # # else
108
+ # # raise 'Not implemented: unable to make subject id from resource without active_support'
109
+ # # end
110
+ # #
111
+ # # end
112
+ #
113
+ # end
114
+ #
115
+ # private
116
+ #
117
+ # # def subject_and_object object
118
+ # # Symbol === object ? object : [objec.class, object]
119
+ # # end
120
+ #
121
+ # # def process arbitration, subject, object
122
+ # # all_index = self.class.rules_index[:all]
123
+ # # subject_index = self.class.rules_index[subject]
124
+ # # type_index = (self.class.rules_index[object] if object)
125
+ # # arbitration.introduce_indices all_index, subject_index, type_index
126
+ # #
127
+ # # status.privileges.any? do |privilege|
128
+ # # arbitration.prepare_sitting object, *privilege.context
129
+ # # TrueClass === arbitration.rule(privilege)
130
+ # # end
131
+ # # end
132
+ #
133
+ # def process arbitration
134
+ # status.privileges.any? do |privilege|
135
+ # arbitration.prepare_sitting object, *privilege.context
136
+ # arbitration.rule_per_privilege(privilege).eql? true
137
+ # end
138
+ # end
139
+ #
140
+ # end
@@ -0,0 +1,6 @@
1
+ module Admission
2
+
3
+ VALID_DECISION = [true, false, :forbidden, nil]
4
+ ALL_ACTION = :all
5
+
6
+ end
@@ -0,0 +1,126 @@
1
+ class Admission::Arbitration
2
+
3
+
4
+ def initialize person, rules_index, request
5
+ @person = person
6
+ @rules_index = rules_index
7
+ @request = request.to_sym
8
+ end
9
+
10
+ def prepare_sitting *context
11
+ @context = context
12
+ @decisions = {}
13
+ end
14
+
15
+ def rule_per_privilege privilege
16
+ decision = @decisions[privilege]
17
+ return decision unless decision.nil?
18
+
19
+ decision = decide privilege
20
+
21
+ decision = false if decision.nil?
22
+ @decisions[privilege] = decision
23
+ end
24
+
25
+ def make_decision from_rules, privilege
26
+ if from_rules
27
+ decision = from_rules[privilege]
28
+ decision = @person.instance_exec *@context, &decision if Proc === decision
29
+
30
+ unless Admission::VALID_DECISION.include? decision
31
+ raise "invalid decision: #{decision}"
32
+ end
33
+
34
+ decision
35
+ end
36
+ end
37
+
38
+ def decide_per_inheritance privilege
39
+ inherited = privilege.inherited
40
+ return nil if inherited.nil? || inherited.empty?
41
+
42
+ explicit_allowance = false
43
+ inherited.each do |p|
44
+ rule = rule_per_privilege p
45
+ return rule if rule == :forbidden
46
+ explicit_allowance ||= rule
47
+ end
48
+ explicit_allowance
49
+ end
50
+
51
+ def decide privilege
52
+ decision = make_decision @rules_index[@request], privilege
53
+ return decision if decision.eql?(:forbidden) || decision.eql?(true)
54
+
55
+ decision = decide_per_inheritance privilege
56
+ return decision if decision.eql?(:forbidden) || decision.eql?(true)
57
+
58
+ make_decision @rules_index[Admission::ALL_ACTION], privilege
59
+ end
60
+
61
+ def self.define_rules privilege_order, &block
62
+ builder = self::RulesBuilder.new privilege_order
63
+ builder.instance_exec &block
64
+ builder.create_index
65
+ end
66
+
67
+ class RulesBuilder
68
+
69
+ attr_reader :privilege_order
70
+
71
+ def initialize privilege_order
72
+ @rules = []
73
+ @privilege_order = privilege_order
74
+ end
75
+
76
+ def privilege name, level=nil
77
+ @privilege = Admission::Privilege.get_from_order privilege_order, name, level
78
+ raise "no such privilege: #{name}-#{level}" unless @privilege
79
+ yield
80
+ @privilege = nil
81
+ end
82
+
83
+ def allow *actions, &block
84
+ raise "reserved action name #{Admission::ALL_ACTION}" if actions.include? Admission::ALL_ACTION
85
+ add_allowance_rule actions.flatten, (block || true)
86
+ end
87
+
88
+ def allow_all &block
89
+ add_allowance_rule [Admission::ALL_ACTION], (block || true)
90
+ end
91
+
92
+ def forbid *actions
93
+ raise "reserved action name #{Admission::ALL_ACTION}" if actions.include? Admission::ALL_ACTION
94
+ add_allowance_rule actions.flatten, :forbidden
95
+ end
96
+
97
+ def add_allowance_rule actions, arbiter, **options
98
+ raise 'must be called within `privilege` block' unless @privilege
99
+
100
+ @rules << options.merge!(
101
+ privilege: @privilege,
102
+ actions: actions,
103
+ arbiter: arbiter
104
+ )
105
+ end
106
+
107
+ def create_index
108
+ index_instance = @rules.reduce Hash.new do |index, allowance|
109
+ privilege = allowance[:privilege]
110
+ actions = allowance[:actions]
111
+ arbiter = allowance[:arbiter]
112
+
113
+ actions.each do |action|
114
+ action_index = (index[action] ||= {})
115
+ action_index[privilege] = arbiter
116
+ end
117
+
118
+ index
119
+ end
120
+
121
+ index_instance.freeze
122
+ end
123
+
124
+ end
125
+
126
+ end
@@ -0,0 +1,15 @@
1
+ class Admission::Denied < ::StandardError
2
+
3
+ attr_reader :status, :request_args
4
+ attr_accessor :message
5
+
6
+ def initialize status, *request_args
7
+ @status = status
8
+ @request_args = request_args
9
+ end
10
+
11
+ def to_s
12
+ @message || 'Admission denied.'
13
+ end
14
+
15
+ end
@@ -0,0 +1,129 @@
1
+ class Admission::Privilege
2
+
3
+ RESERVED_ID = :'^'
4
+ TOP_LEVEL_KEY = RESERVED_ID
5
+ BASE_LEVEL_NAME = :base
6
+
7
+ attr_reader :name, :level, :hash
8
+ attr_reader :inherited, :context
9
+
10
+ def initialize name, level=nil
11
+ name = name.to_sym
12
+ @name = name
13
+ level = level ? level.to_sym : BASE_LEVEL_NAME
14
+ @level = level
15
+ @hash = [name, level].hash
16
+ end
17
+
18
+ def inherits_from *privileges
19
+ @inherited = privileges
20
+ end
21
+
22
+ def dup_with_context *context
23
+ context = context.flatten.compact
24
+ return self if context.empty?
25
+ with_context = dup
26
+ with_context.instance_variable_set :@context, context
27
+ with_context
28
+ end
29
+
30
+ def eql? other
31
+ hash == other.hash
32
+ end
33
+
34
+ def text_key
35
+ level == BASE_LEVEL_NAME ? name.to_s : "#{name}-#{level}"
36
+ end
37
+
38
+ def to_s
39
+ "<#{[
40
+ 'Privilege',
41
+ "key=#{text_key}",
42
+ (inherited && "inherited=[#{inherited.map(&:text_key).join ','}]")
43
+ ].compact.join ' '}>"
44
+ end
45
+ alias :inspect :to_s
46
+
47
+ def self.define_order &block
48
+ Admission::Privilege::OrderDefiner.define &block
49
+ end
50
+
51
+ def self.get_from_order index, name, level=nil
52
+ levels = index[name.to_sym] || return
53
+ if level && !level.empty?
54
+ levels[level.to_sym]
55
+ else
56
+ levels[Admission::Privilege::BASE_LEVEL_NAME]
57
+ end
58
+ end
59
+
60
+ class OrderDefiner
61
+
62
+ attr_reader :definitions
63
+
64
+ def initialize
65
+ @definitions = {}
66
+ end
67
+
68
+ def privilege name, levels: [], inherits: nil
69
+ name = name.to_sym
70
+ if ([name] + levels).any?{|id| id == Admission::Privilege::RESERVED_ID }
71
+ raise "reserved name `#{Admission::Privilege::RESERVED_ID}` !"
72
+ end
73
+
74
+ levels.unshift Admission::Privilege::BASE_LEVEL_NAME
75
+ levels.map!{|level| Admission::Privilege.new name, level}
76
+
77
+ inherits = nil if inherits && inherits.empty?
78
+ if inherits
79
+ inherits = *inherits
80
+ inherits = inherits.map(&:to_sym).uniq
81
+ end
82
+
83
+ @definitions[name] = {levels: levels, inherits: inherits}
84
+ end
85
+
86
+ def self.define &block
87
+ definer = new
88
+ definer.instance_exec &block
89
+
90
+ definer.send :setup_inheritance
91
+ definer.send :build_index
92
+ end
93
+
94
+ private
95
+
96
+ def setup_inheritance
97
+ # set inheritance for all privileges
98
+ definitions.values.each do |levels:, inherits:|
99
+ levels.each_with_index do |privilege, index|
100
+ if index > 0 # higher level of privilege, inherits one step lower level
101
+ privilege.inherits_from levels[index - 1]
102
+
103
+ elsif inherits # lowest level, inherits top level of other privileges
104
+ inherits = inherits.map{|name| definitions[name][:levels].last if definitions.has_key? name}
105
+ privilege.inherits_from *inherits
106
+
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ def build_index
113
+ definitions.each_pair.reduce({}) do |h, pair|
114
+ name = pair[0]
115
+ levels = pair[1][:levels]
116
+
117
+ levels_hash = levels.reduce({Admission::Privilege::TOP_LEVEL_KEY => levels.last}) do |lh, privilege|
118
+ lh[privilege.level] = privilege
119
+ lh
120
+ end.freeze
121
+
122
+ h[name] = levels_hash
123
+ h
124
+ end.freeze
125
+ end
126
+
127
+ end
128
+
129
+ end