winrm-s 0.3.6 → 0.3.7.dev

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,69 +1,69 @@
1
- winrm-s
2
- =======
3
-
4
- `winrm-s` extends the functionality of the
5
- [WinRM gem](http://rubygems.org/gems/winrm) to support the Microsoft
6
- [Negotiate protocol](http://msdn.microsoft.com/en-us/library/windows/desktop/aa378748(v=vs.85).aspx)
7
- when authenticating to a remote [WinRM](http://msdn.microsoft.com/en-us/library/aa384426(v=vs.85).aspx) endpoint from a Windows system.
8
-
9
- This extended functionality is **only** supported when running on Microsoft
10
- Windows. This gem can still be used on other operating systems just like the
11
- `WinRM` gem, but the extended capabilities will not be available.
12
-
13
- Installation
14
- ------------
15
-
16
- To install it, run:
17
-
18
- gem install winrm-s
19
-
20
- Usage
21
- -----
22
-
23
- `winrm-s` provides the same interface as the `winrm` gem -- see `winrm`
24
- [documentation](https://github.com/WinRb/WinRM/blob/master/README.md) for `winrm-s` usage.
25
-
26
- * To use it, simply require `winrm` or `winrm-s`, depending on whether your code
27
- is running on Windows. The extended negotiate protocol is only available if
28
- you include `winrm-s`, which will only work on Windows.
29
- * When you use WinRM::WinRMWebService.new, be sure to specify the
30
- `:sspinegotiate` parameter, along with a user name in the form `domain_name\user_name`
31
- for the user name in order to make use of negotiate protocol. If the user
32
- account is local to the remote system, you can use `.` for the domain. The
33
- example further on demonstrates the negotiate use case.
34
- * All other use cases enabled by the `winrm` gem are also supported.
35
-
36
- Example
37
- -------
38
- Note the argument value of `:sspinegotiate` for transport option, and the
39
- explicit specification of a domain name, in this case `.`, in the user name:
40
- ```ruby
41
- if RUBY_PLATFORM =~ /mswin|mingw32|windows/
42
- require 'winrm-s' # only works on Windows, otherwise use require 'winrm'
43
- endpoint = http://mywinrmhost:5985/wsman
44
- winrm = WinRM::WinRMWebService.new(endpoint, :sspinegotiate, :user => ".\administrator", :pass => "adminpasswd")
45
- winrm.cmd('ipconfig /all') do |stdout, stderr|
46
- STDOUT.print stdout
47
- STDERR.print stderr
48
- end
49
- end
50
- ```
51
-
52
- License
53
- -------
54
-
55
- Copyright:: Copyright (c) 2014 Chef Software, Inc.
56
- License:: Apache License, Version 2.0
57
-
58
- Licensed under the Apache License, Version 2.0 (the "License");
59
- you may not use this file except in compliance with the License.
60
- You may obtain a copy of the License at
61
-
62
- http://www.apache.org/licenses/LICENSE-2.0
63
-
64
- Unless required by applicable law or agreed to in writing, software
65
- distributed under the License is distributed on an "AS IS" BASIS,
66
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
67
- See the License for the specific language governing permissions and
68
- limitations under the License.
69
-
1
+ winrm-s
2
+ =======
3
+
4
+ `winrm-s` extends the functionality of the
5
+ [WinRM gem](http://rubygems.org/gems/winrm) to support the Microsoft
6
+ [Negotiate protocol](http://msdn.microsoft.com/en-us/library/windows/desktop/aa378748(v=vs.85).aspx)
7
+ when authenticating to a remote [WinRM](http://msdn.microsoft.com/en-us/library/aa384426(v=vs.85).aspx) endpoint from a Windows system.
8
+
9
+ This extended functionality is **only** supported when running on Microsoft
10
+ Windows. This gem can still be used on other operating systems just like the
11
+ `WinRM` gem, but the extended capabilities will not be available.
12
+
13
+ Installation
14
+ ------------
15
+
16
+ To install it, run:
17
+
18
+ gem install winrm-s
19
+
20
+ Usage
21
+ -----
22
+
23
+ `winrm-s` provides the same interface as the `winrm` gem -- see `winrm`
24
+ [documentation](https://github.com/WinRb/WinRM/blob/master/README.md) for `winrm-s` usage.
25
+
26
+ * To use it, simply require `winrm` or `winrm-s`, depending on whether your code
27
+ is running on Windows. The extended negotiate protocol is only available if
28
+ you include `winrm-s`, which will only work on Windows.
29
+ * When you use WinRM::WinRMWebService.new, be sure to specify the
30
+ `:sspinegotiate` parameter, along with a user name in the form `domain_name\user_name`
31
+ for the user name in order to make use of negotiate protocol. If the user
32
+ account is local to the remote system, you can use `.` for the domain. The
33
+ example further on demonstrates the negotiate use case.
34
+ * All other use cases enabled by the `winrm` gem are also supported.
35
+
36
+ Example
37
+ -------
38
+ Note the argument value of `:sspinegotiate` for transport option, and the
39
+ explicit specification of a domain name, in this case `.`, in the user name:
40
+ ```ruby
41
+ if RUBY_PLATFORM =~ /mswin|mingw32|windows/
42
+ require 'winrm-s' # only works on Windows, otherwise use require 'winrm'
43
+ endpoint = http://mywinrmhost:5985/wsman
44
+ winrm = WinRM::WinRMWebService.new(endpoint, :sspinegotiate, :user => ".\administrator", :pass => "adminpasswd")
45
+ winrm.cmd('ipconfig /all') do |stdout, stderr|
46
+ STDOUT.print stdout
47
+ STDERR.print stderr
48
+ end
49
+ end
50
+ ```
51
+
52
+ License
53
+ -------
54
+
55
+ Copyright:: Copyright (c) 2014 Chef Software, Inc.
56
+ License:: Apache License, Version 2.0
57
+
58
+ Licensed under the Apache License, Version 2.0 (the "License");
59
+ you may not use this file except in compliance with the License.
60
+ You may obtain a copy of the License at
61
+
62
+ http://www.apache.org/licenses/LICENSE-2.0
63
+
64
+ Unless required by applicable law or agreed to in writing, software
65
+ distributed under the License is distributed on an "AS IS" BASIS,
66
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
67
+ See the License for the specific language governing permissions and
68
+ limitations under the License.
69
+
data/Rakefile CHANGED
@@ -1,16 +1,16 @@
1
- require 'bundler'
2
- require 'rspec/core/rake_task'
3
-
4
- Bundler::GemHelper.install_tasks
5
-
6
- task :default => [:unit_spec]
7
-
8
- desc "Run all functional specs in spec directory"
9
- RSpec::Core::RakeTask.new(:functional_spec) do |t|
10
- t.pattern = 'spec/functional/**/*_spec.rb'
11
- end
12
-
13
- desc "Run all unit specs in spec directory"
14
- RSpec::Core::RakeTask.new(:unit_spec) do |t|
15
- t.pattern = 'spec/unit/**/*_spec.rb'
16
- end
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ task :default => [:unit_spec]
7
+
8
+ desc "Run all functional specs in spec directory"
9
+ RSpec::Core::RakeTask.new(:functional_spec) do |t|
10
+ t.pattern = 'spec/functional/**/*_spec.rb'
11
+ end
12
+
13
+ desc "Run all unit specs in spec directory"
14
+ RSpec::Core::RakeTask.new(:unit_spec) do |t|
15
+ t.pattern = 'spec/unit/**/*_spec.rb'
16
+ end
@@ -1,30 +1,30 @@
1
- #
2
- # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)>
3
- # Copyright:: Copyright (c) 2014 Chef Software, Inc.
4
- # License:: Apache License, Version 2.0
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
- #
18
-
19
- require 'winrm'
20
-
21
- def windows?
22
- !!(RUBY_PLATFORM =~ /mswin|mingw|windows/)
23
- end
24
-
25
- # Patch only on windows
26
- if windows?
27
- require 'winrm/winrm_service_patch'
28
- else
29
- raise "ERROR: winrm-s extensions to the winrm gem for the negotiate protocol are only supported on Windows. Require 'winrm' and not 'winrm-s' on non-Windows platforms."
30
- end
1
+ #
2
+ # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)>
3
+ # Copyright:: Copyright (c) 2014 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'winrm'
20
+
21
+ def windows?
22
+ !!(RUBY_PLATFORM =~ /mswin|mingw|windows/)
23
+ end
24
+
25
+ # Patch only on windows
26
+ if windows?
27
+ require 'winrm/winrm_service_patch'
28
+ else
29
+ raise "ERROR: winrm-s extensions to the winrm gem for the negotiate protocol are only supported on Windows. Require 'winrm' and not 'winrm-s' on non-Windows platforms."
30
+ end
@@ -1,22 +1,22 @@
1
- #
2
- # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
3
- # Copyright:: Copyright (c) 2014 Chef Software, Inc.
4
- # License:: Apache License, Version 2.0
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
- #
18
-
19
- module WinrmS
20
- VERSION = "0.3.6"
21
- MAJOR, MINOR, TINY = VERSION.split('.')
22
- end
1
+ #
2
+ # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
3
+ # Copyright:: Copyright (c) 2014 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ module WinrmS
20
+ VERSION = "0.3.7.dev"
21
+ MAJOR, MINOR, TINY = VERSION.split('.')
22
+ end
@@ -1,25 +1,25 @@
1
-
2
- module PatchAssertions
3
-
4
- def self.assert_major_version(library_name, expected_major, override_var_name)
5
- supported_major_ver = ENV[override_var_name] || expected_major
6
- loaded_ver = Gem.loaded_specs[library_name].version
7
- loaded_major_ver = loaded_ver.to_s.match(/(^\d+\.\d+)\.(.*)$/)[1]
8
-
9
- version_match = false
10
- Array(supported_major_ver).each do |version|
11
- if loaded_major_ver.to_s == version.to_s
12
- version_match = true
13
- end
14
- end
15
- unless version_match
16
- puts "WARNING: Unsupported version of #{library_name}. The supported major version of library is #{library_name} version #{expected_major}. This code path monkey patches few methods in #{library_name} to support additional features. This could possibly work, but it is advised to extensively test your version. If you are aware of the impact of using #{loaded_ver}, this warning can be disabled by setting #{override_var_name} to the major version #{loaded_major_ver}. "
17
- end
18
- end
19
-
20
- def self.assert_arity_of_patched_method(klass, method_name, expected_arity)
21
- if klass.instance_method(method_name).arity != expected_arity
22
- puts "WARNING: Cannot patch method #{klass}::#{method_name} since your latest gem seems to have different method definition that cannot be safely patched. This could possibly work, but it is advised to extensively test your version. Please use the supported version of patched gem."
23
- end
24
- end
1
+
2
+ module PatchAssertions
3
+
4
+ def self.assert_major_version(library_name, expected_major, override_var_name)
5
+ supported_major_ver = ENV[override_var_name] || expected_major
6
+ loaded_ver = Gem.loaded_specs[library_name].version
7
+ loaded_major_ver = loaded_ver.to_s.match(/(^\d+\.\d+)\.(.*)$/)[1]
8
+
9
+ version_match = false
10
+ Array(supported_major_ver).each do |version|
11
+ if loaded_major_ver.to_s == version.to_s
12
+ version_match = true
13
+ end
14
+ end
15
+ unless version_match
16
+ puts "WARNING: Unsupported version of #{library_name}. The supported major version of library is #{library_name} version #{expected_major}. This code path monkey patches few methods in #{library_name} to support additional features. This could possibly work, but it is advised to extensively test your version. If you are aware of the impact of using #{loaded_ver}, this warning can be disabled by setting #{override_var_name} to the major version #{loaded_major_ver}. "
17
+ end
18
+ end
19
+
20
+ def self.assert_arity_of_patched_method(klass, method_name, expected_arity)
21
+ if klass.instance_method(method_name).arity != expected_arity
22
+ puts "WARNING: Cannot patch method #{klass}::#{method_name} since your latest gem seems to have different method definition that cannot be safely patched. This could possibly work, but it is advised to extensively test your version. Please use the supported version of patched gem."
23
+ end
24
+ end
25
25
  end
@@ -1,196 +1,196 @@
1
- #
2
- # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
3
- # Copyright:: Copyright (c) 2014 Chef Software, Inc.
4
- # License:: Apache License, Version 2.0
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
- #
18
-
19
- require 'httpclient/auth'
20
-
21
- require 'winrm/helpers/assert_patch'
22
-
23
- def validate_patch
24
- # The code below patches the httpclient library to add support
25
- # for encrypt/decrypt as described below.
26
- # Add few restriction to make sure the patched methods are still
27
- # available, but still give a way to consciously use later versions
28
- PatchAssertions.assert_major_version("httpclient", [2.6,2.7], "USE_HTTPCLIENT_MAJOR")
29
- PatchAssertions.assert_arity_of_patched_method(HTTPClient::WWWAuth, "filter_request", 1)
30
- PatchAssertions.assert_arity_of_patched_method(HTTPClient::WWWAuth, "filter_response", 2)
31
- PatchAssertions.assert_arity_of_patched_method(HTTPClient::SSPINegotiateAuth, "set", -1)
32
- PatchAssertions.assert_arity_of_patched_method(HTTPClient::SSPINegotiateAuth, "get", 1)
33
- end
34
-
35
- # Perform the patch validations
36
- validate_patch
37
-
38
- # Overrides the HTTPClient::WWWAuth code to add support for encryption/decryption
39
- # of data sent during the NTLM auth over negotiate.
40
-
41
- # Also Overrides HTTPClient::SSPINegotiateAuth to remember user credentials, original
42
- # library code relies on the current login user credentials on the client machine.
43
-
44
- # Below code helps ruby client to perform auth using the credentials provided to
45
- # ruby client, and also enhances to use encrypted channel.
46
-
47
- class HTTPClient
48
-
49
- class WWWAuth
50
-
51
- # Filter API implementation. Traps HTTP request and insert
52
- # 'Authorization' header if needed.
53
- def filter_request(req)
54
- @authenticator.each do |auth|
55
- next unless auth.set? # hasn't be set, don't use it
56
- if cred = auth.get(req)
57
- auth.encrypt_payload(req) if auth.respond_to?(:encrypt_payload)
58
- req.header.set('Authorization', auth.scheme + " " + cred)
59
- return
60
- end
61
- end
62
- end
63
-
64
- # Filter API implementation. Traps HTTP response and parses
65
- # 'WWW-Authenticate' header.
66
- #
67
- # This remembers the challenges for all authentication methods
68
- # available to the client. On the subsequent retry of the request,
69
- # filter_request will select the strongest method.
70
- def filter_response(req, res)
71
- command = nil
72
- if res.status == HTTP::Status::UNAUTHORIZED
73
- if challenge = parse_authentication_header(res, 'www-authenticate')
74
- uri = req.header.request_uri
75
- challenge.each do |scheme, param_str|
76
- @authenticator.each do |auth|
77
- next unless auth.set? # hasn't be set, don't use it
78
- if scheme.downcase == auth.scheme.downcase
79
- challengeable = auth.challenge(uri, param_str)
80
- command = :retry if challengeable
81
- end
82
- end
83
- end
84
- # ignore unknown authentication scheme
85
- end
86
- else
87
- decrypted_content = res.content
88
- @authenticator.each do |auth|
89
- next unless auth.set? # hasn't be set, don't use it
90
- decrypted_content = auth.decrypt_payload(res.content) if auth.respond_to?(:encrypted_channel?) && auth.encrypted_channel? && encrypted_content?(res.content)
91
- end
92
- # update with decrypted content
93
- res.content.replace(decrypted_content) if res.content and !res.content.empty?
94
- end
95
- command
96
- end
97
-
98
- private
99
- def encrypted_content? content
100
- content.start_with?('--Encrypted ')
101
- end
102
- end
103
-
104
- class SSPINegotiateAuth
105
-
106
- begin
107
- require 'win32/sspi'
108
- SSPIEnabled = true
109
- rescue LoadError
110
- SSPIEnabled = false
111
- end
112
-
113
- begin
114
- require 'gssapi'
115
- GSSAPIEnabled = true
116
- rescue LoadError
117
- GSSAPIEnabled = false
118
- end
119
-
120
- # Override to remember creds
121
- # Set authentication credential.
122
- def set(uri, user, passwd)
123
- # Check if user has domain specified in it.
124
- if user
125
- creds = user.split("\\")
126
- creds.length.eql?(2) ? (@domain,@user = creds) : @user = creds[0]
127
- end
128
- @passwd = passwd
129
- end
130
-
131
- def set?
132
- SSPIEnabled || GSSAPIEnabled
133
- end
134
-
135
- # Response handler: returns credential.
136
- # See win32/sspi for negotiation state transition.
137
- def get(req)
138
- return nil unless SSPIEnabled || GSSAPIEnabled
139
- target_uri = req.header.request_uri
140
- domain_uri, param = @challenge.find { |uri, v|
141
- Util.uri_part_of(target_uri, uri)
142
- }
143
-
144
- return nil unless param
145
-
146
- state = param[:state]
147
- authenticator = param[:authenticator]
148
- authphrase = param[:authphrase]
149
- case state
150
- when :init
151
- if SSPIEnabled
152
- # Over-ride ruby win32 sspi to support encrypt/decrypt
153
- require 'winrm/win32/sspi'
154
- authenticator = param[:authenticator] = Win32::SSPI::NegotiateAuth.new(@user, @domain, @passwd)
155
- @authenticator = authenticator # **** Hacky remember as we need this for encrypt/decrypt
156
- return authenticator.get_initial_token
157
- else # use GSSAPI
158
- authenticator = param[:authenticator] = GSSAPI::Simple.new(domain_uri.host, 'HTTP')
159
- # Base64 encode the context token
160
- return [authenticator.init_context].pack('m').gsub(/\n/,'')
161
- end
162
- when :response
163
- @challenge.delete(domain_uri)
164
- if SSPIEnabled
165
- return authenticator.complete_authentication(authphrase)
166
- else # use GSSAPI
167
- return authenticator.init_context(authphrase.unpack('m').pop)
168
- end
169
- end
170
- nil
171
- end
172
-
173
- def encrypted_channel?
174
- @encrypted_channel
175
- end
176
-
177
- def encrypt_payload(req)
178
- if SSPIEnabled
179
- body = @authenticator.encrypt_payload(req.body)
180
- req.http_body = HTTP::Message::Body.new
181
- req.http_body.init_request(body)
182
- req.http_header.body_size = body.length if body
183
- # if body is encrypted update the header
184
- if body.include? "HTTP-SPNEGO-session-encrypted"
185
- @encrypted_channel = true
186
- req.header.set('Content-Type', "multipart/encrypted;protocol=\"application/HTTP-SPNEGO-session-encrypted\";boundary=\"Encrypted Boundary\"")
187
- end
188
- end
189
- end
190
-
191
- def decrypt_payload(body)
192
- body = @authenticator.decrypt_payload(body) if SSPIEnabled
193
- body
194
- end
195
- end
196
- end
1
+ #
2
+ # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
3
+ # Copyright:: Copyright (c) 2014 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'httpclient/auth'
20
+
21
+ require 'winrm/helpers/assert_patch'
22
+
23
+ def validate_patch
24
+ # The code below patches the httpclient library to add support
25
+ # for encrypt/decrypt as described below.
26
+ # Add few restriction to make sure the patched methods are still
27
+ # available, but still give a way to consciously use later versions
28
+ PatchAssertions.assert_major_version("httpclient", [2.6,2.7], "USE_HTTPCLIENT_MAJOR")
29
+ PatchAssertions.assert_arity_of_patched_method(HTTPClient::WWWAuth, "filter_request", 1)
30
+ PatchAssertions.assert_arity_of_patched_method(HTTPClient::WWWAuth, "filter_response", 2)
31
+ PatchAssertions.assert_arity_of_patched_method(HTTPClient::SSPINegotiateAuth, "set", -1)
32
+ PatchAssertions.assert_arity_of_patched_method(HTTPClient::SSPINegotiateAuth, "get", 1)
33
+ end
34
+
35
+ # Perform the patch validations
36
+ validate_patch
37
+
38
+ # Overrides the HTTPClient::WWWAuth code to add support for encryption/decryption
39
+ # of data sent during the NTLM auth over negotiate.
40
+
41
+ # Also Overrides HTTPClient::SSPINegotiateAuth to remember user credentials, original
42
+ # library code relies on the current login user credentials on the client machine.
43
+
44
+ # Below code helps ruby client to perform auth using the credentials provided to
45
+ # ruby client, and also enhances to use encrypted channel.
46
+
47
+ class HTTPClient
48
+
49
+ class WWWAuth
50
+
51
+ # Filter API implementation. Traps HTTP request and insert
52
+ # 'Authorization' header if needed.
53
+ def filter_request(req)
54
+ @authenticator.each do |auth|
55
+ next unless auth.set? # hasn't be set, don't use it
56
+ if cred = auth.get(req)
57
+ auth.encrypt_payload(req) if auth.respond_to?(:encrypt_payload)
58
+ req.header.set('Authorization', auth.scheme + " " + cred)
59
+ return
60
+ end
61
+ end
62
+ end
63
+
64
+ # Filter API implementation. Traps HTTP response and parses
65
+ # 'WWW-Authenticate' header.
66
+ #
67
+ # This remembers the challenges for all authentication methods
68
+ # available to the client. On the subsequent retry of the request,
69
+ # filter_request will select the strongest method.
70
+ def filter_response(req, res)
71
+ command = nil
72
+ if res.status == HTTP::Status::UNAUTHORIZED
73
+ if challenge = parse_authentication_header(res, 'www-authenticate')
74
+ uri = req.header.request_uri
75
+ challenge.each do |scheme, param_str|
76
+ @authenticator.each do |auth|
77
+ next unless auth.set? # hasn't be set, don't use it
78
+ if scheme.downcase == auth.scheme.downcase
79
+ challengeable = auth.challenge(uri, param_str)
80
+ command = :retry if challengeable
81
+ end
82
+ end
83
+ end
84
+ # ignore unknown authentication scheme
85
+ end
86
+ else
87
+ decrypted_content = res.content
88
+ @authenticator.each do |auth|
89
+ next unless auth.set? # hasn't be set, don't use it
90
+ decrypted_content = auth.decrypt_payload(res.content) if auth.respond_to?(:encrypted_channel?) && auth.encrypted_channel? && encrypted_content?(res.content)
91
+ end
92
+ # update with decrypted content
93
+ res.content.replace(decrypted_content) if res.content and !res.content.empty?
94
+ end
95
+ command
96
+ end
97
+
98
+ private
99
+ def encrypted_content? content
100
+ content.start_with?('--Encrypted ')
101
+ end
102
+ end
103
+
104
+ class SSPINegotiateAuth
105
+
106
+ begin
107
+ require 'win32/sspi'
108
+ SSPIEnabled = true
109
+ rescue LoadError
110
+ SSPIEnabled = false
111
+ end
112
+
113
+ begin
114
+ require 'gssapi'
115
+ GSSAPIEnabled = true
116
+ rescue LoadError
117
+ GSSAPIEnabled = false
118
+ end
119
+
120
+ # Override to remember creds
121
+ # Set authentication credential.
122
+ def set(uri, user, passwd)
123
+ # Check if user has domain specified in it.
124
+ if user
125
+ creds = user.split("\\")
126
+ creds.length.eql?(2) ? (@domain,@user = creds) : @user = creds[0]
127
+ end
128
+ @passwd = passwd
129
+ end
130
+
131
+ def set?
132
+ SSPIEnabled || GSSAPIEnabled
133
+ end
134
+
135
+ # Response handler: returns credential.
136
+ # See win32/sspi for negotiation state transition.
137
+ def get(req)
138
+ return nil unless SSPIEnabled || GSSAPIEnabled
139
+ target_uri = req.header.request_uri
140
+ domain_uri, param = @challenge.find { |uri, v|
141
+ Util.uri_part_of(target_uri, uri)
142
+ }
143
+
144
+ return nil unless param
145
+
146
+ state = param[:state]
147
+ authenticator = param[:authenticator]
148
+ authphrase = param[:authphrase]
149
+ case state
150
+ when :init
151
+ if SSPIEnabled
152
+ # Over-ride ruby win32 sspi to support encrypt/decrypt
153
+ require 'winrm/win32/sspi'
154
+ authenticator = param[:authenticator] = Win32::SSPI::NegotiateAuth.new(@user, @domain, @passwd)
155
+ @authenticator = authenticator # **** Hacky remember as we need this for encrypt/decrypt
156
+ return authenticator.get_initial_token
157
+ else # use GSSAPI
158
+ authenticator = param[:authenticator] = GSSAPI::Simple.new(domain_uri.host, 'HTTP')
159
+ # Base64 encode the context token
160
+ return [authenticator.init_context].pack('m').gsub(/\n/,'')
161
+ end
162
+ when :response
163
+ @challenge.delete(domain_uri)
164
+ if SSPIEnabled
165
+ return authenticator.complete_authentication(authphrase)
166
+ else # use GSSAPI
167
+ return authenticator.init_context(authphrase.unpack('m').pop)
168
+ end
169
+ end
170
+ nil
171
+ end
172
+
173
+ def encrypted_channel?
174
+ @encrypted_channel
175
+ end
176
+
177
+ def encrypt_payload(req)
178
+ if SSPIEnabled
179
+ body = @authenticator.encrypt_payload(req.body)
180
+ req.http_body = HTTP::Message::Body.new
181
+ req.http_body.init_request(body)
182
+ req.http_header.body_size = body.length if body
183
+ # if body is encrypted update the header
184
+ if body.include? "HTTP-SPNEGO-session-encrypted"
185
+ @encrypted_channel = true
186
+ req.header.set('Content-Type', "multipart/encrypted;protocol=\"application/HTTP-SPNEGO-session-encrypted\";boundary=\"Encrypted Boundary\"")
187
+ end
188
+ end
189
+ end
190
+
191
+ def decrypt_payload(body)
192
+ body = @authenticator.decrypt_payload(body) if SSPIEnabled
193
+ body
194
+ end
195
+ end
196
+ end