Fresh repository history for elektrine/tarakan hosted at https://git.elektrine.com/elektrine/tarakan.
75 lines
2.6 KiB
Elixir
75 lines
2.6 KiB
Elixir
defmodule TarakanWeb.BadgeController do
|
|
@moduledoc """
|
|
Embeddable SVG badges for a repository's security posture.
|
|
|
|
Served without authentication so a README on any host can render one, and
|
|
cached briefly so an embedded badge cannot be used to hammer the database.
|
|
The SVG is self-contained (no external fonts or images) because most hosts
|
|
proxy and sanitize badge images.
|
|
"""
|
|
|
|
use TarakanWeb, :controller
|
|
|
|
alias Tarakan.Repositories
|
|
alias Tarakan.SecurityPosture
|
|
|
|
@cache_seconds 900
|
|
|
|
def show(conn, %{"host" => host, "owner" => owner, "name" => name}) do
|
|
render_badge(conn, Repositories.get_repository(host, owner, name))
|
|
end
|
|
|
|
def show(conn, %{"owner" => owner, "name" => name}) do
|
|
render_badge(conn, Repositories.get_repository_by_slug(owner, name))
|
|
end
|
|
|
|
defp render_badge(conn, repository) do
|
|
badge =
|
|
case repository do
|
|
%{listing_status: "listed"} = repository ->
|
|
SecurityPosture.cached_badge(repository)
|
|
|
|
_unlisted_or_missing ->
|
|
%{label: "security", message: "not listed", color: "#5a6a72"}
|
|
end
|
|
|
|
conn
|
|
|> put_resp_content_type("image/svg+xml")
|
|
|> put_resp_header("cache-control", "public, max-age=#{@cache_seconds}")
|
|
|> send_resp(200, svg(badge))
|
|
end
|
|
|
|
# Flat two-part badge. Widths are estimated from character count because the
|
|
# SVG carries no font metrics; 6.6px per character matches the 11px
|
|
# sans-serif stack closely enough for the shape to stay tight.
|
|
defp svg(%{label: label, message: message, color: color}) do
|
|
label_width = text_width(label)
|
|
message_width = text_width(message)
|
|
total = label_width + message_width
|
|
|
|
"""
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="#{total}" height="20" role="img" aria-label="#{escape(label)}: #{escape(message)}">
|
|
<title>#{escape(label)}: #{escape(message)}</title>
|
|
<g shape-rendering="crispEdges">
|
|
<rect width="#{label_width}" height="20" fill="#1c1f22"/>
|
|
<rect x="#{label_width}" width="#{message_width}" height="20" fill="#{escape(color)}"/>
|
|
</g>
|
|
<g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,Geneva,sans-serif" font-size="11">
|
|
<text x="#{div(label_width, 2)}" y="14">#{escape(label)}</text>
|
|
<text x="#{label_width + div(message_width, 2)}" y="14">#{escape(message)}</text>
|
|
</g>
|
|
</svg>
|
|
"""
|
|
end
|
|
|
|
defp text_width(text), do: round(String.length(text) * 6.6) + 12
|
|
|
|
defp escape(value) do
|
|
value
|
|
|> to_string()
|
|
|> String.replace("&", "&")
|
|
|> String.replace("<", "<")
|
|
|> String.replace(">", ">")
|
|
|> String.replace("\"", """)
|
|
end
|
|
end
|