isomorfeus-policy 1.0.0.zeta18 → 1.0.0.zeta19

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1aee4c32cad717298123ce27886105547501788f19e2bacea4f71254c4422cca
4
- data.tar.gz: 7808e6bf4fab3edf94262cb974c07a79dc69abbcc27662521c0226acd93ac3ce
3
+ metadata.gz: 74830e3a06aa9d24c2f8df1f0e10b8f0a9f69526559e1cccafc9f2e746bf495d
4
+ data.tar.gz: 2098b84f1bbca4c2b1dec9b68d4b5d377d2eff263477a86b2ccab224700cd0b0
5
5
  SHA512:
6
- metadata.gz: dbf069935d16daf5eb8dae88d046c0d99e745a491a6ec3564e8c176977d2817e0ce731fc4b166a75e7be3897ab949d954b504d4207797273d912474403da7f67
7
- data.tar.gz: 45c97d99ed4d297daada432ba75cbf32896b818568eee4c8b8977266cc0a438d0192ecc9258f211340f848a533f1b5b59d3e8880bea352f3fd44eab0c64f3393
6
+ metadata.gz: bedbf94292cd24c0056571f10fa1b0604ac77cd321380533af70dff8372496d8539eac27883a2c42606e7065a1cee67b1fa16d8d117b3dd8e43f2b3eebf81d0d
7
+ data.tar.gz: c4804f473e8e9ca14c812af6e7e8619f1d208c4eb5c5cda33830868b425e5566cba2c2adf8ec8d892734e62c17a99772ade9fd9dcac2763782c4c3bf8f368acf
data/README.md CHANGED
@@ -12,7 +12,7 @@ Policy is enforced on the server, however, the same policy rules are also availa
12
12
 
13
13
  Everything that is not explicitly allowed somewhere is denied.
14
14
 
15
- Place the policy file in your projects `isomorfeus/policies`.
15
+ Place the policy files in your projects `isomorfeus/policies`.
16
16
 
17
17
  Any class that would like to authorize for accessing a resource needs to include the `LucidAuthorization::Mixin`
18
18
  or inherit from `LucidAuthorization::Base`. Example:
@@ -26,34 +26,63 @@ Any instance of that class then has the following methods available:
26
26
  - `authorized?(target_class, method_name, props)` - returning true or false
27
27
  - `authorized!(target_class, method_name, props)` - raising a exception if access is denied, otherwise returning true
28
28
 
29
- These methods, when called, look for a Policy class. The Policy classs must be named after the user class plus the word 'Policy'.
29
+ These methods, when called, look for a Policy class. The Policy class must be named after the user class plus the word 'Policy'.
30
30
  Example:
31
31
 
32
32
  For a class `MyUser` the policy class must be `MyUserPolicy`.
33
33
 
34
+ ### Simple Rules
34
35
  Example Policy:
35
36
  ```ruby
36
- class MyUserPolicy < LucidPolicy::Base
37
-
38
- allow BlaBlaGraph, :load
39
-
40
- deny BlaGraph, SuperOperation
41
-
42
- deny others # or: allow others
37
+ class MyUserPolicy < LucidPolicy::Base
38
+ # allow access to all methods of a class
39
+ allow BlaBlaGraph
40
+ # class names can be given as strings which benefits autoload performance as the class can be loaded later on
41
+ allow 'BlaBlaGraph'
42
+
43
+ # allow access to all methods of multiple classes
44
+ allow BlaBlaGraph, BlaComposition
45
+
46
+ # allow access to a single method of a class
47
+ allow BlaBlaGraph, :load
48
+
49
+ # or allow multiple methods of a class
50
+ allow BlaBlaGraph, :load, :save
51
+
52
+ # deny access to all methods of multiple classes
53
+ deny BlaGraph, SuperOperation
54
+
55
+ deny others # or: allow others
56
+
57
+ # in a otherwise empty policy the following can be used too:
58
+ # allow all
59
+ # deny all
60
+
61
+ # further conditions can be used
62
+ allow BlaBlaGraph, :member_feature, only_if: :registered?
63
+ # this will call the method registered? on the user instance. The method must return a boolean.
64
+ # permission is granted if the method returned true
65
+ # method name must be given as symbol
66
+ # other versions:
67
+ allow BlaBlaGraph, :member_feature, if: :registered?
68
+
69
+ allow BlaBlaGraph, :public_feature, only_if_not: :registered?
70
+ # this will call the method registered? on the user instance. The method must return a boolean.
71
+ # permission is granted if the method returned false
72
+ # method name must be given as symbol
73
+ # other versions:
74
+ allow BlaBlaGraph, :member_feature, only_unless: :registered?
75
+ allow BlaBlaGraph, :member_feature, unless: :registered?
76
+ allow BlaBlaGraph, :member_feature, if_not: :registered?
43
77
 
44
- # in a otherwise empty policy the following can be used too:
45
- # allow all
46
- # deny all
47
-
48
- with_condition do |user_instance, target_class, target_method, props|
49
- user_instance.class == AdminRole
50
- end
51
-
52
- refine BlaGraph, :load, :count do |user_instance, target_class, target_method, props|
53
- allow if user_instance.verified?
54
- deny
55
- end
56
- end
78
+ # a block can be passed directly to a rules condition
79
+ allow BlaBlaGraph, :member_feature, if: proc { |user, props| user.registered? }
80
+ # permission is granted if the block returns true
81
+
82
+ # similar for deny, though:
83
+ deny BlaBlaGraph, :member_feature, if: proc { |user, props| !user.registered? }
84
+ # permission is denied if the block returns true
85
+ end
57
86
  ```
58
87
  and then any of:
59
88
  ```ruby
@@ -68,3 +97,59 @@ user.authorized!(target_class, target_method)
68
97
  user.authorized!(target_class, target_method, *props)
69
98
  ```
70
99
  which will raise a LucidPolicy::Exception unless authorized
100
+
101
+ ### Custom Rules
102
+ Besides calling methods or executing blocks from allow or deny, custom rules can be defined. Example:
103
+ ```ruby
104
+ class MyUserPolicy < LucidPolicy::Base
105
+ rule BlaGraph, :load, :count do |user, target_class, target_method, props|
106
+ allow if user.verified?
107
+ allow if user.admin?
108
+ deny
109
+ end
110
+ end
111
+ ```
112
+ Within the rule block the `allow` and `deny` methods must be used to allow or deny access.
113
+ They accept no arguments. Within the block the default is to deny, so any matching allow wins.
114
+
115
+ ### Combining Policies
116
+ Policies can be combined. Across policies, the first Policy rule that permits access wins.
117
+ The local policy is considered first, then the other policies.
118
+ If the local policy allows, then the other policies are not considered.
119
+ If the local policy has a `allow all` or a `allow others` rule, then there is a good chance the other policies are never considered.
120
+ The recommended default rule for combined policies is `deny others`.
121
+
122
+ Given a:
123
+ ```ruby
124
+ class AdminRolePolicy < LucidPolicy::Base
125
+ rule BlaGraph, :load, :count do |user, target_class, target_method, props|
126
+ allow if user.verified?
127
+ deny
128
+ end
129
+ end
130
+ ```
131
+ another policy can include the rules. Example:
132
+ ```ruby
133
+ class MyUserPolicy < LucidPolicy::Base
134
+ combine_with AdminRolePolicy
135
+
136
+ # conditions as for allow and deny can be used too
137
+ combine_with AdminRolePolicy, if: :admin?
138
+ # this will call the method admin? on the user instance. The method must return a boolean.
139
+ # this will execute the AdminRolePolicy rules only if the method returned true
140
+ # method name must be given as symbol
141
+
142
+ combine_with AdminRolePolicy, if_not: :normal_guy?
143
+ # this will call the method normal_guy?? on the user instance. The method must return a boolean.
144
+ # this will execute the AdminRolePolicy rules only if the method returned false
145
+ # method name must be given as symbol
146
+ # other versions:
147
+ combine_with AdminRolePolicy, unless: :normal_guy?
148
+
149
+ # a block can be passed directly to a rules condition
150
+ combine_with AdminRolePolicy, if: proc { |user| user.admin? }
151
+ # this will execute the AdminRolePolicy rules only if the condition block returns true
152
+
153
+ deny others
154
+ end
155
+ ```
@@ -1,5 +1,5 @@
1
1
  module Isomorfeus
2
2
  module Policy
3
- VERSION = '1.0.0.zeta18'
3
+ VERSION = '1.0.0.zeta19'
4
4
  end
5
5
  end
@@ -9,7 +9,7 @@ module LucidAuthorization
9
9
  policy_class = nil
10
10
  end
11
11
  return false unless policy_class
12
- policy_class.new(self).authorized?(target_class, target_method, props = nil)
12
+ policy_class.new(self).authorized?(target_class, target_method, props)
13
13
  end
14
14
 
15
15
  def authorized!(target_class, target_method = nil, props = nil)
@@ -17,7 +17,7 @@ module LucidAuthorization
17
17
  class_name = class_name.split('>::').last if class_name.start_with?('#<')
18
18
  policy_class = Isomorfeus.cached_policy_class("#{class_name}Policy")
19
19
  Isomorfeus.raise_error(error_class: LucidPolicy::Exception, message: "#{self}: policy class #{class_name}Policy not found!") unless policy_class
20
- policy_class.new(self).authorized!(target_class, target_method, props = nil)
20
+ policy_class.new(self).authorized!(target_class, target_method, props)
21
21
  end
22
22
  end
23
23
  end
@@ -1,4 +1,4 @@
1
1
  module LucidPolicy
2
- class Exception < Exception
2
+ class Exception < ::Exception
3
3
  end
4
- end
4
+ end
@@ -3,16 +3,15 @@ module LucidPolicy
3
3
  attr_reader :result
4
4
 
5
5
  def initialize
6
- @result= nil
6
+ @result = :deny
7
7
  end
8
8
 
9
9
  def allow
10
- @result = :allow if @result.nil?
10
+ @result = :allow
11
11
  nil
12
12
  end
13
13
 
14
14
  def deny
15
- @result = :deny if @result.nil?
16
15
  nil
17
16
  end
18
17
  end
@@ -7,74 +7,75 @@ module LucidPolicy
7
7
  end
8
8
 
9
9
  def authorization_rules
10
- @authorization_rules ||= { classes: {}, conditions: [], others: :deny }
10
+ @authorization_rules ||= { rules: {}.dup, policies: {}.dup, others: :deny }.dup
11
11
  end
12
12
 
13
13
  def all
14
14
  :others
15
15
  end
16
16
 
17
- def allow(*classes_and_methods)
18
- _raise_allow_deny_first if @refine_used
19
- @allow_deny_used = true
20
- _allow_or_deny(:allow, *classes_and_methods)
17
+ def allow(*classes_and_methods_and_options)
18
+ _allow_or_deny(:allow, *classes_and_methods_and_options)
21
19
  end
22
20
 
23
- def deny(*classes_and_methods)
24
- _raise_allow_deny_first if @refine_used
25
- @allow_deny_used = true
26
- _allow_or_deny(:deny, *classes_and_methods)
21
+ def deny(*classes_and_methods_and_options)
22
+ _allow_or_deny(:deny, *classes_and_methods_and_options)
27
23
  end
28
24
 
29
25
  def others
30
26
  :others
31
27
  end
32
28
 
33
- def refine(*classes_and_methods, &block)
34
- _raise_allow_deny_first unless @allow_deny_used
35
- @refine_used = true
36
- _allow_or_deny(nil, *classes_and_methods, &block)
29
+ def rule(*classes_and_methods, &block)
30
+ _allow_or_deny(:rule, *classes_and_methods, &block)
37
31
  end
38
32
 
39
- def with_condition(&block)
40
- authorization_rules[:conditions] << block
33
+ def combine_with(policy_class, **options)
34
+ authorization_rules[:policies] = { policy_class => options }
41
35
  end
42
36
 
43
37
  private
44
38
 
45
- def _raise_allow_deny_first
46
- Isomorfeus.raise_error(error_class: LucidPolicy::Exception, message: "#{self}: 'allow' or 'deny' must appear before 'refine'")
47
- end
39
+ def _allow_or_deny(thing, *classes_methods_options, &block)
40
+ rules = authorization_rules
48
41
 
49
- def _allow_or_deny(allow_or_deny, *classes_and_methods, &block)
50
- rules = authorization_rules
51
- allow_or_deny_or_block = block_given? ? block : allow_or_deny.to_sym
42
+ if %i[allow deny].include?(thing) && classes_methods_options.first == :others
43
+ rules[:others] = thing
44
+ return
45
+ end
52
46
 
53
47
  target_classes = []
54
48
  target_methods = []
49
+ target_options = {}
55
50
 
56
- if classes_and_methods.first == :others
57
- rules[:others] = allow_or_deny_or_block
58
- return
59
- end
60
-
61
- classes_and_methods.each do |class_or_method|
62
- if (class_or_method.class == String || class_or_method.class == Symbol) && class_or_method.to_s[0].downcase == class_or_method.to_s[0]
63
- target_methods << class_or_method.to_sym
51
+ classes_methods_options.each do |class_method_option|
52
+ if class_method_option.class == Hash
53
+ target_options = class_method_option
64
54
  else
65
- target_classes << class_or_method
55
+ class_or_method_s = class_method_option.to_s
56
+ if class_method_option.class == Symbol && class_or_method_s[0].downcase == class_or_method_s[0]
57
+ target_methods << class_method_option
58
+ else
59
+ class_method_option = class_method_option.to_s unless class_method_option.class == String
60
+ target_classes << class_method_option
61
+ end
66
62
  end
67
63
  end
68
64
 
65
+ thing_or_block = block_given? ? block : thing
66
+
69
67
  target_classes.each do |target_class|
70
- rules[:classes][target_class] = {} unless rules[:classes].key?(target_class)
71
- if allow_or_deny && target_methods.empty?
72
- rules[:classes][target_class][:default] = allow_or_deny_or_block
68
+ rules[:rules][target_class] = {} unless rules[:rules].key?(target_class)
69
+
70
+ if target_methods.empty?
71
+ rules[:rules][target_class][:rule] = thing_or_block
72
+ rules[:rules][target_class][:options] = target_options unless target_options.empty?
73
73
  else
74
- rules[:classes][target_class][:default] = :deny unless rules[:classes][target_class].key?(:default)
75
- rules[:classes][target_class][:methods] = {} unless rules[:classes][target_class].key?(:methods)
74
+ rules[:rules][target_class][:rule] = :deny unless rules[:rules][target_class].key?(:rule)
75
+ rules[:rules][target_class][:methods] = {} unless rules[:rules][target_class].key?(:methods)
76
76
  target_methods.each do |target_method|
77
- rules[:classes][target_class][:methods][target_method] = allow_or_deny_or_block
77
+ rules[:rules][target_class][:methods][target_method] = { rule: thing_or_block }
78
+ rules[:rules][target_class][:methods][target_method][:options] = target_options unless target_options.empty?
78
79
  end
79
80
  end
80
81
  end
@@ -87,35 +88,50 @@ module LucidPolicy
87
88
 
88
89
  def authorized?(target_class, target_method = nil, props = nil)
89
90
  Isomorfeus.raise_error(error_class: LucidPolicy::Exception, message: "#{self}: At least the class must be given!") unless target_class
90
- result = :deny
91
-
92
- rules = self.class.authorization_rules
93
-
94
- props = LucidProps.new(props) unless props.class == LucidProps
95
-
96
- condition_result = true
97
- rules[:conditions].each do |condition|
98
- condition_result = condition.call(@object, target_class, target_method, props, &condition)
99
- break unless condition_result == true
100
- end
101
91
 
102
- if condition_result == true
103
- result = if rules[:classes].key?(target_class)
104
- if target_method && rules[:classes][target_class].key?(:methods) &&
105
- rules[:classes][target_class][:methods].key?(target_method)
106
- rules[:classes][target_class][:methods][target_method]
107
- else
108
- rules[:classes][target_class][:default]
109
- end
110
- else
111
- rules[:others]
112
- end
113
-
114
- if result.class == Proc
115
- policy_helper = LucidPolicy::Helper.new
116
- policy_helper.instance_exec(@object, target_class, target_method, props, &result)
117
- result = policy_helper.result
92
+ target_class = target_class.to_s unless target_class.class == String
93
+
94
+ rules = self.class.authorization_rules
95
+
96
+ result = if rules[:rules].key?(target_class)
97
+ if target_method && rules[:rules][target_class].key?(:methods) && rules[:rules][target_class][:methods].key?(target_method)
98
+ options = rules[:rules][target_class][:methods][target_method][:options]
99
+ rule = rules[:rules][target_class][:methods][target_method][:rule]
100
+ else
101
+ options = rules[:rules][target_class][:options]
102
+ rule = rules[:rules][target_class][:rule]
103
+ end
104
+
105
+ if rule.class == Symbol
106
+ if options
107
+ condition, method_result = __get_condition_and_result(options)
108
+ rule if (condition == :if && method_result == true) || (condition == :if_not && method_result == false)
109
+ else
110
+ rule
111
+ end
112
+ else
113
+ props = LucidProps.new(props) unless props.class == LucidProps
114
+ policy_helper = LucidPolicy::Helper.new
115
+ policy_helper.instance_exec(@object, target_class, target_method, props, &rule)
116
+ policy_helper.result
117
+ end
118
+ else
119
+ rules[:others]
120
+ end
121
+
122
+ return true if result == :allow
123
+
124
+ rules[:policies].each do |policy_class, options|
125
+ combined_policy_result = nil
126
+ if options.empty?
127
+ combined_policy_result = policy_class.new(@object).authorized?(target_class, target_method, props)
128
+ else
129
+ condition, method_result = __get_condition_and_result(options)
130
+ if (condition == :if && method_result == true) || (condition == :if_not && method_result == false)
131
+ combined_policy_result = policy_class.new(@object).authorized?(target_class, target_method, props)
132
+ end
118
133
  end
134
+ return true if combined_policy_result == true
119
135
  end
120
136
 
121
137
  result == :allow ? true : false
@@ -125,6 +141,29 @@ module LucidPolicy
125
141
  return true if authorized?(target_class, target_method, props)
126
142
  Isomorfeus.raise_error(error_class: LucidPolicy::Exception, message: "#{@object}: not authorized to call #{target_class}.#{target_method}(#{props})!")
127
143
  end
144
+
145
+ private
146
+
147
+ def __get_condition_and_result(options)
148
+ condition = nil
149
+ method_name_or_block = if options.key?(:if)
150
+ condition = :if
151
+ options[:if]
152
+ elsif options.key?(:if_not)
153
+ condition = :if_not
154
+ options[:if_not]
155
+ elsif options.key?(:unless)
156
+ condition = :if_not
157
+ options[:unless]
158
+ end
159
+ method_result = if method_name_or_block && method_name_or_block.class == Symbol
160
+ @object.__send__(method_name_or_block)
161
+ else
162
+ props = LucidProps.new(props) unless props.class == LucidProps
163
+ method_name_or_block.call(@object, props)
164
+ end
165
+ [condition, method_result]
166
+ end
128
167
  end
129
168
  end
130
169
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: isomorfeus-policy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.zeta18
4
+ version: 1.0.0.zeta19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Biedermann
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-14 00:00:00.000000000 Z
11
+ date: 2020-02-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: opal
@@ -30,42 +30,42 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 16.12.18
33
+ version: 16.12.20
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 16.12.18
40
+ version: 16.12.20
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: isomorfeus-redux
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 4.0.17
47
+ version: 4.0.18
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 4.0.17
54
+ version: 4.0.18
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: isomorfeus
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - '='
60
60
  - !ruby/object:Gem::Version
61
- version: 1.0.0.zeta18
61
+ version: 1.0.0.zeta19
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - '='
67
67
  - !ruby/object:Gem::Version
68
- version: 1.0.0.zeta18
68
+ version: 1.0.0.zeta19
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: opal-webpack-loader
71
71
  requirement: !ruby/object:Gem::Requirement