rails_admin_next 1.3.0 → 2.0.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: 79af82d2ebf188875574650be98cd21e394a1a324b887cc45518d49f9fa3ec41
4
- data.tar.gz: 3a1d0d3b539034bc59e0019b1d56ff5dd3420285ea91a73ab193533de5f26c53
3
+ metadata.gz: 85cf357bfb48a568e28ee9d031d96f9d9d96546564579a17273d06ddd1643a45
4
+ data.tar.gz: a3850dcf1e9f5fb4bf6797a1175be2112d6f5ecc1d9f4588dfc561a412ce9072
5
5
  SHA512:
6
- metadata.gz: c20bd5d419f98867c7f2528ccf8139e5e09618ee13d4d982457fc983905c41d79b60d04c46971021531ccbc9607ceaace4ddd99072242c6003187c8e656a09f5
7
- data.tar.gz: 3c06806088cbb19f949301c5150eba329392eccde2ee96128c3fb9a8ac23b3639418d358d4074ea59f1995aa186a9a399e17b86374c280953fb526b2ff1deb48
6
+ metadata.gz: 950489886f285808c1d8688a3099361d63bc0f2bead9b15b3b752c3de41e696a48232f63bd6bdce8ec6835a9266b35f3b13a1faebc10572a0de1daf05b87a61b
7
+ data.tar.gz: 6dcc9aa9063071f2008888dff5c1c4e43042c6fd3026ce6c52bdd5844080f0276cbb719241427c84254d95d2b05160cf066aaf699ddd14ac03c5c9204a5d791d
data/Gemfile CHANGED
@@ -11,8 +11,8 @@ group :development, :test do
11
11
  end
12
12
 
13
13
  group :test do
14
+ gem "action_policy"
14
15
  gem "brakeman", require: false
15
- gem "cancancan", "~> 3.0"
16
16
  gem "cuprite"
17
17
  gem "database_cleaner-active_record", require: false
18
18
  gem "factory_bot"
data/README.md CHANGED
@@ -33,7 +33,7 @@ multi-bundler, jQuery-era machinery. RailsAdminNext keeps the admin and drops th
33
33
  - **🧩 Turbo + Stimulus** — the jQuery / jQuery-UI / Bootstrap-JS / `@rails/ujs` stack is gone, replaced by engine-shipped Stimulus controllers; styling is hand-maintained vanilla CSS.
34
34
  - **🔒 Hardened & CDN-free** — a documented CSRF / clickjacking / mass-assignment / CSP posture ([SECURITY.md](SECURITY.md)) and **zero external CDN**, default or opt-in — every asset is self-hosted off a gem.
35
35
  - **🎯 One modern target** — Ruby ≥ 4.0.9, Rails ~> 8.1, ActiveRecord only, latest Chrome + Safari. No Mongoid, no multi-bundler / multi-Rails matrix, no legacy-browser CSS.
36
- - **↩️ Familiar** — `RailsAdminNext.config`, per-model `rails_admin_next do … end` blocks, and CanCanCan / Pundit authorization all work as before; upgrading from RailsAdmin is a mechanical rename.
36
+ - **↩️ Familiar** — `RailsAdminNext.config`, per-model `rails_admin_next do … end` blocks, and Pundit authorization all work as before; upgrading from RailsAdmin is a mechanical rename.
37
37
 
38
38
  ## Quick Start
39
39
 
@@ -113,12 +113,12 @@ always match the installed version.
113
113
  - File uploads (ActiveStorage)
114
114
  - Rich text fields (ActionText / Trix)
115
115
  - Authentication (via [Devise][devise] or any other scheme)
116
- - Authorization (via [CanCanCan][cancancan] or [Pundit][pundit])
116
+ - Authorization (via [Pundit][pundit], [ActionPolicy][action_policy] or a custom adapter)
117
117
  - User action history (via [PaperTrail][papertrail])
118
118
 
119
119
  [devise]: https://github.com/heartcombo/devise
120
- [cancancan]: https://github.com/CanCanCommunity/cancancan
121
120
  [pundit]: https://github.com/varvet/pundit
121
+ [action_policy]: https://github.com/palkan/action_policy
122
122
  [papertrail]: https://github.com/paper-trail-gem/paper_trail
123
123
 
124
124
  ## Configuration
@@ -143,8 +143,15 @@ RailsAdminNext.config do |config|
143
143
  end
144
144
  ```
145
145
 
146
- This is also where you wire up [Devise][devise], [CanCanCan][cancancan]/[Pundit][pundit],
147
- and [PaperTrail][papertrail].
146
+ This is also where you wire up [Devise][devise], [Pundit][pundit], and [PaperTrail][papertrail].
147
+
148
+ Every admin page ends with a small "Made in Tokyo with ❤️ and 🤖" footer. To hide it:
149
+
150
+ ```ruby
151
+ RailsAdminNext.config do |config|
152
+ config.show_footer = false
153
+ end
154
+ ```
148
155
 
149
156
  ### Per model
150
157
 
@@ -170,19 +177,24 @@ The configuration DSL is unchanged from upstream RailsAdmin, so the upstream
170
177
 
171
178
  ### Authorization keys are stable
172
179
 
173
- The authorization contract is held stable, so existing CanCanCan
174
- abilities and Pundit policies keep working **without edits**: the access subject is
175
- `:rails_admin` and every action's
176
- authorization key (`:dashboard`, `:index`, `:show`, `:new`, `:edit`, `:export`,
177
- `:destroy`, `:history`, `:show_in_app`) is unchanged.
178
-
179
- ```ruby
180
- # Still grants access to the admin:
181
- can :access, :rails_admin
182
- ```
180
+ The authorization contract is held stable, so existing Pundit policies keep working
181
+ **without edits**: every action's authorization key (`:dashboard`, `:index`, `:show`,
182
+ `:new`, `:edit`, `:export`, `:destroy`, `:history`, `:show_in_app`) is unchanged, and the
183
+ Pundit adapter calls it with a `?` suffix (`index?`, `edit?`). Pundit and ActionPolicy are
184
+ the built-in adapters; any other authorization library plugs in as a custom adapter, which by convention
185
+ checks the `:access, :rails_admin` subject. See [docs/UPGRADING.md](docs/UPGRADING.md).
183
186
 
184
187
  The `admin.*` I18n namespace is likewise unchanged.
185
188
 
189
+ ### ActionPolicy
190
+
191
+ `config.authorize_with :action_policy` checks each action against the record's policy with
192
+ the same `<key>?` rules the Pundit adapter calls (`index?`, `show?`, `new?`, `edit?`,
193
+ `destroy?`, `export?`, `history?`, `show_in_app?`, `dashboard?`), and scopes lists, export
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
196
+ the "ActionPolicy adapter" section of [docs/UPGRADING.md](docs/UPGRADING.md).
197
+
186
198
  ## Frontend and assets
187
199
 
188
200
  RailsAdminNext ships a single, zero-build asset path:
@@ -294,16 +306,16 @@ action methods**, where each admin operation is an object.
294
306
  - **One controller, many action objects** — to change how e.g. _edit_ behaves, edit `lib/rails_admin_next/config/actions/edit.rb`, not the controller.
295
307
  - **ORM facade** — nothing outside `AbstractModel` references ActiveRecord directly; the adapter seam is intact even though Mongoid was removed.
296
308
  - **Lazy, layered config** — `config/initializers` blocks run before in-model `rails_admin_next do … end` blocks, so per-model config overrides the initializer defaults.
297
- - **A deliberately frozen `rails_admin` surface** — the Ruby layer is `RailsAdminNext`, but the `:access, :rails_admin` authorization subject, the `admin.*` I18n namespace, and the `src/rails_admin/` asset tree stay `rails_admin` on purpose, so host abilities, translations, and assets keep working across the rename.
309
+ - **A deliberately frozen `rails_admin` surface** — the Ruby layer is `RailsAdminNext`, but the action authorization keys, the `admin.*` I18n namespace, and the `src/rails_admin/` asset tree stay `rails_admin`-era on purpose, so host policies, translations, and assets keep working across the rename.
298
310
 
299
311
  ## Upgrading from RailsAdmin
300
312
 
301
313
  See [docs/UPGRADING.md](docs/UPGRADING.md) for the one-time changes a host app needs:
302
314
  the Gemfile gem name, the importmap/Propshaft prerequisites, and a mechanical rename of the
303
315
  `RailsAdmin` constant, initializer, mount, model macro, and route helpers to `RailsAdminNext`
304
- (`rails_admin_next`). The `:access, :rails_admin` authorization subject and the `admin.*`
305
- I18n namespace are deliberately left unchanged, so CanCanCan/Pundit rules and translations
306
- keep working without edits.
316
+ (`rails_admin_next`). The action authorization keys and the `admin.*` I18n namespace are
317
+ deliberately left unchanged, so Pundit policies and translations keep working without edits.
318
+ Its 2.0 section covers hosts whose authorization library is no longer a built-in adapter.
307
319
 
308
320
  ## Security
309
321
 
@@ -333,5 +345,5 @@ RailsAdminNext is released under the [MIT License](LICENSE.md).
333
345
  ---
334
346
 
335
347
  <div align="center">
336
- <sub>Built in Tokyo with ❤️ and 🤖</sub>
348
+ <sub>Made in Tokyo with ❤️ and 🤖</sub>
337
349
  </div>
@@ -460,6 +460,14 @@ body.rails_admin .pagination .page-item {
460
460
  white-space: nowrap;
461
461
  }
462
462
 
463
+ /* === Admin skin: footer ================================================= */
464
+ body.rails_admin .admin-footer {
465
+ padding: 2rem 0 1rem;
466
+ font-size: 0.875em;
467
+ color: var(--ra-gray-600);
468
+ text-align: center;
469
+ }
470
+
463
471
  /* === Admin skin: responsive adjustments ================================= */
464
472
  @media screen and (min-width: 576px) {
465
473
  body.rails_admin .sidebar.btn-toggle-nav {
@@ -20,6 +20,9 @@
20
20
  <div class="container-fluid">
21
21
  <%= render template: 'layouts/rails_admin_next/content' %>
22
22
  </div>
23
+ <% if RailsAdminNext::Config.show_footer %>
24
+ <footer class="admin-footer">Made in Tokyo with ❤️ and 🤖</footer>
25
+ <% end %>
23
26
  </div>
24
27
  </div>
25
28
  </div>
data/docs/UPGRADING.md CHANGED
@@ -8,9 +8,85 @@ the one-time changes a host app needs.
8
8
 
9
9
  The Ruby namespace is renamed **`RailsAdmin` → `RailsAdminNext`** (and `rails_admin` →
10
10
  `rails_admin_next`), so your initializer, engine mount, model macros, and route helpers
11
- need a mechanical rename. The **authorization contract is deliberately unchanged** — the
12
- `:access, :rails_admin` subject and the `admin.*` I18n namespace stay exactly as they were,
13
- so your CanCanCan abilities, Pundit policies, and translations need no edits.
11
+ need a mechanical rename. The **authorization contract is deliberately unchanged** — every
12
+ action's authorization key and the `admin.*` I18n namespace stay exactly as they were, so
13
+ your Pundit policies and translations need no edits. CanCanCan is no longer a built-in
14
+ adapter; see the 2.0 section below.
15
+
16
+ ## 2.0: CanCanCan support removed
17
+
18
+ Pundit and ActionPolicy are the built-in authorization adapters.
19
+ `config.authorize_with :cancancan` now raises `ArgumentError` while the initializer loads,
20
+ listing the registered adapters. Any other unregistered key does the same. Move to Pundit,
21
+ move to ActionPolicy (see [ActionPolicy adapter](#actionpolicy-adapter)), or keep CanCanCan
22
+ with a custom adapter.
23
+
24
+ ### Move to Pundit
25
+
26
+ ```ruby
27
+ RailsAdminNext.config do |config|
28
+ config.authorize_with :pundit
29
+ end
30
+ ```
31
+
32
+ - **`ApplicationPolicy`.** The adapter falls back to `::ApplicationPolicy` for the dashboard
33
+ (record `nil`) and for any model without its own policy.
34
+ - **Action predicates.** The adapter calls each action's authorization key with a `?`
35
+ suffix: `dashboard?`, `index?`, `show?`, `new?`, `edit?`, `export?`, `destroy?`,
36
+ `history?` and `show_in_app?`. Saving a new record also calls `create?`, and saving an
37
+ edit calls `update?`. Member actions pass the record; list-level checks pass the class.
38
+ `edit?` and `destroy?` also get the class: the form's "Save and edit" button checks
39
+ `edit?`, and the index's bulk-delete menu checks `destroy?`. A predicate that reads record
40
+ attributes must handle a class, for example `!record.is_a?(Class) && record.active?`.
41
+ - **A `Scope` for every model the admin lists.** A model whose policy resolves no `Scope`
42
+ lists nothing (see
43
+ [Pundit: a model with no policy Scope lists nothing](#pundit-a-model-with-no-policy-scope-lists-nothing)).
44
+ A policy that subclasses `ApplicationPolicy` without its own `Scope` inherits
45
+ `ApplicationPolicy::Scope`, so that class's `resolve` decides what the model lists.
46
+ - **Optional `attributes_for(action)`** on a policy returns the attributes a new record
47
+ starts with, for example `{suspended: true}` for `:new`.
48
+
49
+ Three differences from CanCanCan to check before switching:
50
+
51
+ - **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.
56
+ - **Nothing checks `:access, :rails_admin`.** Admin access is decided by the same policies,
57
+ and the `ApplicationPolicy` fallback, that the rest of the app uses. If those admit
58
+ ordinary users, gate admin entry separately: in `config.authenticate_with`, or with
59
+ admin-specific `authorization_key` values per action.
60
+ - **The block form only gates entry.** `config.authorize_with { ... }` runs before every
61
+ action, but it checks no records and does not scope lists.
62
+
63
+ ### Keep CanCanCan with a custom adapter
64
+
65
+ Register an adapter before the `RailsAdminNext.config` block that names its key, or the
66
+ `ArgumentError` above fires at boot:
67
+
68
+ ```ruby
69
+ RailsAdminNext.add_extension(:cancancan, MyApp::CanCanCanAdmin, authorization: true)
70
+
71
+ RailsAdminNext.config do |config|
72
+ config.authorize_with :cancancan
73
+ end
74
+ ```
75
+
76
+ The module defines an `AuthorizationAdapter` class. `authorize_with` builds it with
77
+ `new(controller, *args, &block)` and it answers:
78
+
79
+ - `authorize(action, abstract_model, object)`: raises when the action is denied.
80
+ - `authorized?(action, abstract_model, object)`: returns a boolean, for links and buttons.
81
+ - `query(action, abstract_model)`: returns the scope lists, export and bulk actions read from.
82
+ - `attributes_for(action, abstract_model)`: returns the attributes a new record starts with.
83
+ - `self.setup` (optional): runs once when `authorize_with` names the key.
84
+
85
+ `lib/rails_admin_next/extensions/pundit/authorization_adapter.rb` in the gem is the template.
86
+ The removed CanCanCan adapter is a starting point you can copy:
87
+ [v1.3.0 `extensions/cancancan/authorization_adapter.rb`](https://github.com/6temes/rails_admin_next/blob/v1.3.0/lib/rails_admin_next/extensions/cancancan/authorization_adapter.rb),
88
+ with its module in `extensions/cancancan.rb` at the same tag. Abilities keep granting
89
+ `can :access, :rails_admin`.
14
90
 
15
91
  ## 1. Gemfile
16
92
 
@@ -119,19 +195,19 @@ engine no longer provides it.
119
195
 
120
196
  ## Authorization (no change needed)
121
197
 
122
- The authorization contract is held stable — **do not touch your abilities or policies**:
198
+ The authorization contract is held stable — **do not touch your policies**:
123
199
 
124
- - The access subject is `:rails_admin`.
125
200
  - Every action's authorization key (`:dashboard`, `:index`, `:show`, `:new`, `:edit`,
126
201
  `:export`, `:destroy`, `:history`, `:show_in_app`) is unchanged.
202
+ - A custom adapter keeps the `:access, :rails_admin` subject by convention.
127
203
 
128
204
  ```ruby
129
- # This CanCanCan ability keeps working unchanged:
205
+ # A custom CanCanCan adapter's ability still grants access with:
130
206
  can :access, :rails_admin
131
207
  ```
132
208
 
133
- The access subject is deliberately frozen and locked by a spec in the gem — a renamed
134
- subject can fail *open* (a `cannot` rule that no longer matches silently grants access).
209
+ The authorization keys are locked by a spec in the gem — a renamed key can fail *open* (a
210
+ deny rule that no longer matches silently grants access).
135
211
 
136
212
  ## Asset prerequisites
137
213
 
@@ -460,6 +536,10 @@ A model whose policy defines no `Scope` used to list every record. It now lists
460
536
  list, export, bulk delete and dashboard count all return an empty set. Bulk delete deletes
461
537
  whatever the scope returns, with no per-record check, so "no Scope" can't mean "everything".
462
538
 
539
+ This holds only when no `Scope` constant resolves. A policy that subclasses `ApplicationPolicy`
540
+ without its own `Scope` inherits `ApplicationPolicy::Scope`: Pundit's generated `resolve` there
541
+ raises `NoMethodError`, and one that returns `scope.all` lists every record.
542
+
463
543
  Define a `Scope` for each model the admin exposes:
464
544
 
465
545
  ```ruby
@@ -472,7 +552,49 @@ class PlayerPolicy < ApplicationPolicy
472
552
  end
473
553
  ```
474
554
 
475
- CanCanCan hosts are unaffected.
555
+ ## ActionPolicy adapter
556
+
557
+ [ActionPolicy](https://github.com/palkan/action_policy) is a built-in adapter next to Pundit.
558
+ Bundle `action_policy` in your app, then:
559
+
560
+ ```ruby
561
+ RailsAdminNext.config do |config|
562
+ config.authorize_with :action_policy
563
+ config.current_user_method(&:current_user)
564
+ end
565
+ ```
566
+
567
+ The adapter authorizes as the user `current_user_method` returns. It checks the record's
568
+ policy with the rule names the Pundit adapter uses: `dashboard?`, `index?`, `show?`, `new?`,
569
+ `edit?`, `export?`, `destroy?`, `history?`, `show_in_app?`, plus `create?` and `update?` when a
570
+ form is submitted, and `<authorization_key>?` for custom actions. Two ActionPolicy defaults
571
+ change how a ported Pundit policy behaves:
572
+
573
+ - `new?` is an alias of `create?` unless the policy defines `new?` itself.
574
+ - A rule the policy does not define resolves to `manage?`, which is `false` on
575
+ `ActionPolicy::Base`, instead of raising.
576
+
577
+ A model with no policy, and the dashboard, are checked against `ApplicationPolicy`. Lists,
578
+ export, bulk delete and dashboard counts use the policy's default `relation_scope`; a model
579
+ with no policy, no relation scope, or only named ones lists nothing. To prefill new records,
580
+ define `attributes_for(action)` on the policy, as with Pundit.
581
+
582
+ ```ruby
583
+ class ApplicationPolicy < ActionPolicy::Base
584
+ def dashboard? = user.admin?
585
+ def index? = user.admin?
586
+ def show? = user.admin?
587
+ def create? = user.admin?
588
+ def update? = user.admin?
589
+ alias_rule :edit?, to: :update?
590
+ def destroy? = user.admin?
591
+ def export? = user.admin?
592
+ end
593
+
594
+ class PlayerPolicy < ApplicationPolicy
595
+ relation_scope { |relation| relation }
596
+ end
597
+ ```
476
598
 
477
599
  ## An `:enum` field with no option source offers no options
478
600
 
@@ -572,6 +694,12 @@ the primary key descending, so an export with no sort chosen is now newest first
572
694
  Rows are read in batches of 1000 instead of loading every record up front. An export with no
573
695
  matching records now contains the header row instead of an empty line.
574
696
 
697
+ ## Admin pages show a footer
698
+
699
+ Every admin page now ends with a small, muted "Made in Tokyo with ❤️ and 🤖" line under the page
700
+ content. It is not shown inside modals. To hide it, set `config.show_footer = false` in your
701
+ initializer.
702
+
575
703
  ## Removed: options deprecated before RailsAdmin 3.0
576
704
 
577
705
  These six were deprecated upstream between 2019-07 and 2022-01 and had no effect for
data/docs/security.md CHANGED
@@ -16,6 +16,10 @@ destructive-action controls itself; a few things are left for the host to opt in
16
16
  can't name a hidden column, an association the export doesn't show, or a method to call.
17
17
  - **Pundit scopes fail closed.** A model whose policy has no `Scope` lists no records, rather
18
18
  than all of them.
19
+ - **ActionPolicy scopes fail closed.** A model with no policy, or whose policy has no default
20
+ `relation_scope`, lists no records. A rule the policy does not define (`history?`,
21
+ `show_in_app?`, `export?`, a custom authorization key) resolves to the policy's `manage?`, so a
22
+ permissive `manage?` grants every admin action the policy leaves undefined.
19
23
  - **Destructive actions.** `delete` and `bulk_delete` render a confirmation page and additionally
20
24
  carry `data-turbo-confirm` on the destructive submit (Turbo's confirmation, not `@rails/ujs`).
21
25
  - **Mass assignment.** Submitted attributes are sliced to the model config's `visible_fields`
@@ -7,12 +7,12 @@ RailsAdminNext.config do |config|
7
7
  # end
8
8
  # config.current_user_method(&:current_user)
9
9
 
10
- ## == CancanCan ==
11
- # config.authorize_with :cancancan
12
-
13
10
  ## == Pundit ==
14
11
  # config.authorize_with :pundit
15
12
 
13
+ ## == ActionPolicy ==
14
+ # config.authorize_with :action_policy
15
+
16
16
  ## == PaperTrail ==
17
17
  # config.audit_with :paper_trail, 'User', 'PaperTrail::Version' # PaperTrail >= 3.0.0
18
18
 
@@ -22,6 +22,10 @@ RailsAdminNext.config do |config|
22
22
  ## To disable Gravatar integration in Navigation Bar set to false
23
23
  # config.show_gravatar = true
24
24
 
25
+ ## == Footer ==
26
+ ## To hide the "Made in Tokyo" footer under admin pages set to false
27
+ # config.show_footer = true
28
+
25
29
  config.actions do
26
30
  dashboard # mandatory
27
31
  index # mandatory
@@ -104,7 +104,6 @@ module RailsAdminNext
104
104
  key.to_sym
105
105
  end
106
106
 
107
- # For CanCanCan and the like
108
107
  register_instance_option :authorization_key do
109
108
  key.to_sym
110
109
  end
@@ -73,6 +73,9 @@ module RailsAdminNext
73
73
  # show Gravatar in Navigation bar
74
74
  attr_accessor :show_gravatar
75
75
 
76
+ # show the "Made in Tokyo" footer under every admin page
77
+ attr_accessor :show_footer
78
+
76
79
  # accepts a hash of static links to be shown below the main navigation
77
80
  attr_accessor :navigation_static_links
78
81
  attr_accessor :navigation_static_label
@@ -171,21 +174,37 @@ module RailsAdminNext
171
174
  # end
172
175
  # end
173
176
  #
177
+ # The block form only gates entry: it runs before every action, but nothing checks
178
+ # individual records or scopes the list.
179
+ #
174
180
  # To use an authorization adapter, pass the name of the adapter. For example,
175
- # to use with CanCanCan[https://github.com/CanCanCommunity/cancancan/], pass it like this.
181
+ # to use with Pundit[https://github.com/varvet/pundit], pass it like this.
182
+ #
183
+ # @example Pundit
184
+ # RailsAdminNext.config do |config|
185
+ # config.authorize_with :pundit
186
+ # end
176
187
  #
177
- # @example CanCanCan
188
+ # @example ActionPolicy
178
189
  # RailsAdminNext.config do |config|
179
- # config.authorize_with :cancancan
190
+ # config.authorize_with :action_policy
180
191
  # end
181
192
  #
182
- # See the wiki[https://github.com/railsadminteam/rails_admin/wiki] for more on authorization.
193
+ # A custom adapter is registered with
194
+ # RailsAdminNext.add_extension(key, mod, authorization: true) before it is named here.
195
+ # See docs/UPGRADING.md for the adapter contract.
183
196
  #
184
197
  # @see RailsAdminNext::Config::DEFAULT_AUTHORIZE
185
198
  def authorize_with(*args, &block)
186
199
  extension = args.shift
187
200
  if extension
188
- klass = RailsAdminNext::AUTHORIZATION_ADAPTERS[extension]
201
+ klass = RailsAdminNext::AUTHORIZATION_ADAPTERS.fetch(extension) do
202
+ raise ArgumentError, "Unknown authorization adapter #{extension.inspect}. " \
203
+ "Registered adapters: #{RailsAdminNext::AUTHORIZATION_ADAPTERS.keys.map(&:inspect).join(", ")}. " \
204
+ "Register a custom adapter with RailsAdminNext.add_extension(key, mod, authorization: true) " \
205
+ "before this config block, or pass a block to authorize_with to gate admin entry only " \
206
+ "(no per-record checks or list scoping). See docs/UPGRADING.md."
207
+ end
189
208
  klass.setup if klass.respond_to? :setup
190
209
  @authorize = proc do
191
210
  @authorization_adapter = klass.new(*([self] + args).compact, &block)
@@ -355,6 +374,7 @@ module RailsAdminNext
355
374
  @registry = {}
356
375
  @navbar_css_classes = %w[navbar-dark bg-primary border-bottom]
357
376
  @show_gravatar = true
377
+ @show_footer = true
358
378
  @navigation_static_links = {}
359
379
  @navigation_static_label = nil
360
380
  @composite_keys_serializer = RailsAdminNext::Support::CompositeKeysSerializer
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAdminNext
4
+ module Extensions
5
+ module ActionPolicy
6
+ class AuthorizationAdapter
7
+ # ActionPolicy is referenced only inside methods, so hosts without the gem still boot.
8
+ def self.setup
9
+ include ::ActionPolicy::Behaviour
10
+
11
+ authorize :user, through: -> { @controller._current_user }
12
+ end
13
+
14
+ def initialize(controller)
15
+ @controller = controller
16
+ end
17
+
18
+ def authorize(action, abstract_model = nil, model_object = nil)
19
+ return unless action
20
+
21
+ record = model_object || abstract_model&.model
22
+ authorize!(record, to: rule_for(action), with: policy_class_for(record))
23
+ @controller.skip_verify_authorized! if @controller.respond_to?(:skip_verify_authorized!)
24
+ end
25
+
26
+ def authorized?(action, abstract_model = nil, model_object = nil)
27
+ return unless action
28
+
29
+ record = model_object || abstract_model&.model
30
+ allowed_to?(rule_for(action), record, with: policy_class_for(record))
31
+ end
32
+
33
+ def query(_action, abstract_model)
34
+ @controller.skip_verify_authorized_scoped! if @controller.respond_to?(:skip_verify_authorized_scoped!)
35
+ 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
38
+ abstract_model.model.none
39
+ end
40
+
41
+ def attributes_for(action, abstract_model)
42
+ record = abstract_model&.model
43
+ policy_for(record:, with: policy_class_for(record)).try(:attributes_for, action) || {}
44
+ end
45
+
46
+ private
47
+
48
+ def policy_class_for(record)
49
+ ::ActionPolicy.lookup(record, default: ::ApplicationPolicy)
50
+ end
51
+
52
+ # A Symbol, because ActionPolicy keys its rule aliases (new? -> create?) by Symbol and a
53
+ # String would silently skip them.
54
+ def rule_for(action)
55
+ action.to_s.end_with?("?") ? action.to_sym : :"#{action}?"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_admin_next/extensions/action_policy/authorization_adapter"
4
+
5
+ RailsAdminNext.add_extension(:action_policy, RailsAdminNext::Extensions::ActionPolicy, authorization: true)
@@ -46,7 +46,7 @@ module RailsAdminNext
46
46
  def query(_action, abstract_model)
47
47
  @controller.send(:policy_scope, abstract_model.model.all)
48
48
  rescue ::Pundit::NotDefinedError
49
- abstract_model.model.none
49
+ host_policy_scope? ? abstract_model.model.none : model_scope(abstract_model.model)
50
50
  end
51
51
 
52
52
  # This is called in the new/create actions to determine the initial attributes for new
@@ -65,6 +65,22 @@ module RailsAdminNext
65
65
  ::ApplicationPolicy.new(@controller.send(:pundit_user), record)
66
66
  end
67
67
 
68
+ # action_policy defines ActiveRecord::Relation#policy_class as nil, which hides every
69
+ # Scope from Pundit's relation lookup when both gems are bundled; the model still finds it.
70
+ # A host override of policy_scope (e.g. namespacing) chose its own Scope, so it is never
71
+ # swapped for the model's.
72
+ def host_policy_scope?
73
+ @controller.method(:policy_scope).owner != (defined?(::Pundit::Authorization) ? ::Pundit::Authorization : ::Pundit)
74
+ end
75
+
76
+ def model_scope(model)
77
+ scope_class = ::Pundit::PolicyFinder.new(model).scope
78
+ return model.none unless scope_class
79
+
80
+ @controller.instance_variable_set(:@_pundit_policy_scoped, true)
81
+ scope_class.new(@controller.send(:pundit_user), model.all).resolve
82
+ end
83
+
68
84
  def action_for_pundit(action)
69
85
  (action[-1, 1] == "?") ? action : "#{action}?"
70
86
  end
@@ -5,8 +5,8 @@ module RailsAdminNext
5
5
  # Releases are cut by publishing a Release-Drafter draft: the gem-push workflow stamps
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
- MAJOR = 1
9
- MINOR = 3
8
+ MAJOR = 2
9
+ MINOR = 0
10
10
  PATCH = 0
11
11
  PRE = nil
12
12
 
@@ -6,8 +6,8 @@ require "rails_admin_next/config"
6
6
  require "rails_admin_next/config/const_load_suppressor"
7
7
  require "rails_admin_next/icons"
8
8
  require "rails_admin_next/extension"
9
- require "rails_admin_next/extensions/cancancan"
10
9
  require "rails_admin_next/extensions/pundit"
10
+ require "rails_admin_next/extensions/action_policy"
11
11
  require "rails_admin_next/extensions/paper_trail"
12
12
  require "rails_admin_next/support/csv_converter"
13
13
  require "rails_admin_next/support/hash_helper"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: rails-admin-next
3
- description: Configure or extend the rails_admin_next admin in a host Rails app (models, fields, custom actions and field types, CanCanCan/Pundit authorization, PaperTrail history, theming, CSP). Use instead of upstream RailsAdmin knowledge, which is wrong for this fork.
3
+ description: Configure or extend the rails_admin_next admin in a host Rails app (models, fields, custom actions and field types, Pundit, ActionPolicy or custom-adapter authorization, PaperTrail history, theming, CSP). Use instead of upstream RailsAdmin knowledge, which is wrong for this fork.
4
4
  ---
5
5
 
6
6
  # rails_admin_next
@@ -35,7 +35,7 @@ Renamed (write the new form):
35
35
 
36
36
  Not renamed (leave as is):
37
37
 
38
- - The authorization subject is still `:rails_admin`. CanCanCan abilities grant `can :access, :rails_admin`. Never rewrite it to `:rails_admin_next`, which fails authorization.
38
+ - The authorization subject is still `:rails_admin`. A custom adapter that keeps CanCanCan grants `can :access, :rails_admin`. Never rewrite it to `:rails_admin_next`, which fails authorization.
39
39
  - Action authorization keys (`:dashboard`, `:index`, `:show`, `:new`, `:edit`, `:export`, `:destroy`, `:history`, `:show_in_app`).
40
40
  - The `admin.*` I18n namespace.
41
41
 
@@ -75,9 +75,10 @@ Icons take logical names, never Font Awesome classes: `link_icon :show_in_app` o
75
75
 
76
76
  ## Authorization and history
77
77
 
78
- - `config.authorize_with :cancancan` uses the app's `Ability`. Keep the `:rails_admin` subject (see Naming).
78
+ - Pundit and ActionPolicy are the built-in adapters. `RailsAdminNext.add_extension(key, mod, authorization: true)` registers a custom one before the config block names its key; copy `<gem>/lib/rails_admin_next/extensions/pundit/authorization_adapter.rb` as the template. An unregistered key raises `ArgumentError` at boot.
79
79
  - `config.authorize_with :pundit` calls the record's policy with `<action>?` methods (`index?`, `edit?`, `dashboard?`) and scopes lists with `policy_scope`.
80
- - `config.authorize_with do ... end` runs a custom block as a before filter.
80
+ - `config.authorize_with :action_policy` checks the same `<action>?` rules with `allowed_to?` and scopes lists with the policy's default `relation_scope`. `new?` resolves to `create?` and undefined rules to `manage?`. A model with no policy is checked against `ApplicationPolicy`, and one with no default relation scope lists nothing.
81
+ - `config.authorize_with do ... end` runs a custom block as a before filter. It gates admin entry only: no per-record checks or list scoping.
81
82
  - `config.authenticate_with do ... end` and `config.current_user_method do ... end` wire in the app's authentication, such as Devise or Warden.
82
83
  - `config.audit_with :paper_trail` turns on the history action. Read the "History / audit table" section of `<gem>/docs/UPGRADING.md` first.
83
84
 
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: 1.3.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel López Prat
@@ -285,8 +285,8 @@ files:
285
285
  - lib/rails_admin_next/config/sections/update.rb
286
286
  - lib/rails_admin_next/engine.rb
287
287
  - lib/rails_admin_next/extension.rb
288
- - lib/rails_admin_next/extensions/cancancan.rb
289
- - lib/rails_admin_next/extensions/cancancan/authorization_adapter.rb
288
+ - lib/rails_admin_next/extensions/action_policy.rb
289
+ - lib/rails_admin_next/extensions/action_policy/authorization_adapter.rb
290
290
  - lib/rails_admin_next/extensions/controller_extension.rb
291
291
  - lib/rails_admin_next/extensions/paper_trail.rb
292
292
  - lib/rails_admin_next/extensions/paper_trail/auditing_adapter.rb
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RailsAdminNext
4
- module Extensions
5
- module CanCanCan
6
- # This adapter is for the CanCanCan[https://github.com/CanCanCommunity/cancancan] authorization library.
7
- class AuthorizationAdapter
8
- module ControllerExtension
9
- def current_ability
10
- # use _current_user instead of default current_user so it works with
11
- # whatever current user method is defined with RailsAdminNext
12
- @current_ability ||= ability_class.new(_current_user)
13
- end
14
- end
15
-
16
- include RailsAdminNext::Config::Configurable
17
-
18
- def self.setup
19
- RailsAdminNext::Extensions::ControllerExtension.include ControllerExtension
20
- end
21
-
22
- # See the +authorize_with+ config method for where the initialization happens.
23
- def initialize(controller, ability = nil, &block)
24
- @controller = controller
25
- ability_class { ability } if ability
26
- instance_eval(&block) if block
27
-
28
- adapter = self
29
- ControllerExtension.define_method(:ability_class) do
30
- adapter.ability_class
31
- end
32
- @controller.current_ability.authorize! :access, :rails_admin
33
- end
34
-
35
- register_instance_option :ability_class do
36
- Ability
37
- end
38
-
39
- # This method is called in every controller action and should raise an exception
40
- # when the authorization fails. The first argument is the name of the controller
41
- # action as a symbol (:create, :bulk_delete, etc.). The second argument is the
42
- # AbstractModel instance that applies. The third argument is the actual model
43
- # instance if it is available.
44
- def authorize(action, abstract_model = nil, model_object = nil)
45
- return unless action
46
-
47
- action, subject = resolve_action_and_subject(action, abstract_model, model_object)
48
- @controller.current_ability.authorize!(action, subject)
49
- end
50
-
51
- # This method is called primarily from the view to determine whether the given user
52
- # has access to perform the action on a given model. It should return true when authorized.
53
- # This takes the same arguments as +authorize+. The difference is that this will
54
- # return a boolean whereas +authorize+ will raise an exception when not authorized.
55
- def authorized?(action, abstract_model = nil, model_object = nil)
56
- return unless action
57
-
58
- action, subject = resolve_action_and_subject(action, abstract_model, model_object)
59
- @controller.current_ability.can?(action, subject)
60
- end
61
-
62
- # This is called when needing to scope a database query. It is called within the list
63
- # and bulk_delete/destroy actions and should return a scope which limits the records
64
- # to those which the user can perform the given action on.
65
- def query(action, abstract_model)
66
- abstract_model.model.accessible_by(@controller.current_ability, action)
67
- end
68
-
69
- # This is called in the new/create actions to determine the initial attributes for new
70
- # records. It should return a hash of attributes which match what the user
71
- # is authorized to create.
72
- def attributes_for(action, abstract_model)
73
- @controller.current_ability.attributes_for(action, abstract_model&.model)
74
- end
75
-
76
- private
77
-
78
- def resolve_action_and_subject(action, abstract_model, model_object)
79
- subject = model_object || abstract_model&.model
80
- if subject
81
- [action, subject]
82
- else
83
- # For :dashboard compatibility
84
- [:read, action]
85
- end
86
- end
87
- end
88
- end
89
- end
90
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "rails_admin_next/extensions/cancancan/authorization_adapter"
4
-
5
- RailsAdminNext.add_extension(:cancancan, RailsAdminNext::Extensions::CanCanCan, authorization: true)