When does field policies are applied?

So, I was playing around with field policies and I’m a little bit confused when they are applied.

I added the following field policy to my resource:

field_policies do
  field_policy :* do
    forbid_if always()
  end
end

I expected that this policy would make all fields from my resource, except the id to be retrieve as ForbiddenField.

But when I run a read action from that resource, all fields are retrieved normally, no field has been forbidden.

Is that correct?

Field policies do not apply to private fields by default, perhaps that is what you are seeing?

https://hexdocs.pm/ash/policies.html#handeling-private-fields-in-internal-functions

Not really, for example, I have this field:

   attribute :country, :string do
      allow_nil? false
      public? true

      default "usa"

      constraints max_length: 3
    end

When I run my query with that field_policy, I still can see that attribute value: country: "USA",

Aha, I think I find the issue.

I’m using a fragment to store the policies for that resource, and I incorrectly added it to the extensions option instead of the fragments… my bad…

Now the policies are being applied as they should.

So, by default all private values will be retrieve by default since they are not handled by field_policies by default:

  county: "fwefwe",
  state: #Ash.ForbiddenField<field: :state, type: :attribute, ...>,

Note: county is a private attribute, state is public

If I want to also forbid the private fields, then I can just add private_fields :include:

  county: #Ash.ForbiddenField<field: :county, type: :attribute, ...>,
  state: #Ash.ForbiddenField<field: :state, type: :attribute, ...>,

Now, how can I only authorize specific fields? I tried this:

field_policies do
  private_fields :include

  field_policy :zip do
    authorize_if always()
  end

  field_policy :* do
    forbid_if always()
  end
end

But I believe that the field checks works the same way as the normal policies, meaning that it will authorize zip, but then it will forbid in the catch all. Is that correct?

Is there some easy way to express that I want to block all by default except some specific fields by some check?

That is the way that field policies work by default. If you add any field policies, then all fields that aren’t included are hidden by default.

So just this:

field_policies do
  private_fields :include

  field_policy :zip do
    authorize_if always()
  end
end

Does what you’re describing.

Hmm, If I do that then It doesn’t even compile:

== Compilation error in file lib/core/marketplace/markets/property.ex ==
** (Spark.Error.DslError) [Core.Marketplace.Markets.Property]
field_policies:
  Missing field reference(s) in field policies: [:last_offer, :total_valid_offer, :non_rebuilt_favorite_count, :unique_view_count, :total_offers, :max_offer, :full_address, :offeror_current_offer, :favorite?, :total_unique_offers, :gross_profit, :average_offer, :street, :house_number, :city, :county, :state, :country, :type, :sub_type, :bedrooms, :bathrooms, :square_foot, :lot_size, :year_built, :ap_link, :cma_url, :description, :repairs, :price, :acquisition_price, :due_diligence_date, :status, :processing_status, :images, :external_id, :external_id_type, :metadata, :latitude, :longitude, :off_market, :view_count, :showing, :comment, :over_under_paid, :adj_mao, :opened_at, :normalized_full_address, :normalized_county, :normalized_state, ...]

If any field policies are present, *all* public, non-primary key fields must be accounted for.

To create a catch-all field policy that allows any fields that aren't covered
by other policies, use:

    field_policy :* do
      authorize_if always()
    end

Keep in mind that all policies relevant to a given field must pass, so this will
not override other field policies.

    (ash 3.4.8) /var/home/sezdocs/projects/rebuilt/platform/core/deps/spark/lib/spark/dsl/builder.ex:86: Ash.Policy.Authorizer.Transformers.AddMissingFieldPolicies.ensure_field_coverage/2
    (spark 2.2.26) lib/spark/dsl/extension.ex:639: anonymous fn/4 in Spark.Dsl.Extension.run_transformers/4
    (elixir 1.17.2) lib/enum.ex:4858: Enumerable.List.reduce/3
    (elixir 1.17.2) lib/enum.ex:2585: Enum.reduce_while/3
    /var/home/sezdocs/projects/rebuilt/platform/core/lib/core/marketplace/markets/property.ex:1: (file)
    (stdlib 6.0.1) erl_eval.erl:904: :erl_eval.do_apply/7
    (stdlib 6.0.1) erl_eval.erl:1192: :erl_eval.expr_list/7
    (stdlib 6.0.1) erl_eval.erl:610: :erl_eval.expr/6
    (stdlib 6.0.1) erl_eval.erl:1192: :erl_eval.expr_list/7
    (stdlib 6.0.1) erl_eval.erl:610: :erl_eval.expr/6
    (stdlib 6.0.1) erl_eval.erl:271: :erl_eval.exprs/6
    (stdlib 6.0.1) erl_eval.erl:436: :erl_eval.expr/6
    (spark 2.2.26) /var/home/sezdocs/projects/rebuilt/platform/core/lib/core/marketplace/markets/property.ex:1: Spark.Dsl.__before_compile__/1
:error

oh. Yeah I guess you’re right. I forgot we did that.

I guess you’ll have to manually list all the fields in your fallback field for now. Not ideal. Alternatively:

field_policies do
  private_fields :include

  field_policy_bypass :zip do
    authorize_if always()
  end

  field_policy :* do
    forbid_if always()
  end
end
1 Like