rubysspi 0.0.1-i386-mswin32

Sign up to get free protection for your applications and to get access to all the features.
data/README.txt ADDED
@@ -0,0 +1,93 @@
1
+ = Introduction
2
+
3
+ This library provides bindings to the Win32 SSPI libraries, which implement various security protocols for Windows. The library was primarily developed to give Negotiate/NTLM proxy authentication abilities to Net::HTTP, similar to support found in Internet Explorer or Firefox.
4
+
5
+ The libary is NOT an implementation of the NTLM protocol, and does not give the ability to authenticate as any given user. It is able to authenticate with a proxy server as the current user.
6
+
7
+ This project can be found on rubyforge at:
8
+
9
+ http://rubyforge.org/projects/rubysspi
10
+
11
+ = Using with open-uri
12
+
13
+ To use the library with open-uri, make sure to set the environment variable +http_proxy+ to your proxy server. This must be a hostname and port in URL form. E.g.:
14
+
15
+ http://proxy.corp.com:8080
16
+
17
+ The library will grab your current username and domain from the environment variables +USERNAME+ and +USERDOMAIN+. This should be set for you by Windows already.
18
+
19
+ The library implements a patch on top of Net::HTTP, which means open-uri gets it too. At the top of your script, make sure to require the patch after +open-uri+:
20
+
21
+ require +open-uri+
22
+ require +rubysspi/proxy_auth+
23
+
24
+ open("http://www.google.com") { |f| puts(f.gets(nil)) }
25
+
26
+ Note that this patch does NOT work with the +http_proxy_user+ and +http_proxy_password+ environment variables. The library will ONLY authenticate as the current user.
27
+
28
+ = Using with Net::HTTP
29
+
30
+ Net::HTTP will not use the proxy server supplied in the environment variable automatically, so you have to supply the proxy address yourself. Otherwise, it's exactly the same:
31
+
32
+ require 'net/http'
33
+ require 'rubysspi/proxy_auth+
34
+
35
+ Net::HTTP::Proxy("proxy.corp.com", 8080).start("www.google.com") do |http|
36
+ resp = http.request_get "/"
37
+ puts resp.body
38
+ end
39
+
40
+ = Using rubysspi directly
41
+
42
+ As stated, the library is geared primarily towards supporting Negotiate/NTLM authentication with proxy servers. In this vein, you can manually authenticate a given HTTP connection with a single call:
43
+
44
+ require 'rubysspi'
45
+
46
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
47
+ resp = SSPI::NegotiateAuth.proxy_auth_get http, "/"
48
+ end
49
+
50
+ The +resp+ variable will contain the response from Google, with any proxy authorization necessary taken care of automatically. Note that if the +http+ connection is not closed, any subsequent requests will NOT require authentication.
51
+
52
+ If the above method is used, it is recommended that you do NOT require the 'rubysspi/proxy_auth' library, as the interaction between the two will fail.
53
+
54
+ The library can be used directly to generate tokens appropriate for the current user, too.
55
+
56
+ To get started, first create an instance of the SSPI::NegotiateAuth class:
57
+
58
+ require 'rubysspi'
59
+
60
+ n = SSPI::NegotiateAuth.new
61
+
62
+ Next, get the first token by calling get_initial_token:
63
+
64
+ token = n.get_initial_token
65
+
66
+ This token returned will be Base64 encoded and can be directly placed in an HTTP header. This token can be easily decoded, however, and is usually an NTLM Type 1 message.
67
+
68
+ After getting a response from the server (usually an NTLM Type 2 message), pass it into the complete_authentication:
69
+
70
+ token = n.complete_authentication(server_token)
71
+
72
+ Note that server_token can be Base64 encoded or not, and if it starts with "Negotiate", that phrase will be stripped off. This allows the response from a Proxy-Authentication header to be passed into the method directly. The token can be decoded externally and passed in, too.
73
+
74
+ The token returned (usually an NTLM Type 3) message can then be sent to the server and the connection should be authenticated.
75
+
76
+ = Thanks & References
77
+
78
+ Many references were used in decoding both NTLM messages and integrating with the SSPI library. Among them are:
79
+
80
+ * Managed SSPI Sample - A .NET implementation of a simple client/server using SSPI. A complex undertaking but provides a great resource for playing with the API.
81
+ * http://msdn.microsoft.com/library/?url=/library/en-us/dndotnet/html/remsspi.asp?frame=true
82
+ * John Lam's RubyCLR - http://www.rubyclr.com
83
+ * Originally, I used RubyCLR to call into the Managed SSPI sample which really helped me decode what the SSPI interface did and how it worked. I did not end up using that implementation but it was great for research.
84
+ * The NTLM Authentication Protocol - The definitive explanation for the NTLM protocol (outside MS internal documents, I presume).
85
+ * http://davenport.sourceforge.net/ntlm.html
86
+ * Ruby/NTLM - A pure Ruby implementation of the NTLM protocol. Again, not used in this project but invaluable for decoding NTLM messages and figuring out what SSPI was returning.
87
+ * http://rubyforge.org/projects/rubyntlm/
88
+ * Seamonkey/Mozilla NTLM implementation - The only source for an implementation in an actual browser. How they figured out how to use SSPI themselves is beyond me.
89
+ * http://lxr.mozilla.org/seamonkey/source/mailnews/base/util/nsMsgProtocol.cpp#899
90
+
91
+ And of course, thanks to my Lord and Savior, Jesus Christ. In the name of the Father, the Son, and the Holy Spirit.
92
+
93
+
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ require 'rubygems'
2
+ Gem::manage_gems
3
+ require 'rake/gempackagetask'
4
+
5
+ spec = Gem::Specification.new do |s|
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.1"
9
+ s.author = "Justin Bailey"
10
+ s.email = "jgbailey @nospan@ gmail.com"
11
+ s.homepage = "http://rubyforge.org/projects/rubysspi/"
12
+ s.rubyforge_project = "http://rubyforge.org/projects/rubysspi/"
13
+ s.description = <<EOS
14
+ This gem provides bindings to the Win32 SSPI libraries, primarily to support Negotiate (i.e. SPNEGO, NTLM)
15
+ authentication with a proxy server. Enough support is implemented to provide the necessary support for
16
+ the authentication.
17
+
18
+ A module is also provided which overrides Net::HTTP and adds support for Negotiate authentication to it.
19
+
20
+ This implies that open-uri automatically gets support for it, as long as the http_proxy environment variable
21
+ is set.
22
+ EOS
23
+
24
+ s.platform = Gem::Platform::CURRENT
25
+ s.files = FileList["lib/**/*", "test/*", "*.txt", "Rakefile"].to_a
26
+
27
+ s.require_path = "lib"
28
+ s.autorequire = "rubysspi"
29
+
30
+ s.has_rdoc = true
31
+ s.extra_rdoc_files = ["README.txt"]
32
+ s.rdoc_options << '--title' << 'Ruby SSPI -- Win32 SSPI Bindings for Ruby' <<
33
+ '--main' << 'README.txt' <<
34
+ '--line-numbers'
35
+ end
36
+
37
+ Rake::GemPackageTask.new(spec) do |pkg|
38
+ pkg.need_tar = true
39
+ end
data/lib/rubysspi.rb ADDED
@@ -0,0 +1,303 @@
1
+ require 'dl/win32'
2
+ require 'base64'
3
+
4
+ # Implements bindings to Win32 SSPI functions, focused on authentication to a proxy server over HTTP.
5
+ module SSPI
6
+ # Specifies how credential structure requested will be used. Only SECPKG_CRED_OUTBOUND is used
7
+ # here.
8
+ SECPKG_CRED_INBOUND = 0x00000001
9
+ SECPKG_CRED_OUTBOUND = 0x00000002
10
+ SECPKG_CRED_BOTH = 0x00000003
11
+
12
+ # Format of token. NETWORK format is used here.
13
+ SECURITY_NATIVE_DREP = 0x00000010
14
+ SECURITY_NETWORK_DREP = 0x00000000
15
+
16
+ # InitializeSecurityContext Requirement flags
17
+ ISC_REQ_REPLAY_DETECT = 0x00000004
18
+ ISC_REQ_SEQUENCE_DETECT = 0x00000008
19
+ ISC_REQ_CONFIDENTIALITY = 0x00000010
20
+ ISC_REQ_USE_SESSION_KEY = 0x00000020
21
+ ISC_REQ_PROMPT_FOR_CREDS = 0x00000040
22
+ ISC_REQ_CONNECTION = 0x00000800
23
+
24
+ # Win32 API Functions. Uses dl/win32 to bind methods to constants contained in class.
25
+ module API
26
+ # Can be called with AcquireCredentialsHandle.call()
27
+ AcquireCredentialsHandle = Win32API.new("secur32", "AcquireCredentialsHandle", ['ppLpppppp'], 'L')
28
+ # Can be called with AcquireCredentialsHandle.call()
29
+ InitializeSecurityContext = Win32API.new("secur32", "InitializeSecurityContext", [ 'pppLLLpLpppp'], 'L')
30
+ # Can be called with AcquireCredentialsHandle.call()
31
+ DeleteSecurityContext = Win32API.new("secur32", "DeleteSecurityContext", ['P'], 'L')
32
+ # Can be called with AcquireCredentialsHandle.call()
33
+ FreeCredentialsHandle = Win32API.new("secur32", "FreeCredentialsHandle", ['P'], 'L')
34
+ end
35
+
36
+ # SecHandle struct
37
+ class SecurityHandle
38
+ def upper
39
+ @struct.unpack("LL")[1]
40
+ end
41
+
42
+ def lower
43
+ @struct.unpack("LL")[0]
44
+ end
45
+
46
+ def to_p
47
+ @struct ||= "\0" * 8
48
+ end
49
+ end
50
+
51
+ # Some familiar aliases for the SecHandle structure
52
+ CredHandle = CtxtHandle = SecurityHandle
53
+
54
+ # TimeStamp struct
55
+ class TimeStamp
56
+ attr_reader :struct
57
+
58
+ def to_p
59
+ @struct ||= "\0" * 8
60
+ end
61
+ end
62
+
63
+ # Creates binary representaiton of a SecBufferDesc structure,
64
+ # including the SecBuffer contained inside.
65
+ class SecurityBuffer
66
+
67
+ SECBUFFER_TOKEN = 2 # Security token
68
+
69
+ TOKENBUFSIZE = 12288
70
+ SECBUFFER_VERSION = 0
71
+
72
+ def initialize(buffer = nil)
73
+ @buffer = buffer || "\0" * TOKENBUFSIZE
74
+ @bufferSize = @buffer.length
75
+ @type = SECBUFFER_TOKEN
76
+ end
77
+
78
+ def bufferSize
79
+ unpack
80
+ @bufferSize
81
+ end
82
+
83
+ def bufferType
84
+ unpack
85
+ @type
86
+ end
87
+
88
+ def token
89
+ unpack
90
+ @buffer
91
+ end
92
+
93
+ def to_p
94
+ # Assumption is that when to_p is called we are going to get a packed structure. Therefore,
95
+ # set @unpacked back to nil so we know to unpack when accessors are next accessed.
96
+ @unpacked = nil
97
+ # Assignment of inner structure to variable is very important here. Without it,
98
+ # will not be able to unpack changes to the structure. Alternative, nested unpacks,
99
+ # does not work (i.e. @struct.unpack("LLP12")[2].unpack("LLP12") results in "no associated pointer")
100
+ @sec_buffer ||= [@bufferSize, @type, @buffer].pack("LLP")
101
+ @struct ||= [SECBUFFER_VERSION, 1, @sec_buffer].pack("LLP")
102
+ end
103
+
104
+ private
105
+
106
+ # Unpacks the SecurityBufferDesc structure into member variables. We
107
+ # only want to do this once per struct, so the struct is deleted
108
+ # after unpacking.
109
+ def unpack
110
+ if ! @unpacked && @sec_buffer && @struct
111
+ @bufferSize, @type = @sec_buffer.unpack("LL")
112
+ @buffer = @sec_buffer.unpack("LLP#{@bufferSize}")[2]
113
+ @struct = nil
114
+ @sec_buffer = nil
115
+ @unpacked = true
116
+ end
117
+ end
118
+ end
119
+
120
+ # SEC_WINNT_AUTH_IDENTITY structure
121
+ class Identity
122
+ SEC_WINNT_AUTH_IDENTITY_ANSI = 0x1
123
+
124
+ attr_accessor :user, :domain, :password
125
+
126
+ def initialize(user = nil, domain = nil, password = nil)
127
+ @user = user
128
+ @domain = domain
129
+ @password = password
130
+ @flags = SEC_WINNT_AUTH_IDENTITY_ANSI
131
+ end
132
+
133
+ def to_p
134
+ [@user, @user ? @user.length : 0,
135
+ @domain, @domain ? @domain.length : 0,
136
+ @password, @password ? @password.length : 0,
137
+ @flags].pack("PLPLPLL")
138
+ end
139
+ end
140
+
141
+ # Takes a return result from an SSPI function and interprets the value.
142
+ class SSPIResult
143
+ # Good results
144
+ SEC_E_OK = 0x00000000
145
+ SEC_I_CONTINUE_NEEDED = 0x00090312
146
+
147
+ # These are generally returned by InitializeSecurityContext
148
+ SEC_E_INSUFFICIENT_MEMORY = 0x80090300
149
+ SEC_E_INTERNAL_ERROR = 0x80090304
150
+ SEC_E_INVALID_HANDLE = 0x80090301
151
+ SEC_E_INVALID_TOKEN = 0x80090308
152
+ SEC_E_LOGON_DENIED = 0x8009030C
153
+ SEC_E_NO_AUTHENTICATING_AUTHORITY = 0x80090311
154
+ SEC_E_NO_CREDENTIALS = 0x8009030E
155
+ SEC_E_TARGET_UNKNOWN = 0x80090303
156
+ SEC_E_UNSUPPORTED_FUNCTION = 0x80090302
157
+ SEC_E_WRONG_PRINCIPAL = 0x80090322
158
+
159
+ # These are generally returned by AcquireCredentialsHandle
160
+ SEC_E_NOT_OWNER = 0x80090306
161
+ SEC_E_SECPKG_NOT_FOUND = 0x80090305
162
+ SEC_E_UNKNOWN_CREDENTIALS = 0x8009030D
163
+
164
+ @@map = {}
165
+ constants.each { |v| @@map[self.const_get(v.to_s)] = v }
166
+
167
+ attr_reader :value
168
+
169
+ def initialize(value)
170
+ # convert to unsigned long
171
+ value = [value].pack("L").unpack("L").first
172
+ raise "#{value.to_s(16)} is not a recognized result" unless @@map.has_key? value
173
+ @value = value
174
+ end
175
+
176
+ def to_s
177
+ @@map[@value].to_s
178
+ end
179
+
180
+ def ok?
181
+ @value == SEC_I_CONTINUE_NEEDED || @value == SEC_E_OK
182
+ end
183
+
184
+ def ==(other)
185
+ if other.is_a?(SSPIResult)
186
+ @value == other.value
187
+ elsif other.is_a?(Fixnum)
188
+ @value == @@map[other]
189
+ else
190
+ false
191
+ end
192
+ end
193
+ end
194
+
195
+ # Handles "Negotiate" type authentication. Geared towards authenticating with a proxy server over HTTP
196
+ class NegotiateAuth
197
+ attr_accessor :credentials, :context, :contextAttributes, :user, :domain
198
+ REQUEST_FLAGS = ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONNECTION
199
+
200
+ # Given a connection and a request path, performs authentication as the current user and returns
201
+ # the response from a GET request. The connnection should be a Net::HTTP object, and it should
202
+ # have been constructed using the Net::HTTP.Proxy method, but anything that responds to "get" will work.
203
+ # If a user and domain are given, will authenticate as the given user.
204
+ # Returns the response received from the get method (usually Net::HTTPResponse)
205
+ def NegotiateAuth.proxy_auth_get(http, path, user = nil, domain = nil)
206
+ raise "http must respond to :get" unless http.respond_to?(:get)
207
+ nego_auth = self.new user, domain
208
+
209
+ resp = http.get path, { "Proxy-Authorization" => "Negotiate " + nego_auth.get_initial_token }
210
+ if resp["Proxy-Authenticate"]
211
+ resp = http.get path, { "Proxy-Authorization" => "Negotiate " + nego_auth.complete_authentication(resp["Proxy-Authenticate"].split(" ").last.strip) }
212
+ end
213
+
214
+ resp
215
+ end
216
+
217
+ # Creates a new instance ready for authentication as the given user in the given domain.
218
+ # Defaults to current user and domain as defined by ENV["USERDOMAIN"] and ENV["USERNAME"] if
219
+ # no arguments are supplied.
220
+ def initialize(user = nil, domain = nil)
221
+ if user.nil? && domain.nil? && ENV["USERNAME"].nil? && ENV["USERDOMAIN"].nil?
222
+ raise "A username or domain must be supplied since they cannot be retrieved from the environment"
223
+ end
224
+
225
+ @user = user || ENV["USERNAME"]
226
+ @domain = domain || ENV["USERDOMAIN"]
227
+ end
228
+
229
+ # Gets the initial Negotiate token. Returns it as a base64 encoded string suitable for use in HTTP. Can
230
+ # be easily decoded, however.
231
+ def get_initial_token
232
+ raise "This object is no longer usable because its resources have been freed." if @cleaned_up
233
+ get_credentials
234
+
235
+ outputBuffer = SecurityBuffer.new
236
+ @context = CtxtHandle.new
237
+ @contextAttributes = "\0" * 4
238
+
239
+ result = SSPIResult.new(API::InitializeSecurityContext.call(@credentials.to_p, nil, nil,
240
+ REQUEST_FLAGS,0, SECURITY_NETWORK_DREP, nil, 0, @context.to_p, outputBuffer.to_p, @contextAttributes, TimeStamp.new.to_p))
241
+
242
+ if result.ok? then
243
+ return encode_token(outputBuffer.token)
244
+ else
245
+ raise "Error: #{result.to_s}"
246
+ end
247
+ end
248
+
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.
251
+ # Does not indicate if SEC_I_CONTINUE or SEC_E_OK was returned.
252
+ # Token returned is Base64 encoded.
253
+ def complete_authentication(token)
254
+ 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.
257
+ if token.include? "Negotiate"
258
+ token = token.split(" ").last.strip
259
+ end
260
+ outputBuffer = SecurityBuffer.new
261
+
262
+ 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,
264
+ @context.to_p,
265
+ outputBuffer.to_p, @contextAttributes, TimeStamp.new.to_p))
266
+
267
+ if result.ok? then
268
+ return encode_token(outputBuffer.token)
269
+ else
270
+ raise "Error: #{result.to_s}"
271
+ end
272
+ ensure
273
+ # need to make sure we don't clean up if we've already cleaned up.
274
+ clean_up unless @cleaned_up
275
+ end
276
+
277
+ private
278
+
279
+ def clean_up
280
+ # free structures allocated
281
+ @cleaned_up = true
282
+ API::FreeCredentialsHandle.call(@credentials.to_p)
283
+ API::DeleteSecurityContext.call(@context.to_p)
284
+ @context = nil
285
+ @credentials = nil
286
+ @contextAttributes = nil
287
+ end
288
+
289
+ # Gets credentials based on user, domain or both. If both are nil, an error occurs
290
+ def get_credentials
291
+ @credentials = CredHandle.new
292
+ ts = TimeStamp.new
293
+ @identity = Identity.new @user, @domain
294
+ result = SSPIResult.new(API::AcquireCredentialsHandle.call(nil, "Negotiate", SECPKG_CRED_OUTBOUND, nil, @identity.to_p, nil, nil, @credentials.to_p, ts.to_p))
295
+ raise "Error acquire credentials: #{result}" unless result.ok?
296
+ end
297
+
298
+ def encode_token(t)
299
+ # encode64 will add newlines every 60 characters so we need to remove those.
300
+ Base64.encode64(t).gsub(/\n/, "")
301
+ end
302
+ end
303
+ end
@@ -0,0 +1,56 @@
1
+ require 'net/http'
2
+ require 'rubysspi'
3
+
4
+
5
+ module Net
6
+ # Replaces Net::HTTP.request to understand Negotate HTTP proxy authorization. Uses native Win32
7
+ # libraries to authentic as the current user (as defined by the ENV["USERNAME"] and ENV["USERDOMAIN"]
8
+ # environment variables.
9
+ class HTTP
10
+ def request(req, body = nil, &block) # :yield: +response+
11
+ unless started?
12
+ start {
13
+ req['connection'] ||= 'close'
14
+ return request(req, body, &block)
15
+ }
16
+ end
17
+ if proxy_user()
18
+ unless use_ssl?
19
+ req.proxy_basic_auth proxy_user(), proxy_pass()
20
+ end
21
+ end
22
+
23
+ req.set_body_internal body
24
+ begin_transport req
25
+ req.exec @socket, @curr_http_version, edit_path(req.path)
26
+ begin
27
+ res = HTTPResponse.read_new(@socket)
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")
31
+ n = SSPI::NegotiateAuth.new
32
+ res.reading_body(@socket, req.response_body_permitted?) { }
33
+ req["Proxy-Authorization"] = "Negotiate #{n.get_initial_token}"
34
+ req.exec @socket, @curr_http_version, edit_path(req.path)
35
+ begin
36
+ res = HTTPResponse.read_new(@socket)
37
+ end while res.kind_of?(HTTPContinue)
38
+
39
+ if res["Proxy-Authenticate"]
40
+ res.reading_body(@socket, req.response_body_permitted?) { }
41
+ req["Proxy-Authorization"] = "Negotiate #{n.complete_authentication res["Proxy-Authenticate"]}"
42
+ req.exec @socket, @curr_http_version, edit_path(req.path)
43
+ begin
44
+ res = HTTPResponse.read_new(@socket)
45
+ end while res.kind_of?(HTTPContinue)
46
+ end
47
+ end
48
+ res.reading_body(@socket, req.response_body_permitted?) {
49
+ yield res if block_given?
50
+ }
51
+ end_transport req, res
52
+
53
+ res
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,54 @@
1
+ require 'test/unit'
2
+ require 'net/http'
3
+ require 'pathname'
4
+ $: << (File.dirname(__FILE__) << "/../lib")
5
+ require 'rubysspi'
6
+
7
+ 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
+ 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)."
17
+
18
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
19
+ nego_auth = SSPI::NegotiateAuth.new
20
+ sr = http.request_get "/", { "Proxy-Authorization" => "Negotiate " + nego_auth.get_initial_token }
21
+ resp = http.get "/", { "Proxy-Authorization" => "Negotiate " + nego_auth.complete_authentication(sr["Proxy-Authenticate"].split(" ").last.strip) }
22
+ assert resp.code.to_i == 200, "Resposne code not as expected: #{resp.inspect}"
23
+ resp = http.get "/foobar.html"
24
+ assert resp.code.to_i == 404, "Response code not as expected: #{resp.inspect}"
25
+ end
26
+ end
27
+
28
+ 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)."
32
+
33
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
34
+ resp = SSPI::NegotiateAuth.proxy_auth_get http, "/"
35
+ assert resp.code.to_i == 200, "Response code not as expected: #{resp.inspect}"
36
+ end
37
+ end
38
+
39
+ 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)."
43
+
44
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
45
+ nego_auth = SSPI::NegotiateAuth.new
46
+ sr = http.request_get "/", { "Proxy-Authorization" => "Negotiate " + nego_auth.get_initial_token }
47
+ resp = http.get "/", { "Proxy-Authorization" => "Negotiate " + nego_auth.complete_authentication(sr["Proxy-Authenticate"].split(" ").last.strip) }
48
+ assert resp.code.to_i == 200, "Response code not as expected: #{resp.inspect}"
49
+ assert_raises(RuntimeError, "Should not be able to call complete_authentication again") do
50
+ nego_auth.complete_authentication "foo"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,23 @@
1
+ require 'test/unit'
2
+ require 'net/http'
3
+ require 'pathname'
4
+ $: << (File.dirname(__FILE__) << "/../lib")
5
+ require 'rubysspi/proxy_auth'
6
+
7
+ class NTLMTest < Test::Unit::TestCase
8
+ def setup
9
+ assert ENV["http_proxy"], "http_proxy must be set before running tests."
10
+ end
11
+
12
+ def test_net_http
13
+
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)."
17
+
18
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
19
+ resp = http.get("/")
20
+ assert resp.code.to_i == 200, "Did not get response from Google as expected."
21
+ end
22
+ end
23
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: rubysspi
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2006-07-14 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.
9
+ require_paths:
10
+ - lib
11
+ email: jgbailey @nospan@ gmail.com
12
+ homepage: http://rubyforge.org/projects/rubysspi/
13
+ rubyforge_project: http://rubyforge.org/projects/rubysspi/
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.
15
+ autorequire: rubysspi
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: i386-mswin32
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Justin Bailey
31
+ files:
32
+ - lib/rubysspi
33
+ - lib/rubysspi.rb
34
+ - lib/rubysspi/proxy_auth.rb
35
+ - test/ruby_sspi_test.rb
36
+ - test/test_net_http.rb
37
+ - README.txt
38
+ - Rakefile
39
+ test_files: []
40
+
41
+ rdoc_options:
42
+ - --title
43
+ - Ruby SSPI -- Win32 SSPI Bindings for Ruby
44
+ - --main
45
+ - README.txt
46
+ - --line-numbers
47
+ extra_rdoc_files:
48
+ - README.txt
49
+ executables: []
50
+
51
+ extensions: []
52
+
53
+ requirements: []
54
+
55
+ dependencies: []
56
+