Chris Roberts d580fbbee2 Use the server url helper method within the client
Some client setup locations where not using the custom helper
    method for the vagrant server URL value so they have been
    updated. The api path is also appended if it is not set for
    custom server URLs
2021-03-15 17:17:10 -07:00

241 lines
7.9 KiB
Ruby

require "vagrant_cloud"
require "vagrant/util/downloader"
require "vagrant/util/presence"
require Vagrant.source_root.join("plugins/commands/cloud/errors")
module VagrantPlugins
module CloudCommand
class Client
# @private
# Reset the cached values for scrubber. This is not considered a public
# API and should only be used for testing.
def self.reset!
class_variables.each(&method(:remove_class_variable))
end
######################################################################
# Class that deals with managing users 'local' token for Vagrant Cloud
######################################################################
APP = "app".freeze
include Util
include Vagrant::Util::Presence
attr_accessor :client
attr_accessor :username_or_email
attr_accessor :password
attr_reader :two_factor_default_delivery_method
attr_reader :two_factor_delivery_methods
# Initializes a login client with the given Vagrant::Environment.
#
# @param [Vagrant::Environment] env
def initialize(env)
@logger = Log4r::Logger.new("vagrant::cloud::client")
@env = env
@client = VagrantCloud::Client.new(
access_token: token,
url_base: api_server_url
)
end
# Removes the token, effectively logging the user out.
def clear_token
@logger.info("Clearing token")
token_path.delete if token_path.file?
end
# Checks if the user is logged in by verifying their authentication
# token.
#
# @return [Boolean]
def logged_in?
return false if !client.access_token
Vagrant::Util::CredentialScrubber.sensitive(client.access_token)
with_error_handling do
client.authentication_token_validate
true
end
rescue Errors::Unauthorized
false
end
# Login logs a user in and returns the token for that user. The token
# is _not_ stored unless {#store_token} is called.
#
# @param [String] description
# @param [String] code
# @return [String] token The access token, or nil if auth failed.
def login(description: nil, code: nil)
@logger.info("Logging in '#{username_or_email}'")
Vagrant::Util::CredentialScrubber.sensitive(password)
with_error_handling do
r = client.authentication_token_create(username: username_or_email,
password: password, description: description, code: code)
Vagrant::Util::CredentialScrubber.sensitive(r[:token])
@client = VagrantCloud::Client.new(
access_token: r[:token],
url_base: api_server_url
)
r[:token]
end
end
# Requests a 2FA code
# @param [String] delivery_method
def request_code(delivery_method)
@env.ui.warn("Requesting 2FA code via #{delivery_method.upcase}...")
Vagrant::Util::CredentialScrubber.sensitive(password)
with_error_handling do
r = client.authentication_request_2fa_code(
username: username_or_email, password: password, delivery_method: delivery_method)
two_factor = r[:two_factor]
obfuscated_destination = two_factor[:obfuscated_destination]
@env.ui.success("2FA code sent to #{obfuscated_destination}.")
end
end
# Stores the given token locally, removing any previous tokens.
#
# @param [String] token
def store_token(token)
Vagrant::Util::CredentialScrubber.sensitive(token)
@logger.info("Storing token in #{token_path}")
token_path.open("w") do |f|
f.write(token)
end
# Reset after we store the token since this is now _our_ token
@client = VagrantCloud::Client.new(access_token: token, url_base: api_server_url)
nil
end
# Reads the access token if there is one. This will first read the
# `VAGRANT_CLOUD_TOKEN` environment variable and then fallback to the stored
# access token on disk.
#
# @return [String]
def token
if present?(ENV["VAGRANT_CLOUD_TOKEN"]) && token_path.exist?
# Only show warning if it has not been previously shown
if !defined?(@@double_token_warning)
@env.ui.warn <<-EOH.strip
Vagrant detected both the VAGRANT_CLOUD_TOKEN environment variable and a Vagrant login
token are present on this system. The VAGRANT_CLOUD_TOKEN environment variable takes
precedence over the locally stored token. To remove this error, either unset
the VAGRANT_CLOUD_TOKEN environment variable or remove the login token stored on disk:
~/.vagrant.d/data/vagrant_login_token
EOH
@@double_token_warning = true
end
end
if present?(ENV["VAGRANT_CLOUD_TOKEN"])
@logger.debug("Using authentication token from environment variable")
t = ENV["VAGRANT_CLOUD_TOKEN"]
elsif token_path.exist?
@logger.debug("Using authentication token from disk at #{token_path}")
t = token_path.read.strip
elsif present?(ENV["ATLAS_TOKEN"])
@logger.warn("ATLAS_TOKEN detected within environment. Using ATLAS_TOKEN in place of VAGRANT_CLOUD_TOKEN.")
t = ENV["ATLAS_TOKEN"]
end
if !t.nil?
Vagrant::Util::CredentialScrubber.sensitive(t)
return t
end
@logger.debug("No authentication token in environment or #{token_path}")
nil
end
protected
def with_error_handling(&block)
yield
rescue Excon::Error::Unauthorized
@logger.debug("Unauthorized!")
raise Errors::Unauthorized
rescue Excon::Error::BadRequest => e
@logger.debug("Bad request:")
@logger.debug(e.message)
@logger.debug(e.backtrace.join("\n"))
parsed_response = JSON.parse(e.response.body)
errors = parsed_response["errors"].join("\n")
raise Errors::ServerError, errors: errors
rescue Excon::Error::NotAcceptable => e
@logger.debug("Got unacceptable response:")
@logger.debug(e.message)
@logger.debug(e.backtrace.join("\n"))
parsed_response = JSON.parse(e.response.body)
if two_factor = parsed_response['two_factor']
store_two_factor_information two_factor
if two_factor_default_delivery_method != APP
request_code two_factor_default_delivery_method
end
raise Errors::TwoFactorRequired
end
begin
errors = parsed_response["errors"].join("\n")
raise Errors::ServerError, errors: errors
rescue JSON::ParserError; end
@logger.debug("Got an unexpected error:")
@logger.debug(e.inspect)
raise Errors::Unexpected, error: e.inspect
rescue SocketError
@logger.info("Socket error")
raise Errors::ServerUnreachable, url: Vagrant.server_url.to_s
end
def token_path
@env.data_dir.join("vagrant_login_token")
end
def store_two_factor_information(two_factor)
@two_factor_default_delivery_method =
two_factor['default_delivery_method']
@two_factor_delivery_methods =
two_factor['delivery_methods']
@env.ui.warn "2FA is enabled for your account."
if two_factor_default_delivery_method == APP
@env.ui.info "Enter the code from your authenticator."
else
@env.ui.info "Default method is " \
"'#{two_factor_default_delivery_method}'."
end
other_delivery_methods =
two_factor_delivery_methods - [APP]
if other_delivery_methods.any?
other_delivery_methods_sentence = other_delivery_methods
.map { |word| "'#{word}'" }
.join(' or ')
@env.ui.info "You can also type #{other_delivery_methods_sentence} " \
"to request a new code."
end
end
end
end
end