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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb076b8106150620c0cbe71b07bf2cf8f3c83234d6269fdb01395726fe83833f
4
- data.tar.gz: e40f7c0787d5aafb14a41f889093fb488d0ed0c9faaaf4dcb81cc839c6d15f96
3
+ metadata.gz: c13f91fb3fc199763adb101abd81452f10540df9977625ae846a6935ba5c8b4f
4
+ data.tar.gz: 30374987e2de06084366a8a90cdeee83412385fad96b33d2e8b40d99f6da2cf5
5
5
  SHA512:
6
- metadata.gz: c79b6a45e34b558a77e3aaaba9caa6e6963cab5e97f7ceff932fdf1150a75c7d94386baf173508923c6e87bcc0b7e5e5f1e69325cc64ba5d878b1c052980b835
7
- data.tar.gz: e5f01ac761f171f755196057dbfe667171bccc5165c5ebd2cef1fa73df490f3ef6a5a50aa0065d343b1440ec2e1e076911c3b0f95953d6868d2ddcbd2b8631cc
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.0.0/),
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
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "hubbado-policy"
3
- s.version = "1.2.1"
3
+ s.version = "1.4.0"
4
4
  s.summary = "A lightweight, flexible policy framework for Ruby applications"
5
5
 
6
6
  s.authors = ["Hubbado Devs"]
@@ -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, i18n_scope: i18n_scope)
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.2.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.7
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: []