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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f52de80007a2b162a4a4874efa2606e2ec24a056f63a2efbd3603f30867d0e8f
4
- data.tar.gz: aa67aa9fbe010180f5313cebe83a569fdeb1808d1e936b86ee9201ff05b25f7e
3
+ metadata.gz: 373f306f26c44449a2adeabf854452a3592fdef08eb87ffb31c818f7ce662ddb
4
+ data.tar.gz: 435fbec5b7817f3f1d7b53eacf233f04208c27de4a87e5eb15fdd6585f395328
5
5
  SHA512:
6
- metadata.gz: 40f67da005ec5b1c3b5d8825dc0b6d7a8bb92d01ba93c035f8371353b557fee9e1b0b76a7275a9f100ad79e4eb1b5a74dd59cd438ef79fb692bea50bc17c401e
7
- data.tar.gz: 4204094e7e91124b1df69629a853988b4cc16284bf49003b59e13f486f33566ce3392f148c2a6d89e33ca24cb46d35c2e4c48c9b822e4fe95eba1513e23c514e
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. Bundle `action_policy` yourself; see
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 = RailsAdminNext::AbstractModel.new(@model_name))
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 policy.
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 destroy deletes every
53
- selected record that `Scope` returns, with no per-record `destroy?` check. If an ability
54
- allowed destroy or export on fewer records than read, resolve the `Scope` to the narrowest
55
- of them, or stay on a custom adapter whose `query` honours the action.
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. Bulk delete deletes
537
- whatever the scope returns, with no per-record check, so "no Scope" can't mean "everything".
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 `ApplicationPolicy`. Lists,
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 initialize(controller)
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
- authorized_scope(model.all, type: :active_record_relation, with: ::ActionPolicy.lookup(model))
37
- rescue ::ActionPolicy::NotFound, ::ActionPolicy::UnknownScopeType, ::ActionPolicy::UnknownNamedScope
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
- ::ActionPolicy.lookup(record, default: ::ApplicationPolicy)
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: bulk_delete deletes whatever this returns without a
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
- ::ApplicationPolicy.new(@controller.send(:pundit_user), record)
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
- scope_class = ::Pundit::PolicyFinder.new(model).scope
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)
@@ -6,8 +6,8 @@ module RailsAdminNext
6
6
  # the tag's version over these constants at build time, so the in-repo values are only
7
7
  # the floor a local `gem build` produces.
8
8
  MAJOR = 2
9
- MINOR = 0
10
- PATCH = 1
9
+ MINOR = 2
10
+ PATCH = 0
11
11
  PRE = nil
12
12
 
13
13
  class << self
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.1
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