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 +16 -0
- data/Rakefile +6 -3
- data/bin/apply_sspi_patch.rb +34 -0
- data/lib/rubysspi.rb +23 -10
- data/lib/rubysspi/proxy_auth.rb +3 -4
- data/test/ruby_sspi_test.rb +40 -14
- data/test/test_patched_net_http.rb +21 -0
- metadata +7 -6
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
|
8
|
-
s.version = "0.0
|
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 @
|
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
|
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
|
250
|
-
#
|
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
|
-
#
|
260
|
+
|
261
|
+
# Nil token OK, just set it to empty string
|
262
|
+
token = "" if token.nil?
|
263
|
+
|
257
264
|
if token.include? "Negotiate"
|
258
|
-
|
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(
|
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).
|
314
|
+
Base64.encode64(t).delete("\n")
|
302
315
|
end
|
303
316
|
end
|
304
317
|
end
|
data/lib/rubysspi/proxy_auth.rb
CHANGED
@@ -3,7 +3,7 @@ require 'rubysspi'
|
|
3
3
|
|
4
4
|
|
5
5
|
module Net
|
6
|
-
# Replaces Net::HTTP.request to understand
|
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,
|
30
|
-
if
|
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"]}"
|
data/test/ruby_sspi_test.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
7
|
-
date: 2006-
|
8
|
-
summary: A library which
|
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 @
|
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: []
|