defmodule GitGudWeb.RepoAdmin do
@moduledoc """
Authorization for the repository pages that change things: settings
pages (repo admin) and the wiki editor (write access, not banned).
`RepoAuth :require_read` is the only gate the router puts in front of
these pages, and any signed-in user can read a public or internal
repository. So each page checks in `mount/3` and — because a client
can push any event with any params whatever the page rendered —
again in every handler that changes something. The handler check
re-reads the repository and the user's rights from the database
rather than trusting an assign made at mount: a socket can outlive
the admin role it was opened with.
"""
use GitGudWeb, :verified_routes
import Phoenix.LiveView, only: [put_flash: 3, redirect: 2]
alias GitGud.Moderation
alias GitGud.Organizations
alias GitGud.Repositories.Repository
alias GitGud.Repositories.Storage
alias GitGudWeb.RepoLive.Header
@doc "Whether `user` administers `repo` (see `RepoLive.Header.admin?/2`)."
def admin?(user, repo), do: Header.admin?(user, repo)
@doc """
Whether `user` may write to `repo` — at least `:write` and not banned
from it. What the wiki editor requires.
"""
def writer?(nil, _repo), do: false
def writer?(_user, nil), do: false
def writer?(user, %Repository{} = repo),
do: !!Organizations.can?(user, repo, :write) and not Moderation.banned?(repo, user)
@doc "`admin?/2` for the socket's user, against a fresh copy of its `:repo`."
def admin_now?(socket), do: admin?(user(socket), fresh_repo(socket))
@doc "`writer?/2` for the socket's user, against a fresh copy of its `:repo`."
def writer_now?(socket), do: writer?(user(socket), fresh_repo(socket))
@doc """
Runs `fun` (a handler body returning `{:noreply, socket}`) only if the
socket's user still administers the repository; otherwise flashes and
changes nothing.
"""
def with_admin(socket, fun) when is_function(fun, 0) do
if admin_now?(socket),
do: fun.(),
else:
{:noreply, put_flash(socket, :error, "You don't have access to manage this repository.")}
end
@doc "Like `with_admin/2`, for write access."
def with_writer(socket, fun) when is_function(fun, 0) do
if writer_now?(socket),
do: fun.(),
else: {:noreply, put_flash(socket, :error, "You can't edit this repository.")}
end
@doc "Where a mount sends someone who isn't allowed in: the repository's page."
def deny(socket, %Repository{} = repo, message \\ nil) do
socket
|> put_flash(:error, message || "You don't have access to manage this repository.")
|> redirect(to: ~p"/r/#{Storage.repo_handle(repo)}/#{repo.name}")
end
@doc """
Parses an id pushed by the client. Anything but a positive integer is
`nil`, so a junk id finds nothing instead of crashing the view.
"""
def parse_id(id) when is_integer(id) and id > 0, do: id
def parse_id(id) when is_binary(id) do
case Integer.parse(id) do
{n, ""} when n > 0 -> n
_ -> nil
end
end
def parse_id(_), do: nil
defp user(socket), do: socket.assigns[:current_scope] && socket.assigns.current_scope.user
defp fresh_repo(%{assigns: %{repo: %Repository{id: id}}}) do
case GitGud.Repo.get(Repository, id) do
nil -> nil
repo -> GitGud.Repo.preload(repo, :organization)
end
end
defp fresh_repo(_), do: nil
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
3.3 KiB · text
History
b868907