Justin Campbell 92578aed4b command/login: Add description to created token
This adds a prompt for a token description, which is now supported in
Vagrant Cloud. Pressing enter on the prompt uses the default description
of `"Vagrant login"`.

    $ vagrant login
    In a moment we will ask for your username and password to HashiCorp's
    Vagrant Cloud. After authenticating, we will store an access token locally on
    disk. Your login details will be transmitted over a secure connection, and
    are never stored on disk locally.

    If you do not have an Vagrant Cloud account, sign up at
    https://www.vagrantcloud.com

    Vagrant Cloud Username: justincampbell
    Password (will be hidden):
    Token description (Defaults to "Vagrant login"):
    You are now logged in.
    $

Which created a token with the default description of "Vagrant login":

![](http://c.justincampbell.me/2V0p0T0U0d0O/Screen%20Shot%202017-08-10%20at%205.08.21%20PM.png)

Entering a description:

    Token description (Defaults to "Vagrant login"): Justin's MacBook Pro

![](http://c.justincampbell.me/2m1N0d1M3k3P/Screen%20Shot%202017-08-10%20at%205.09.39%20PM.png)
2017-08-10 17:12:38 -04:00

164 lines
4.8 KiB
Ruby

require "rest_client"
require "vagrant/util/downloader"
require "vagrant/util/presence"
module VagrantPlugins
module LoginCommand
class Client
include Vagrant::Util::Presence
# Initializes a login client with the given Vagrant::Environment.
#
# @param [Vagrant::Environment] env
def initialize(env)
@logger = Log4r::Logger.new("vagrant::login::client")
@env = env
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?
token = self.token
return false if !token
with_error_handling do
url = "#{Vagrant.server_url}/api/v1/authenticate" +
"?access_token=#{token}"
RestClient.get(url, content_type: :json)
true
end
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] username_or_email
# @param [String] password
# @param [String] description
# @return [String] token The access token, or nil if auth failed.
def login(username_or_email, password, description: nil)
@logger.info("Logging in '#{username_or_email}'")
with_error_handling do
url = "#{Vagrant.server_url}/api/v1/authenticate"
request = {
user: {
login: username_or_email,
password: password
},
token: {
description: description
}
}
proxy = nil
proxy ||= ENV["HTTPS_PROXY"] || ENV["https_proxy"]
proxy ||= ENV["HTTP_PROXY"] || ENV["http_proxy"]
RestClient.proxy = proxy
response = RestClient::Request.execute(
method: :post,
url: url,
payload: JSON.dump(request),
proxy: proxy,
headers: {
accept: :json,
content_type: :json,
user_agent: Vagrant::Util::Downloader::USER_AGENT,
},
)
data = JSON.load(response.to_s)
data["token"]
end
end
# Stores the given token locally, removing any previous tokens.
#
# @param [String] token
def store_token(token)
@logger.info("Storing token in #{token_path}")
token_path.open("w") do |f|
f.write(token)
end
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?
@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
end
if present?(ENV["VAGRANT_CLOUD_TOKEN"])
@logger.debug("Using authentication token from environment variable")
return ENV["VAGRANT_CLOUD_TOKEN"]
end
if token_path.exist?
@logger.debug("Using authentication token from disk at #{token_path}")
return token_path.read.strip
end
if present?(ENV["ATLAS_TOKEN"])
@logger.warn("ATLAS_TOKEN detected within environment. Using ATLAS_TOKEN in place of VAGRANT_CLOUD_TOKEN.")
return ENV["ATLAS_TOKEN"]
end
@logger.debug("No authentication token in environment or #{token_path}")
nil
end
protected
def with_error_handling(&block)
yield
rescue RestClient::Unauthorized
@logger.debug("Unauthorized!")
false
rescue RestClient::NotAcceptable => e
@logger.debug("Got unacceptable response:")
@logger.debug(e.message)
@logger.debug(e.backtrace.join("\n"))
begin
errors = JSON.parse(e.response)["errors"].join("\n")
raise Errors::ServerError, errors: errors
rescue JSON::ParserError; end
raise "An unexpected error occurred: #{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
end
end
end