rails_admin_next 2.0.0 → 2.1.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: 85cf357bfb48a568e28ee9d031d96f9d9d96546564579a17273d06ddd1643a45
4
- data.tar.gz: a3850dcf1e9f5fb4bf6797a1175be2112d6f5ecc1d9f4588dfc561a412ce9072
3
+ metadata.gz: 821ddc2c14943ce137265d8c99d9784492cde1b2041dab27a1b5651795ce3cd4
4
+ data.tar.gz: f3c9d9906d117bb55e0eb4c2fcfc585d129b98c1bb2b32689fdd2a734f656d90
5
5
  SHA512:
6
- metadata.gz: 950489886f285808c1d8688a3099361d63bc0f2bead9b15b3b752c3de41e696a48232f63bd6bdce8ec6835a9266b35f3b13a1faebc10572a0de1daf05b87a61b
7
- data.tar.gz: 6dcc9aa9063071f2008888dff5c1c4e43042c6fd3026ce6c52bdd5844080f0276cbb719241427c84254d95d2b05160cf066aaf699ddd14ac03c5c9204a5d791d
6
+ metadata.gz: 993ba29f1ae5ce41e276c0637660c791c0f3a6a47379082143b15079052c4f43ebc7a24a0d00f7433b4c0c1c37359154e0ef4ae3bf1c5a95340ac78472fca376
7
+ data.tar.gz: fa8ef20e12e1a670877e0d8f8719d87967e0398a11063d30e58a70a5bdff77984dc04e70695b7f12dfd7f632e04606388e4fd08c027b4e57dcc1121f2eec458c
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
@@ -470,6 +470,16 @@ body.rails_admin .admin-footer {
470
470
 
471
471
  /* === Admin skin: responsive adjustments ================================= */
472
472
  @media screen and (min-width: 576px) {
473
+ /* Only from this width: below it the sidebar stacks above the content, and a
474
+ full-height column would leave blank space under a short page. */
475
+ body.rails_admin .admin-main {
476
+ display: flex;
477
+ flex-direction: column;
478
+ min-height: calc(100dvh - 3.275rem);
479
+ }
480
+ body.rails_admin .admin-main > .admin-footer {
481
+ margin-top: auto;
482
+ }
473
483
  body.rails_admin .sidebar.btn-toggle-nav {
474
484
  position: fixed;
475
485
  top: 3.275rem;
@@ -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 }
@@ -16,7 +16,7 @@
16
16
  <div class="col-sm-3 col-md-2 flex-wrap p-0">
17
17
  <%= render "layouts/rails_admin_next/sidebar_navigation" %>
18
18
  </div>
19
- <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2">
19
+ <div class="admin-main col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2">
20
20
  <div class="container-fluid">
21
21
  <%= render template: 'layouts/rails_admin_next/content' %>
22
22
  </div>
data/docs/UPGRADING.md CHANGED
@@ -74,13 +74,17 @@ end
74
74
  ```
75
75
 
76
76
  The module defines an `AuthorizationAdapter` class. `authorize_with` builds it with
77
- `new(controller, *args, &block)` and it answers:
77
+ `new(controller, *args, **options, &block)` and it answers:
78
78
 
79
79
  - `authorize(action, abstract_model, object)`: raises when the action is denied.
80
80
  - `authorized?(action, abstract_model, object)`: returns a boolean, for links and buttons.
81
81
  - `query(action, abstract_model)`: returns the scope lists, export and bulk actions read from.
82
82
  - `attributes_for(action, abstract_model)`: returns the attributes a new record starts with.
83
83
  - `self.setup` (optional): runs once when `authorize_with` names the key.
84
+ - `self.validate_options!(*args, **options)` (optional): receives the extra arguments while the
85
+ config block runs; raise `ArgumentError` there so a misconfigured option fails at boot. A
86
+ subclass of a built-in adapter inherits its `validate_options!`, so one that takes options of
87
+ its own overrides it.
84
88
 
85
89
  `lib/rails_admin_next/extensions/pundit/authorization_adapter.rb` in the gem is the template.
86
90
  The removed CanCanCan adapter is a starting point you can copy:
@@ -596,6 +600,39 @@ class PlayerPolicy < ApplicationPolicy
596
600
  end
597
601
  ```
598
602
 
603
+ ### Authorization context beyond the user
604
+
605
+ A policy that declares more context than `user`, such as a tenant's `authorize :account`,
606
+ needs the adapter to supply it. Pass each key as a block:
607
+
608
+ ```ruby
609
+ RailsAdminNext.config do |config|
610
+ config.authorize_with :action_policy, context: {account: -> { _current_user.account }}
611
+ end
612
+ ```
613
+
614
+ Each block runs on the admin controller once per request, the first time anything is
615
+ authorized, whichever policy is checked; a block should return `nil` rather than raise for a
616
+ user with no such context. A helper from your own controller, such as `current_account`, is
617
+ only available there when `config.parent_controller` names that controller:
618
+
619
+ ```ruby
620
+ config.parent_controller = "::ApplicationController"
621
+ config.authorize_with :action_policy, context: {account: -> { current_account }}
622
+ ```
623
+
624
+ The value is an authorization input, so derive it from the signed-in user, or check the user's
625
+ membership when it comes from request data such as `params` or the subdomain. The relation scope
626
+ only narrows lists, export and bulk actions: show, edit, delete and history load the record by
627
+ id and check the rule alone, so a tenant rule such as `update?` must compare `record` with
628
+ `account` itself.
629
+
630
+ `user` always comes from `current_user_method` and is rejected inside `context:`. A key that is
631
+ not a Symbol, a value that is not a block taking no arguments, or an unknown option raises
632
+ `ArgumentError` while the config block runs. A key a policy declares that the host omits, or
633
+ whose block returns `nil`, raises `ActionPolicy::AuthorizationContextMissing` on the request
634
+ unless the policy declares it with `allow_nil:` or `optional:`.
635
+
599
636
  ## An `:enum` field with no option source offers no options
600
637
 
601
638
  An `:enum` field takes its options from `enum_method`, which defaults to `<name>_enum` on the
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`
@@ -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
@@ -11,8 +11,19 @@ module RailsAdminNext
11
11
  authorize :user, through: -> { @controller._current_user }
12
12
  end
13
13
 
14
- def initialize(controller)
14
+ def self.validate_options!(context: {})
15
+ context.each do |key, value|
16
+ raise ArgumentError, "ActionPolicy context key #{key.inspect} must be a Symbol" unless key.is_a?(Symbol)
17
+ raise ArgumentError, "ActionPolicy context #{key.inspect} is the current user; set it with config.current_user_method" if key == :user
18
+ unless value.is_a?(Proc) && value.parameters.none? { |type, _| type in :req | :keyreq }
19
+ raise ArgumentError, "ActionPolicy context #{key.inspect} must be a block taking no arguments, run on the admin controller"
20
+ end
21
+ end
22
+ end
23
+
24
+ def initialize(controller, context: {})
15
25
  @controller = controller
26
+ @context = context
16
27
  end
17
28
 
18
29
  def authorize(action, abstract_model = nil, model_object = nil)
@@ -45,6 +56,10 @@ module RailsAdminNext
45
56
 
46
57
  private
47
58
 
59
+ def build_authorization_context
60
+ super.merge(@context.transform_values { @controller.instance_exec(&it) })
61
+ end
62
+
48
63
  def policy_class_for(record)
49
64
  ::ActionPolicy.lookup(record, default: ::ApplicationPolicy)
50
65
  end
@@ -12,6 +12,10 @@ module RailsAdminNext
12
12
  RailsAdminNext::Extensions::ControllerExtension.include defined?(::Pundit::Authorization) ? ::Pundit::Authorization : ::Pundit
13
13
  end
14
14
 
15
+ # Pundit takes no options, so any passed to authorize_with fail at boot.
16
+ def self.validate_options!
17
+ end
18
+
15
19
  # See the +authorize_with+ config method for where the initialization happens.
16
20
  def initialize(controller)
17
21
  @controller = controller
@@ -6,7 +6,7 @@ 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
9
+ MINOR = 1
10
10
  PATCH = 0
11
11
  PRE = nil
12
12
 
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.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel López Prat