class Aptible::CLI::Agent
Public Class Methods
exit_on_failure?()
click to toggle source
Forward return codes on failures.
# File lib/aptible/cli/agent.rb, line 69 def self.exit_on_failure? true end
new(*)
click to toggle source
Calls superclass method
# File lib/aptible/cli/agent.rb, line 73 def initialize(*) nag_toolbelt unless toolbelt? Aptible::Resource.configure { |conf| conf.user_agent = version_string } warn_sso_enforcement super end
Public Instance Methods
login()
click to toggle source
# File lib/aptible/cli/agent.rb, line 97 def login if options[:sso] begin token = options[:sso] token = ask('Paste token copied from Dashboard:') if token == 'sso' Base64.urlsafe_decode64(token.split('.').first) save_token(token) CLI.logger.info "Token written to #{token_file}" return rescue StandardError raise Thor::Error, 'Invalid token provided for SSO' end end email = options[:email] || ask('Email: ') password = options[:password] || ask_then_line( 'Password: ', echo: false ) token_options = { email: email, password: password } otp_token = options[:otp_token] token_options[:otp_token] = otp_token if otp_token begin lifetime = '1w' lifetime = '12h' if token_options[:otp_token] || token_options[:u2f] lifetime = options[:lifetime] if options[:lifetime] duration = ChronicDuration.parse(lifetime) if duration.nil? raise Thor::Error, "Invalid token lifetime requested: #{lifetime}" end token_options[:expires_in] = duration token = Aptible::Auth::Token.create(token_options) rescue OAuth2::Error => e # If a MFA is require but a token wasn't provided, # prompt the user for MFA authentication and retry if e.code != 'otp_token_required' raise Thor::Error, 'Could not authenticate with given ' \ "credentials: #{e.code}" end u2f = (e.response.parsed['exception_context'] || {})['u2f'] q = Queue.new mfa_threads = [] # If the user has added a security key and their computer supports it, # allow them to use it # https://developers.yubico.com/libfido2/Manuals # installation: https://github.com/Yubico/libfido2#installation if u2f && !which('fido2-assert').nil? && !which('fido2-token').nil? origin = Aptible::Auth::Resource.new.get.href app_id = Aptible::Auth::Resource.new.utf_trusted_facets.href challenge = u2f.fetch('challenge') device_info = security_key_device(u2f, app_id) if device_info[:locations].count > 0 && device_info[:device] puts "\nEnter your 2FA token or touch your Security Key " \ 'once it starts blinking.' mfa_threads << Thread.new do token_options[:u2f] = Helpers::SecurityKey.authenticate( origin, app_id, challenge, device_info[:device], device_info[:locations] ) puts '' q.push(nil) end end end mfa_threads << Thread.new do token_options[:otp_token] = options[:otp_token] || ask( '2FA Token: ' ) q.push(nil) end # Block until one of the threads completes q.pop mfa_threads.each do |thr| sleep 0.5 until thr.status != 'run' thr.kill end.each(&:join) retry end save_token(token.access_token) CLI.logger.info "Token written to #{token_file}" lifetime_format = { units: 2, joiner: ', ' } token_lifetime = (token.expires_at - token.created_at).round expires_in = ChronicDuration.output(token_lifetime, lifetime_format) CLI.logger.info "This token will expire after #{expires_in} " \ '(use --lifetime to customize)' end
version()
click to toggle source
# File lib/aptible/cli/agent.rb, line 81 def version Formatter.render(Renderer.current) do |root| root.keyed_object('version') do |node| node.value('version', version_string) end end end
Private Instance Methods
deprecated(msg)
click to toggle source
# File lib/aptible/cli/agent.rb, line 280 def deprecated(msg) CLI.logger.warn([ "DEPRECATION NOTICE: #{msg}", 'Please contact support@aptible.com with any questions.' ].join("\n")) end
nag_toolbelt()
click to toggle source
# File lib/aptible/cli/agent.rb, line 287 def nag_toolbelt # If you're reading this, it's possible you decided to not use the # toolbelt and are a looking for a way to disable this warning. Look no # further: to do so, edit the file `.aptible/nag_toolbelt` and put a # timestamp far into the future. For example, writing 1577836800 will # disable the warning until 2020. nag_file = File.join aptible_config_path, 'nag_toolbelt' nag_frequency = 12.hours last_nag = begin Integer(File.read(nag_file)) rescue Errno::ENOENT, ArgumentError 0 end now = Time.now.utc.to_i if last_nag < now - nag_frequency CLI.logger.warn([ 'You have installed the Aptible CLI from source.', 'This is not recommended: some functionality may not work!', 'Review this support topic for more information:', 'https://www.aptible.com/support/topics/cli/how-to-install-cli/' ].join("\n")) FileUtils.mkdir_p(File.dirname(nag_file)) File.open(nag_file, 'w', 0o600) { |f| f.write(now.to_s) } end end
security_credential(devices)
click to toggle source
The name for our backend model is U2FDevice. However, really what we are storing is a security credential. Here we figure out which security credential to pass to fido2-assert.
# File lib/aptible/cli/agent.rb, line 254 def security_credential(devices) puts 'There are multiple credentials associated ' \ 'with this user. Please select the ' \ "credential you want to use for authentication:\n" device = nil while device.nil? devices.each_with_index do |dev, index| puts "#{index}: #{dev.name}" end puts '' device_index = ask( 'Enter the credential number you want to use: ' ) # https://stackoverflow.com/a/1235990 next unless /\A\d+\z/ =~ device_index device = devices[device_index.to_i] end device end
security_key_device(u2f, app_id)
click to toggle source
# File lib/aptible/cli/agent.rb, line 208 def security_key_device(u2f, app_id) devices = u2f.fetch('devices').map do |dev| version = dev.fetch('version') rp_id = if version == 'U2F_V2' app_id else u2f['payload']['rpId'] end Helpers::SecurityKey::Device.new( dev.fetch('version'), dev.fetch('key_handle'), dev.fetch('name'), rp_id ) end result = { locations: [], device: nil } device_locations = Helpers::SecurityKey.device_locations if device_locations.count.zero? no_keys = 'WARNING: no security keys detected on machine' CLI.logger.warn(no_keys) if device_locations.count.zero? else result[:locations] = device_locations no_creds = 'No credentials associated with user' raise Error, no_creds if devices.count.zero? result[:device] = devices[0] if devices.count > 1 credential = security_credential(devices) result[:device] = credential end end result end
toolbelt?()
click to toggle source
# File lib/aptible/cli/agent.rb, line 339 def toolbelt? ENV['APTIBLE_TOOLBELT'] end
version_string()
click to toggle source
# File lib/aptible/cli/agent.rb, line 330 def version_string bits = [ 'aptible-cli', "v#{Aptible::CLI::VERSION}" ] bits << 'toolbelt' if toolbelt? bits.join ' ' end
warn_sso_enforcement()
click to toggle source
# File lib/aptible/cli/agent.rb, line 317 def warn_sso_enforcement # If the user is also a member of token = fetch_token reauth = Aptible::Auth::ReauthenticateOrganization.all(token: token) return if reauth.empty? CLI.logger.warn(['WARNING: You will need to use the appropriate', 'login method (SSO or Aptible credentials) to access', 'these organizations:', reauth.map(&:name)].join(' ')) rescue StandardError end