# When policy says no

Your code never handles a policy refusal itself. The compiler adds policy
to the SQL `WHERE` of every query and write, and indexes every field a
policy condition filters on. `jadpo sql` shows the policy each statement
carries. What a refused caller sees depends on what they asked for:

| Situation | The client sees |
|---|---|
| Not signed in, on a route that needs it | 401 `unauthenticated` ([Routes: signed in by default](routes-signed-in-by-default.md)) |
| A record the caller may not see, read or changed by id | the code's own `not_found` failure, as if it did not exist |
| A list | only the records the caller may see, filtered before paging |
| A create, or an action they may not call | 403 `not_permitted` |

A hidden record reports the same `not_found` failure the code already
maps, so a caller can't tell it from one that doesn't exist. The log
records that policy concealed it
([Concealing records](concealing-records.md)).

`not_permitted` is raised by the runtime and never listed in `fails`. The
generated API description lists the 403 wherever it can happen.

Writes are checked in the same statement as the change, so a membership
removed a moment earlier can't slip through.

A token issued to another application ([Identity](identity.md))
carries scopes, which say what that application may do. Scopes only
narrow: the token may do what both the person's grants and its scopes
allow.

## Policy, end to end

A whole application whose customers are companies. Members sign up, found
a company and give each other roles in it, and a company's projects are
seen and changed only by its members.

First, the principal, its one sign-in method, and root `policy.jadpo`.
The policy names the tenant, and the one action that needs other people
than its entities' grants: founding a company, since a company with no
members yet has no owner to grant anything:

```jadpo
use work/entities { Member }

application {}

principal {
    user: {
        subject: Member.handle
    }
}
```

```jadpo
use work/entities { Company }
use work

policy {
    tenant: Company
    actions: {
        work.found_company: [Access.authenticated]
    }
}
```

A member may read only their own record, and anyone may sign up. Members
are shared by every company. The membership says where its role is held,
who holds it and which field holds it. A company's records follow the
roles held in it:

```jadpo
use work/types { Handle }

/// Someone who signs in.
entity Member {
    id: Uuid
    handle: Handle {
        unique: true
    }

    policy: {
        create: [Access.public]
        read: [id]
        tenant: Tenancy.shared
    }
}
```

```jadpo
use work/types { CompanyName, CompanyRole }

/// A customer: the tenant every other record belongs to.
entity Company {
    id: Uuid
    name: CompanyName

    policy: {
        read: [CompanyRole.owner, CompanyRole.editor, CompanyRole.viewer]
    }
}
```

```jadpo
use work/entities { Company, Member }
use work/types { CompanyRole }

/// A member's role in a company.
entity CompanyMembership {
    id: Uuid
    company_id: Company.id {
        on_delete: OnDelete.cascade
    }
    member_id: Member.id {
        on_delete: OnDelete.cascade
    }
    role: CompanyRole

    membership: {
        scope: company_id
        member: member_id
        role: role
    }

    policy: {
        create: [CompanyRole.owner]
        read: [CompanyRole.owner, member_id]
        delete: [CompanyRole.owner, member_id]
    }
}
```

```jadpo
use work/entities { Company }
use work/types { CompanyRole, ProjectName }

/// A piece of work in a company.
entity Project {
    id: Uuid
    company_id: Company.id {
        on_delete: OnDelete.cascade
    }
    name: ProjectName

    policy: {
        create: [CompanyRole.owner, CompanyRole.editor]
        read: [CompanyRole.owner, CompanyRole.editor, CompanyRole.viewer]
        update: [CompanyRole.owner, CompanyRole.editor]
        delete: [CompanyRole.owner]
    }
}
```

Founding a company is the listed action: anyone signed in may call it,
and it makes its founder the owner. Adding a member is an ordinary create
of a membership, so only the company's owners may do it. The code says
nothing about who may do either:

```jadpo
use work/entities { Company, CompanyMembership }
use work/types { CompanyName, CompanyRole }

/// Starts a company with its founder as its owner.
action found_company(name: CompanyName, founder: Principal.user) -> Company {
    var company: Company = Company.create {
        set: {
            name: name
        }
    }
    var owner: CompanyMembership = CompanyMembership.create {
        set: {
            company_id: company.id
            member_id: founder.id
            role: CompanyRole.owner
        }
    }
    assert owner.role == CompanyRole.owner
    return company
}
```

```jadpo
use work/entities { Company, CompanyMembership, CompanyNotFound, Member, MemberNotFound }
use work/types { CompanyRole }

/// Gives a member a role in a company; only its owners may.
action add_member(
    company_id: Company.id,
    member_id: Member.id,
    role: CompanyRole
) fails CompanyNotFound, MemberNotFound -> CompanyMembership {
    return attempt CompanyMembership.create {
        set: {
            company_id: company_id
            member_id: member_id
            role: role
        }
    }
}
```

The query names no caller and no company check: policy adds both to its
`WHERE`, so a caller sees only the projects of companies where they hold a
role that may read them:

```jadpo
use work/entities { Company, Project }
use work/types { ProjectView }

/// A company's projects, by name.
query company_projects(company_id: Company.id) -> List<ProjectView> {
    from: Project
    where: Project.company_id == company_id
    order: [Project.name asc]
    limit: 100
}
```

```jadpo
use work/entities { Company, Project }
use work/types { ProjectView }
use work/actions { RenameProject, StartProject, rename_project, start_project }
use work/queries { company_projects }

/// A company's projects.
routes Projects {
    prefix: /projects

    /// Starts a project.
    POST / {
        input: StartProject
        output: ProjectView
        status: SuccessStatus.created
        run: start_project(input)
    }

    /// A company's projects the caller may see.
    GET /{company_id: Company.id} {
        output: List<ProjectView>
        run: company_projects(company_id)
    }

    /// Renames a project.
    PATCH /{project_id: Project.id} {
        input: RenameProject
        output: ProjectView
        run: rename_project(project_id, input)
    }
}
```

A route test signs in as each member, so what it checks is exactly what a
browser would see. The test sets up its data by calling actions without a
caller, which runs them without policy. `found_company` takes its founder,
so it runs as the founder and its grants apply:

```jadpo
use work/entities { Company, CompanyMembership, Member }
use work/types { CompanyRole, ProjectView }
use work/actions { RenameProject, StartProject, add_member, found_company, register, start_project }

test "a company's projects are listed only to its members" {
    var ann: Member = attempt register("ann")
    var bob: Member = attempt register("bob")
    var cy: Member = attempt register("cyd")
    var northwind: Company = found_company("Northwind", Principal.user(ann))
    var viewer: CompanyMembership = attempt add_member(northwind.id, bob.id, CompanyRole.viewer)
    assert viewer.role == CompanyRole.viewer
    var launch: ProjectView = attempt start_project(
        StartProject {
            company_id: northwind.id
            name: "Launch"
        }
    )
    var seen: Response = request {
        method: HttpMethod.get
        path: "/projects/{company_id}"
        params: {
            company_id: northwind.id
        }
        as: Principal.user(bob)
    }
    assert seen.status == 200
    assert seen.output == [launch]
    var outsider: Response = request {
        method: HttpMethod.get
        path: "/projects/{company_id}"
        params: {
            company_id: northwind.id
        }
        as: Principal.user(cy)
    }
    assert outsider.output == []
}

test "a viewer can't rename a project, which reads as missing" {
    var ann: Member = attempt register("ann")
    var bob: Member = attempt register("bob")
    var northwind: Company = found_company("Northwind", Principal.user(ann))
    var viewer: CompanyMembership = attempt add_member(northwind.id, bob.id, CompanyRole.viewer)
    assert viewer.role == CompanyRole.viewer
    var launch: ProjectView = attempt start_project(
        StartProject {
            company_id: northwind.id
            name: "Launch"
        }
    )
    var by_bob: Response = request {
        method: HttpMethod.patch
        path: "/projects/{project_id}"
        params: {
            project_id: launch.id
        }
        as: Principal.user(bob)
        input: RenameProject {
            name: "Plans"
        }
    }
    assert by_bob.status == 404
    assert by_bob.code == "project_not_found"
    var by_ann: Response = request {
        method: HttpMethod.patch
        path: "/projects/{project_id}"
        params: {
            project_id: launch.id
        }
        as: Principal.user(ann)
        input: RenameProject {
            name: "Plans"
        }
    }
    assert by_ann.status == 200
}

test "someone outside a company can't start a project in it" {
    var ann: Member = attempt register("ann")
    var cyd: Member = attempt register("cyd")
    var northwind: Company = found_company("Northwind", Principal.user(ann))
    var response: Response = request {
        method: HttpMethod.post
        path: "/projects"
        as: Principal.user(cyd)
        input: StartProject {
            company_id: northwind.id
            name: "Takeover"
        }
    }
    assert response.status == 403
    assert response.code == "not_permitted"
}
```
