defmodule GitGudWeb.SecurityLive.Repo do
@moduledoc """
`/r/:owner/:name/security[/:state]` — a repository's dependency alerts
(`GitGud.Advisories`): open, fixed, dismissed.
For whoever may see them (`Advisories.readable?/2`, asked again on
every `handle_params` and live update): an org repository's org
members, a personal repository's owner and writers. Shows how to turn
alerts on where they're off, and says so where the instance can't run
them.
"""
use GitGudWeb, :live_view
alias GitGud.Advisories
alias GitGud.Advisories.Alert
alias GitGud.Repositories
alias GitGud.Repositories.Storage
@states ~w(open fixed dismissed)
@impl true
def mount(%{"owner" => owner, "name" => name}, _session, socket) do
repo =
Repositories.get_repository_by_path!(owner, name)
|> GitGud.Repo.preload([:owner, :organization])
if Advisories.readable?(socket.assigns.current_scope.user, repo) do
if connected?(socket), do: Phoenix.PubSub.subscribe(GitGud.PubSub, Advisories.topic(repo))
{:ok,
socket
|> assign(:repo, repo)
|> assign(:handle, Storage.repo_handle(repo))
|> GitGudWeb.RepoLive.Header.assign_chrome(repo)
|> assign(:page_title, "Security — #{owner}/#{name}")
|> stream_configure(:alerts, dom_id: &"alert-#{&1.id}")}
else
{:ok, deny(socket, repo)}
end
end
@impl true
def handle_params(params, _uri, socket) do
repo = socket.assigns.repo
base = ~p"/r/#{socket.assigns.handle}/#{repo.name}/security"
case params["state"] do
nil -> {:noreply, load(socket, "open")}
"open" -> {:noreply, push_patch(socket, to: base, replace: true)}
state when state in @states -> {:noreply, load(socket, state)}
_ -> {:noreply, socket |> put_flash(:error, "No such list.") |> push_patch(to: base)}
end
end
@impl true
def handle_info({:advisories, _repo_id}, socket),
do: {:noreply, load(socket, socket.assigns.state)}
defp load(socket, state) do
repo = socket.assigns.repo
if Advisories.readable?(socket.assigns.current_scope.user, repo) do
socket
|> assign(:state, state)
|> assign(:counts, Advisories.counts(repo))
|> assign(:enabled?, Advisories.enabled?(repo))
|> assign(:available, Advisories.available())
|> stream(:alerts, Advisories.list(repo, state), reset: true)
else
deny(socket, repo)
end
end
defp deny(socket, repo) do
socket
|> put_flash(:error, "Dependency alerts are for this repository's members.")
|> redirect(to: ~p"/r/#{Storage.repo_handle(repo)}/#{repo.name}")
end
defp tab_path(handle, repo, "open"), do: ~p"/r/#{handle}/#{repo.name}/security"
defp tab_path(handle, repo, state), do: ~p"/r/#{handle}/#{repo.name}/security/#{state}"
defp osv_url(id), do: "https://osv.dev/vulnerability/#{id}"
defp severity_class(alert) do
case Alert.severity_label(alert) do
"critical" -> "badge-error"
"high" -> "badge-error badge-outline"
"medium" -> "badge-warning"
_ -> "badge-ghost"
end
end
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_scope={@current_scope}>
<div class="space-y-6">
<GitGudWeb.RepoLive.Header.header
repo={@repo}
handle={@handle}
current_scope={@current_scope}
has_packages?={@has_packages?}
can_admin?={@can_admin?}
latest_run={@latest_run}
open_pulls={@open_pulls}
security_alerts={assigns[:security_alerts]}
/>
<header class="space-y-1">
<h2 class="text-lg font-semibold">Dependency alerts</h2>
<p class="text-sm opacity-70">
Known vulnerabilities (<a href="https://osv.dev" class="link">OSV</a>) in the
versions this repository's lockfiles pin on its default branch.
<span :if={@repo.advisories_scanned_at}>
Last scanned {Calendar.strftime(@repo.advisories_scanned_at, "%Y-%m-%d %H:%M UTC")}.
</span>
</p>
</header>
<div :if={@available != :ok} id="security-unavailable" class="alert alert-warning text-sm">
<.icon name="hero-exclamation-triangle" class="size-5" />
<span :if={@available == :off}>Dependency alerts are turned off on this instance.</span>
<span :if={@available == :no_scanner}>
This instance has no osv-scanner installed, so nothing is scanned.
</span>
</div>
<div :if={@available == :ok and not @enabled?} id="security-off" class="alert text-sm">
<.icon name="hero-shield-exclamation" class="size-5" />
<span :if={@repo.organization_id}>
Off for this organization's repositories. An org admin can turn them on in <.link
navigate={~p"/orgs/#{@handle}/settings/profile"}
class="link"
>the org's settings</.link>.
</span>
<span :if={is_nil(@repo.organization_id)}>
Off for this repository. Its owner can turn them on in <.link
navigate={~p"/r/#{@handle}/#{@repo.name}/settings"}
class="link"
>settings</.link>.
</span>
</div>
<nav id="security-tabs" class="flex gap-1" aria-label="Alerts">
<.link
:for={state <- ~w(open fixed dismissed)}
id={"security-tab-#{state}"}
patch={tab_path(@handle, @repo, state)}
class={["btn btn-xs", if(@state == state, do: "btn-primary", else: "btn-ghost")]}
>
{String.capitalize(state)}
<span class="badge badge-xs">{Map.get(@counts, state, 0)}</span>
</.link>
</nav>
<ul id="security-alerts" phx-update="stream" class="divide-y divide-base-300">
<li id="security-empty" class="hidden only:block py-8 text-center text-sm opacity-60">
{if @state == "open", do: "No open alerts.", else: "None."}
</li>
<li :for={{id, alert} <- @streams.alerts} id={id} class="py-3 space-y-1 text-sm">
<div class="flex flex-wrap items-center gap-2">
<span class={["badge badge-sm", severity_class(alert)]}>
{Alert.severity_label(alert)}<span :if={alert.severity}> {alert.severity}</span>
</span>
<span class="font-mono font-medium">{alert.package}</span>
<span class="font-mono opacity-70">{alert.version}</span>
<span :if={alert.fixed_versions != []} class="opacity-70">
→ fixed in <span class="font-mono">{Enum.join(alert.fixed_versions, ", ")}</span>
</span>
<span class="badge badge-ghost badge-sm">{alert.ecosystem}</span>
</div>
<p :if={alert.summary} class="opacity-80">{alert.summary}</p>
<p class="text-xs opacity-60 flex flex-wrap gap-x-3">
<a href={osv_url(alert.advisory_id)} class="link font-mono" rel="noopener noreferrer">
{alert.advisory_id}
</a>
<span :for={a <- alert.aliases} class="font-mono">{a}</span>
<span class="font-mono">{alert.manifest_path}</span>
<span :if={alert.fixed_at}>fixed {Calendar.strftime(alert.fixed_at, "%Y-%m-%d")}</span>
<span>first seen {Calendar.strftime(alert.first_seen_at, "%Y-%m-%d")}</span>
</p>
</li>
</ul>
</div>
</Layouts.app>
"""
end
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
7.4 KiB · text
History
b868907