-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Restricting specific fields visible in show/edit #1862
Comments
Have a look at the documentation for Pundit. A policy object typically includes the current user and the resource to be authorized. In Administrate, we have an
Then these can be used in the policy methods. Here's an example from the example app: administrate/spec/example_app/app/policies/order_policy.rb Lines 16 to 18 in c40c317
Does this answer your question? Perhaps we could improve the documentation on this point. |
Oh, wait. You say fields, not records! Apologies, I see now... I don't think there's a way to do it at the moment, no 🤔 |
Thinking a bit about this, perhaps a way to achieve it would be to use different dashboards for different users. Say that you have a I haven't tried this though, and it would quickly get out of hand if there are many fields to authorize across many models. However, I think we should try out these solutions first before complicating Administrate's internal APIs in order to add more options that will rarely be used. Ultimately we may implement new APIs, but we'll benefit from trying other things first. Would this solution work for you? |
Thanks @pablobm for sharing your thoughts on this. In practice I probably would not be willing to make several copies of the same dashboard as it likely would quickly get out of hand and lead to more issues than it solves. It would also not cover all scenarios, such as the second and third points below. Here are some use cases:
NB: we already had a discussion in another issue about making dashboards inheritable/extensible (don't know where to find it however). It might not be an ideal solution but might be an improvement on the idea of just copying the dashboards. |
Good thinking. To add to the use cases, here's a generalisation of your second one:
The third one is probably the trickiest, due to the reasons I explained at #1877. However there may be a way to tackle the other two... here's a little hack I just tried.
For example, I just tried the following to hide the field "name" in the form for "customer": <% page.attributes.each do |attribute| -%>
<% unless page.resource.is_a?(Customer) && attribute.attribute == :name %>
<div class="field-unit field-unit--<%= attribute.html_class %> field-unit--<%= requireness(attribute) %>">
<%= render_field attribute, f: f %>
</div>
<% end %>
<% end -%> If you want security, you'll also want to add checks at the If you are using an authentication mechanism, you should be able to access So that's a first step! Now this could be improved...
|
Thank you @pablobm, that is helpful. About point 2, I guess there needs to be access to the field, which itself knows about the attribute name, data, page, and resource, to which could be added the action. So we could define a method <% page.attributes.select { |field| show_field?(field, action) }.each do |field| -%>
<div class="field-unit field-unit--<%= field.html_class %> field-unit--<%= requireness(field) %>">
<%= render_field field, f: f %>
</div>
<% end -%> NB: in the code above I've used |
Sorry for the delay. This is a difficult problem that requires some dedicated thinking... Starting with your notate bene:
As for where that hypothetical |
See #1949 for another use case: loading fields depending on the application domain name. |
A nice API for this might be to provide customer: Field::HasOne.with_options(skip: [:name]), or customer: Field::HasOne.with_options(only: [:id, :favorite_color]), In addition to an array, these could take a callback to return fields to show/hide, for example if you wanted to call Pundit or whatever: customer: Field::HasOne.with_options(skip: ->(field) { Pundit.whatever ? [:name] : [] }), customer: Field::HasOne.with_options(skip: ->(field) { Pundit.whatever ? [] : [:id, :favorite_color] }), |
Has there been any evolution on this ? |
I did it like this. module AttributesFilter
extend ActiveSupport::Concern
included do
def form_attributes
apply_attributes_filters(super, :form)
end
def show_page_attributes
apply_attributes_filters(super, :show)
end
def collection_attributes
apply_attributes_filters(super, :collection)
end
private
def apply_attributes_filters(attributes, action)
self.class.filters.each do |key, filters|
if key.nil? || key == action
filters.each do |filter|
attributes = filter.call(attributes)
end
end
end
attributes
end
end
class_methods do
def filters
@filters ||= {}
@filters.dup
end
protected
def filter_attributes(action = nil, &block)
@filters ||= {}
@filters[action] ||= []
@filters[action] << block
end
end
end
class MyDashboard < ApplicationDashboard
include AttributesFilter
filter_attributes do |attributes|
attributes -= %i[some_attribute] unless Current.user.admin?
attributes
end
filter_attributes :collection do |attributes|
# ....
end
filter_attributes :show do |attributes|
# ....
end
filter_attributes :form do |attributes|
# ....
end
end |
Thanks @Nitr, this workaround is helpful for filtering administrate dashboard attributes based on some runtime condition. It can be used to show the localized version of some attributes which can't be localized at the model level (we use Mobility for translations but there is no integration with ActiveStorage, and some attachments need to change per language). class ContentDashboard < BaseDashboard
include Admin::AttributesFilter
ATTRIBUTE_TYPES = {
# ...
media_en: Field::ActiveStorage.with_options(...),
media_fr: Field::ActiveStorage.with_options(...),
}
# ...
SHOW_PAGE_ATTRIBUTES = %i[
...
media_en
media_fr
]
# ...
filter_attributes do |attributes|
attributes -= %i[media_fr] unless I18n.locale == :fr
attributes -= %i[media_en] unless I18n.locale == :en
attributes
end
end NB: I had to update module Admin::AttributesFilter
extend ActiveSupport::Concern
included do
def form_attributes(action = nil)
apply_attributes_filters(super, :form)
end
def show_page_attributes
apply_attributes_filters(super, :show)
end
def collection_attributes
apply_attributes_filters(super, :collection)
end
private
def apply_attributes_filters(attributes, action)
self.class.filters.each do |key, filters|
if key.nil? || key == action
filters.each do |filter|
attributes = filter.call(attributes)
end
end
end
attributes
end
end
class_methods do
def filters
@filters ||= {}
@filters.dup
end
protected
def filter_attributes(action = nil, &block)
@filters ||= {}
@filters[action] ||= []
@filters[action] << block
end
end
end |
The authorization/pundit system allows to restrict which resources an admin can view/edit, but how to authorize them to only view/edit specific fields within specific records? The authorization doc does not seem to cover this.
I did not find a way to do this.
The text was updated successfully, but these errors were encountered: