Enforce authorization checks in LiveView callbacks#2549
Open
lawik wants to merge 7 commits into
Open
Conversation
Contributor
Author
|
So the authorisation checks should not just check that you have the role but that you have the role on the relevant entity. |
Contributor
Author
|
For checking a DeploymentGroup-focused permission. Should we add the deployment group to the scope and pass the scope or should we just pass the deployment group? @joshk I feel like you'll have some thoughts on this PR overall but also these specifics. I'm very happy for us to overhaul things in this. What I want to prevent is that we can introduce authz failures inadvertently. |
The decorator was unquoting the entire arg AST (e.g. `%{...} = socket`)
which re-injected the pattern match into the wrapped body and shadowed
any variables bound in the head. Extract just the bare socket variable
via socket_var/1 so head pattern matching on the function head works
normally.
Also replace the `{reply, socket} = body` destructure with a
mark_socket/2 helper that handles every LiveView callback return shape:
`{:noreply, _}`, `{:reply, _, _}`, `{:ok, _}`, `{:ok, _, opts}`,
`{:halt, _}`, `{:cont, _}`. The old version only matched 2-tuples and
silently produced MatchError for `{:reply, msg, socket}` returns.
The auth-check no longer rebinds `socket` inside the wrapper; it only
captures the resulting private flags and re-merges them into whatever
socket the body returned.
After rebasing onto main two upstream changes collide with the original enforcement strategy: - PR #2681 added self-removal-by-non-admin for org users via a can_remove!/3 private check. The `requires_permission(:"org_user:delete")` decorator (admin-only) blocks that case, so the handler is moved to `special_permission(:enforced_by_can_remove)` and lets the existing can_remove! call act as the gate. - PR #2543 added Product.Notifications, a LiveView that did not exist when this branch was opened and therefore has no decorators. The wrapper now raises AuthorizationNotApplied on each callback. Decorate mount/handle_params/paginate/set-paginate-opts with `requires_permission(:"product:view")`, and `dismiss-all` with `special_permission(:enforced_by_authorized_bang)` (its body already calls authorized!/2). - The "view role can't dismiss notifications" test asserted the old process-exit behaviour via catch_exit/1. Update it to assert the flash-error response the new wrapper produces, matching the convention used elsewhere in the PR's denied-access tests.
…ply/opts tuples
Adds four new tests to AuthorizedLiveViewTest:
- head-matched scope variables survive `requires_permission` unchanged
- `special_permission(reason)` marks the socket authorized with the
supplied annotation
- `{:reply, msg, socket}` 3-tuple event returns are preserved
- `{:ok, socket, opts}` 3-tuple mount returns are preserved
These exercise the new socket_var/1 extraction and mark_socket/2
helper in NervesHubWeb.Access.AuthDecorator and would have failed
against the previous decorator implementation.
Also drops the unused default arg on `build_scope/3` (all callers
pass a product).
`AuthDecorator.mark_socket/2` is called from inside `quote do ... end` blocks, so it has to be referenced by its full module name (the generated code runs in the user's module, not ours). Adding `alias __MODULE__` at the top lets the body use the short `AuthDecorator.mark_socket/2` form, which also silences credo's Design.AliasUsage warning.
The original PR avoided destructuring scope in the function head
because the decorator's `unquote(socket)` injected the whole pattern
match AST into the body, shadowing the head's bindings. With the
decorator's socket_var/1 extraction in place, the standard
`%{assigns: %{current_scope: scope}} = socket` pattern works
correctly. Convert the workaround back to the idiomatic form across
the LiveViews touched by this PR.
lawik
force-pushed
the
enforce-authz-checks-2
branch
from
May 21, 2026 12:15
25e3a48 to
71b021d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The goal of this is to pro-actively prevent leaving LiveView callbacks open to security issues. The mechanism should force you to consider what authorization your functionality actually requires. This retrofits a lot of authorization checks. We may find that some of them are too tight or too loose. But in some cases they weren't there at all. It is intended to harmonize with the use of Scopes.