4.2 KiB · text History 95e9713
defmodule GitGud.Packages.AccessTest do
  @moduledoc """
  `GitGud.Packages.Access` as the package registries see it, through
  `GitGud.Packages.Auth.authorize/3`. The container registry's view is in
  `GitGudWeb.RegistryControllerTest`.
  """
  use GitGud.DataCase, async: false

  import GitGud.AccountsFixtures
  import GitGud.ForgeFixtures

  alias GitGud.Organizations
  alias GitGud.Packages.Access
  alias GitGud.Packages.Auth
  alias GitGud.Repositories

  @moduletag :git_required

  setup do
    if System.find_executable("git") == nil, do: {:skip, "git not on PATH"}, else: :ok
  end

  describe "personal access token scopes" do
    setup do
      {owner, repo} = repository_fixture()
      %{owner: owner, repo: repo}
    end

    test "a token without a package scope can't publish, however much its owner could",
         %{owner: owner, repo: repo} do
      refute Auth.authorize({:user, owner, ["ci:read"]}, repo, :write)
      refute Auth.authorize({:user, owner, ["ci:read"]}, repo, :read)
    end

    test "packages:read reads, packages:write publishes", %{owner: owner, repo: repo} do
      assert Auth.authorize({:user, owner, ["packages:read"]}, repo, :read)
      refute Auth.authorize({:user, owner, ["packages:read"]}, repo, :write)
      assert Auth.authorize({:user, owner, ["packages:write"]}, repo, :write)
    end

    test "repo still means everything", %{owner: owner, repo: repo} do
      assert Auth.authorize({:user, owner, ["repo"]}, repo, :write)
    end

    test "a password (or browser session) isn't limited", %{owner: owner, repo: repo} do
      assert Auth.authorize({:user, owner, :all}, repo, :write)
    end

    test "scopes never add to what the account itself may do", %{repo: repo} do
      stranger = user_fixture()
      refute Auth.authorize({:user, stranger, ["repo"]}, repo, :read)
    end
  end

  describe "organizations follow the repository's permissions" do
    setup do
      owner = user_fixture()
      handle = "acc#{System.unique_integer([:positive])}"
      {:ok, org} = Organizations.create_organization(owner, %{"handle" => handle, "name" => handle})

      {:ok, repo} =
        Repositories.create_repository_for_org(org, owner, %{
          "name" => unique_repo_name(),
          "visibility" => "private"
        })

      %{org: org, repo: repo}
    end

    test "a plain member neither reads nor publishes a private repository's packages",
         %{org: org, repo: repo} do
      member = user_fixture()
      {:ok, _} = Organizations.add_member(org, member)

      refute Auth.authorize({:user, member, :all}, repo, :read)
      refute Auth.authorize({:user, member, :all}, repo, :write)
    end

    test "a team's write permission publishes", %{org: org, repo: repo} do
      member = user_fixture()
      {:ok, _} = Organizations.add_member(org, member)
      {:ok, team} = Organizations.create_team(org, %{"name" => "pub"})
      {:ok, _} = Organizations.add_team_member(team, member)
      {:ok, _} = Organizations.grant_team_repo(team, repo, "write")

      assert Auth.authorize({:user, member, :all}, repo, :write)
    end

    test "an org admin publishes", %{org: org, repo: repo} do
      admin = user_fixture()
      {:ok, _} = Organizations.add_member(org, admin, "admin")

      assert Auth.authorize({:user, admin, :all}, repo, :write)
    end
  end

  describe "anonymous" do
    test "reads public, not internal" do
      {_, public} = repository_fixture(%{visibility: "public"})
      {_, internal} = repository_fixture(%{visibility: "internal"})

      assert Access.allowed?(:anonymous, public, :read)
      refute Access.allowed?(:anonymous, internal, :read)
      refute Access.allowed?(:anonymous, public, :write)
    end
  end

  describe "repository_for_image/1" do
    test "takes the first two segments as the repository" do
      {_, repo} = repository_fixture()
      repo = GitGud.Repo.preload(repo, [:owner, :organization])
      base = Repositories.Storage.repo_handle(repo) <> "/" <> repo.name

      assert {:ok, %{id: id}} = Access.repository_for_image(base)
      assert id == repo.id
      assert {:ok, %{id: ^id}} = Access.repository_for_image(base <> "/server/arm64")
      assert :error = Access.repository_for_image(base <> "/")
      assert :error = Access.repository_for_image(repo.name)
    end
  end
end