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
|
class Download
|
||||||
attr_reader :cask
|
attr_reader :cask
|
||||||
|
|
||||||
def initialize(cask, force: false, quarantine: true)
|
def initialize(cask, force: false, quarantine: nil)
|
||||||
@cask = cask
|
@cask = cask
|
||||||
@force = force
|
@force = force
|
||||||
@quarantine = quarantine
|
@quarantine = quarantine
|
||||||
@ -46,11 +46,10 @@ module Cask
|
|||||||
end
|
end
|
||||||
|
|
||||||
def quarantine
|
def quarantine
|
||||||
return unless @quarantine
|
return if @quarantine.nil?
|
||||||
return unless Quarantine.available?
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -184,4 +184,18 @@ module Cask
|
|||||||
s
|
s
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|||||||
@ -55,26 +55,58 @@ module Cask
|
|||||||
print_stderr: false).stdout.rstrip
|
print_stderr: false).stdout.rstrip
|
||||||
end
|
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?
|
return if cask.nil? || download_path.nil?
|
||||||
|
|
||||||
odebug "Quarantining #{download_path}"
|
if action
|
||||||
|
return if detect(download_path)
|
||||||
|
|
||||||
quarantiner = system_command(swift,
|
odebug "Quarantining #{download_path}"
|
||||||
args: [
|
|
||||||
QUARANTINE_SCRIPT,
|
|
||||||
download_path,
|
|
||||||
cask.url.to_s,
|
|
||||||
cask.homepage.to_s,
|
|
||||||
])
|
|
||||||
|
|
||||||
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
|
return if quarantiner.success?
|
||||||
when 2
|
|
||||||
raise CaskQuarantineError.new(download_path, "Insufficient parameters")
|
case quarantiner.exit_status
|
||||||
|
when 2
|
||||||
|
raise CaskQuarantineError.new(download_path, "Insufficient parameters")
|
||||||
|
else
|
||||||
|
raise CaskQuarantineError.new(download_path, quarantiner.stderr)
|
||||||
|
end
|
||||||
else
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -85,10 +117,12 @@ module Cask
|
|||||||
|
|
||||||
odebug "Propagating quarantine from #{from} to #{to}"
|
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)
|
resolved_paths = Pathname.glob(to/"**/*", File::FNM_DOTMATCH)
|
||||||
|
|
||||||
|
FileUtils.chmod "u+w", resolved_paths
|
||||||
|
|
||||||
quarantiner = system_command("/usr/bin/xargs",
|
quarantiner = system_command("/usr/bin/xargs",
|
||||||
args: [
|
args: [
|
||||||
"-0",
|
"-0",
|
||||||
|
|||||||
@ -37,7 +37,7 @@ describe Cask::Cmd::Fetch, :cask do
|
|||||||
|
|
||||||
old_ctime = File.stat(cached_location).ctime
|
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
|
new_ctime = File.stat(cached_location).ctime
|
||||||
|
|
||||||
expect(old_ctime.to_i).to eq(new_ctime.to_i)
|
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
|
old_ctime = File.stat(cached_location).ctime
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
described_class.run("local-transmission", "--force")
|
described_class.run("local-transmission", "--force", "--no-quarantine")
|
||||||
new_ctime = File.stat(cached_location).ctime
|
new_ctime = File.stat(cached_location).ctime
|
||||||
|
|
||||||
expect(new_ctime.to_i).to be > old_ctime.to_i
|
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
|
it "quarantines Cask fetches" do
|
||||||
Cask::Cmd::Fetch.run("local-transmission")
|
Cask::Cmd::Fetch.run("local-transmission")
|
||||||
local_transmission = Cask::CaskLoader.load(cask_path("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
|
expect(cached_location).to be_quarantined
|
||||||
end
|
end
|
||||||
@ -32,11 +32,23 @@ describe Cask::Quarantine, :cask do
|
|||||||
Cask::Cmd::Audit.run("local-transmission", "--download")
|
Cask::Cmd::Audit.run("local-transmission", "--download")
|
||||||
|
|
||||||
local_transmission = Cask::CaskLoader.load(cask_path("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
|
expect(cached_location).to be_quarantined
|
||||||
end
|
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
|
it "quarantines dmg-based Casks" do
|
||||||
Cask::Cmd::Install.run("container-dmg")
|
Cask::Cmd::Install.run("container-dmg")
|
||||||
|
|
||||||
@ -124,11 +136,32 @@ describe Cask::Quarantine, :cask do
|
|||||||
it "does not quarantine Cask fetches" do
|
it "does not quarantine Cask fetches" do
|
||||||
Cask::Cmd::Fetch.run("local-transmission", "--no-quarantine")
|
Cask::Cmd::Fetch.run("local-transmission", "--no-quarantine")
|
||||||
local_transmission = Cask::CaskLoader.load(cask_path("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_not be_quarantined
|
expect(cached_location).to_not be_quarantined
|
||||||
end
|
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
|
it "does not quarantine dmg-based Casks" do
|
||||||
Cask::Cmd::Install.run("container-dmg", "--no-quarantine")
|
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
|
expect(Cask::Config.global.appdir.join("MyNestedApp.app")).to_not be_quarantined
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
Loading…
x
Reference in New Issue
Block a user