Merge pull request #17213 from Homebrew/allowed-taps

env_config: add `HOMEBREW_ALLOWED_TAPS`
This commit is contained in:
Mike McQuaid 2024-05-07 08:47:57 +01:00 committed by GitHub
commit fc13eb83c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 200 additions and 56 deletions

View File

@ -574,15 +574,7 @@ on_request: true)
sig { void } sig { void }
def forbidden_tap_check def forbidden_tap_check
forbidden_taps = Homebrew::EnvConfig.forbidden_taps return if Tap.allowed_taps.blank? && Tap.forbidden_taps.blank?
return if forbidden_taps.blank?
forbidden_taps_set = Set.new(forbidden_taps.split.filter_map do |tap|
Tap.fetch(tap)
rescue Tap::InvalidNameError
opoo "Invalid tap name in `HOMEBREW_FORBIDDEN_TAPS`: #{tap}"
nil
end)
owner = Homebrew::EnvConfig.forbidden_owner owner = Homebrew::EnvConfig.forbidden_owner
owner_contact = if (contact = Homebrew::EnvConfig.forbidden_owner_contact.presence) owner_contact = if (contact = Homebrew::EnvConfig.forbidden_owner_contact.presence)
@ -592,27 +584,31 @@ on_request: true)
unless skip_cask_deps? unless skip_cask_deps?
cask_and_formula_dependencies.each do |cask_or_formula| cask_and_formula_dependencies.each do |cask_or_formula|
dep_tap = cask_or_formula.tap dep_tap = cask_or_formula.tap
next if dep_tap.blank? next if dep_tap.blank? || (dep_tap.allowed_by_env? && !dep_tap.forbidden_by_env?)
next unless forbidden_taps_set.include?(dep_tap)
dep_full_name = cask_or_formula.full_name dep_full_name = cask_or_formula.full_name
raise CaskCannotBeInstalledError.new(@cask, <<~EOS error_message = +"The installation of #{@cask} has a dependency #{dep_full_name}\n" \
The installation of #{@cask} has a dependency #{dep_full_name} "from the #{dep_tap} tap but #{owner} "
but the #{dep_tap} tap was forbidden by #{owner} in `HOMEBREW_FORBIDDEN_TAPS`.#{owner_contact} error_message << "has not allowed this tap in `HOMEBREW_ALLOWED_TAPS`" unless dep_tap.allowed_by_env?
EOS error_message << " and\n" if !dep_tap.allowed_by_env? && dep_tap.forbidden_by_env?
) error_message << "has forbidden this tap in `HOMEBREW_FORBIDDEN_TAPS`" if dep_tap.forbidden_by_env?
error_message << ".#{owner_contact}"
raise CaskCannotBeInstalledError.new(@cask, error_message)
end end
end end
cask_tap = @cask.tap cask_tap = @cask.tap
return if cask_tap.blank? return if cask_tap.blank? || (cask_tap.allowed_by_env? && !cask_tap.forbidden_by_env?)
return unless forbidden_taps_set.include?(cask_tap)
raise CaskCannotBeInstalledError.new(@cask, <<~EOS error_message = +"The installation of #{@cask.full_name} has the tap #{cask_tap}\n" \
The installation of #{@cask.full_name} has the tap #{cask_tap} "but #{owner} "
which was forbidden by #{owner} in `HOMEBREW_FORBIDDEN_TAPS`.#{owner_contact} error_message << "has not allowed this tap in `HOMEBREW_ALLOWED_TAPS`" unless cask_tap.allowed_by_env?
EOS error_message << " and\n" if !cask_tap.allowed_by_env? && cask_tap.forbidden_by_env?
) error_message << "has forbidden this tap in `HOMEBREW_FORBIDDEN_TAPS`" if cask_tap.forbidden_by_env?
error_message << ".#{owner_contact}"
raise CaskCannotBeInstalledError.new(@cask, error_message)
end end
sig { void } sig { void }

View File

@ -11,6 +11,11 @@ module Homebrew
module_function module_function
ENVS = { ENVS = {
HOMEBREW_ALLOWED_TAPS: {
description: "A space-separated list of taps. Homebrew will refuse to install a " \
"formula unless it and all of its dependencies are in an official tap " \
"or in a tap on this list.",
},
HOMEBREW_API_AUTO_UPDATE_SECS: { HOMEBREW_API_AUTO_UPDATE_SECS: {
description: "Check Homebrew's API for new formulae or cask data every " \ description: "Check Homebrew's API for new formulae or cask data every " \
"`HOMEBREW_API_AUTO_UPDATE_SECS` seconds. Alternatively, disable API auto-update " \ "`HOMEBREW_API_AUTO_UPDATE_SECS` seconds. Alternatively, disable API auto-update " \

View File

@ -1391,15 +1391,7 @@ on_request: installed_on_request?, options:)
sig { void } sig { void }
def forbidden_tap_check def forbidden_tap_check
forbidden_taps = Homebrew::EnvConfig.forbidden_taps return if Tap.allowed_taps.blank? && Tap.forbidden_taps.blank?
return if forbidden_taps.blank?
forbidden_taps_set = Set.new(forbidden_taps.split.filter_map do |tap|
Tap.fetch(tap)
rescue Tap::InvalidNameError
opoo "Invalid tap name in `HOMEBREW_FORBIDDEN_TAPS`: #{tap}"
nil
end)
owner = Homebrew::EnvConfig.forbidden_owner owner = Homebrew::EnvConfig.forbidden_owner
owner_contact = if (contact = Homebrew::EnvConfig.forbidden_owner_contact.presence) owner_contact = if (contact = Homebrew::EnvConfig.forbidden_owner_contact.presence)
@ -1409,26 +1401,32 @@ on_request: installed_on_request?, options:)
unless ignore_deps? unless ignore_deps?
compute_dependencies.each do |(dep, _options)| compute_dependencies.each do |(dep, _options)|
dep_tap = dep.tap dep_tap = dep.tap
next if dep_tap.blank? next if dep_tap.blank? || (dep_tap.allowed_by_env? && !dep_tap.forbidden_by_env?)
next unless forbidden_taps_set.include?(dep_tap)
raise CannotInstallFormulaError, <<~EOS error_message = +"The installation of #{formula.name} has a dependency #{dep.name}\n" \
The installation of #{formula.name} has a dependency #{dep.name} "from the #{dep_tap} tap but #{owner} "
but the #{dep_tap} tap was forbidden by #{owner} in `HOMEBREW_FORBIDDEN_TAPS`.#{owner_contact} error_message << "has not allowed this tap in `HOMEBREW_ALLOWED_TAPS`" unless dep_tap.allowed_by_env?
EOS error_message << " and\n" if !dep_tap.allowed_by_env? && dep_tap.forbidden_by_env?
error_message << "has forbidden this tap in `HOMEBREW_FORBIDDEN_TAPS`" if dep_tap.forbidden_by_env?
error_message << ".#{owner_contact}"
raise CannotInstallFormulaError, error_message
end end
end end
return if only_deps? return if only_deps?
formula_tap = formula.tap formula_tap = formula.tap
return if formula_tap.blank? return if formula_tap.blank? || (formula_tap.allowed_by_env? && !formula_tap.forbidden_by_env?)
return unless forbidden_taps_set.include?(formula_tap)
raise CannotInstallFormulaError, <<~EOS error_message = +"The installation of #{formula.full_name} has the tap #{formula_tap}\n" \
The installation of #{formula.full_name} has the tap #{formula_tap} "but #{owner} "
which was forbidden by #{owner} in `HOMEBREW_FORBIDDEN_TAPS`.#{owner_contact} error_message << "has not allowed this tap in `HOMEBREW_ALLOWED_TAPS`" unless formula_tap.allowed_by_env?
EOS error_message << " and\n" if !formula_tap.allowed_by_env? && formula_tap.forbidden_by_env?
error_message << "has forbidden this tap in `HOMEBREW_FORBIDDEN_TAPS`" if formula_tap.forbidden_by_env?
error_message << ".#{owner_contact}"
raise CannotInstallFormulaError, error_message
end end
sig { void } sig { void }

View File

@ -9,6 +9,9 @@ module Homebrew::EnvConfig
sig { returns(T.nilable(::String)) } sig { returns(T.nilable(::String)) }
def all_proxy; end def all_proxy; end
sig { returns(T.nilable(::String)) }
def allowed_taps; end
sig { returns(Integer) } sig { returns(Integer) }
def api_auto_update_secs; end def api_auto_update_secs; end

View File

@ -132,6 +132,36 @@ class Tap
false false
end end
sig { returns(T::Set[Tap]) }
def self.allowed_taps
cache_key = :"allowed_taps_#{Homebrew::EnvConfig.allowed_taps.to_s.tr(" ", "_")}"
cache[cache_key] ||= begin
allowed_tap_list = Homebrew::EnvConfig.allowed_taps.to_s.split
Set.new(allowed_tap_list.filter_map do |tap|
Tap.fetch(tap)
rescue Tap::InvalidNameError
opoo "Invalid tap name in `HOMEBREW_ALLOWED_TAPS`: #{tap}"
nil
end).freeze
end
end
sig { returns(T::Set[Tap]) }
def self.forbidden_taps
cache_key = :"forbidden_taps_#{Homebrew::EnvConfig.forbidden_taps.to_s.tr(" ", "_")}"
cache[cache_key] ||= begin
forbidden_tap_list = Homebrew::EnvConfig.forbidden_taps.to_s.split
Set.new(forbidden_tap_list.filter_map do |tap|
Tap.fetch(tap)
rescue Tap::InvalidNameError
opoo "Invalid tap name in `HOMEBREW_FORBIDDEN_TAPS`: #{tap}"
nil
end).freeze
end
end
# @api public # @api public
extend Enumerable extend Enumerable
@ -1056,6 +1086,20 @@ class Tap
end end
end end
sig { returns(T::Boolean) }
def allowed_by_env?
@allowed_by_env ||= begin
allowed_taps = self.class.allowed_taps
official? || allowed_taps.blank? || allowed_taps.include?(self)
end
end
sig { returns(T::Boolean) }
def forbidden_by_env?
@forbidden_by_env ||= self.class.forbidden_taps.include?(self)
end
private private
sig { params(file: Pathname).returns(T.any(T::Array[String], Hash)) } sig { params(file: Pathname).returns(T.any(T::Array[String], Hash)) }

View File

@ -326,22 +326,48 @@ RSpec.describe Cask::Installer, :cask do
end end
describe "#forbidden_tap_check" do describe "#forbidden_tap_check" do
it "raises on forbidden tap on cask" do before do
ENV["HOMEBREW_FORBIDDEN_TAPS"] = tap = "homebrew/forbidden" allow(Tap).to receive_messages(allowed_taps: allowed_taps_set, forbidden_taps: forbidden_taps_set)
end
cask = Cask::Cask.new("homebrew-forbidden-tap", tap: Tap.fetch(tap)) do let(:homebrew_forbidden) { Tap.fetch("homebrew/forbidden") }
let(:allowed_third_party) { Tap.fetch("nothomebrew/allowed") }
let(:disallowed_third_party) { Tap.fetch("nothomebrew/notallowed") }
let(:allowed_taps_set) { Set.new([allowed_third_party]) }
let(:forbidden_taps_set) { Set.new([homebrew_forbidden]) }
it "raises on forbidden tap on cask" do
cask = Cask::Cask.new("homebrew-forbidden-tap", tap: homebrew_forbidden) do
url "file://#{TEST_FIXTURE_DIR}/cask/container.tar.gz" url "file://#{TEST_FIXTURE_DIR}/cask/container.tar.gz"
end end
expect do expect do
described_class.new(cask).forbidden_tap_check described_class.new(cask).forbidden_tap_check
end.to raise_error(Cask::CaskCannotBeInstalledError, /has the tap #{tap}/) end.to raise_error(Cask::CaskCannotBeInstalledError, /has the tap #{homebrew_forbidden}/)
end
it "raises on not allowed third-party tap on cask" do
cask = Cask::Cask.new("homebrew-not-allowed-tap", tap: disallowed_third_party) do
url "file://#{TEST_FIXTURE_DIR}/cask/container.tar.gz"
end
expect do
described_class.new(cask).forbidden_tap_check
end.to raise_error(Cask::CaskCannotBeInstalledError, /has the tap #{disallowed_third_party}/)
end
it "does not raise on allowed tap on cask" do
cask = Cask::Cask.new("third-party-allowed-tap", tap: allowed_third_party) do
url "file://#{TEST_FIXTURE_DIR}/cask/container.tar.gz"
end
expect { described_class.new(cask).forbidden_tap_check }.not_to raise_error
end end
it "raises on forbidden tap on dependency" do it "raises on forbidden tap on dependency" do
ENV["HOMEBREW_FORBIDDEN_TAPS"] = dep_tap = "homebrew/forbidden" dep_tap = homebrew_forbidden
dep_name = "homebrew-forbidden-dependency-tap" dep_name = "homebrew-forbidden-dependency-tap"
dep_path = Tap.fetch(dep_tap).new_formula_path(dep_name) dep_path = dep_tap.new_formula_path(dep_name)
dep_path.parent.mkpath dep_path.parent.mkpath
dep_path.write <<~RUBY dep_path.write <<~RUBY
class #{Formulary.class_s(dep_name)} < Formula class #{Formulary.class_s(dep_name)} < Formula
@ -358,7 +384,7 @@ RSpec.describe Cask::Installer, :cask do
expect do expect do
described_class.new(cask).forbidden_tap_check described_class.new(cask).forbidden_tap_check
end.to raise_error(Cask::CaskCannotBeInstalledError, /but the #{dep_tap} tap was forbidden/) end.to raise_error(Cask::CaskCannotBeInstalledError, /from the #{dep_tap} tap but/)
ensure ensure
dep_path.parent.parent.rmtree dep_path.parent.parent.rmtree
end end

View File

@ -258,10 +258,20 @@ RSpec.describe FormulaInstaller do
end end
describe "#forbidden_tap_check" do describe "#forbidden_tap_check" do
before do
allow(Tap).to receive_messages(allowed_taps: allowed_taps_set, forbidden_taps: forbidden_taps_set)
end
let(:homebrew_forbidden) { Tap.fetch("homebrew/forbidden") }
let(:allowed_third_party) { Tap.fetch("nothomebrew/allowed") }
let(:disallowed_third_party) { Tap.fetch("nothomebrew/notallowed") }
let(:allowed_taps_set) { Set.new([allowed_third_party]) }
let(:forbidden_taps_set) { Set.new([homebrew_forbidden]) }
it "raises on forbidden tap on formula" do it "raises on forbidden tap on formula" do
ENV["HOMEBREW_FORBIDDEN_TAPS"] = f_tap = "homebrew/forbidden" f_tap = homebrew_forbidden
f_name = "homebrew-forbidden-tap" f_name = "homebrew-forbidden-tap"
f_path = Tap.fetch(f_tap).new_formula_path(f_name) f_path = homebrew_forbidden.new_formula_path(f_name)
f_path.parent.mkpath f_path.parent.mkpath
f_path.write <<~RUBY f_path.write <<~RUBY
class #{Formulary.class_s(f_name)} < Formula class #{Formulary.class_s(f_name)} < Formula
@ -281,10 +291,54 @@ RSpec.describe FormulaInstaller do
f_path.parent.parent.rmtree f_path.parent.parent.rmtree
end end
it "raises on not allowed third-party tap on formula" do
f_tap = disallowed_third_party
f_name = "homebrew-not-allowed-tap"
f_path = disallowed_third_party.new_formula_path(f_name)
f_path.parent.mkpath
f_path.write <<~RUBY
class #{Formulary.class_s(f_name)} < Formula
url "foo"
version "0.1"
end
RUBY
Formulary.cache.delete(f_path)
f = Formulary.factory("#{f_tap}/#{f_name}")
fi = described_class.new(f)
expect do
fi.forbidden_tap_check
end.to raise_error(CannotInstallFormulaError, /has the tap #{f_tap}/)
ensure
f_path.parent.parent.parent.rmtree
end
it "does not raise on allowed tap on formula" do
f_tap = allowed_third_party
f_name = "homebrew-allowed-tap"
f_path = allowed_third_party.new_formula_path(f_name)
f_path.parent.mkpath
f_path.write <<~RUBY
class #{Formulary.class_s(f_name)} < Formula
url "foo"
version "0.1"
end
RUBY
Formulary.cache.delete(f_path)
f = Formulary.factory("#{f_tap}/#{f_name}")
fi = described_class.new(f)
expect { fi.forbidden_tap_check }.not_to raise_error
ensure
f_path.parent.parent.parent.rmtree
end
it "raises on forbidden tap on dependency" do it "raises on forbidden tap on dependency" do
ENV["HOMEBREW_FORBIDDEN_TAPS"] = dep_tap = "homebrew/forbidden" dep_tap = homebrew_forbidden
dep_name = "homebrew-forbidden-dependency-tap" dep_name = "homebrew-forbidden-dependency-tap"
dep_path = Tap.fetch(dep_tap).new_formula_path(dep_name) dep_path = homebrew_forbidden.new_formula_path(dep_name)
dep_path.parent.mkpath dep_path.parent.mkpath
dep_path.write <<~RUBY dep_path.write <<~RUBY
class #{Formulary.class_s(dep_name)} < Formula class #{Formulary.class_s(dep_name)} < Formula
@ -310,7 +364,7 @@ RSpec.describe FormulaInstaller do
expect do expect do
fi.forbidden_tap_check fi.forbidden_tap_check
end.to raise_error(CannotInstallFormulaError, /but the #{dep_tap} tap was forbidden/) end.to raise_error(CannotInstallFormulaError, /from the #{dep_tap} tap but/)
ensure ensure
dep_path.parent.parent.rmtree dep_path.parent.parent.rmtree
end end

View File

@ -142,6 +142,24 @@ RSpec.describe Tap do
end end
end end
describe "::allowed_taps" do
before { allow(Homebrew::EnvConfig).to receive(:allowed_taps).and_return("homebrew/allowed") }
it "returns a set of allowed taps according to the environment" do
expect(described_class.allowed_taps)
.to contain_exactly(described_class.fetch("homebrew/allowed"))
end
end
describe "::forbidden_taps" do
before { allow(Homebrew::EnvConfig).to receive(:forbidden_taps).and_return("homebrew/forbidden") }
it "returns a set of forbidden taps according to the environment" do
expect(described_class.forbidden_taps)
.to contain_exactly(described_class.fetch("homebrew/forbidden"))
end
end
specify "::names" do specify "::names" do
expect(described_class.names.sort).to eq(["homebrew/core", "homebrew/foo"]) expect(described_class.names.sort).to eq(["homebrew/core", "homebrew/foo"])
end end