MishkaCmsCore.Runtime.Resources.Validations.SameSiteReference (Mishka CMS Core v0.0.1)

Copy Markdown View Source

Refuses a record that names, by id, a record of another site.

The record named must belong to the site the record being written is stored with — on a create given a tenant, the tenant, unless the action bypasses multitenancy; otherwise its site_id; for a record with no site of its own (a join row), the site of the parent named in :own — or, with shared?: true, to no site. A record that belongs to no site names only shared records. A record of another site and a record that does not exist are refused with the same message, so the answer does not tell a caller which ids exist elsewhere. A record the site could name but that is archived is refused with "That <label> is archived. Restore it first."

Checked whoever writes, the system included: the rule is about the two records, not the actor. Nothing is checked when the id is absent or unchanged.

Each refusal is written in the Gettext locale of the process the action runs in, errors domain, with the :label translated in the same domain; a request with no locale reads it in English.

A user is named the same way: an account of the record's site, or with shared?: true a master (a user with no site). Read users with read_action: :related, which reaches every account.

shared?: :master also asks who writes: a record with no site is named only by a master (MishkaCmsCore.Accounts.Helper.master_user?/1) or by the system, a call with no actor made with authorize?: false. A site's user, whatever it holds and with authorize?: false too, gets the refusal a record of another site gets. Declare it with before_action?: true, so the one asked is the actor the action runs as.

MishkaCmsCore.Runtime.Resources.Validations.TenantIsolation holds a write to the caller's tenant and refuses shared records; this holds one record to another.

The record named

With moves: true and a :label, it guards the other end of the same links: an update may change the record's site_id only while every record naming it could still name it there. The records naming it are found through every SameSiteReference on the resources of the host's Ash domains, archived rows included, and through the versions that name an account as the one who wrote them. A record with no site of its own, or kept outside Postgres, is taken to be on another site. Refused with "Records outside that site still use this <label>, so it cannot move there.", or for a move to no site "Records that cannot use a shared <label> still use this one, so it cannot become shared."

Options

  • :field — required. The attribute holding the id.
  • :resource — required. The resource the id names. It has a site_id. For an id that names one of several resources, {kind_field, %{kind => resource}}: the record's kind_field picks the resource, and a kind the map does not hold is refused. The id is then checked again when only the kind changes.
  • :label — required. What the named record is called in the message, such as "layout".
  • :shared? — whether a record with no site may be named: true, false, or :master for only when a master or the system writes. Defaults to false.
  • :read_action — the read that reaches every site's rows. Defaults to :read_any. With a kind map, every resource in it has this read.
  • :own — for a record with no site_id: {field, resource} or {field, resource, read_action} naming the parent whose site the record has. Its read action defaults to :read_any.

Usage

validate {SameSiteReference,
          field: :layout_id, resource: MishkaCmsCore.Runtime.Layout, label: "layout",
          shared?: true}

validate {SameSiteReference,
          field: :tag_id, resource: MyApp.Tag, label: "tag", own: {:post_id, MyApp.Post}}

validate {SameSiteReference,
          field: :document_id,
          resource: {:section, %{page: MishkaCmsCore.Runtime.Page,
                                 layout: MishkaCmsCore.Runtime.Layout}},
          label: "document", shared?: true}

validate {SameSiteReference,
          field: :author_id, resource: MishkaCmsCore.Accounts.User, label: "author",
          shared?: true, read_action: :related}

validate {SameSiteReference,
          field: :user_id, resource: MishkaCmsCore.Accounts.User, label: "user",
          shared?: :master, read_action: :related},
         before_action?: true

validate {SameSiteReference, moves: true, label: "layout"}