6.5 KiB · text History 202c083
defmodule GitGud.PackageSigningKeysTest do
  @moduledoc """
  Signing keys belong to the namespace that publishes, not to the
  repository. The whole point is what a *consumer* has to do: one
  keyring entry per organization, not one per repository.
  """
  use GitGudWeb.ConnCase, async: false

  alias GitGud.Packages
  alias GitGud.Packages.Apt.Native
  alias GitGud.Repositories.Storage

  import GitGud.AccountsFixtures
  import GitGud.ForgeFixtures

  describe "scoping" do
    test "two repositories in one organization share a key" do
      {_user, org, one, two} = org_with_two_repos()

      assert {:ok, %{public_pem: first}} = Packages.signing_key(one, "deb")
      assert {:ok, %{public_pem: second}} = Packages.signing_key(two, "deb")

      assert first == second
      assert Packages.get_signing_key(org, "deb")
    end

    test "and the apt repositories they serve verify against that one key" do
      {_user, _org, one, two} = org_with_two_repos()

      key = Packages.signing_key(one, "deb") |> elem(1) |> Map.fetch!(:public_pem)

      for repo <- [one, two] do
        root = "/pkg/deb/#{Storage.repo_handle(repo)}/#{repo.name}"

        release = get(build_conn(), root <> "/dists/stable/Release").resp_body
        signature = get(build_conn(), root <> "/dists/stable/Release.gpg").resp_body

        assert {:ok, true} = Native.verify_detached(key, release, signature),
               "#{repo.name}'s Release did not verify against the organization key"
      end
    end

    test "different organizations get different keys" do
      {_u1, _org1, one, _} = org_with_two_repos()
      {_u2, _org2, two, _} = org_with_two_repos()

      assert {:ok, %{public_pem: first}} = Packages.signing_key(one, "deb")
      assert {:ok, %{public_pem: second}} = Packages.signing_key(two, "deb")

      refute first == second
    end

    test "a personal repository's key belongs to its user, and is shared too" do
      user = user_fixture()
      {_u, one} = repository_fixture(%{owner: user})
      {_u, two} = repository_fixture(%{owner: user})

      assert {:ok, %{public_pem: first}} = Packages.signing_key(one, "hex")
      assert {:ok, %{public_pem: second}} = Packages.signing_key(two, "hex")

      assert first == second
      assert Packages.get_signing_key(user, "hex")
    end

    test "formats don't share a key — apt wants OpenPGP, Hex wants RSA" do
      {_user, _org, repo, _} = org_with_two_repos()

      {:ok, %{public_pem: deb}} = Packages.signing_key(repo, "deb")
      {:ok, %{public_pem: hex}} = Packages.signing_key(repo, "hex")

      assert deb =~ "-----BEGIN PGP PUBLIC KEY BLOCK-----"
      assert hex =~ "-----BEGIN PUBLIC KEY-----"
    end

    test "a key is generated once and then reused verbatim" do
      {_user, _org, repo, _} = org_with_two_repos()

      assert {:ok, %{private_pem: first}} = Packages.signing_key(repo, "deb")
      assert {:ok, %{private_pem: second}} = Packages.signing_key(repo, "deb")

      assert first == second
    end

    test "get_signing_key/2 never generates" do
      {_user, org, _one, _two} = org_with_two_repos()

      assert Packages.get_signing_key(org, "deb") == nil
    end
  end

  describe "GET /pkg/_keys/:format/:owner" do
    test "serves the namespace key once it exists", %{conn: conn} do
      {_user, org, repo, _two} = org_with_two_repos()
      {:ok, %{public_pem: key}} = Packages.signing_key(repo, "deb")

      conn = get(conn, "/pkg/_keys/deb/#{org.handle}")

      assert conn.status == 200
      assert conn.resp_body == key
      assert get_resp_header(conn, "content-type") == ["application/pgp-keys"]
    end

    test "and it is the same key every repository in the org signs with", %{conn: conn} do
      {_user, org, one, two} = org_with_two_repos()
      {:ok, _} = Packages.signing_key(one, "deb")

      shared = get(conn, "/pkg/_keys/deb/#{org.handle}").resp_body

      for repo <- [one, two] do
        root = "/pkg/deb/#{Storage.repo_handle(repo)}/#{repo.name}"
        assert get(build_conn(), root <> "/key.asc").resp_body == shared
      end
    end

    test "mints the key on first ask — pinning it is the first setup step", %{conn: conn} do
      {_user, org, _one, _two} = org_with_two_repos()

      refute Packages.get_signing_key(org, "deb")

      conn = get(conn, "/pkg/_keys/deb/#{org.handle}")

      assert conn.status == 200
      assert conn.resp_body =~ "-----BEGIN PGP PUBLIC KEY BLOCK-----"
      assert Packages.get_signing_key(org, "deb")
    end

    test "and hands back the same key every time after that", %{conn: conn} do
      {_user, org, _one, _two} = org_with_two_repos()

      first = get(conn, "/pkg/_keys/deb/#{org.handle}").resp_body
      second = get(build_conn(), "/pkg/_keys/deb/#{org.handle}").resp_body

      assert first == second
    end

    test "a handle that doesn't resolve 404s, and mints nothing", %{conn: conn} do
      assert get(conn, "/pkg/_keys/deb/nobody-at-all").status == 404
    end

    test "404s for a format the forge doesn't serve", %{conn: conn} do
      {_user, org, repo, _two} = org_with_two_repos()
      {:ok, _} = Packages.signing_key(repo, "deb")

      assert get(conn, "/pkg/_keys/nonsense/#{org.handle}").status == 404
    end

    test "needs no credentials — apt and mix pin the key before they have any", %{conn: conn} do
      {_user, org, repo, _two} = org_with_two_repos()
      {:ok, _} = Packages.signing_key(repo, "deb")

      assert get(conn, "/pkg/_keys/deb/#{org.handle}").status == 200
    end

    test "serves a Hex public key as text", %{conn: conn} do
      {_user, org, repo, _two} = org_with_two_repos()
      {:ok, _} = Packages.signing_key(repo, "hex")

      conn = get(conn, "/pkg/_keys/hex/#{org.handle}")

      assert conn.status == 200
      assert conn.resp_body =~ "-----BEGIN PUBLIC KEY-----"
    end
  end

  defp org_with_two_repos do
    user = user_fixture()

    {:ok, org} =
      GitGud.Organizations.create_organization(user, %{
        "handle" => "org#{System.unique_integer([:positive])}",
        "name" => "Test Org"
      })

    {:ok, one} =
      GitGud.Repositories.create_repository_for_org(org, user, %{
        "name" => unique_repo_name(),
        "visibility" => "public"
      })

    {:ok, two} =
      GitGud.Repositories.create_repository_for_org(org, user, %{
        "name" => unique_repo_name(),
        "visibility" => "public"
      })

    # `Storage.repo_handle/1` refuses to guess, so hand back repos that
    # can actually name their own namespace.
    preload = &GitGud.Repo.preload(&1, [:owner, :organization])

    {user, org, preload.(one), preload.(two)}
  end
end