hubbado-policy 1.2.1 → 1.4.0
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/CHANGELOG.md +14 -1
- data/README.md +35 -7
- data/hubbado-policy.gemspec +1 -1
- data/lib/hubbado/policy/base.rb +5 -4
- data/lib/hubbado/policy/rspec_matchers/deny.rb +76 -0
- data/lib/hubbado/policy/rspec_matchers/permit.rb +49 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c13f91fb3fc199763adb101abd81452f10540df9977625ae846a6935ba5c8b4f
|
|
4
|
+
data.tar.gz: 30374987e2de06084366a8a90cdeee83412385fad96b33d2e8b40d99f6da2cf5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d7f7d1193d9b680df272a3cb477a21fb7c3b441fda943b3a291e6620d5811178af47cb4b2c64a21e0ad67e6280fd28ae0d53bde8a9d3caa15d404e2fdf01d7de
|
|
7
|
+
data.tar.gz: 2e60851e3c9f8a3f32640475a2335b99503446863ebd2618d73a2e9fae0794f0e2570e2010014fa38387e068fb20b26af8eaf34458f96260fb1a0e6322e50721
|
data/CHANGELOG.md
CHANGED
|
@@ -2,9 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.4.0] - 2025-10-04
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- rspec matcher `:permit` for policies
|
|
13
|
+
- rspec matcher `:deny` for policies
|
|
14
|
+
|
|
15
|
+
## [1.3.0] - 2025-06-03
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Ability to configure the i18n_scope for a non-generic denied reason
|
|
20
|
+
|
|
8
21
|
## [1.2.1] - 2025-06-02
|
|
9
22
|
|
|
10
23
|
### Fixed
|
data/README.md
CHANGED
|
@@ -40,17 +40,17 @@ Policy objects encapsulate authorization logic and determine whether certain act
|
|
|
40
40
|
class ArticlePolicy < Hubbado::Policy::Base
|
|
41
41
|
define_policy :view do
|
|
42
42
|
return permitted if user.admin?
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
if record.published?
|
|
45
|
-
permitted
|
|
45
|
+
permitted
|
|
46
46
|
else
|
|
47
47
|
denied(:not_published)
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
define_policy :edit do
|
|
52
52
|
return permitted if user.admin?
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
if record.author == user
|
|
55
55
|
permitted
|
|
56
56
|
else
|
|
@@ -131,6 +131,27 @@ en:
|
|
|
131
131
|
denied: "Access denied"
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
+
You can also specify a custom i18n scope when returning denied results:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
define_policy :edit do
|
|
138
|
+
# Use a different i18n scope for this specific denial
|
|
139
|
+
return denied(:not_authorized, i18n_scope: "custom_errors.article")
|
|
140
|
+
|
|
141
|
+
permitted
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Both the class method and instance method versions of `denied` support the `i18n_scope` parameter:
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
# Class method
|
|
149
|
+
ArticlePolicy.denied(:custom_reason, i18n_scope: "errors.custom")
|
|
150
|
+
|
|
151
|
+
# Instance method
|
|
152
|
+
policy.denied(:custom_reason, i18n_scope: "errors.custom")
|
|
153
|
+
```
|
|
154
|
+
|
|
134
155
|
### Testing
|
|
135
156
|
|
|
136
157
|
Policies can be mimiced using a built-in control
|
|
@@ -172,6 +193,13 @@ mimic_policy.deny(:view, data: { reason: "custom data" })
|
|
|
172
193
|
mimic_policy.deny(:view, :not_authorized, data: { user_id: 123 })
|
|
173
194
|
```
|
|
174
195
|
|
|
196
|
+
In addition, there are two RSpec matcher `permit` and `deny` and
|
|
197
|
+
have to be required manually:
|
|
198
|
+
```ruby
|
|
199
|
+
require 'hubbado/policy/rspec_matchers/permit'
|
|
200
|
+
require 'hubbado/policy/rspec_matchers/deny'
|
|
201
|
+
```
|
|
202
|
+
|
|
175
203
|
## Result Objects
|
|
176
204
|
|
|
177
205
|
Result objects represent the outcome of a policy check, containing:
|
|
@@ -250,11 +278,11 @@ class ArticleScope < Hubbado::Policy::Scope
|
|
|
250
278
|
def self.default_scope
|
|
251
279
|
Article.all
|
|
252
280
|
end
|
|
253
|
-
|
|
281
|
+
|
|
254
282
|
# Required: Implement the filtering logic
|
|
255
283
|
def resolve(record, scope, **options)
|
|
256
284
|
return scope if record.admin?
|
|
257
|
-
|
|
285
|
+
|
|
258
286
|
scope.where(published: true).or(scope.where(author_id: record.id))
|
|
259
287
|
end
|
|
260
288
|
end
|
|
@@ -278,7 +306,7 @@ You can pass custom base scopes:
|
|
|
278
306
|
```ruby
|
|
279
307
|
# Scope only to a specific category
|
|
280
308
|
category_articles = ArticleScope.call(
|
|
281
|
-
current_user,
|
|
309
|
+
current_user,
|
|
282
310
|
Article.where(category_id: params[:category_id])
|
|
283
311
|
)
|
|
284
312
|
|
data/hubbado-policy.gemspec
CHANGED
data/lib/hubbado/policy/base.rb
CHANGED
|
@@ -48,13 +48,14 @@ module Hubbado
|
|
|
48
48
|
# Define this in a subclass if there are dependencies to be configure
|
|
49
49
|
template_method :configure
|
|
50
50
|
|
|
51
|
-
def self.denied(reason = nil, data: nil)
|
|
51
|
+
def self.denied(reason = nil, data: nil, i18n_scope: nil)
|
|
52
|
+
i18n_scope ||= self.i18n_scope
|
|
52
53
|
reason ||= :denied
|
|
53
54
|
Result.new(false, reason, i18n_scope: i18n_scope, data: data)
|
|
54
55
|
end
|
|
55
56
|
|
|
56
57
|
def self.permitted
|
|
57
|
-
Result.new(true, :permitted
|
|
58
|
+
Result.new(true, :permitted)
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
def self.i18n_scope
|
|
@@ -72,8 +73,8 @@ module Hubbado
|
|
|
72
73
|
self.class == other.class && user == other.user && record == other.record
|
|
73
74
|
end
|
|
74
75
|
|
|
75
|
-
def denied(reason = nil, data: nil)
|
|
76
|
-
self.class.denied(reason, data: data)
|
|
76
|
+
def denied(reason = nil, data: nil, i18n_scope: nil)
|
|
77
|
+
self.class.denied(reason, data: data, i18n_scope: i18n_scope)
|
|
77
78
|
end
|
|
78
79
|
|
|
79
80
|
def permitted
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module Hubbado
|
|
2
|
+
module Policy
|
|
3
|
+
module RspecMatchers
|
|
4
|
+
module Deny
|
|
5
|
+
def deny(...)
|
|
6
|
+
DenyMatcher.new(...)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class DenyMatcher
|
|
10
|
+
def initialize(action, *args, **kwargs)
|
|
11
|
+
@action = action
|
|
12
|
+
@args = args
|
|
13
|
+
@kwargs = kwargs
|
|
14
|
+
@reason = :denied
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def with(reason)
|
|
18
|
+
@reason = reason
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def matches?(policy)
|
|
23
|
+
@policy = policy
|
|
24
|
+
@result = policy.send @action, *@args, **@kwargs
|
|
25
|
+
|
|
26
|
+
unless @result.denied?
|
|
27
|
+
@incorrect_repsonse = true
|
|
28
|
+
return false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if @reason && @result.reason != @reason
|
|
32
|
+
@incorrect_reason = true
|
|
33
|
+
return false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
begin
|
|
37
|
+
@result.message
|
|
38
|
+
rescue I18n::MissingTranslationData
|
|
39
|
+
@missing_translation = true
|
|
40
|
+
return false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def description
|
|
47
|
+
"#{@policy.class.name} deny #{@action} with #{@reason}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def failure_message
|
|
51
|
+
if @incorrect_reason
|
|
52
|
+
"Expected #{@policy.class.name} to deny #{@action}, with #{@reason} " \
|
|
53
|
+
"but it was denied with #{@result.reason}"
|
|
54
|
+
elsif @missing_translation
|
|
55
|
+
"Translation missing for policy #{@policy.class.name} and #{@reason}"
|
|
56
|
+
else
|
|
57
|
+
"Expected #{@policy.class.name} to deny #{@action}, but it was permitted"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def failure_message_when_negated
|
|
62
|
+
if @missing_translation
|
|
63
|
+
"Translation missing for policy #{@policy.class.name} and #{@reason}"
|
|
64
|
+
else
|
|
65
|
+
"Expected #{@policy.class.name} to permit #{@action}, but it was denied"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
RSpec.configure do |rspec|
|
|
71
|
+
rspec.include self, type: :policy
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Hubbado
|
|
2
|
+
module Policy
|
|
3
|
+
module RspecMatchers
|
|
4
|
+
module Permit
|
|
5
|
+
def permit(...)
|
|
6
|
+
PermitMatcher.new(...)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class PermitMatcher
|
|
10
|
+
def initialize(action, *args, **kwargs)
|
|
11
|
+
@action = action
|
|
12
|
+
@args = args
|
|
13
|
+
@kwargs = kwargs
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def matches?(policy)
|
|
17
|
+
@policy = policy
|
|
18
|
+
|
|
19
|
+
if policy.respond_to?(@action)
|
|
20
|
+
@result = policy.send @action, *@args, **@kwargs
|
|
21
|
+
|
|
22
|
+
@result.permitted?
|
|
23
|
+
else
|
|
24
|
+
# TODO: Get rid of this branch once the Navigation policy uses the DSL
|
|
25
|
+
policy.send "#{@action}?", *@args, **@kwargs
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def description
|
|
30
|
+
"#{@policy.class.name} permit #{@action}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def failure_message
|
|
34
|
+
"Expected #{@policy.class.name} to permit #{@action}, " \
|
|
35
|
+
"but it was revoked with #{@reason ? @result.reason : 'no reason'}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def failure_message_when_negated
|
|
39
|
+
"Expected #{@policy.class.name} to deny #{@action}, but it was permitted"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
RSpec.configure do |rspec|
|
|
44
|
+
rspec.include self, type: :policy
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hubbado-policy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hubbado Devs
|
|
@@ -124,6 +124,8 @@ files:
|
|
|
124
124
|
- lib/hubbado/policy/controls/policy.rb
|
|
125
125
|
- lib/hubbado/policy/railtie.rb
|
|
126
126
|
- lib/hubbado/policy/result.rb
|
|
127
|
+
- lib/hubbado/policy/rspec_matchers/deny.rb
|
|
128
|
+
- lib/hubbado/policy/rspec_matchers/permit.rb
|
|
127
129
|
- lib/hubbado/policy/scope.rb
|
|
128
130
|
homepage: https://github.com/hubbado/hubbado-policy
|
|
129
131
|
licenses:
|
|
@@ -146,7 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
146
148
|
- !ruby/object:Gem::Version
|
|
147
149
|
version: '0'
|
|
148
150
|
requirements: []
|
|
149
|
-
rubygems_version: 3.6.
|
|
151
|
+
rubygems_version: 3.6.9
|
|
150
152
|
specification_version: 4
|
|
151
153
|
summary: A lightweight, flexible policy framework for Ruby applications
|
|
152
154
|
test_files: []
|