rails_admin_next 2.0.1 → 2.2.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/README.md +2 -1
- data/app/controllers/rails_admin_next/application_controller.rb +7 -2
- data/app/controllers/rails_admin_next/main_controller.rb +2 -2
- data/docs/UPGRADING.md +75 -10
- data/docs/security.md +3 -0
- data/lib/rails_admin_next/config/actions/bulk_delete.rb +5 -1
- data/lib/rails_admin_next/config.rb +15 -2
- data/lib/rails_admin_next/extensions/action_policy/authorization_adapter.rb +28 -4
- data/lib/rails_admin_next/extensions/model_lineage.rb +14 -0
- data/lib/rails_admin_next/extensions/pundit/authorization_adapter.rb +19 -4
- data/lib/rails_admin_next/version.rb +2 -2
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 373f306f26c44449a2adeabf854452a3592fdef08eb87ffb31c818f7ce662ddb
|
|
4
|
+
data.tar.gz: 435fbec5b7817f3f1d7b53eacf233f04208c27de4a87e5eb15fdd6585f395328
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ea6a58ab5f4276088b29bde851ef2802316f370a208d50c0619dc3c6a2218e981f690b959f1776d8b6d74687a703595887f798b1ef11866b398fd4ec5e4d90e0
|
|
7
|
+
data.tar.gz: 5e43c1a785d4bbc4bcfa06aea22a24989d2e5832ad7de1cef45e388f7a2f002823da4f50421dd1fbe9d4121b0324f05409194ea6f7f75f0751881c713253ca7e
|
data/README.md
CHANGED
|
@@ -192,7 +192,8 @@ The `admin.*` I18n namespace is likewise unchanged.
|
|
|
192
192
|
the same `<key>?` rules the Pundit adapter calls (`index?`, `show?`, `new?`, `edit?`,
|
|
193
193
|
`destroy?`, `export?`, `history?`, `show_in_app?`, `dashboard?`), and scopes lists, export
|
|
194
194
|
and bulk delete through the policy's default `relation_scope`. A model with no policy, or a
|
|
195
|
-
policy with no default relation scope, lists nothing.
|
|
195
|
+
policy with no default relation scope, lists nothing. Policies that declare context beyond
|
|
196
|
+
`user` get it from `authorize_with :action_policy, context: {...}`. Bundle `action_policy` yourself; see
|
|
196
197
|
the "ActionPolicy adapter" section of [docs/UPGRADING.md](docs/UPGRADING.md).
|
|
197
198
|
|
|
198
199
|
## Frontend and assets
|
|
@@ -28,12 +28,17 @@ module RailsAdminNext
|
|
|
28
28
|
|
|
29
29
|
def get_model
|
|
30
30
|
@model_name = to_model_name(params[:model_name])
|
|
31
|
-
raise RailsAdminNext::ModelNotFound unless (@abstract_model =
|
|
32
|
-
raise RailsAdminNext::ModelNotFound if (@model_config = @abstract_model.config).excluded?
|
|
31
|
+
raise RailsAdminNext::ModelNotFound unless (@abstract_model = visible_abstract_model(@model_name))
|
|
33
32
|
|
|
33
|
+
@model_config = @abstract_model.config
|
|
34
34
|
@properties = @abstract_model.properties
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
def visible_abstract_model(model_name)
|
|
38
|
+
abstract_model = RailsAdminNext::AbstractModel.new(model_name)
|
|
39
|
+
abstract_model if abstract_model && !abstract_model.config.excluded?
|
|
40
|
+
end
|
|
41
|
+
|
|
37
42
|
def get_object
|
|
38
43
|
raise RailsAdminNext::ObjectNotFound unless (@object = @abstract_model.get(params[:id], @model_config.scope))
|
|
39
44
|
end
|
|
@@ -231,9 +231,9 @@ module RailsAdminNext
|
|
|
231
231
|
end
|
|
232
232
|
|
|
233
233
|
def get_association_scope_from_params
|
|
234
|
-
return nil unless params[:associated_collection].present?
|
|
234
|
+
return nil unless params[:associated_collection].present? && params[:source_abstract_model].present?
|
|
235
|
+
return nil unless (source_abstract_model = visible_abstract_model(to_model_name(params[:source_abstract_model])))
|
|
235
236
|
|
|
236
|
-
source_abstract_model = RailsAdminNext::AbstractModel.new(to_model_name(params[:source_abstract_model]))
|
|
237
237
|
source_model_config = source_abstract_model.config
|
|
238
238
|
action = params[:current_action].in?(%w[create update]) ? params[:current_action] : "edit"
|
|
239
239
|
field = source_model_config.send(action).fields.detect { |f| f.name == params[:associated_collection].to_sym }
|
data/docs/UPGRADING.md
CHANGED
|
@@ -30,7 +30,8 @@ end
|
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
- **`ApplicationPolicy`.** The adapter falls back to `::ApplicationPolicy` for the dashboard
|
|
33
|
-
(record `nil`) and for any model without its own
|
|
33
|
+
(record `nil`) and for any model without a policy of its own or a parent model's (see
|
|
34
|
+
[STI subclasses use their parent model's policy](#sti-subclasses-use-their-parent-models-policy)).
|
|
34
35
|
- **Action predicates.** The adapter calls each action's authorization key with a `?`
|
|
35
36
|
suffix: `dashboard?`, `index?`, `show?`, `new?`, `edit?`, `export?`, `destroy?`,
|
|
36
37
|
`history?` and `show_in_app?`. Saving a new record also calls `create?`, and saving an
|
|
@@ -49,10 +50,10 @@ end
|
|
|
49
50
|
Three differences from CanCanCan to check before switching:
|
|
50
51
|
|
|
51
52
|
- **One `Scope` bounds every list.** The adapter passes no action to the `Scope`, so the same
|
|
52
|
-
scope bounds index, export, bulk delete and the dashboard count. Bulk
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
scope bounds index, export, bulk delete and the dashboard count. Bulk delete also asks
|
|
54
|
+
`destroy?` of each selected record, but export does not. If an ability allowed export on
|
|
55
|
+
fewer records than read, resolve the `Scope` to the narrowest of them, or stay on a custom
|
|
56
|
+
adapter whose `query` honours the action.
|
|
56
57
|
- **Nothing checks `:access, :rails_admin`.** Admin access is decided by the same policies,
|
|
57
58
|
and the `ApplicationPolicy` fallback, that the rest of the app uses. If those admit
|
|
58
59
|
ordinary users, gate admin entry separately: in `config.authenticate_with`, or with
|
|
@@ -74,13 +75,17 @@ end
|
|
|
74
75
|
```
|
|
75
76
|
|
|
76
77
|
The module defines an `AuthorizationAdapter` class. `authorize_with` builds it with
|
|
77
|
-
`new(controller, *args, &block)` and it answers:
|
|
78
|
+
`new(controller, *args, **options, &block)` and it answers:
|
|
78
79
|
|
|
79
80
|
- `authorize(action, abstract_model, object)`: raises when the action is denied.
|
|
80
81
|
- `authorized?(action, abstract_model, object)`: returns a boolean, for links and buttons.
|
|
81
82
|
- `query(action, abstract_model)`: returns the scope lists, export and bulk actions read from.
|
|
82
83
|
- `attributes_for(action, abstract_model)`: returns the attributes a new record starts with.
|
|
83
84
|
- `self.setup` (optional): runs once when `authorize_with` names the key.
|
|
85
|
+
- `self.validate_options!(*args, **options)` (optional): receives the extra arguments while the
|
|
86
|
+
config block runs; raise `ArgumentError` there so a misconfigured option fails at boot. A
|
|
87
|
+
subclass of a built-in adapter inherits its `validate_options!`, so one that takes options of
|
|
88
|
+
its own overrides it.
|
|
84
89
|
|
|
85
90
|
`lib/rails_admin_next/extensions/pundit/authorization_adapter.rb` in the gem is the template.
|
|
86
91
|
The removed CanCanCan adapter is a starting point you can copy:
|
|
@@ -533,8 +538,8 @@ erroring.
|
|
|
533
538
|
## Pundit: a model with no policy Scope lists nothing
|
|
534
539
|
|
|
535
540
|
A model whose policy defines no `Scope` used to list every record. It now lists none: the
|
|
536
|
-
list, export, bulk delete and dashboard count all return an empty set
|
|
537
|
-
|
|
541
|
+
list, export, bulk delete and dashboard count all return an empty set, so "no Scope" can't
|
|
542
|
+
mean "everything".
|
|
538
543
|
|
|
539
544
|
This holds only when no `Scope` constant resolves. A policy that subclasses `ApplicationPolicy`
|
|
540
545
|
without its own `Scope` inherits `ApplicationPolicy::Scope`: Pundit's generated `resolve` there
|
|
@@ -572,9 +577,11 @@ change how a ported Pundit policy behaves:
|
|
|
572
577
|
|
|
573
578
|
- `new?` is an alias of `create?` unless the policy defines `new?` itself.
|
|
574
579
|
- A rule the policy does not define resolves to `manage?`, which is `false` on
|
|
575
|
-
`ActionPolicy::Base`, instead of raising.
|
|
580
|
+
`ActionPolicy::Base`, instead of raising. That includes a mistyped `authorization_key`,
|
|
581
|
+
so a policy whose `manage?` is true lets that action through.
|
|
576
582
|
|
|
577
|
-
A model with no policy, and the dashboard, are checked against
|
|
583
|
+
A model with no policy of its own or a parent model's, and the dashboard, are checked against
|
|
584
|
+
`ApplicationPolicy`. Lists,
|
|
578
585
|
export, bulk delete and dashboard counts use the policy's default `relation_scope`; a model
|
|
579
586
|
with no policy, no relation scope, or only named ones lists nothing. To prefill new records,
|
|
580
587
|
define `attributes_for(action)` on the policy, as with Pundit.
|
|
@@ -596,6 +603,39 @@ class PlayerPolicy < ApplicationPolicy
|
|
|
596
603
|
end
|
|
597
604
|
```
|
|
598
605
|
|
|
606
|
+
### Authorization context beyond the user
|
|
607
|
+
|
|
608
|
+
A policy that declares more context than `user`, such as a tenant's `authorize :account`,
|
|
609
|
+
needs the adapter to supply it. Pass each key as a block:
|
|
610
|
+
|
|
611
|
+
```ruby
|
|
612
|
+
RailsAdminNext.config do |config|
|
|
613
|
+
config.authorize_with :action_policy, context: {account: -> { _current_user.account }}
|
|
614
|
+
end
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
Each block runs on the admin controller once per request, the first time anything is
|
|
618
|
+
authorized, whichever policy is checked; a block should return `nil` rather than raise for a
|
|
619
|
+
user with no such context. A helper from your own controller, such as `current_account`, is
|
|
620
|
+
only available there when `config.parent_controller` names that controller:
|
|
621
|
+
|
|
622
|
+
```ruby
|
|
623
|
+
config.parent_controller = "::ApplicationController"
|
|
624
|
+
config.authorize_with :action_policy, context: {account: -> { current_account }}
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
The value is an authorization input, so derive it from the signed-in user, or check the user's
|
|
628
|
+
membership when it comes from request data such as `params` or the subdomain. The relation scope
|
|
629
|
+
only narrows lists, export and bulk actions: show, edit, delete and history load the record by
|
|
630
|
+
id and check the rule alone, so a tenant rule such as `update?` must compare `record` with
|
|
631
|
+
`account` itself.
|
|
632
|
+
|
|
633
|
+
`user` always comes from `current_user_method` and is rejected inside `context:`. A key that is
|
|
634
|
+
not a Symbol, a value that is not a block taking no arguments, or an unknown option raises
|
|
635
|
+
`ArgumentError` while the config block runs. A key a policy declares that the host omits, or
|
|
636
|
+
whose block returns `nil`, raises `ActionPolicy::AuthorizationContextMissing` on the request
|
|
637
|
+
unless the policy declares it with `allow_nil:` or `optional:`.
|
|
638
|
+
|
|
599
639
|
## An `:enum` field with no option source offers no options
|
|
600
640
|
|
|
601
641
|
An `:enum` field takes its options from `enum_method`, which defaults to `<name>_enum` on the
|
|
@@ -676,6 +716,15 @@ own model, its `additional_scope` defaults to `nil`, and it no longer reads the
|
|
|
676
716
|
`associated_collection` picker params. Custom actions that listed picker records through
|
|
677
717
|
it should call `association_entries(model_config, collection_scope)` instead.
|
|
678
718
|
|
|
719
|
+
## Bulk delete checks `destroy?` on each record
|
|
720
|
+
|
|
721
|
+
Bulk delete now asks the policy's `destroy?` (or the rule named by the action's
|
|
722
|
+
`authorization_key`) of every selected record, as the single delete does, with both Pundit
|
|
723
|
+
and ActionPolicy. A record it refuses is left out of the confirmation
|
|
724
|
+
page, is not destroyed, and counts in the "failed to be deleted" message. Before, bulk
|
|
725
|
+
delete honoured only the scope, so a `destroy?` rule that reads the record, such as
|
|
726
|
+
`!record.locked?`, stopped the single delete but not the bulk one.
|
|
727
|
+
|
|
679
728
|
## `associated_collection_scope` runs after the list sort
|
|
680
729
|
|
|
681
730
|
Association pickers apply `associated_collection_scope` after the picked model's list sort,
|
|
@@ -700,6 +749,22 @@ Every admin page now ends with a small, muted "Made in Tokyo with ❤️ and
|
|
|
700
749
|
content. It is not shown inside modals. To hide it, set `config.show_footer = false` in your
|
|
701
750
|
initializer.
|
|
702
751
|
|
|
752
|
+
## STI subclasses use their parent model's policy
|
|
753
|
+
|
|
754
|
+
Both the Pundit and the ActionPolicy adapter used to check an STI subclass with no policy of its
|
|
755
|
+
own against `ApplicationPolicy`, and to list none of its records. Such a subclass now takes the
|
|
756
|
+
nearest parent model's policy, for its rules and its scope alike, before `ApplicationPolicy`:
|
|
757
|
+
a `Hardball < Ball` with no `HardballPolicy` uses `BallPolicy`. A subclass that defines its own
|
|
758
|
+
policy still uses it, and lists nothing if that policy has no scope.
|
|
759
|
+
|
|
760
|
+
If `ApplicationPolicy` granted a subclass what its parent's policy denies, it is now denied. If
|
|
761
|
+
`ApplicationPolicy` denied a subclass what its parent's policy grants, it is now granted, and the
|
|
762
|
+
subclass lists what the parent's scope returns. Either way, give the subclass its own policy to
|
|
763
|
+
keep the old access.
|
|
764
|
+
|
|
765
|
+
The walk follows Ruby class inheritance, not the `type` column: a concrete subclass of a model
|
|
766
|
+
takes that model's policy even when it is not STI.
|
|
767
|
+
|
|
703
768
|
## Removed: options deprecated before RailsAdmin 3.0
|
|
704
769
|
|
|
705
770
|
These six were deprecated upstream between 2019-07 and 2022-01 and had no effect for
|
data/docs/security.md
CHANGED
|
@@ -20,6 +20,9 @@ destructive-action controls itself; a few things are left for the host to opt in
|
|
|
20
20
|
`relation_scope`, lists no records. A rule the policy does not define (`history?`,
|
|
21
21
|
`show_in_app?`, `export?`, a custom authorization key) resolves to the policy's `manage?`, so a
|
|
22
22
|
permissive `manage?` grants every admin action the policy leaves undefined.
|
|
23
|
+
- **ActionPolicy context fails loudly.** A context key a policy declares that the host omits, or
|
|
24
|
+
whose block returns `nil`, raises `ActionPolicy::AuthorizationContextMissing` on every adapter
|
|
25
|
+
check rather than listing or allowing anything; `user` cannot be replaced through `context:`.
|
|
23
26
|
- **Destructive actions.** `delete` and `bulk_delete` render a confirmation page and additionally
|
|
24
27
|
carry `data-turbo-confirm` on the destructive submit (Turbo's confirmation, not `@rails/ujs`).
|
|
25
28
|
- **Mass assignment.** Submitted attributes are sliced to the model config's `visible_fields`
|
|
@@ -16,8 +16,10 @@ module RailsAdminNext
|
|
|
16
16
|
|
|
17
17
|
register_instance_option :controller do
|
|
18
18
|
proc do
|
|
19
|
+
destroyable = ->(object) { @authorization_adapter.nil? || @authorization_adapter.authorized?(@action.authorization_key, @abstract_model, object) }
|
|
20
|
+
|
|
19
21
|
if request.post? # BULK DELETE
|
|
20
|
-
@objects = list_entries(@model_config, :destroy)
|
|
22
|
+
@objects = list_entries(@model_config, :destroy).select(&destroyable)
|
|
21
23
|
|
|
22
24
|
if @objects.blank?
|
|
23
25
|
flash[:error] = t("admin.flash.error", name: pluralize(0, @model_config.label, plural: @model_config.label_plural), action: t("admin.actions.delete.done"))
|
|
@@ -37,6 +39,8 @@ module RailsAdminNext
|
|
|
37
39
|
# Rescue per record: dependent: :restrict_with_exception raises, and an
|
|
38
40
|
# escaping raise would abandon every record behind it in the batch.
|
|
39
41
|
destroyed, not_destroyed = Array.wrap(@objects).partition do |object|
|
|
42
|
+
next false unless destroyable.call(object)
|
|
43
|
+
|
|
40
44
|
object.destroy
|
|
41
45
|
object.destroyed?
|
|
42
46
|
rescue ActiveRecord::DeleteRestrictionError
|
|
@@ -190,13 +190,25 @@ module RailsAdminNext
|
|
|
190
190
|
# config.authorize_with :action_policy
|
|
191
191
|
# end
|
|
192
192
|
#
|
|
193
|
+
# @example ActionPolicy with authorization context beyond the user
|
|
194
|
+
# RailsAdminNext.config do |config|
|
|
195
|
+
# config.authorize_with :action_policy, context: {account: -> { _current_user.account }}
|
|
196
|
+
# end
|
|
197
|
+
#
|
|
198
|
+
# An adapter may define a class method validate_options!, which receives the extra arguments
|
|
199
|
+
# while this config block runs, so a misconfigured option fails at boot.
|
|
200
|
+
#
|
|
193
201
|
# A custom adapter is registered with
|
|
194
202
|
# RailsAdminNext.add_extension(key, mod, authorization: true) before it is named here.
|
|
195
203
|
# See docs/UPGRADING.md for the adapter contract.
|
|
196
204
|
#
|
|
197
205
|
# @see RailsAdminNext::Config::DEFAULT_AUTHORIZE
|
|
198
|
-
def authorize_with(*args, &block)
|
|
206
|
+
def authorize_with(*args, **options, &block)
|
|
199
207
|
extension = args.shift
|
|
208
|
+
if extension.nil? && options.any?
|
|
209
|
+
raise ArgumentError, "authorize_with options need an adapter key, such as authorize_with(:action_policy, **options)"
|
|
210
|
+
end
|
|
211
|
+
|
|
200
212
|
if extension
|
|
201
213
|
klass = RailsAdminNext::AUTHORIZATION_ADAPTERS.fetch(extension) do
|
|
202
214
|
raise ArgumentError, "Unknown authorization adapter #{extension.inspect}. " \
|
|
@@ -206,8 +218,9 @@ module RailsAdminNext
|
|
|
206
218
|
"(no per-record checks or list scoping). See docs/UPGRADING.md."
|
|
207
219
|
end
|
|
208
220
|
klass.setup if klass.respond_to? :setup
|
|
221
|
+
klass.validate_options!(*args, **options) if klass.respond_to? :validate_options!
|
|
209
222
|
@authorize = proc do
|
|
210
|
-
@authorization_adapter = klass.new(*([self] + args).compact, &block)
|
|
223
|
+
@authorization_adapter = klass.new(*([self] + args).compact, **options, &block)
|
|
211
224
|
end
|
|
212
225
|
elsif block
|
|
213
226
|
@authorize = block
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "rails_admin_next/extensions/model_lineage"
|
|
4
|
+
|
|
3
5
|
module RailsAdminNext
|
|
4
6
|
module Extensions
|
|
5
7
|
module ActionPolicy
|
|
@@ -11,8 +13,19 @@ module RailsAdminNext
|
|
|
11
13
|
authorize :user, through: -> { @controller._current_user }
|
|
12
14
|
end
|
|
13
15
|
|
|
14
|
-
def
|
|
16
|
+
def self.validate_options!(context: {})
|
|
17
|
+
context.each do |key, value|
|
|
18
|
+
raise ArgumentError, "ActionPolicy context key #{key.inspect} must be a Symbol" unless key.is_a?(Symbol)
|
|
19
|
+
raise ArgumentError, "ActionPolicy context #{key.inspect} is the current user; set it with config.current_user_method" if key == :user
|
|
20
|
+
unless value.is_a?(Proc) && value.parameters.none? { |type, _| type in :req | :keyreq }
|
|
21
|
+
raise ArgumentError, "ActionPolicy context #{key.inspect} must be a block taking no arguments, run on the admin controller"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def initialize(controller, context: {})
|
|
15
27
|
@controller = controller
|
|
28
|
+
@context = context
|
|
16
29
|
end
|
|
17
30
|
|
|
18
31
|
def authorize(action, abstract_model = nil, model_object = nil)
|
|
@@ -33,8 +46,11 @@ module RailsAdminNext
|
|
|
33
46
|
def query(_action, abstract_model)
|
|
34
47
|
@controller.skip_verify_authorized_scoped! if @controller.respond_to?(:skip_verify_authorized_scoped!)
|
|
35
48
|
model = abstract_model.model
|
|
36
|
-
|
|
37
|
-
|
|
49
|
+
policy_class = model_policy_class(model)
|
|
50
|
+
return model.none unless policy_class
|
|
51
|
+
|
|
52
|
+
authorized_scope(model.all, type: :active_record_relation, with: policy_class)
|
|
53
|
+
rescue ::ActionPolicy::UnknownScopeType, ::ActionPolicy::UnknownNamedScope
|
|
38
54
|
abstract_model.model.none
|
|
39
55
|
end
|
|
40
56
|
|
|
@@ -45,8 +61,16 @@ module RailsAdminNext
|
|
|
45
61
|
|
|
46
62
|
private
|
|
47
63
|
|
|
64
|
+
def build_authorization_context
|
|
65
|
+
super.merge(@context.transform_values { @controller.instance_exec(&it) })
|
|
66
|
+
end
|
|
67
|
+
|
|
48
68
|
def policy_class_for(record)
|
|
49
|
-
|
|
69
|
+
model_policy_class(record) || ::ApplicationPolicy
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def model_policy_class(record)
|
|
73
|
+
[record, *ModelLineage.superclasses(record)].lazy.filter_map { ::ActionPolicy.lookup(it, allow_nil: true) }.first
|
|
50
74
|
end
|
|
51
75
|
|
|
52
76
|
# A Symbol, because ActionPolicy keys its rule aliases (new? -> create?) by Symbol and a
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsAdminNext
|
|
4
|
+
module Extensions
|
|
5
|
+
# Pundit and ActionPolicy look a policy up by the record's own class name only, so without
|
|
6
|
+
# walking these an STI subclass with no policy skips its parent model's for ApplicationPolicy.
|
|
7
|
+
module ModelLineage
|
|
8
|
+
def self.superclasses(record)
|
|
9
|
+
model = record.is_a?(Class) ? record : record.class
|
|
10
|
+
model.ancestors.grep(Class).drop(1).take_while { it < ::ActiveRecord::Base && !it.abstract_class? }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "rails_admin_next/extensions/model_lineage"
|
|
4
|
+
|
|
3
5
|
module RailsAdminNext
|
|
4
6
|
module Extensions
|
|
5
7
|
module Pundit
|
|
@@ -12,6 +14,10 @@ module RailsAdminNext
|
|
|
12
14
|
RailsAdminNext::Extensions::ControllerExtension.include defined?(::Pundit::Authorization) ? ::Pundit::Authorization : ::Pundit
|
|
13
15
|
end
|
|
14
16
|
|
|
17
|
+
# Pundit takes no options, so any passed to authorize_with fail at boot.
|
|
18
|
+
def self.validate_options!
|
|
19
|
+
end
|
|
20
|
+
|
|
15
21
|
# See the +authorize_with+ config method for where the initialization happens.
|
|
16
22
|
def initialize(controller)
|
|
17
23
|
@controller = controller
|
|
@@ -41,8 +47,7 @@ module RailsAdminNext
|
|
|
41
47
|
# This is called when needing to scope a database query. It is called within the list
|
|
42
48
|
# and bulk_delete/destroy actions and should return a scope which limits the records
|
|
43
49
|
# to those which the user can perform the given action on. A model with no policy
|
|
44
|
-
# Scope gets no records
|
|
45
|
-
# per-record check, so a forgotten Scope must not mean "everything".
|
|
50
|
+
# Scope gets no records, so a forgotten Scope must not mean "everything".
|
|
46
51
|
def query(_action, abstract_model)
|
|
47
52
|
@controller.send(:policy_scope, abstract_model.model.all)
|
|
48
53
|
rescue ::Pundit::NotDefinedError
|
|
@@ -62,7 +67,16 @@ module RailsAdminNext
|
|
|
62
67
|
def policy(record)
|
|
63
68
|
@controller.send(:policy, record)
|
|
64
69
|
rescue ::Pundit::NotDefinedError
|
|
65
|
-
|
|
70
|
+
policy_class = ModelLineage.superclasses(record).lazy.filter_map { host_policy(it)&.class }.first
|
|
71
|
+
(policy_class || ::ApplicationPolicy).new(@controller.send(:pundit_user), record)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Through the controller, so a host override of policy (e.g. namespacing) also picks the
|
|
75
|
+
# parent model's policy.
|
|
76
|
+
def host_policy(model)
|
|
77
|
+
@controller.send(:policy, model)
|
|
78
|
+
rescue ::Pundit::NotDefinedError
|
|
79
|
+
nil
|
|
66
80
|
end
|
|
67
81
|
|
|
68
82
|
# action_policy defines ActiveRecord::Relation#policy_class as nil, which hides every
|
|
@@ -74,7 +88,8 @@ module RailsAdminNext
|
|
|
74
88
|
end
|
|
75
89
|
|
|
76
90
|
def model_scope(model)
|
|
77
|
-
|
|
91
|
+
finder = [model, *ModelLineage.superclasses(model)].lazy.map { ::Pundit::PolicyFinder.new(it) }.find(&:policy)
|
|
92
|
+
scope_class = finder&.scope
|
|
78
93
|
return model.none unless scope_class
|
|
79
94
|
|
|
80
95
|
@controller.instance_variable_set(:@_pundit_policy_scoped, true)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_admin_next
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel López Prat
|
|
@@ -288,6 +288,7 @@ files:
|
|
|
288
288
|
- lib/rails_admin_next/extensions/action_policy.rb
|
|
289
289
|
- lib/rails_admin_next/extensions/action_policy/authorization_adapter.rb
|
|
290
290
|
- lib/rails_admin_next/extensions/controller_extension.rb
|
|
291
|
+
- lib/rails_admin_next/extensions/model_lineage.rb
|
|
291
292
|
- lib/rails_admin_next/extensions/paper_trail.rb
|
|
292
293
|
- lib/rails_admin_next/extensions/paper_trail/auditing_adapter.rb
|
|
293
294
|
- lib/rails_admin_next/extensions/pundit.rb
|