Skip to content

Commit

Permalink
Fixes #11317 - Support basic auth for external sources
Browse files Browse the repository at this point in the history
(cherry picked from commit 3173a4f)
  • Loading branch information
ofedoren committed Feb 28, 2023
1 parent 65ba8d9 commit 1776108
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 67 deletions.
5 changes: 5 additions & 0 deletions config/foreman.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
:username: 'admin'
#:password: 'example'

# Basic Auth External:
#:default_auth_type: 'Basic_Auth_External'
#:username: 'admin'
#:password: 'example'

# Oauth using the Password Grant Flow:
# This authentication method requires sessions to be enabled, uncomment the following
# lines to use this authentication method.
Expand Down
20 changes: 20 additions & 0 deletions lib/hammer_cli_foreman/api/authenticator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ def fetch
void_auth
elsif auth_type == AUTH_TYPES[:basic_auth]
basic_auth
elsif auth_type == AUTH_TYPES[:basic_auth_external]
basic_auth_external
elsif auth_type == AUTH_TYPES[:negotiate]
negotiate_auth
elsif auth_type == AUTH_TYPES[:oauth_password_grant]
Expand Down Expand Up @@ -45,6 +47,24 @@ def basic_auth
end
end

def basic_auth_external
if HammerCLIForeman::Sessions.enabled?
authenticator = InteractiveBasicAuthExternal.new(
settings.get(:_params, :username) || ENV['FOREMAN_USERNAME'],
settings.get(:_params, :password) || ENV['FOREMAN_PASSWORD'],
uri
)
SessionAuthenticatorWrapper.new(authenticator, uri, auth_type)
else
username = settings.get(:_params, :username) || ENV['FOREMAN_USERNAME'] || settings.get(:foreman, :username)
password = settings.get(:_params, :password) || ENV['FOREMAN_PASSWORD']
if password.nil? && (username == settings.get(:foreman, :username))
password = settings.get(:foreman, :password)
end
InteractiveBasicAuthExternal.new(username, password, uri)
end
end

def negotiate_auth
return unless HammerCLIForeman::Sessions.enabled?

Expand Down
71 changes: 71 additions & 0 deletions lib/hammer_cli_foreman/api/basic_auth.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
module HammerCLIForeman
module Api
module BasicAuth
def authenticate(request, args)
if HammerCLI.interactive?
get_user
get_password
end
super
end

def error(ex)
return unless ex.is_a?(RestClient::Unauthorized)

clear
default_message = _('Invalid username or password.')
message = begin
response_msg = JSON.parse(ex.response.body)['error']
response_msg.is_a?(Hash) ? response_msg['message'] : response_msg
rescue
end
return UnauthorizedError.new(default_message) unless message

UnauthorizedError.new("#{message}\n#{default_message}")
end

def status
unless @user.nil? || @password.nil?
_("Using configured credentials for user '%s'.") % @user
else
_('Credentials are not configured.')
end
end

def user(ask = nil)
@user ||= ask && get_user
end

def password(ask = nil)
@password ||= ask && get_password
end

def set_credentials(user, password)
@user = user
@password = password
end

def clear
set_credentials(nil, nil)
end

private

def get_user
@user ||= ask_user(_('[Foreman] Username:%s') % ' ')
end

def get_password
@password ||= ask_user(_("[Foreman] Password for %{user}:%{wsp}") % { user: @user, wsp: ' ' }, true)
end

def ask_user(prompt, silent = false)
if silent
HammerCLI.interactive_output.ask(prompt) { |q| q.echo = false }
else
HammerCLI.interactive_output.ask(prompt)
end
end
end
end
end
5 changes: 3 additions & 2 deletions lib/hammer_cli_foreman/api/connection.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'hammer_cli_foreman/api/session_authenticator_wrapper'
require 'hammer_cli_foreman/api/authenticator'
require 'hammer_cli_foreman/api/interactive_basic_auth'
require 'hammer_cli_foreman/api/interactive_basic_auth_external'
require 'hammer_cli_foreman/api/negotiate_auth'
require 'hammer_cli_foreman/api/oauth/authentication_code_grant'
require 'hammer_cli_foreman/api/oauth/password_grant'
Expand All @@ -11,6 +12,7 @@ module HammerCLIForeman
CONNECTION_NAME = 'foreman'
AUTH_TYPES = {
basic_auth: 'Basic_Auth',
basic_auth_external: 'Basic_Auth_External',
negotiate: 'Negotiate_Auth',
oauth_authentication_code_grant: 'Oauth_Authentication_Code_Grant',
oauth_password_grant: 'Oauth_Password_Grant'
Expand Down Expand Up @@ -44,8 +46,7 @@ def login_status

protected

def default_auth_type(settings)
return AUTH_TYPES[:basic_auth] unless HammerCLIForeman::Sessions.enabled?
def default_auth_type(_settings)
HammerCLI::Settings.get(:foreman, :default_auth_type) || AUTH_TYPES[:basic_auth]
end

Expand Down
65 changes: 3 additions & 62 deletions lib/hammer_cli_foreman/api/interactive_basic_auth.rb
Original file line number Diff line number Diff line change
@@ -1,68 +1,9 @@
require 'hammer_cli_foreman/api/basic_auth'

module HammerCLIForeman
module Api
class InteractiveBasicAuth < ApipieBindings::Authenticators::BasicAuth
def authenticate(request, args)
if HammerCLI.interactive?
get_user
get_password
end
super
end

def error(ex)
if ex.is_a?(RestClient::Unauthorized)
self.clear
message = _('Invalid username or password.')
begin
message = JSON.parse(ex.response.body)['error']['message']
rescue
end
UnauthorizedError.new(message)
end
end

def status
unless @user.nil? || @password.nil?
_("Using configured credentials for user '%s'.") % @user
else
_("Credentials are not configured.")
end
end

def user(ask=nil)
@user ||= ask && get_user
end

def password(ask=nil)
@password ||= ask && get_password
end

def set_credentials(user, password)
@user = user
@password = password
end

def clear
set_credentials(nil, nil)
end

private

def get_user
@user ||= ask_user(_("[Foreman] Username:%s") % " ")
end

def get_password
@password ||= ask_user(_("[Foreman] Password for %{user}:%{wsp}") % {:user => @user, :wsp => " "}, true)
end

def ask_user(prompt, silent=false)
if silent
HammerCLI.interactive_output.ask(prompt) { |q| q.echo = false }
else
HammerCLI.interactive_output.ask(prompt)
end
end
include HammerCLIForeman::Api::BasicAuth
end
end
end
17 changes: 17 additions & 0 deletions lib/hammer_cli_foreman/api/interactive_basic_auth_external.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require 'hammer_cli_foreman/api/basic_auth'

module HammerCLIForeman
module Api
class InteractiveBasicAuthExternal < ApipieBindings::Authenticators::BasicAuthExternal
include HammerCLIForeman::Api::BasicAuth

def initialize(user, password, foreman_url)
super(user, password, "#{foreman_url}/api/users/extlogin", HammerCLI::SSLOptions.new.get_options(foreman_url))
end

def session_id
auth_cookie&.delete_prefix('_session_id=')
end
end
end
end
4 changes: 2 additions & 2 deletions lib/hammer_cli_foreman/api/session_authenticator_wrapper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def response(r)

def user(ask=nil)
return unless @authenticator.respond_to?(:user)
if @auth_type == AUTH_TYPES[:basic_auth]
if [AUTH_TYPES[:basic_auth], AUTH_TYPES[:basic_auth_external]].include?(@auth_type)
@authenticator.user(ask)
elsif @auth_type == AUTH_TYPES[:oauth_authentication_code_grant] ||
@auth_type = AUTH_TYPES[:oauth_password_grant]
Expand All @@ -93,7 +93,7 @@ def password(ask=nil)
end

def set_auth_params(*args)
if @auth_type == AUTH_TYPES[:basic_auth]
if [AUTH_TYPES[:basic_auth], AUTH_TYPES[:basic_auth_external]].include?(@auth_type)
@authenticator.set_credentials(*args)
elsif @auth_type == AUTH_TYPES[:oauth_authentication_code_grant] ||
@auth_type == AUTH_TYPES[:oauth_password_grant]
Expand Down
21 changes: 21 additions & 0 deletions lib/hammer_cli_foreman/auth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,27 @@ def execute
end
end

class BasicExternal < HammerCLI::AbstractCommand
extend HammerCLIForeman::Authenticate::Login

command_name 'basic-external'
desc _('Authenticate against external source (IPA/PAM) with credentials')

option ['-u', '--username'], 'USERNAME', _('Username to access the remote system')
option ['-p', '--password'], 'PASSWORD', _('Password to access the remote system')

def execute
Basic.execute_with_params(
AUTH_TYPES[:basic_auth_external],
option_username || HammerCLI::Settings.get('_params', 'username'),
option_password || HammerCLI::Settings.get('_params', 'password')
)
logged_user = HammerCLIForeman.foreman_api_connection.authenticator.user
print_message(_("Successfully logged in as '%s'.") % logged_user)
HammerCLI::EX_OK
end
end

class Negotiate < HammerCLI::AbstractCommand
extend HammerCLIForeman::Authenticate::Login

Expand Down
3 changes: 2 additions & 1 deletion test/unit/api/interactive_basic_auth_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@
response.stubs(:body).returns('{"error": {"message": "Unable to authenticate user admin"}}')
ex.response = response
new_ex = auth.error(ex)
expected = "Unable to authenticate user admin\nInvalid username or password."

assert_equal 'Unable to authenticate user admin', new_ex.message
assert_equal expected, new_ex.message
end
end

Expand Down

0 comments on commit 1776108

Please sign in to comment.