class Kontena::Cli::Master::LoginCommand
Public Instance Methods
Check if the existing (or –token) authentication works without reauthenticating
# File lib/kontena/cli/master/login_command.rb, line 112 def auth_works?(server) return false unless (server && server.token && server.token.access_token) vspinner "Testing if authentication works using current access token" do Kontena::Client.new(server.url, server.token).authentication_ok?(master_account.userinfo_endpoint) end end
Build a path for master authentication
@param local_port [Fixnum] tcp port where localhost webserver is listening @param invite_code [String] an invitation code generated when user was invited @param expires_in [Fixnum] expiration time for the requested access token @param remote [Boolean] true when performing a login where the code is displayed on the web page @return [String]
# File lib/kontena/cli/master/login_command.rb, line 126 def authentication_path(local_port: nil, invite_code: nil, expires_in: nil, remote: false) auth_url_params = {} if remote auth_url_params[:redirect_uri] = "/code" elsif local_port auth_url_params[:redirect_uri] = "http://localhost:#{local_port}/cb" else raise ArgumentError, "Local port not defined and not performing remote login" end auth_url_params[:invite_code] = invite_code if invite_code auth_url_params[:expires_in] = expires_in if expires_in "/authenticate?#{URI.encode_www_form(auth_url_params)}" end
Request a redirect to the authentication url from master
@param master_url [String] master root url @param auth_params [Hash] auth parameters (keyword arguments of authentication_path
) @return [String] url to begin authentication web flow
# File lib/kontena/cli/master/login_command.rb, line 145 def authentication_url_from_master(master_url, auth_params) client = Kontena::Client.new(master_url) vspinner "Sending authentication request to receive an authorization URL" do response = client.request( http_method: :get, path: authentication_path(auth_params), expects: [501, 400, 302, 403], auth: false ) if client.last_response.status == 302 client.last_response.headers['Location'] elsif response.kind_of?(Hash) exit_with_error [response['error'], response['error_description']].compact.join(' : ') elsif response.kind_of?(String) && response.length > 1 exit_with_error response else exit_with_error "Invalid response to authentication request : HTTP#{client.last_response.status} #{client.last_response.body if debug?}" end end end
# File lib/kontena/cli/master/login_command.rb, line 167 def display_remote_message(server, auth_params) url = authentication_url_from_master(server.url, auth_params.merge(remote: true)) if running_silent? sputs url else puts "Visit this URL in a browser:" puts "#{url}" end end
# File lib/kontena/cli/master/login_command.rb, line 21 def execute if self.code exit_with_error "Can't use --token and --code together" if self.token exit_with_error "Can't use --join and --code together" if self.join end if self.force? exit_with_error "Can't use --code and --force together" if self.code exit_with_error "Can't use --token and --force together" if self.token end server = select_a_server(self.name, self.url) if self.token # If a --token was given create a token with access_token set to --token value server.token = Kontena::Cli::Config::Token.new(access_token: self.token, parent_type: :master, parent_name: server.name) elsif server.token.nil? || self.force? # Force reauth or no existing token, create a token with no access_token server.token = Kontena::Cli::Config::Token.new(parent_type: :master, parent_name: server.name) end if self.grid self.skip_grid_auto_select = true if self.respond_to?(:skip_grid_auto_select?) server.grid = self.grid end # set server token by exchanging code if --code given if self.code use_authorization_code(server, self.code) exit 0 end # unless an invitation code was supplied, check auth and exit # if existing auth works already. unless self.join || self.force? if auth_works?(server) update_server_to_config(server) display_login_info(only: :master) unless self.no_login_info? exit 0 end end auth_params = { remote: self.remote?, invite_code: self.join, expires_in: self.expires_in } if self.remote? # no local browser? tell user to launch an external one display_remote_message(server, auth_params) auth_code = prompt.ask("Enter code displayed in browser:") use_authorization_code(server, auth_code) else # local web flow web_flow(server, auth_params) end display_login_info(only: :master) unless (running_silent? || self.no_login_info?) end
# File lib/kontena/cli/master/login_command.rb, line 99 def master_account @master_account ||= config.find_account('master') end
# File lib/kontena/cli/master/login_command.rb, line 82 def next_default_name next_name('kontena-master') end
# File lib/kontena/cli/master/login_command.rb, line 86 def next_name(base) if config.find_server(base) new_name = base.dup unless new_name =~ /\-\d+$/ new_name += "-2" end new_name.succ! until config.find_server(new_name).nil? new_name else base end end
Figure out or create a server based on url or name.
No name or url provided: try to use current_master A name provided with –name but no url defined: try to find a server by name from config An URL starting with 'http' provided: try to find a server by url from config An URL not starting with 'http' provided: try to find a server by name An URL and a name provided
- If a server is found by name: use entry and update URL to the provided url - Else create a new entry with the url and name
@param name [String] master name @param url [String] master url or name @return [Kontena::Cli::Config::Server]
# File lib/kontena/cli/master/login_command.rb, line 273 def select_a_server(name, url) # no url, no name, try to use current master if url.nil? && name.nil? if config.current_master return config.current_master else exit_with_error 'URL not specified and current master not selected' end end if name && url exact_match = config.find_server_by(url: url, name: name) return exact_match if exact_match # found an exact match, going to use that one. name_match = config.find_server(name) if name_match #found a server with the provided name, set the provided url to it and return name_match.url = url return name_match else # nothing found, create new. return Kontena::Cli::Config::Server.new(name: name, url: url) end elsif name # only --name provided, try to find a server with that name name_match = config.find_server(name) if name_match && name_match.url return name_match else exit_with_error "Master #{name} was found from config, but it does not have an URL and no URL was provided on command line" end elsif url # only url provided if url =~ /^https?:\/\// # url is actually an url url_match = config.find_server_by(url: url) if url_match return url_match else return Kontena::Cli::Config::Server.new(url: url, name: nil) end else name_match = config.find_server(url) if name_match unless name_match.url exit_with_error "Master #{url} was found from config, but it does not have an URL and no URL was provided on command line" end return name_match else exit_with_error "Can't find a master with name #{name} from configuration" end end end end
# File lib/kontena/cli/master/login_command.rb, line 215 def update_server(server, response) update_server_token(server, response) update_server_name(server, response) update_server_username(server, response) end
# File lib/kontena/cli/master/login_command.rb, line 221 def update_server_name(server, response) return nil unless server.name.nil? if response.kind_of?(Hash) && response['server'] && response['server']['name'] server.name = next_name(response['server']['name']) else server.name = next_default_name end end
# File lib/kontena/cli/master/login_command.rb, line 252 def update_server_to_config(server) server.name ||= next_default_name config.servers << server unless config.servers.include?(server) config.current_master = server.name config.write config.reset_instance end
# File lib/kontena/cli/master/login_command.rb, line 237 def update_server_token(server, response) if !response.kind_of?(Hash) raise TypeError, "Response type mismatch - expected Hash, got #{response.class}" elsif response['code'] use_authorization_code(server, response['code']) elsif response['error'] exit_with_error "Authentication failed: #{response['error']} #{response['error_description']}" else server.token = Kontena::Cli::Config::Token.new server.token.access_token = response['access_token'] server.token.refresh_token = response['refresh_token'] server.token.expires_at = response['expires_at'] end end
# File lib/kontena/cli/master/login_command.rb, line 230 def update_server_username(server, response) return nil unless response.kind_of?(Hash) return nil unless response['user'] server.token.username = response['user']['name'] || response['user']['email'] server.username = server.token.username end
# File lib/kontena/cli/master/login_command.rb, line 177 def web_flow(server, auth_params) require_relative '../localhost_web_server' require 'kontena/cli/browser_launcher' web_server = Kontena::LocalhostWebServer.new url = authentication_url_from_master(server.url, auth_params.merge(local_port: web_server.port)) uri = URI.parse(url) puts "Opening a browser to #{uri.scheme}://#{uri.host}" puts puts "If you are running this command over an ssh connection or it's" puts "otherwise not possible to open a browser from this terminal" puts "then you must use the --remote flag or use a pregenerated" puts "access token using the --token option." puts puts "Once the authentication is complete you can close the browser" puts "window or tab and return to this window to continue." puts any_key_to_continue(10) puts "If the browser does not open, try visiting this URL manually:" puts "#{uri.to_s}" puts server_thread = Thread.new { Thread.main['response'] = web_server.serve_one } Kontena::Cli::BrowserLauncher.open(uri.to_s) spinner "Waiting for browser authorization response" do server_thread.join end update_server(server, Thread.main['response']) update_server_to_config(server) end