# `MishkaCmsCore.Runtime.Resources.Validations.TenantIsolation`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/runtime/resources/__validations/tenant_isolation.ex#L5)

Refuses a write that names a record of another site.

A row that points at a site's record — a bookmark at a post, a follow at a blogger, a comment at
an issue — may only point at a record of the site the write is made for: the tenant, or the
actor's own site when there is no tenant. A resource with no multitenancy of its own, such as
`MishkaBlog.PostBookmark`, has nothing else that says so.

A site user writes for its own site only. Naming another site as the tenant is refused, the same
way, whatever record the write names: the tenant is not the site user's to choose. A master (a
user whose `site_id` is nil) writes for whichever site it names as the tenant, and a visitor with
no actor for the site it is visiting.

A record of another site and a record that does not exist are refused with the same error, so the
answer does not tell a caller which ids exist elsewhere. Each refusal is announced on
`runtime:tenant:cross_site_refused` with `%{resource: resource, field: field, id: id, tenant:
tenant, actor_id: actor_id}`.

A write with neither a tenant nor an actor's site passes only for a master admin: a master who is
an admin of the surface the write came from (`MishkaCmsCore.Accounts.Helper.master_admin?/2`, with
`MishkaCmsCore.Accounts.Checks.AccessPermissions.surface/1`). Anyone else is refused the same way —
a master with no admin permission of that surface, a visitor, an actor with no `site_id`. Nothing
is checked for a call made with `authorize?: false`.

## Usage

    validate {MishkaCmsCore.Runtime.Resources.Validations.TenantIsolation,
              field: :section_id,
              resource: MishkaDocument.Section,
              message: "Section not found or does not belong to your site."}

## Options

* `:field` - Required. The attribute holding the id of the record.
* `:resource` - Required. The Ash resource that record belongs to. It must have a `site_id`.
* `:message` - Optional. The error message. Defaults to "{Resource} not found or does not belong
  to your site (field: {field})".
* `:optional` - Optional. When true, a `nil` id passes. Defaults to true.

The record is read with the first of `:read_any`, `:master_read` and `:read` the resource has,
so a resource that is multitenant needs one of the first two.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
