class Boxen::Preflight::Creds

Attributes

otp[R]
password[R]

Public Instance Methods

get_otp() click to toggle source
# File lib/boxen/preflight/creds.rb, line 35
def get_otp
  console = HighLine.new

  # junk API call to send OTP until we implement PUT
  tmp_api.create_authorization rescue nil

  @otp = console.ask "One time password (via SMS or device):" do |q|
    q.echo = '*'
  end
end
get_tokens() click to toggle source

Attempt to use the username+password to get a list of the user's OAuth authorizations from the API. If it fails because of 2FA, ask the user for her OTP and try again.

Returns a list of authorizations

# File lib/boxen/preflight/creds.rb, line 51
def get_tokens
  begin
    tmp_api.authorizations(:headers => headers)
  rescue Octokit::Unauthorized
    abort "Sorry, I can't auth you on GitHub.",
      "Please check your credentials and teams and give it another try."
  rescue Octokit::OneTimePasswordRequired
    puts
    if otp.nil?
      warn "It looks like you have two-factor auth enabled."
    else
      warn "That one time password didn't work. Let's try again."
    end
    get_otp
    get_tokens
  end
end
headers() click to toggle source
# File lib/boxen/preflight/creds.rb, line 31
def headers
  otp.nil? ? {} : {"X-GitHub-OTP" => otp}
end
ok?() click to toggle source
# File lib/boxen/preflight/creds.rb, line 16
def ok?
  if config.token && config.api.user
    # There was a period of time when login wasn't geting set on first run.
    # This should correct that.
    config.login = config.api.user.login
    true
  end
rescue
  nil
end
run() click to toggle source
# File lib/boxen/preflight/creds.rb, line 69
def run
  fetch_login_and_password
  tokens = get_tokens

  # Boxen now supports the updated GitHub Authorizations API by using a unique
  # `fingerprint` for each Boxen installation for a user. We delete any older
  # authorization that does not make use of `fingerprint` so that the "legacy"
  # authorization doesn't persist in the user's list of personal access
  # tokens.
  legacy_auth = tokens.detect { |a| a.note == "Boxen" && a.fingerprint == nil }
  tmp_api.delete_authorization(legacy_auth.id, :headers => headers) if legacy_auth

  # The updated GitHub authorizations API, in order to improve security, no
  # longer returns a plaintext `token` for existing authorizations. So, if an
  # authorization already exists for this machine we need to first delete it
  # so that we can create a new one.
  auth = tokens.detect { |a| a.note == note && a.fingerprint == fingerprint }
  tmp_api.delete_authorization(auth.id, :headers => headers) if auth

  auth = tmp_api.create_authorization(
    :note => note,
    :scopes => %w(repo user),
    :fingerprint => fingerprint,
    :headers => headers
  )

  config.token = auth.token

  unless ok?
    puts
    abort "Something went terribly wrong.",
      "I was able to get your OAuth token, but was unable to use it."
  end
end
tmp_api() click to toggle source
# File lib/boxen/preflight/creds.rb, line 27
def tmp_api
  @tmp_api ||= Octokit::Client.new :login => config.login, :password => password, :auto_paginate => true
end

Private Instance Methods

fetch_from_env(thing) click to toggle source
# File lib/boxen/preflight/creds.rb, line 119
def fetch_from_env(thing)
  key = "BOXEN_GITHUB_#{thing.upcase}"
  return unless found = ENV[key]
  warn "Oh, looks like you've provided your #{thing} as environmental variable..."
  found
end
fetch_login_and_password() click to toggle source
# File lib/boxen/preflight/creds.rb, line 106
def fetch_login_and_password
  console = HighLine.new

  config.login = fetch_from_env("login") || console.ask("GitHub login: ") do |q|
    q.default = config.login || config.user
    q.validate = /\A[^@]+\Z/
  end

  @password = fetch_from_env("password") || console.ask("GitHub password: ") do |q|
    q.echo = "*"
  end
end
fingerprint() click to toggle source
# File lib/boxen/preflight/creds.rb, line 126
def fingerprint
  @fingerprint ||= begin
    # See Apple technical note TN1103, "Uniquely Identifying a Macintosh
    # Computer."
    serial_number_match_data = IO.popen(
      ["ioreg", "-c", "IOPlatformExpertDevice", "-d", "2"]
    ).read.match(/"IOPlatformSerialNumber" = "([[:alnum:]]+)"/)
    if serial_number_match_data
      # The fingerprint must be unique across all personal access tokens for a
      # given user. We prefix the serial number with the application name to
      # differentiate between any other personal access token that uses the
      # Mac serial number for the fingerprint.
      Digest::SHA256.hexdigest("Boxen: #{serial_number_match_data[1]}")
    else
      abort "Sorry, I was unable to obtain your Mac's serial number.",
        "Boxen requires access to your Mac's serial number in order to generate a unique GitHub personal access token."
    end
  end
end
note() click to toggle source
# File lib/boxen/preflight/creds.rb, line 146
def note
  @note ||= "Boxen: #{Socket.gethostname}"
end