Cask: fixes for quarantining
Gatekeeper's Path Randomization is currently making automated installation a nightmare. Let's manually toggle the (undocumented) app translocation bit in the `com.apple.quarantine` extended attribute. While we're at it, let's also toss in some fixes: - zip downloads with improper permissions that prevent us from quarantining - quarantine/release/skip downloads as requested by the user
This commit is contained in:
parent
13869a7558
commit
ab31af2b4b
@ -7,7 +7,7 @@ module Cask
|
||||
class Download
|
||||
attr_reader :cask
|
||||
|
||||
def initialize(cask, force: false, quarantine: true)
|
||||
def initialize(cask, force: false, quarantine: nil)
|
||||
@cask = cask
|
||||
@force = force
|
||||
@quarantine = quarantine
|
||||
@ -46,11 +46,10 @@ module Cask
|
||||
end
|
||||
|
||||
def quarantine
|
||||
return unless @quarantine
|
||||
return if @quarantine.nil?
|
||||
return unless Quarantine.available?
|
||||
return if Quarantine.detect(@downloaded_path)
|
||||
|
||||
Quarantine.cask(cask: @cask, download_path: @downloaded_path)
|
||||
Quarantine.cask!(cask: @cask, download_path: @downloaded_path, action: @quarantine)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -184,4 +184,18 @@ module Cask
|
||||
s
|
||||
end
|
||||
end
|
||||
|
||||
class CaskQuarantineReleaseError < CaskQuarantineError
|
||||
def to_s
|
||||
s = "Failed to release #{path} from quarantine."
|
||||
|
||||
unless reason.empty?
|
||||
s << " Here's the reason:\n"
|
||||
s << Formatter.error(reason)
|
||||
s << "\n" unless reason.end_with?("\n")
|
||||
end
|
||||
|
||||
s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -55,26 +55,58 @@ module Cask
|
||||
print_stderr: false).stdout.rstrip
|
||||
end
|
||||
|
||||
def cask(cask: nil, download_path: nil)
|
||||
def disable_translocation!(xattr)
|
||||
fields = xattr.split(";")
|
||||
|
||||
# Fields: status, epoch, download agent, event ID
|
||||
# Let's toggle the app translocation bit, bit 8
|
||||
# http://openradar.me/radar?id=5022734169931776
|
||||
|
||||
fields[0] = (fields[0].to_i(16) | 0x0100).to_s(16).rjust(4, "0")
|
||||
|
||||
fields.join(";")
|
||||
end
|
||||
|
||||
def cask!(cask: nil, download_path: nil, action: true)
|
||||
return if cask.nil? || download_path.nil?
|
||||
|
||||
odebug "Quarantining #{download_path}"
|
||||
if action
|
||||
return if detect(download_path)
|
||||
|
||||
quarantiner = system_command(swift,
|
||||
args: [
|
||||
QUARANTINE_SCRIPT,
|
||||
download_path,
|
||||
cask.url.to_s,
|
||||
cask.homepage.to_s,
|
||||
])
|
||||
odebug "Quarantining #{download_path}"
|
||||
|
||||
return if quarantiner.success?
|
||||
quarantiner = system_command(swift,
|
||||
args: [
|
||||
QUARANTINE_SCRIPT,
|
||||
download_path,
|
||||
cask.url.to_s,
|
||||
cask.homepage.to_s,
|
||||
])
|
||||
|
||||
case quarantiner.exit_status
|
||||
when 2
|
||||
raise CaskQuarantineError.new(download_path, "Insufficient parameters")
|
||||
return if quarantiner.success?
|
||||
|
||||
case quarantiner.exit_status
|
||||
when 2
|
||||
raise CaskQuarantineError.new(download_path, "Insufficient parameters")
|
||||
else
|
||||
raise CaskQuarantineError.new(download_path, quarantiner.stderr)
|
||||
end
|
||||
else
|
||||
raise CaskQuarantineError.new(download_path, quarantiner.stderr)
|
||||
return unless detect(download_path)
|
||||
|
||||
odebug "Releasing #{download_path} from quarantine"
|
||||
|
||||
quarantiner = system_command("/usr/bin/xattr",
|
||||
args: [
|
||||
"-d",
|
||||
QUARANTINE_ATTRIBUTE,
|
||||
download_path,
|
||||
],
|
||||
print_stderr: false)
|
||||
|
||||
return if quarantiner.success?
|
||||
|
||||
raise CaskQuarantineReleaseError.new(download_path, quarantiner.stderr)
|
||||
end
|
||||
end
|
||||
|
||||
@ -85,10 +117,12 @@ module Cask
|
||||
|
||||
odebug "Propagating quarantine from #{from} to #{to}"
|
||||
|
||||
quarantine_status = status(from)
|
||||
quarantine_status = disable_translocation!(status(from))
|
||||
|
||||
resolved_paths = Pathname.glob(to/"**/*", File::FNM_DOTMATCH)
|
||||
|
||||
FileUtils.chmod "u+w", resolved_paths
|
||||
|
||||
quarantiner = system_command("/usr/bin/xargs",
|
||||
args: [
|
||||
"-0",
|
||||
|
||||
@ -37,7 +37,7 @@ describe Cask::Cmd::Fetch, :cask do
|
||||
|
||||
old_ctime = File.stat(cached_location).ctime
|
||||
|
||||
described_class.run("local-transmission")
|
||||
described_class.run("local-transmission", "--no-quarantine")
|
||||
new_ctime = File.stat(cached_location).ctime
|
||||
|
||||
expect(old_ctime.to_i).to eq(new_ctime.to_i)
|
||||
@ -49,7 +49,7 @@ describe Cask::Cmd::Fetch, :cask do
|
||||
old_ctime = File.stat(cached_location).ctime
|
||||
sleep(1)
|
||||
|
||||
described_class.run("local-transmission", "--force")
|
||||
described_class.run("local-transmission", "--force", "--no-quarantine")
|
||||
new_ctime = File.stat(cached_location).ctime
|
||||
|
||||
expect(new_ctime.to_i).to be > old_ctime.to_i
|
||||
|
||||
@ -23,7 +23,7 @@ describe Cask::Quarantine, :cask do
|
||||
it "quarantines Cask fetches" do
|
||||
Cask::Cmd::Fetch.run("local-transmission")
|
||||
local_transmission = Cask::CaskLoader.load(cask_path("local-transmission"))
|
||||
cached_location = Cask::Download.new(local_transmission, force: false, quarantine: false).perform
|
||||
cached_location = Cask::Download.new(local_transmission).perform
|
||||
|
||||
expect(cached_location).to be_quarantined
|
||||
end
|
||||
@ -32,11 +32,23 @@ describe Cask::Quarantine, :cask do
|
||||
Cask::Cmd::Audit.run("local-transmission", "--download")
|
||||
|
||||
local_transmission = Cask::CaskLoader.load(cask_path("local-transmission"))
|
||||
cached_location = Cask::Download.new(local_transmission, force: false, quarantine: false).perform
|
||||
cached_location = Cask::Download.new(local_transmission).perform
|
||||
|
||||
expect(cached_location).to be_quarantined
|
||||
end
|
||||
|
||||
it "quarantines Cask installs even if the fetch was not" do
|
||||
Cask::Cmd::Fetch.run("local-transmission", "--no-quarantine")
|
||||
|
||||
Cask::Cmd::Install.run("local-transmission")
|
||||
|
||||
expect(
|
||||
Cask::CaskLoader.load(cask_path("local-transmission")),
|
||||
).to be_installed
|
||||
|
||||
expect(Cask::Config.global.appdir.join("Transmission.app")).to be_quarantined
|
||||
end
|
||||
|
||||
it "quarantines dmg-based Casks" do
|
||||
Cask::Cmd::Install.run("container-dmg")
|
||||
|
||||
@ -124,11 +136,32 @@ describe Cask::Quarantine, :cask do
|
||||
it "does not quarantine Cask fetches" do
|
||||
Cask::Cmd::Fetch.run("local-transmission", "--no-quarantine")
|
||||
local_transmission = Cask::CaskLoader.load(cask_path("local-transmission"))
|
||||
cached_location = Cask::Download.new(local_transmission, force: false, quarantine: false).perform
|
||||
cached_location = Cask::Download.new(local_transmission).perform
|
||||
|
||||
expect(cached_location).to_not be_quarantined
|
||||
end
|
||||
|
||||
it "does not quarantine Cask audits" do
|
||||
Cask::Cmd::Audit.run("local-transmission", "--download", "--no-quarantine")
|
||||
|
||||
local_transmission = Cask::CaskLoader.load(cask_path("local-transmission"))
|
||||
cached_location = Cask::Download.new(local_transmission).perform
|
||||
|
||||
expect(cached_location).to_not be_quarantined
|
||||
end
|
||||
|
||||
it "does not quarantine Cask installs even if the fetch was" do
|
||||
Cask::Cmd::Fetch.run("local-transmission")
|
||||
|
||||
Cask::Cmd::Install.run("local-transmission", "--no-quarantine")
|
||||
|
||||
expect(
|
||||
Cask::CaskLoader.load(cask_path("local-transmission")),
|
||||
).to be_installed
|
||||
|
||||
expect(Cask::Config.global.appdir.join("Transmission.app")).to_not be_quarantined
|
||||
end
|
||||
|
||||
it "does not quarantine dmg-based Casks" do
|
||||
Cask::Cmd::Install.run("container-dmg", "--no-quarantine")
|
||||
|
||||
@ -200,14 +233,5 @@ describe Cask::Quarantine, :cask do
|
||||
|
||||
expect(Cask::Config.global.appdir.join("MyNestedApp.app")).to_not be_quarantined
|
||||
end
|
||||
|
||||
it "does not quarantine Cask audits" do
|
||||
Cask::Cmd::Audit.run("local-transmission", "--download", "--no-quarantine")
|
||||
|
||||
local_transmission = Cask::CaskLoader.load(cask_path("local-transmission"))
|
||||
cached_location = Cask::Download.new(local_transmission, force: false, quarantine: false).perform
|
||||
|
||||
expect(cached_location).to_not be_quarantined
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
x
Reference in New Issue
Block a user