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:
L. E. Segovia 2018-09-07 15:37:31 +00:00
parent 13869a7558
commit ab31af2b4b
No known key found for this signature in database
GPG Key ID: D5D1DC48B52B7AD5
5 changed files with 104 additions and 33 deletions

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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