checken 0.0.4 → 0.0.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26f7fc494eacd3729c46774a936ba735a317c2835f7df07abf3920afe3cbf792
4
- data.tar.gz: 5d449e1b99a7fe53e366796c13ad73d09ca332115b770ab6dbac0e04ba709ead
3
+ metadata.gz: 4113a0625a091278bb2c8fff39ebd8d44c36ba0a9b61ca7e6670d4c606e6e300
4
+ data.tar.gz: 16814ed3fc998c70a12ba021f9dc2081eda7139fdaab656e2ae87557956e1eb1
5
5
  SHA512:
6
- metadata.gz: 83c122e8c10a61bfab0a0da4709cc7fbe9241514398b7d1e90a88b8ceecf24a8be0864dd29de0e819f799a5ae3b23544ac18bcbcf493bee04eb69a210d7c5b71
7
- data.tar.gz: fbea10f7112b0e37480e50a33480a93c5dc91732735c1c1f477009b87b45c00ec1b7ba173462f6ac14b068cb7e1bf0621a5dd975faf186dc81e3d77ce906a06b
6
+ metadata.gz: d65beeffd78dcbab44c52d4e9a27da6999fbd856d3488a271410e381637a7f509766230da6bb28926727a9ee22d984551da4cdc5bee8262d7a6fca69592e5c47
7
+ data.tar.gz: 97da186a28f704b5ca785a1a6b55246302fa8666d3f0fa1a0e78016d371c6f4dbd8b5259750e6e93be206b4eabd818f85aa114bc4ae32178e3733b84b7b6b25f
@@ -9,6 +9,13 @@ module Checken
9
9
  @key.nil? ? nil : [@group.path, @key].compact.join('.')
10
10
  end
11
11
 
12
+ # Return the full path to this permission with the namespace
13
+ #
14
+ # @return [String]
15
+ def path_with_namespace
16
+ [@schema.config.namespace, path].compact.join(@schema.config.namespace_delimiter)
17
+ end
18
+
12
19
  # Return the parents for ths group
13
20
  #
14
21
  # @return [Array<Checken::PermissionGroup, Checken::Permission>]
@@ -4,6 +4,30 @@ require 'checken/user_proxy'
4
4
  module Checken
5
5
  class Config
6
6
 
7
+ DEFAULT_NAMESPACE_DELIMITER = ":"
8
+
9
+ # An optional namespace that will prefix all group and permission paths.
10
+ #
11
+ attr_accessor :namespace
12
+
13
+ # The delimiter that should be used to separate namespaces
14
+ # in group and permission paths.
15
+ #
16
+ # @return [String]
17
+ def namespace_delimiter
18
+ @namespace_delimiter ||= DEFAULT_NAMESPACE_DELIMITER
19
+ end
20
+ attr_writer :namespace_delimiter
21
+
22
+ # Should the namespace be optional when checking permissions?
23
+ # Defaults to false.
24
+ #
25
+ # @return [Boolean]
26
+ def namespace_optional?
27
+ @namespace_optional || false
28
+ end
29
+ attr_writer :namespace_optional
30
+
7
31
  # The class that should be used to create user proxies.
8
32
  #
9
33
  # @return [Class]
@@ -17,14 +17,14 @@ module Checken
17
17
  # when performing permission checks using `restrict`.
18
18
  end
19
19
 
20
- def restrict(permission_path, object = nil, options = {})
20
+ def restrict(permission_path, object = nil, options = {}, strict: true)
21
21
  if checken_user_proxy.nil?
22
22
  user = send(Checken.current_schema.config.current_user_method_name)
23
23
  user_proxy = Checken.current_schema.config.user_proxy_class.new(user)
24
24
  else
25
25
  user_proxy = checken_user_proxy
26
26
  end
27
- granted_permissions = Checken.current_schema.check_permission!(permission_path, user_proxy, object)
27
+ granted_permissions = Checken.current_schema.check_permission!(permission_path, user_proxy, object, strict: strict)
28
28
  granted_permissions.each do |permission|
29
29
  granted_checken_permissions << permission
30
30
  end
@@ -35,7 +35,7 @@ module Checken
35
35
  end
36
36
 
37
37
  module ClassMethods
38
- def restrict(permission_path, object_or_options = {}, options_if_object_provided = {})
38
+ def restrict(permission_path, object_or_options = {}, options_if_object_provided = {}, strict: true)
39
39
  if object_or_options.is_a?(Hash)
40
40
  object = nil
41
41
  options = object_or_options
@@ -61,7 +61,7 @@ module Checken
61
61
  resolved_object = nil
62
62
  end
63
63
 
64
- restrict(permission_path, resolved_object, restrict_options)
64
+ restrict(permission_path, resolved_object, restrict_options, strict: strict)
65
65
  end
66
66
 
67
67
  end
@@ -43,6 +43,7 @@ module Checken
43
43
  end
44
44
 
45
45
  @group = group
46
+ @schema = group.schema
46
47
  @key = key
47
48
  @required_object_types = []
48
49
  @dependencies = []
@@ -84,7 +85,15 @@ module Checken
84
85
  end
85
86
 
86
87
  # Check the user has this permission
87
- unless user_proxy.granted_permissions.include?(self.path)
88
+ if @group.schema.config.namespace && @group.schema.config.namespace_optional?
89
+ found_permission = (user_proxy.granted_permissions & [self.path, self.path_with_namespace]).any?
90
+ elsif @group.schema.config.namespace
91
+ found_permission = user_proxy.granted_permissions.include?(self.path_with_namespace)
92
+ else
93
+ found_permission = user_proxy.granted_permissions.include?(self.path)
94
+ end
95
+
96
+ unless found_permission
88
97
  @group.schema.logger.info "`#{self.path}` not granted to #{user_proxy.description}"
89
98
  error = PermissionDeniedError.new('PermissionNotGranted', "User has not been granted the '#{self.path}' permission", self)
90
99
  error.user = user_proxy.user
@@ -111,6 +111,7 @@ module Checken
111
111
  raise PermissionNotFoundError, "Must provide a permission path"
112
112
  end
113
113
 
114
+ path = parse_namespace(path)
114
115
  path_parts = path.split('.').map(&:to_sym)
115
116
  last_group_or_permission = self
116
117
  while part = path_parts.shift
@@ -180,5 +181,32 @@ module Checken
180
181
  )
181
182
  end
182
183
 
184
+ private
185
+
186
+ # Parse the namespace from the path
187
+ # Depending on the schema configuration, the namespace may be optional or required.
188
+ # If the namespace is required, the namespace must match the schema namespace.
189
+ # If the namespace is optional, the namespace may be omitted.
190
+ # On success the namespace will be removed from the path and the remaining path will be returned.
191
+ #
192
+ # @param path [String]
193
+ # @return [String]
194
+ def parse_namespace(path)
195
+ match_data = path.match(/^(.*)#{@schema.config.namespace_delimiter}(.*)$/)
196
+
197
+ if match_data.nil? && @schema.config.namespace && !@schema.config.namespace_optional?
198
+ raise PermissionNotFoundError, "Namespace: '#{@schema.config.namespace}' is missing in path: '#{path}'"
199
+ elsif match_data.nil?
200
+ return path
201
+ end
202
+
203
+ namespace, remaining_path = match_data.captures
204
+ if namespace && namespace != @schema.config.namespace
205
+ raise PermissionNotFoundError, "Namespace: '#{namespace}' does not match the schema namespace: '#{@schema.config.namespace}'"
206
+ end
207
+
208
+ remaining_path || path
209
+ end
210
+
183
211
  end
184
212
  end
@@ -32,48 +32,11 @@ module Checken
32
32
  # @param permission_path [String]
33
33
  # @param user [User]
34
34
  # @param object [Object]
35
- def check_permission!(permission_path, user_proxy, object = nil)
36
- permissions = @root_group.find_permissions_from_path(permission_path)
37
-
38
- if permissions.size == 1
39
- # If we only have a single permission, we'll just run the check
40
- # as normal through the check process. This will work as normal and raise
41
- # and return directly.
42
- permissions.first.check!(user_proxy, object)
43
-
44
- elsif permissions.size == 0
45
- # No permissions found
46
- raise Checken::NoPermissionsFoundError, "No permissions found matching #{permission_path}"
47
-
35
+ def check_permission!(permission_path, user_proxy, object = nil, strict: true)
36
+ if strict # permission(s) for the path are expected to be defined within the Checken Schema
37
+ handle_strict_permission_check!(permission_path, user_proxy, object)
48
38
  else
49
- # If we have multiple permissions, we need to loop through each permission
50
- # and handle them as appropriate.
51
- granted_permissions = []
52
- ungranted_permissions = 0
53
- permissions.each do |permission|
54
- begin
55
- permission.check!(user_proxy, object).each do |permission|
56
- granted_permissions << permission
57
- end
58
- rescue Checken::PermissionDeniedError => e
59
- if e.code == 'PermissionNotGranted'
60
- # If the permission isn't granted, update the counter so we can
61
- # keep track of the number of ungranted permissions.
62
- ungranted_permissions += 1
63
- else
64
- # Raise other errors as normal
65
- raise
66
- end
67
- end
68
- end
69
-
70
- if permissions.size == ungranted_permissions
71
- # If the user is ungranted to all the found permissions, they do not
72
- # have access and should be denied.
73
- raise PermissionDeniedError.new('PermissionNotGranted', "User does not have any permissions #{permissions.map(&:path).join(', ')} permission.", permissions.first)
74
- else
75
- granted_permissions
76
- end
39
+ handle_unstrict_permission_check!(permission_path, user_proxy)
77
40
  end
78
41
  end
79
42
 
@@ -144,5 +107,67 @@ module Checken
144
107
  @schema.sort.to_h
145
108
  end
146
109
 
110
+ private
111
+
112
+ def handle_strict_permission_check!(permission_path, user_proxy, object)
113
+ permissions = @root_group.find_permissions_from_path(permission_path)
114
+
115
+ if permissions.size == 1
116
+ # If we only have a single permission, we'll just run the check
117
+ # as normal through the check process. This will work as normal and raise
118
+ # and return directly.
119
+ permissions.first.check!(user_proxy, object)
120
+
121
+ elsif permissions.size == 0
122
+ # No permissions found
123
+ raise Checken::NoPermissionsFoundError, "No permissions found matching #{permission_path}"
124
+
125
+ else
126
+ # If we have multiple permissions, we need to loop through each permission
127
+ # and handle them as appropriate.
128
+ granted_permissions = []
129
+ ungranted_permissions = 0
130
+ permissions.each do |permission|
131
+ begin
132
+ permission.check!(user_proxy, object).each do |permission|
133
+ granted_permissions << permission
134
+ end
135
+ rescue Checken::PermissionDeniedError => e
136
+ if e.code == 'PermissionNotGranted'
137
+ # If the permission isn't granted, update the counter so we can
138
+ # keep track of the number of ungranted permissions.
139
+ ungranted_permissions += 1
140
+ else
141
+ # Raise other errors as normal
142
+ raise
143
+ end
144
+ end
145
+ end
146
+
147
+ if permissions.size == ungranted_permissions
148
+ # If the user is ungranted to all the found permissions, they do not
149
+ # have access and should be denied.
150
+ raise PermissionDeniedError.new('PermissionNotGranted', "User does not have any permissions #{permissions.map(&:path).join(', ')} permission.", permissions.first)
151
+ else
152
+ granted_permissions
153
+ end
154
+ end
155
+ end
156
+
157
+ def handle_unstrict_permission_check!(permission_path, user_proxy)
158
+ if permission_path.include?('*')
159
+ raise Checken::PermissionNotFoundError, "Permission path cannot contain wildcards when strict is false"
160
+ end
161
+
162
+ unless user_proxy.is_a?(Checken::UserProxy)
163
+ user_proxy = config.user_proxy_class.new(user_proxy)
164
+ end
165
+ return [permission_path] if user_proxy.granted_permissions.include?(permission_path)
166
+
167
+ error = PermissionDeniedError.new('PermissionNotGranted', "User has not been granted the '#{permission_path}' permission")
168
+ error.user = user_proxy.user
169
+ raise error
170
+ end
171
+
147
172
  end
148
173
  end
data/lib/checken/user.rb CHANGED
@@ -21,8 +21,9 @@ module Checken
21
21
  raise Error, "Could not determine a schema. Make sure you set Checken.current_schema or pass :schema to can? methods."
22
22
  end
23
23
 
24
+ strict = options.delete(:strict) { true }
24
25
  user_proxy = schema.config.user_proxy_class.new(self)
25
- schema.check_permission!(permission_path, user_proxy, object)
26
+ schema.check_permission!(permission_path, user_proxy, object, strict: strict)
26
27
  end
27
28
 
28
29
  def can?(*args)
@@ -1,3 +1,3 @@
1
1
  module Checken
2
- VERSION = '0.0.4'
2
+ VERSION = '0.0.5'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: checken
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Cooke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-27 00:00:00.000000000 Z
11
+ date: 2025-04-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: An authorization framework for Ruby & Rails applications.
14
14
  email:
@@ -41,7 +41,7 @@ licenses:
41
41
  - MIT
42
42
  metadata:
43
43
  rubygems_mfa_required: 'false'
44
- changelog_uri: https://github.com/krystal/checken/CHANGELOG.md
44
+ changelog_uri: https://github.com/krystal/checken/blob/master/CHANGELOG.md
45
45
  post_install_message:
46
46
  rdoc_options: []
47
47
  require_paths: