Back to Feed
article 3m read

My sidebar was checking permissions with two hand-typed strings

The Users menu link did its own permission check while most page guards asked a helper, so the area rules moved into one table that the guards and the menu's standard links both read, and an empty permission list should still keep people out.

Article DepthPractice

Flow

Moving to one access table

  1. 1Find every copy

    List each place that decides who can enter an area, including one-off checks in the menu.

  2. 2Name the areas

    Give each area a fixed name so a typo fails before it ships.

  3. 3Keep the rules small

    Only the rule shapes you have: any of these permissions, or admin.

  4. 4Wrap the old helpers

    Old per-area functions call the table, so nothing breaks mid-move.

  5. 5Point guards and menu at it

    Both call the same check; an empty permission list must mean no entry.

The Users link in my ordering system's sidebar checked your permissions with two typed-out strings. Most other links asked a shared helper. This one did its own check, right in the menu code. It matched the real rule, so it worked. But it was a copy, and copies drift.

I spotted it while reading a big cleanup change. Most of that change just moved code around. The access part was small, but it's the part I care about most, because when it's wrong you just get a link that kicks you out, or a page with no link to it.

Three places, one rule

The question "can this person get into this area?" was answered in three places. A helper file had one small function per area, each with its own list of permissions. The page guards on the server called those functions and sent you back to the dashboard on a no. The menu called most of them, except for Users. And two guards wrote out their own lists instead of asking the helpers.

As far as I could tell, they all agreed. But they only agreed because people kept them in sync by hand. Change the guard and forget the menu, and people see a link that bounces them.

Moving the rules into a table

Now each area gets one row in one table: a name and a rule. A rule is either "has any of these permissions" or "is an admin". The menu, the guards and the old helper functions all read that table. If you want this in your own app:

  • Give each area a fixed name, so a typo fails before it ships.
  • Keep only the rule shapes you actually have.
  • Let the guards and the menu both call the same check.
  • Turn the old helpers into one-line wrappers so nothing breaks while you move callers over.

An area with no list

The admin-only area has no permission list, so asking for its list returns nothing. That could read as "nothing required". My read is that the guard only lets you in if at least one check passes, and with no checks, none would. So an empty list should mean no entry, though I couldn't see the guard's body to confirm it. A test now locks that in, along with tests that area names are unique and that the Users link follows the right permission.

A repeated pair and hand-built links

One broad rule still repeats two permissions from a narrower one. Some menu links are still built by hand. And I suspect the menu can run behind the server right after someone's permissions change. Of the three, the repeated pair looks like the cheapest fix: build the broad rule out of the narrow one.

Keep reading

Pick up a connected idea or branch into a nearby one.

5 paths forward
Continue the thread

New delete button hands the server a path, and the server doesn't take its word for it

A Delete button was added to the backup page in the scheduling app. It sends a file path, so the server resolves it, fences it to the selected company's folder with a separator-aware check, refuses anything but a regular file, and only then deletes. What still bugs me: name-derived folders, hard deletes, no password, no tests and a vague audit entry.

via #authorization · #web-security
Related idea

Device limit counts first and saves later, and the test never sends two at once

The licensing code I shipped checks how many devices a key already has, then inserts the new one in a separate step, so two activations racing for the last slot can both get in. The test activates devices one at a time and can't see it, a device racing itself gets a server error, and a key-only check passes for keys never bound anywhere. I found this by reading, not by running anything concurrent.

via #testing · #authorization