rubysspi 0.0.2-i386-mswin32 → 1.0.0-i386-mswin32

Sign up to get free protection for your applications and to get access to all the features.
data/README.txt CHANGED
@@ -85,6 +85,22 @@ Note that server_token can be Base64 encoded or not, and if it starts with "Nego
85
85
 
86
86
  The token returned (usually an NTLM Type 3) message can then be sent to the server and the connection should be authenticated.
87
87
 
88
+ = Patching Ruby
89
+
90
+ A short script to patch Net::HTTP is also included. This patch will give the Net::HTTP (and consequently, open-uri) the ability to directly authenticate with Negotiate/NTLM proxy servers. The main advantage of patching Ruby itself is that other scripts, such as gems, will work directly with these proxy servers and will not require running with the 'spa' library. Similarly, any scripts which use open-uri or Net::HTTP will gain the ability to authenticate with the proxy server.
91
+
92
+ To run the script, just run <tt>apply_sspi_patch</tt>. The backup will be made of the original 'http.rb' file, and a new patched file will be copied in. After patching, run the test script 'test\test_patched_net_http.rb'. It ensures that proxy authentication now works without extra libraries.
93
+
94
+ Be aware - this has only been tested on Ruby 1.8.4 and 1.8.5. Though the Net::HTTP libraries are not likely to change much, do some testing afterwards.
95
+
96
+ If you want the patch disabled for a certain script, just define the constant DISABLE_RUBY_SSPI_PATCH at the top level. If you want to back the patch out, go to the ruby library directory, open the net folder, and rename the file called "http.orig.X.rb" (where X is some number) to http.rb. If the patch has been applied multiple times, uses the lowest "X" found.
97
+
98
+ = Disabling proxy for certain servers
99
+
100
+ Sometimes it is necessary to NOT use the proxy for certain addresses, especially in a LAN environment. While not a feature directly provided by this library, setting the 'no_proxy' environment variable to a list of servers will ensure no proxy connection is made for them. The list should be comma-delimited, and can consist of any mix of domain names and ports. URLs, however, are not allowed. For example "somehost, somehost.corporate.com, myhost:9000, myhost.corporate.com:80" would all be legal. Note that if a port is NOT specified, then no proxying occurs for that host regardless of the port requested by open-uri.
101
+
102
+ Finally, the above technique will only work with the open-uri library. Net::HTTP will not use a proxy automatically in any case.
103
+
88
104
  = Thanks & References
89
105
 
90
106
  Many references were used in decoding both NTLM messages and integrating with the SSPI library. Among them are:
data/Rakefile CHANGED
@@ -4,10 +4,10 @@ require 'rake/gempackagetask'
4
4
 
5
5
  spec = Gem::Specification.new do |s|
6
6
  s.name = "rubysspi"
7
- s.summary = "A library which adds implements Ruby bindings to the Win32 SSPI library. Also includes a module to add Negotate authentication support to Net::HTTP."
8
- s.version = "0.0.2"
7
+ s.summary = "A library which implements Ruby bindings to the Win32 SSPI library. Also includes a module to add Negotate authentication support to Net::HTTP."
8
+ s.version = "1.0.0"
9
9
  s.author = "Justin Bailey"
10
- s.email = "jgbailey @nospan@ gmail.com"
10
+ s.email = "jgbailey @nospam@ gmail.com"
11
11
  s.homepage = "http://rubyforge.org/projects/rubysspi/"
12
12
  s.rubyforge_project = "http://rubyforge.org/projects/rubysspi/"
13
13
  s.description = <<EOS
@@ -24,6 +24,9 @@ EOS
24
24
  s.platform = Gem::Platform::CURRENT
25
25
  s.files = FileList["lib/**/*", "test/*", "*.txt", "Rakefile", "spa.rb"].to_a
26
26
 
27
+ s.bindir = "bin"
28
+ s.executables = ["apply_sspi_patch.rb"]
29
+
27
30
  s.require_path = "lib"
28
31
  s.autorequire = "rubysspi"
29
32
 
@@ -0,0 +1,34 @@
1
+ # This file will patch the local net/http installation to include support for NTLM/Negotiate authentication.
2
+ # The original http.rb file will be renamed to http.orig.rb
3
+
4
+ require 'rbconfig'
5
+ require 'pathname'
6
+ require 'fileutils'
7
+
8
+ # Original http.rb file
9
+ orig_file = Pathname.new(Config::CONFIG["rubylibdir"]) + 'net\http.rb'
10
+
11
+ # Determine that original actually exists
12
+ raise "Original http.rb not not found at expected location #{orig_file}" unless orig_file.exist?
13
+
14
+ # Determine name to copy original file to
15
+ @i += 1 while (orig_dest = Pathname.new(Config::CONFIG["rubylibdir"]) + "net\\http.orig.#{@i ||= 1}.rb").exist?
16
+
17
+ # Copy original to back up
18
+ puts "Backing up original #{orig_file} to #{orig_dest}"
19
+ FileUtils.cp orig_file, orig_dest
20
+
21
+ # Write patch file.
22
+ puts "Creating patch file to replace #{orig_file}"
23
+ orig_file.open("w") { |f| f.write(<<EOS) }
24
+ # Include original net/http
25
+ require 'net/#{orig_dest.basename(orig_dest.extname)}'
26
+
27
+ # This magic constant can be defined to disable the patch.
28
+ unless defined? DISABLE_RUBY_SSPI_PATCH
29
+ require 'rubygems'
30
+ require 'rubysspi/proxy_auth'
31
+ end
32
+ EOS
33
+
34
+ puts 'Patch applied! Please run ..\test\test_patched_net_http.rb to test the patch.'
data/lib/rubysspi.rb CHANGED
@@ -21,7 +21,7 @@ module SSPI
21
21
  ISC_REQ_PROMPT_FOR_CREDS = 0x00000040
22
22
  ISC_REQ_CONNECTION = 0x00000800
23
23
 
24
- # Win32 API Functions. Uses dl/win32 to bind methods to constants contained in class.
24
+ # Win32 API Functions. Uses Win32API to bind methods to constants contained in class.
25
25
  module API
26
26
  # Can be called with AcquireCredentialsHandle.call()
27
27
  AcquireCredentialsHandle = Win32API.new("secur32", "AcquireCredentialsHandle", 'ppLpppppp', 'L')
@@ -195,8 +195,13 @@ module SSPI
195
195
  # Handles "Negotiate" type authentication. Geared towards authenticating with a proxy server over HTTP
196
196
  class NegotiateAuth
197
197
  attr_accessor :credentials, :context, :contextAttributes, :user, :domain
198
+
199
+ # Default request flags for SSPI functions
198
200
  REQUEST_FLAGS = ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONNECTION
199
201
 
202
+ # NTLM tokens start with this header always. Encoding alone adds "==" and newline, so remove those
203
+ B64_TOKEN_PREFIX = Base64.encode64("NTLMSSP").delete("=\n")
204
+
200
205
  # Given a connection and a request path, performs authentication as the current user and returns
201
206
  # the response from a GET request. The connnection should be a Net::HTTP object, and it should
202
207
  # have been constructed using the Net::HTTP.Proxy method, but anything that responds to "get" will work.
@@ -246,21 +251,29 @@ module SSPI
246
251
  end
247
252
  end
248
253
 
249
- # Takes a Base64 encoded token and gets the next token in the
250
- # Negotiate authentication chain. The token can include the "Negotiate" header and it will be stripped.
254
+ # Takes a token and gets the next token in the Negotiate authentication chain. Token can be Base64 encoded or not.
255
+ # The token can include the "Negotiate" header and it will be stripped.
251
256
  # Does not indicate if SEC_I_CONTINUE or SEC_E_OK was returned.
252
- # Token returned is Base64 encoded.
257
+ # Token returned is Base64 encoded w/ all new lines removed.
253
258
  def complete_authentication(token)
254
259
  raise "This object is no longer usable because its resources have been freed." if @cleaned_up
255
-
256
- # If the Negotiate prefix is passed in, assume we are seeing "Negotiate <token>" and get the token.
260
+
261
+ # Nil token OK, just set it to empty string
262
+ token = "" if token.nil?
263
+
257
264
  if token.include? "Negotiate"
258
- token = token.split(" ").last.strip
265
+ # If the Negotiate prefix is passed in, assume we are seeing "Negotiate <token>" and get the token.
266
+ token = token.split(" ").last
267
+ end
268
+
269
+ if token.include? B64_TOKEN_PREFIX
270
+ # indicates base64 encoded token
271
+ token = Base64.decode64(token.strip)
259
272
  end
260
- outputBuffer = SecurityBuffer.new
261
273
 
274
+ outputBuffer = SecurityBuffer.new
262
275
  result = SSPIResult.new(API::InitializeSecurityContext.call(@credentials.to_p, @context.to_p, nil,
263
- REQUEST_FLAGS, 0, SECURITY_NETWORK_DREP, SecurityBuffer.new(Base64.decode64(token)).to_p, 0,
276
+ REQUEST_FLAGS, 0, SECURITY_NETWORK_DREP, SecurityBuffer.new(token).to_p, 0,
264
277
  @context.to_p,
265
278
  outputBuffer.to_p, @contextAttributes, TimeStamp.new.to_p))
266
279
 
@@ -298,7 +311,7 @@ module SSPI
298
311
 
299
312
  def encode_token(t)
300
313
  # encode64 will add newlines every 60 characters so we need to remove those.
301
- Base64.encode64(t).gsub(/\n/, "")
314
+ Base64.encode64(t).delete("\n")
302
315
  end
303
316
  end
304
317
  end
@@ -3,7 +3,7 @@ require 'rubysspi'
3
3
 
4
4
 
5
5
  module Net
6
- # Replaces Net::HTTP.request to understand Negotate HTTP proxy authorization. Uses native Win32
6
+ # Replaces Net::HTTP.request to understand Negotiate HTTP proxy authorization. Uses native Win32
7
7
  # libraries to authentic as the current user (as defined by the ENV["USERNAME"] and ENV["USERDOMAIN"]
8
8
  # environment variables.
9
9
  class HTTP
@@ -26,8 +26,8 @@ module Net
26
26
  begin
27
27
  res = HTTPResponse.read_new(@socket)
28
28
  end while res.kind_of?(HTTPContinue)
29
- # If proxy was specified, and the server is demanding authentication, negotatiate with it
30
- if proxy? && res.code.to_i == 407 && res["Proxy-Authenticate"].include?("Negotiate")
29
+ # If proxy was specified, and the server is demanding authentication, negotiate with it
30
+ if res.kind_of?(HTTPProxyAuthenticationRequired) && proxy? && res["Proxy-Authenticate"].include?("Negotiate")
31
31
  n = SSPI::NegotiateAuth.new
32
32
  res.reading_body(@socket, req.response_body_permitted?) { }
33
33
  req["Proxy-Authorization"] = "Negotiate #{n.get_initial_token}"
@@ -35,7 +35,6 @@ module Net
35
35
  begin
36
36
  res = HTTPResponse.read_new(@socket)
37
37
  end while res.kind_of?(HTTPContinue)
38
-
39
38
  if res["Proxy-Authenticate"]
40
39
  res.reading_body(@socket, req.response_body_permitted?) { }
41
40
  req["Proxy-Authorization"] = "Negotiate #{n.complete_authentication res["Proxy-Authenticate"]}"
@@ -1,3 +1,6 @@
1
+ # Magic constant will ensure that, if the SSPI patch has been applied, it won't break these tests
2
+ DISABLE_RUBY_SSPI_PATCH = true
3
+
1
4
  require 'test/unit'
2
5
  require 'net/http'
3
6
  require 'pathname'
@@ -5,15 +8,8 @@ $: << (File.dirname(__FILE__) << "/../lib")
5
8
  require 'rubysspi'
6
9
 
7
10
  class NTLMTest < Test::Unit::TestCase
8
-
9
- def setup
10
- assert ENV["http_proxy"], "http_proxy must be set before running tests."
11
- end
12
-
13
11
  def test_auth
14
- assert ENV["http_proxy"], "http_proxy environment variable must be set."
15
- proxy = URI.parse(ENV["http_proxy"])
16
- assert proxy.host && proxy.port, "Could not parse http_proxy (#{ENV["http_proxy"]}). http_proxy should be a URL with a port (e.g. http://proxy.corp.com:8080)."
12
+ proxy = get_proxy
17
13
 
18
14
  Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
19
15
  nego_auth = SSPI::NegotiateAuth.new
@@ -26,9 +22,7 @@ class NTLMTest < Test::Unit::TestCase
26
22
  end
27
23
 
28
24
  def test_proxy_auth_get
29
- assert ENV["http_proxy"], "http_proxy environment variable must be set."
30
- proxy = URI.parse(ENV["http_proxy"])
31
- assert proxy.host && proxy.port, "Could not parse http_proxy (#{ENV["http_proxy"]}). http_proxy should be a URL with a port (e.g. http://proxy.corp.com:8080)."
25
+ proxy = get_proxy
32
26
 
33
27
  Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
34
28
  resp = SSPI::NegotiateAuth.proxy_auth_get http, "/"
@@ -37,9 +31,7 @@ class NTLMTest < Test::Unit::TestCase
37
31
  end
38
32
 
39
33
  def test_one_time_use_only
40
- assert ENV["http_proxy"], "http_proxy environment variable must be set."
41
- proxy = URI.parse(ENV["http_proxy"])
42
- assert proxy.host && proxy.port, "Could not parse http_proxy (#{ENV["http_proxy"]}). http_proxy should be a URL with a port (e.g. http://proxy.corp.com:8080)."
34
+ proxy = get_proxy
43
35
 
44
36
  Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
45
37
  nego_auth = SSPI::NegotiateAuth.new
@@ -51,4 +43,38 @@ class NTLMTest < Test::Unit::TestCase
51
43
  end
52
44
  end
53
45
  end
46
+
47
+ def test_token_variations
48
+ proxy = get_proxy
49
+
50
+ # Test that raw token works
51
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
52
+ nego_auth = SSPI::NegotiateAuth.new
53
+ sr = http.request_get "/", { "Proxy-Authorization" => "Negotiate " + nego_auth.get_initial_token }
54
+ token = Base64.decode64(sr["Proxy-Authenticate"].split(" ").last.strip)
55
+ completed_token = nego_auth.complete_authentication(token)
56
+ resp = http.get "/", { "Proxy-Authorization" => "Negotiate " + completed_token }
57
+ assert resp.code.to_i == 200, "Response code not as expected: #{resp.inspect}"
58
+ end
59
+
60
+ # Test that token w/ "Negotiate" header included works
61
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
62
+ nego_auth = SSPI::NegotiateAuth.new
63
+ sr = http.request_get "/", { "Proxy-Authorization" => "Negotiate " + nego_auth.get_initial_token }
64
+ resp = http.get "/", { "Proxy-Authorization" => "Negotiate " + nego_auth.complete_authentication(sr["Proxy-Authenticate"]) }
65
+ assert resp.code.to_i == 200, "Response code not as expected: #{resp.inspect}"
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ # Gets the proxy from the environment and makes some assertions
72
+ def get_proxy
73
+ assert ENV["http_proxy"], "http_proxy environment variable must be set."
74
+ proxy = URI.parse(ENV["http_proxy"])
75
+ assert proxy.host && proxy.port, "Could not parse http_proxy (#{ENV["http_proxy"]}). http_proxy should be a URL with a port (e.g. http://proxy.corp.com:8080)."
76
+
77
+ return proxy
78
+ end
79
+
54
80
  end
@@ -0,0 +1,21 @@
1
+ require 'test/unit'
2
+ require 'net/http'
3
+
4
+ # Intended to test 'patched' version of Net::HTTP. Notice lack of requires at the top.
5
+ class PatchedRubyTest < Test::Unit::TestCase
6
+ def setup
7
+ assert ENV["http_proxy"], "http_proxy must be set before running tests."
8
+ end
9
+
10
+ def test_net_http
11
+
12
+ assert ENV["http_proxy"], "http_proxy environment variable must be set."
13
+ proxy = URI.parse(ENV["http_proxy"])
14
+ assert proxy.host && proxy.port, "Could not parse http_proxy (#{ENV["http_proxy"]}). http_proxy should be a URL with a port (e.g. http://proxy.corp.com:8080)."
15
+
16
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
17
+ resp = http.get("/")
18
+ assert resp.code.to_i == 200, "Did not get response from Google as expected."
19
+ end
20
+ end
21
+ end
metadata CHANGED
@@ -3,12 +3,12 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: rubysspi
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.2
7
- date: 2006-07-17 00:00:00 -07:00
8
- summary: A library which adds implements Ruby bindings to the Win32 SSPI library. Also includes a module to add Negotate authentication support to Net::HTTP.
6
+ version: 1.0.0
7
+ date: 2006-09-28 00:00:00 -07:00
8
+ summary: A library which implements Ruby bindings to the Win32 SSPI library. Also includes a module to add Negotate authentication support to Net::HTTP.
9
9
  require_paths:
10
10
  - lib
11
- email: jgbailey @nospan@ gmail.com
11
+ email: jgbailey @nospam@ gmail.com
12
12
  homepage: http://rubyforge.org/projects/rubysspi/
13
13
  rubyforge_project: http://rubyforge.org/projects/rubysspi/
14
14
  description: This gem provides bindings to the Win32 SSPI libraries, primarily to support Negotiate (i.e. SPNEGO, NTLM) authentication with a proxy server. Enough support is implemented to provide the necessary support for the authentication. A module is also provided which overrides Net::HTTP and adds support for Negotiate authentication to it. This implies that open-uri automatically gets support for it, as long as the http_proxy environment variable is set.
@@ -35,6 +35,7 @@ files:
35
35
  - test/ruby_sspi_test.rb
36
36
  - test/test_gem_list.rb
37
37
  - test/test_net_http.rb
38
+ - test/test_patched_net_http.rb
38
39
  - README.txt
39
40
  - Rakefile
40
41
  - spa.rb
@@ -48,8 +49,8 @@ rdoc_options:
48
49
  - --line-numbers
49
50
  extra_rdoc_files:
50
51
  - README.txt
51
- executables: []
52
-
52
+ executables:
53
+ - apply_sspi_patch.rb
53
54
  extensions: []
54
55
 
55
56
  requirements: []