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 +4 -4
- data/lib/checken/concerns/has_parents.rb +7 -0
- data/lib/checken/config.rb +24 -0
- data/lib/checken/extensions/action_controller.rb +4 -4
- data/lib/checken/permission.rb +10 -1
- data/lib/checken/permission_group.rb +28 -0
- data/lib/checken/schema.rb +66 -41
- data/lib/checken/user.rb +2 -1
- data/lib/checken/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4113a0625a091278bb2c8fff39ebd8d44c36ba0a9b61ca7e6670d4c606e6e300
|
4
|
+
data.tar.gz: 16814ed3fc998c70a12ba021f9dc2081eda7139fdaab656e2ae87557956e1eb1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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>]
|
data/lib/checken/config.rb
CHANGED
@@ -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
|
data/lib/checken/permission.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/checken/schema.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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)
|
data/lib/checken/version.rb
CHANGED
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
|
+
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-
|
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:
|