Skip to content

Instantly share code, notes, and snippets.

@coliver
Forked from antoniusostermann/net_ntlm_monkeypatch.rb
Created February 27, 2019 21:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save coliver/5660cf421f96faa2d3906ce4fbee1b97 to your computer and use it in GitHub Desktop.
Save coliver/5660cf421f96faa2d3906ce4fbee1b97 to your computer and use it in GitHub Desktop.
A monkey patch to solve the problem described in https://github.com/savonrb/httpi/issues/139. This monkey patch prioritizes ntlm over negotiate and makes it possible to use the httpi gem if server supports both / sends header including both arguments.
# A monkey patch concerning this issue: https://github.com/savonrb/httpi/issues/139
# Basically, this monkey patch priors NTLM over Negotiate and not vice-versa
# All monkey patched spots are marked with "## MONKEY PATCHED"
# All in all, there are 2 monkey patched spots, both in private method "negotiate_ntlm_auth"
# Compare it with: https://github.com/savonrb/httpi/blob/d6a3825a8e896f794e54b634c39521e6956f72ff/lib/httpi/adapter/net_http.rb
require "uri"
require "httpi/adapter/base"
require "httpi/response"
require 'kconv'
require 'socket'
module HTTPI
module Adapter
class NetHTTP < Base
private
def negotiate_ntlm_auth(http, &requester)
unless Net.const_defined?(:NTLM)
raise NotSupportedError, 'Net::NTLM is not available. Install via gem install rubyntlm.'
end
## MONKEY PATCHED
## !! change if statements: prior NTLM over Negotiate
## !! it would be much better if we have a config for that (force_ntlm: true)
## !! luckily, include? exists on String and on Array, so we do not need to differ if we have an array or a string (unlike below)
nego_auth_response = respond_with(requester.call(http, request_client(:head)))
if nego_auth_response.headers['www-authenticate'].include? 'NTLM'
auth_method = 'NTLM'
elsif nego_auth_response.headers['www-authenticate'].include? 'Negotiate'
auth_method = 'Negotiate'
else
auth_method = 'NTLM'
HTTPI.logger.debug 'Server does not support NTLM/Negotiate. Trying NTLM anyway'
end
# initiate a request is to authenticate (exchange secret and auth) using the method determined above...
ntlm_message_type1 = Net::NTLM::Message::Type1.new
%w(workstation domain).each do |a|
ntlm_message_type1.send("#{a}=",'')
ntlm_message_type1.enable(a.to_sym)
end
@request.headers["Authorization"] = "#{auth_method} #{ntlm_message_type1.encode64}"
auth_response = respond_with(requester.call(http, request_client(:head)))
## MONKEY PATCHED
## !! If there are two possible authentication methods given (ntlm AND negotiate), use that one which contains auth_method
if auth_response.headers["WWW-Authenticate"].is_a?(Array)
fitting_headers = auth_response.headers["WWW-Authenticate"].select{|h| h.downcase.include?(auth_method.downcase)}
auth_response.headers["WWW-Authenticate"] = fitting_headers.first
end
# build an authentication request based on the token provided by the server
if auth_response.headers["WWW-Authenticate"] =~ /(NTLM|Negotiate) (.+)/
auth_token = $2
ntlm_message = Net::NTLM::Message.decode64(auth_token)
message_builder = {}
# copy the username and password from the authorization parameters
message_builder[:user] = @request.auth.ntlm[0]
message_builder[:password] = @request.auth.ntlm[1]
# we need to provide a domain in the packet if an only if it was provided by the user in the auth request
if @request.auth.ntlm[2]
message_builder[:domain] = @request.auth.ntlm[2].upcase
else
message_builder[:domain] = ''
end
ntlm_response = ntlm_message.response(message_builder ,
{:ntlmv2 => true})
# Finally add header of Authorization
@request.headers["Authorization"] = "#{auth_method} #{ntlm_response.encode64}"
end
nil
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment