skip to content

Your role-administration endpoint lets a district supervisor grant roles to inspectors — what must it refuse, and what happens when it does not?

level: seniorimportance: should knowfreq 54%

answer

  1. granting changes tomorrow's answer
  2. no conferring what you lack
  3. bounded by set and by unit
  4. expanded sets, not role names
  5. bootstrap stays the residual path

basics

~20 s

It must refuse any grant whose permissions exceed the granter's own effective set, and any grant outside the unit the granter administers. Without both bounds, a district supervisor can hand themselves or a colleague a role carrying more than they hold, and walk up to service-wide in a step or two.

solid answer

~50 s

A grant endpoint is a privilege-escalation path unless it is bounded twice. First, **by set**: the permissions carried by the role being granted must be a subset of the granter's own effective permissions, so nobody can confer what they do not hold. Second, **by unit**: the grant's district must be one the granter holds the administration right in, and a service-wide grant needs a service-wide administrator. Compare permission **sets**, not role names, or a role that quietly gains a permission next quarter turns an old check into a hole. The endpoint must also take the role and the target from its own tables rather than trusting a role key sent by the caller, and record `granted_by` on the row. Bounded this way, the self-service route to more privilege is gone; what remains is the bootstrap administrator, which is deliberate and should be few, named and rarely used.

code

pseudocode · 19 lines
pseudocode
function grant_role(granter, target_user, role_key, district_id):
    role = role_table.find_by_key(role_key)        # never trust the body's idea of the role
    if role is null:
        deny(404)

    # bound 1: the granter must administer THIS unit
    granter_units = units_where(granter, "role:grant")
    if district_id not in granter_units:            # covers the service-wide (null) case too
        deny(403, "you do not administer that district")

    # bound 2: the conferred set must not exceed the granter's own set IN that unit
    conferred = permissions_of(role)
    held      = effective_permissions(granter, district_id)
    missing   = conferred - held
    if missing is not empty:
        deny(403, "cannot grant " + missing + ": not held here")

    role_grant.upsert(user_id=target_user, role_id=role.id,
                      district_id=district_id, granted_by=granter.id)

go deeper

for a junior

Understand that the endpoint which hands out roles is itself a privileged operation: being allowed to grant is not the same as being allowed to grant anything to anyone.

for a middle

State both bounds and why the subset test runs against expanded permission sets rather than role names, and which inputs on the request must never be trusted.

for a senior

Walk the two-step escalation through a real administration screen, say what the refusal should tell the supervisor, and describe the tests that assert the denial rather than the happy path.

for a principal

Decide who holds the unbounded starting point at all, how many such holders the organisation tolerates, and what makes gaining one an event rather than a routine task.

## The escalation nobody writes down Every application that models roles eventually grows an administration screen: someone must be able to make a new inspector an inspector. That endpoint is authorization's own back door, because its effect is *to change what the check will answer tomorrow*. Guarding it with a single `role:grant` permission is not enough — it says the caller may grant, not **what** and **to whom**. The classic path, in a district office: 1. A district supervisor holds `role:grant` because they onboard inspectors. 2. The endpoint accepts any `role_id` in the role table. 3. The supervisor grants themselves `national_administrator`, or grants it to a colleague who returns the favour. 4. Every later check answers correctly. Nothing is broken; the decision was simply made by the wrong person. No request in that sequence is malformed, so nothing in the stack objects. The defect is in the grant surface's shape, not in the enforcement. ## The two bounds 1. **Bound by set.** The effective permission set carried by the role being granted must be a **subset** of the granter's own effective set in that unit. This is the rule that makes "you cannot confer what you do not hold" structural rather than aspirational. 2. **Bound by unit.** The `district_id` on the new grant must be a unit the granter holds the administration right in. A service-wide grant — the nullable district — requires a service-wide administrator; it is never reachable from a district posting. A third bound is worth stating explicitly because it is the one most often forgotten: **the right to administer is itself a permission that can be granted**. If `role:grant` is inside a role a district supervisor may confer, the set bound already covers it, and that is usually acceptable — the granter is passing on something they hold. What is not acceptable is a special case that lets administration be conferred outside the two bounds. ### Compare sets, never names Name-based rules — "a supervisor may grant `inspector`" — encode today's meaning of the role in a place that never changes when the role does. Add `establishment:reopen` to `inspector` next quarter, and an allow-list written last year silently starts conferring it. Comparing the **expanded permission sets** at grant time means the bound tracks the definitions automatically. | | Allow-list of role names | Subset test on expanded permission sets | |---|---|---| | A role gains a permission | Rule silently widens | Bound re-evaluates on the next grant | | Reviewing the rule | Read code, then read the role table | One comparison, both sides queried | | A new role appears | Needs a new rule or it is ungrantable | Governed by the same test | | Failure mode | Quiet over-grant | Loud refusal a supervisor can understand | ## What the endpoint must not trust - **The role key from the request body** is a designation, not an entitlement: resolve it in the role table and apply the bounds to what it actually carries. - **The granter's claimed unit**: take it from the granter's own grant rows, not from a field in the payload. - **The client's rendering**: hiding the `national_administrator` option in the administration screen is a usability choice; the endpoint is the control. A refusal here is an authorization refusal with the caller already identified, so it is a `403`, and the message should say *which* bound failed — "you cannot grant a role carrying report:sign_off, which you do not hold in this district" — because a supervisor who cannot tell an over-reach from an outage will open a ticket asking for more privilege. ## Making the grant reviewable The grant row carries `granted_by` and `granted_at` as ordinary columns, which is what makes any later question about a posting answerable from the table itself: who created this, and did they hold the bound at the time. Keep this as schema, and keep it honest — a grant row edited by a later migration should say so. ## What is still open after both bounds Bounding the surface removes the **self-service** path; it does not remove the need for an unbounded starting point. Somebody had to hold the first service-wide role, and that bootstrap is the residual escalation path: - Create it by seed or by a deliberate operational step, not by an endpoint anyone can reach. - Keep the number of holders small enough to name out loud. - Treat gaining it as an event, not as an administration task. And two habits that keep the surface honest over time: - **Test the bound, not the happy path.** A test that a supervisor *can* grant `inspector` says nothing; the tests that matter are the ones asserting a refusal when the role carries one permission too many, and when the district is not the granter's. - **Re-check on every grant, including re-grants.** Re-issuing an existing posting is still a grant and must pass the same two bounds; an idempotent "already exists, returning success" shortcut that skips the check is a bypass wearing a convenience's clothes.

  • Should a supervisor be able to grant the administration right itself?
    If it sits inside a role whose permissions are a subset of what the supervisor holds in that district, the set bound already permits it and that is coherent: they are passing on something they hold, still confined to their unit. What must not exist is a special case that confers administration outside the two bounds.
  • The subset test refuses a grant your product genuinely needs — an onboarding clerk who may assign inspectors but holds no inspection permissions. How do you resolve that?
    Give the clerk the permissions in question, or accept a narrow, explicitly modelled delegation: a named list of roles that role may confer, stored as data, reviewed like a role definition. Do not widen the general rule; an exception written as data can be read, whereas a relaxed bound cannot.
  • How would you find out, months later, whether the bound ever leaked?
    The grant rows carry who granted what and when, so you can recompute: for each grant, was the granter's effective set at the time a superset, and was the unit theirs? Any row that fails is either a leak or a legitimate exception nobody recorded, and both are worth knowing.

saying these in an interview costs you the question

  • Guards the endpoint with a single grant permission and calls it bounded.
  • Allow-lists role names, so a role that gains a permission widens the rule silently.
  • Trusts the role key or the district sent in the request body.
  • Hides the dangerous role in the interface and treats that as the control.
  • Skips the bound when re-granting an existing posting because it already exists.
  • Claims bounding the endpoint makes escalation impossible, ignoring the bootstrap administrator.