3.0 KiB · text History b868907
defmodule GitGudWeb.SecretsAuditLogTest do
  @moduledoc """
  Changing a secret or variable leaves an audit entry, and a line in the
  log, naming who, what and where — and never the value: not in the
  table, not in the log, not on the audit page.
  """
  use GitGudWeb.ConnCase, async: false

  import Ecto.Query
  import ExUnit.CaptureLog
  import Phoenix.LiveViewTest
  import GitGud.AccountsFixtures
  import GitGud.ForgeFixtures

  alias GitGud.Audit.Entry
  alias GitGud.Repositories.Storage

  setup do
    # The suite logs at :warning; the audit line is a :notice.
    Logger.put_module_level(GitGud.Audit, :notice)
    on_exit(fn -> Logger.delete_module_level(GitGud.Audit) end)
    :ok
  end

  defp entries, do: GitGud.Repo.all(from e in Entry, order_by: e.id)

  test "a repository's secrets and variables", %{conn: conn} do
    {user, repo} = repository_fixture()

    conn =
      conn
      |> log_in_user(user)
      |> Plug.Test.put_peer_data(%{address: {203, 0, 113, 9}, port: 4000, ssl_cert: nil})

    {:ok, lv, _} = live(conn, "/r/#{Storage.owner_handle(user)}/#{repo.name}/settings/secrets")

    log =
      capture_log([level: :notice], fn ->
        lv
        |> form("#secret-form", %{"name" => "DEPLOY_KEY", "value" => "hunter2-value"})
        |> render_submit()

        lv
        |> form("#variable-form", %{"name" => "REGION", "value" => "us-east-plain"})
        |> render_submit()

        render_click(lv, "delete_secret", %{"name" => "DEPLOY_KEY"})
        # Not there: nothing to record.
        render_click(lv, "delete_secret", %{"name" => "NEVER_SET"})
      end)

    assert [set, var, deleted] = entries()
    assert {set.action, set.target_label} == {"repo_secret.set", "DEPLOY_KEY"}
    assert {var.action, var.target_label} == {"repo_variable.set", "REGION"}
    assert {deleted.action, deleted.target_label} == {"repo_secret.deleted", "DEPLOY_KEY"}
    assert set.actor_id == user.id
    assert set.repository_id == repo.id
    assert set.ip == "203.0.113.9"

    assert log =~ "audit: repo_secret.set by #{user.handle}"
    assert log =~ "audit: repo_secret.deleted by #{user.handle}"

    stored = Jason.encode!(Enum.map(entries(), &Map.take(&1, Entry.__schema__(:fields))))

    for value <- ["hunter2-value", "us-east-plain"] do
      refute log =~ value
      refute stored =~ value
    end
  end

  test "an organization's, shown on its audit page without the value", %{conn: conn} do
    admin = user_fixture()
    handle = "sec#{System.unique_integer([:positive])}"

    {:ok, org} =
      GitGud.Organizations.create_organization(admin, %{"handle" => handle, "name" => handle})

    conn = log_in_user(conn, admin)
    {:ok, lv, _} = live(conn, "/orgs/#{handle}/settings/secrets")

    lv
    |> form("#secret-form", %{"name" => "ORG_TOKEN", "value" => "S3CR3T-org-value"})
    |> render_submit()

    assert [%Entry{action: "org_secret.set", organization_id: org_id}] = entries()
    assert org_id == org.id

    {:ok, _audit, html} = live(conn, "/orgs/#{handle}/settings/audit")
    assert html =~ "ORG_TOKEN"
    refute html =~ "S3CR3T-org-value"
  end
end