Merge pull request #11835 from soapy1/remove-url-token
Download a box by setting auth headers
This commit is contained in:
commit
70d37e8a9c
@ -436,13 +436,16 @@ module Vagrant
|
||||
downloader_options[:location_trusted] = env[:box_download_location_trusted]
|
||||
downloader_options[:box_extra_download_options] = env[:box_extra_download_options]
|
||||
|
||||
Util::Downloader.new(url, temp_path, downloader_options)
|
||||
d = Util::Downloader.new(url, temp_path, downloader_options)
|
||||
env[:hook].call(:authenticate_box_downloader, downloader: d)
|
||||
d
|
||||
end
|
||||
|
||||
def download(url, env, **opts)
|
||||
opts[:ui] = true if !opts.key?(:ui)
|
||||
|
||||
d = downloader(url, env, **opts)
|
||||
env[:hook].call(:authenticate_box_downloader, downloader: d)
|
||||
|
||||
# Download the box to a temporary path. We store the temporary
|
||||
# path as an instance variable so that the `#recover` method can
|
||||
@ -486,6 +489,7 @@ module Vagrant
|
||||
# @return [Boolean] true if metadata
|
||||
def metadata_url?(url, env)
|
||||
d = downloader(url, env, json: true, ui: false)
|
||||
env[:hook].call(:authenticate_box_downloader, downloader: d)
|
||||
|
||||
# If we're downloading a file, cURL just returns no
|
||||
# content-type (makes sense), so we just test if it is JSON
|
||||
|
||||
@ -58,12 +58,14 @@ module Vagrant
|
||||
# @param [Pathname] directory The directory where this box exists on
|
||||
# disk.
|
||||
# @param [String] metadata_url Metadata URL for box
|
||||
def initialize(name, provider, version, directory, metadata_url: nil)
|
||||
# @param [Hook] hook A hook to apply to the box downloader, for example, for authentication
|
||||
def initialize(name, provider, version, directory, metadata_url: nil, hook: nil)
|
||||
@name = name
|
||||
@version = version
|
||||
@provider = provider
|
||||
@directory = directory
|
||||
@metadata_url = metadata_url
|
||||
@hook = hook
|
||||
|
||||
metadata_file = directory.join("metadata.json")
|
||||
raise Errors::BoxMetadataFileNotFound, name: @name if !metadata_file.file?
|
||||
@ -133,7 +135,11 @@ module Vagrant
|
||||
end
|
||||
|
||||
opts = { headers: ["Accept: application/json"] }.merge(download_options)
|
||||
Util::Downloader.new(url, tf.path, opts).download!
|
||||
d = Util::Downloader.new(url, tf.path, opts)
|
||||
if @hook
|
||||
@hook.call(:authenticate_box_downloader, downloader: d)
|
||||
end
|
||||
d.download!
|
||||
BoxMetadata.new(File.open(tf.path, "r"))
|
||||
rescue Errors::DownloaderError => e
|
||||
raise Errors::BoxMetadataDownloadError,
|
||||
|
||||
@ -325,7 +325,7 @@ module Vagrant
|
||||
|
||||
return Box.new(
|
||||
name, provider, version_dir_map[v.to_s], provider_dir,
|
||||
metadata_url: metadata_url,
|
||||
metadata_url: metadata_url, hook: @hook
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@ -29,8 +29,9 @@ module Vagrant
|
||||
"vagrantup.com".freeze
|
||||
].freeze
|
||||
|
||||
attr_reader :source
|
||||
attr_accessor :source
|
||||
attr_reader :destination
|
||||
attr_accessor :headers
|
||||
|
||||
def initialize(source, destination, options=nil)
|
||||
options ||= {}
|
||||
@ -58,7 +59,7 @@ module Vagrant
|
||||
@ca_cert = options[:ca_cert]
|
||||
@ca_path = options[:ca_path]
|
||||
@continue = options[:continue]
|
||||
@headers = options[:headers]
|
||||
@headers = Array(options[:headers])
|
||||
@insecure = options[:insecure]
|
||||
@ui = options[:ui]
|
||||
@client_cert = options[:client_cert]
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
require "cgi"
|
||||
require "uri"
|
||||
|
||||
require "vagrant/util/credential_scrubber"
|
||||
require_relative "./add_authentication"
|
||||
|
||||
require Vagrant.source_root.join("plugins/commands/cloud/client/client")
|
||||
|
||||
# Similar to AddAuthentication this middleware will add authentication for interacting
|
||||
# with Vagrant cloud. It does this by adding Authentication headers to a
|
||||
# Vagrant::Util::Downloader object.
|
||||
module VagrantPlugins
|
||||
module CloudCommand
|
||||
class AddDownloaderAuthentication < AddAuthentication
|
||||
|
||||
@@logger = Log4r::Logger.new("vagrant::clout::add_download_authentication")
|
||||
|
||||
def call(env)
|
||||
client = Client.new(env[:env])
|
||||
token = client.token
|
||||
Vagrant::Util::CredentialScrubber.sensitive(token)
|
||||
|
||||
begin
|
||||
target_url = URI.parse(env[:downloader].source)
|
||||
if target_url.host != TARGET_HOST && REPLACEMENT_HOSTS.include?(target_url.host)
|
||||
target_url.host = TARGET_HOST
|
||||
env[:downloader].source = target_url.to_s
|
||||
end
|
||||
rescue URI::Error
|
||||
# if there is an error, use current target_url
|
||||
end
|
||||
|
||||
server_uri = URI.parse(Vagrant.server_url.to_s)
|
||||
if token && !server_uri.host.to_s.empty?
|
||||
if target_url.host == server_uri.host
|
||||
if server_uri.host != TARGET_HOST && !self.class.custom_host_notified?
|
||||
env[:ui].warn(I18n.t("cloud_command.middleware.authentication.different_target",
|
||||
custom_host: server_uri.host, known_host: TARGET_HOST) + "\n")
|
||||
sleep CUSTOM_HOST_NOTIFY_WAIT
|
||||
self.class.custom_host_notified!
|
||||
end
|
||||
|
||||
if Array(env[:downloader].headers).any? { |h| h.include?("Authorization") }
|
||||
@@logger.info("Not adding an authentication header, one already found")
|
||||
else
|
||||
env[:downloader].headers << "Authorization: Bearer #{token}"
|
||||
end
|
||||
end
|
||||
|
||||
env[:downloader]
|
||||
end
|
||||
|
||||
@app.call(env)
|
||||
end.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -22,6 +22,11 @@ module VagrantPlugins
|
||||
hook.prepend(AddAuthentication)
|
||||
end
|
||||
|
||||
action_hook(:cloud_authenticated_boxes, :authenticate_box_downloader) do |hook|
|
||||
require_relative "auth/middleware/add_downloader_authentication"
|
||||
hook.prepend(AddDownloaderAuthentication)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def self.init!
|
||||
|
||||
@ -0,0 +1,155 @@
|
||||
require File.expand_path("../../../../../../base", __FILE__)
|
||||
|
||||
require Vagrant.source_root.join("plugins/commands/cloud/auth/middleware/add_downloader_authentication")
|
||||
require "vagrant/util/downloader"
|
||||
|
||||
describe VagrantPlugins::CloudCommand::AddDownloaderAuthentication do
|
||||
include_context "unit"
|
||||
|
||||
let(:app) { lambda { |env| } }
|
||||
let(:ui) { double("ui") }
|
||||
let(:env) { {
|
||||
env: iso_env,
|
||||
ui: ui
|
||||
} }
|
||||
|
||||
let(:iso_env) { isolated_environment.create_vagrant_env }
|
||||
let(:server_url) { "http://vagrantcloud.com/box.box" }
|
||||
let(:dwnloader) { Vagrant::Util::Downloader.new(server_url, "/some/path", {}) }
|
||||
|
||||
subject { described_class.new(app, env) }
|
||||
|
||||
before do
|
||||
allow(Vagrant).to receive(:server_url).and_return(server_url)
|
||||
allow(ui).to receive(:warn)
|
||||
stub_env("ATLAS_TOKEN" => nil)
|
||||
end
|
||||
|
||||
describe "#call" do
|
||||
context "non full paths" do
|
||||
let(:server_url) { "http://vagrantcloud.com" }
|
||||
let(:dwnloader) { Vagrant::Util::Downloader.new(server_url, "/some/path", {}) }
|
||||
|
||||
it "does nothing if we have no server set" do
|
||||
allow(Vagrant).to receive(:server_url).and_return(nil)
|
||||
VagrantPlugins::CloudCommand::Client.new(iso_env).store_token("fooboohoo")
|
||||
|
||||
env[:downloader] = dwnloader
|
||||
subject.call(env)
|
||||
expect(env[:downloader].headers.empty?).to eq(true)
|
||||
end
|
||||
|
||||
it "does nothing if we aren't logged in" do
|
||||
env[:downloader] = dwnloader
|
||||
subject.call(env)
|
||||
expect(env[:downloader].headers.empty?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "custom server" do
|
||||
let(:server_url) { "http://surprise.com/box.box" }
|
||||
let(:dwnloader) { Vagrant::Util::Downloader.new(server_url, "/some/path", {}) }
|
||||
|
||||
it "warns when adding token to custom server" do
|
||||
server_url = "https://surprise.com"
|
||||
allow(Vagrant).to receive(:server_url).and_return(server_url)
|
||||
|
||||
token = "foobarbaz"
|
||||
VagrantPlugins::CloudCommand::Client.new(iso_env).store_token(token)
|
||||
|
||||
expect(subject).to receive(:sleep).once
|
||||
expect(ui).to receive(:warn).once
|
||||
|
||||
env[:downloader] = dwnloader
|
||||
subject.call(env)
|
||||
|
||||
expect(env[:downloader].headers).to eq(["Authorization: Bearer #{token}"])
|
||||
end
|
||||
end
|
||||
|
||||
context "replacement hosts" do
|
||||
let(:dwnloader) { Vagrant::Util::Downloader.new("https://app.vagrantup.com", "/some/path", {}) }
|
||||
|
||||
it "modifies host URL to target if authorized host" do
|
||||
token = "foobarbaz"
|
||||
VagrantPlugins::CloudCommand::Client.new(iso_env).store_token(token)
|
||||
env[:downloader] = dwnloader
|
||||
subject.call(env)
|
||||
expect(env[:downloader].headers).to eq(["Authorization: Bearer #{token}"])
|
||||
expect(URI.parse(env[:downloader].source).host).to eq(VagrantPlugins::CloudCommand::AddDownloaderAuthentication::TARGET_HOST)
|
||||
end
|
||||
end
|
||||
|
||||
context "malformed url" do
|
||||
let(:bad_url) { "this is not a valid url" }
|
||||
let(:dwnloader) { Vagrant::Util::Downloader.new(bad_url, "/some/path", {}) }
|
||||
|
||||
it "ignores urls that it cannot parse" do
|
||||
# Ensure the bad URL does cause an exception
|
||||
expect{ URI.parse(bad_url) }.to raise_error URI::Error
|
||||
env[:downloader] = dwnloader
|
||||
subject.call(env)
|
||||
expect(env[:downloader].source).to eq(bad_url)
|
||||
end
|
||||
end
|
||||
|
||||
context "with an headers already added" do
|
||||
let(:auth_header) { "Authorization Bearer: token" }
|
||||
let(:other_header) {"some: thing"}
|
||||
let(:dwnloader) { Vagrant::Util::Downloader.new(server_url, "/some/path", {headers: [other_header]}) }
|
||||
|
||||
it "appends the auth header" do
|
||||
token = "foobarbaz"
|
||||
VagrantPlugins::CloudCommand::Client.new(iso_env).store_token(token)
|
||||
|
||||
env[:downloader] = dwnloader
|
||||
subject.call(env)
|
||||
|
||||
expect(env[:downloader].headers).to eq([other_header, "Authorization: Bearer #{token}"])
|
||||
end
|
||||
|
||||
context "with local file path" do
|
||||
let(:file_path) { "file:////path/to/box.box" }
|
||||
let(:dwnloader) { Vagrant::Util::Downloader.new(file_path, "/some/path", {}) }
|
||||
|
||||
it "returns original urls when not modified" do
|
||||
env[:downloader] = dwnloader
|
||||
subject.call(env)
|
||||
|
||||
expect(env[:downloader].source).to eq(file_path)
|
||||
expect(env[:downloader].headers.empty?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
it "does not append multiple access_tokens" do
|
||||
dwnloader.headers << auth_header
|
||||
token = "foobarbaz"
|
||||
VagrantPlugins::CloudCommand::Client.new(iso_env).store_token(token)
|
||||
|
||||
env[:downloader] = dwnloader
|
||||
subject.call(env)
|
||||
|
||||
expect(env[:downloader].headers).to eq([other_header, auth_header])
|
||||
end
|
||||
end
|
||||
|
||||
it "adds a token to the headers" do
|
||||
token = "foobarbaz"
|
||||
VagrantPlugins::CloudCommand::Client.new(iso_env).store_token(token)
|
||||
env[:downloader] = dwnloader
|
||||
subject.call(env)
|
||||
expect(env[:downloader].headers).to eq(["Authorization: Bearer #{token}"])
|
||||
end
|
||||
|
||||
it "does not append the access token to vagrantcloud.com URLs if Atlas" do
|
||||
server_url = "https://atlas.hashicorp.com"
|
||||
allow(Vagrant).to receive(:server_url).and_return(server_url)
|
||||
allow(subject).to receive(:sleep)
|
||||
token = "foobarbaz"
|
||||
VagrantPlugins::CloudCommand::Client.new(iso_env).store_token(token)
|
||||
env[:downloader] = dwnloader
|
||||
subject.call(env)
|
||||
expect(env[:downloader].headers.empty?).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -595,8 +595,10 @@ describe Vagrant::Action::Builtin::BoxAdd, :skip_windows, :bsdtar do
|
||||
env[:box_url] = "foo"
|
||||
|
||||
env[:hook] = double("hook")
|
||||
allow(env[:hook]).to receive(:call) do |name, opts|
|
||||
expect(name).to eq(:authenticate_box_url)
|
||||
|
||||
expect(env[:hook]).to receive(:call).with(:authenticate_box_downloader, any_args).at_least(:once)
|
||||
|
||||
allow(env[:hook]).to receive(:call).with(:authenticate_box_url, any_args).at_least(:once) do |name, opts|
|
||||
if opts[:box_urls] == ["foo"]
|
||||
next { box_urls: [real_url] }
|
||||
elsif opts[:box_urls] == ["bar"]
|
||||
|
||||
@ -285,6 +285,24 @@ describe Vagrant::Box, :skip_windows do
|
||||
expect { subject.load_metadata }.
|
||||
to raise_error(Vagrant::Errors::BoxMetadataDownloadError)
|
||||
end
|
||||
|
||||
context "box has a hook for adding authentication" do
|
||||
|
||||
let(:hook){ double("hook") }
|
||||
|
||||
subject do
|
||||
described_class.new(
|
||||
name, provider, version, directory,
|
||||
metadata_url: metadata_url.path, hook: hook)
|
||||
end
|
||||
|
||||
it "add authentication headers to the url" do
|
||||
expect(hook).to receive(:call).with(:authenticate_box_downloader, any_args)
|
||||
result = subject.load_metadata
|
||||
expect(result.name).to eq("foo")
|
||||
expect(result.description).to eq("bar")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "destroying" do
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user