1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
|
require_relative '../common'
require 'net/ssh/transport/session'
require 'net/ssh/proxy/http'
require 'logger'
# mocha adds #verify to Object, which throws off the host-key-verifier part of
# these tests.
# can't use .include? because ruby18 uses strings and ruby19 uses symbols :/
Object.send(:undef_method, :verify) if Object.instance_methods.any? { |v| v.to_sym == :verify }
module Transport
class TestSession < NetSSHTest
include Net::SSH::Transport::Constants
TEST_HOST = "net.ssh.test"
TEST_PORT = 22
def test_constructor_defaults
assert_equal TEST_HOST, session.host
assert_equal TEST_PORT, session.port
assert_instance_of(
Net::SSH::Verifiers::AcceptNewOrLocalTunnel,
session.host_key_verifier
)
end
def test_verify_host_key_true_uses_accept_new_or_local_tunnel_verifier
Kernel.expects(:warn).with(
'verify_host_key: true is deprecated, use :accept_new_or_local_tunnel'
)
assert_instance_of(
Net::SSH::Verifiers::AcceptNewOrLocalTunnel,
session(verify_host_key: true).host_key_verifier
)
end
def test_verify_host_key_accept_new_or_local_tunnel_uses_accept_new_or_local_tunnel_verifier
assert_instance_of(
Net::SSH::Verifiers::AcceptNewOrLocalTunnel,
session(verify_host_key: :accept_new_or_local_tunnel).host_key_verifier
)
end
def test_verify_host_key_nil_uses_accept_new_or_local_tunnel_verifier
assert_instance_of(
Net::SSH::Verifiers::AcceptNewOrLocalTunnel,
session(verify_host_key: nil).host_key_verifier
)
end
def test_verify_host_key_very_uses_accept_new_verifier
Kernel.expects(:warn).with('verify_host_key: :very is deprecated, use :accept_new')
assert_instance_of(
Net::SSH::Verifiers::AcceptNew,
session(verify_host_key: :very).host_key_verifier
)
end
def test_verify_host_key_accept_new_uses_accept_new_verifier
assert_instance_of(
Net::SSH::Verifiers::AcceptNew,
session(verify_host_key: :accept_new).host_key_verifier
)
end
def test_verify_host_key_secure_uses_always_verifier
Kernel.expects(:warn).with('verify_host_key: :secure is deprecated, use :always')
assert_instance_of(
Net::SSH::Verifiers::Always,
session(verify_host_key: :secure).host_key_verifier
)
end
def test_verify_host_key_false_uses_never_verifier
Kernel.expects(:warn).with('verify_host_key: false is deprecated, use :never')
assert_instance_of(
Net::SSH::Verifiers::Never,
session(verify_host_key: false).host_key_verifier
)
end
def test_verify_host_key_null_uses_never_verifier
assert_instance_of(
Net::SSH::Verifiers::Never,
session(verify_host_key: :never).host_key_verifier
)
end
def test_unknown_verify_host_key_value_raises_exception_if_value_does_not_respond_to_verify
assert_raises(ArgumentError) { session(verify_host_key: :bogus).host_key_verifier }
end
def test_verify_host_key_value_responding_to_verify_should_pass_muster
object = stub("thingy", verify: true, verify_signature: true)
assert_equal object, session(verify_host_key: object).host_key_verifier
end
def test_deprecated_host_key_verifier
Kernel.expects(:warn).with('Warning: verifier without :verify_signature is deprecated')
object = stub("thingy", verify: true)
assert_not_nil session(verify_host_key: object).host_key_verifier
end
def test_host_as_string_should_return_host_and_ip_when_port_is_default
session!
socket.stubs(:peer_ip).returns("1.2.3.4")
assert_equal "#{TEST_HOST},1.2.3.4", session.host_as_string
end
def test_host_as_string_should_return_host_and_ip_with_port_when_port_is_not_default
session(port: 1234) # force session to be instantiated
socket.stubs(:peer_ip).returns("1.2.3.4")
assert_equal "[#{TEST_HOST}]:1234,[1.2.3.4]:1234", session.host_as_string
end
def test_host_as_string_should_return_only_host_when_host_is_ip
session!(host: "1.2.3.4")
socket.stubs(:peer_ip).returns("1.2.3.4")
assert_equal "1.2.3.4", session.host_as_string
end
def test_host_as_string_should_return_only_host_and_port_when_host_is_ip_and_port_is_not_default
session!(host: "1.2.3.4", port: 1234)
socket.stubs(:peer_ip).returns("1.2.3.4")
assert_equal "[1.2.3.4]:1234", session.host_as_string
end
def test_host_as_string_should_return_only_host_when_proxy_command_is_set
session!(host: "1.2.3.4")
socket.stubs(:peer_ip).returns(Net::SSH::Transport::PacketStream::PROXY_COMMAND_HOST_IP)
assert_equal "1.2.3.4", session.host_as_string
end
def test_host_as_string_should_return_only_host_and_port_when_host_is_ip_and_port_is_not_default_and_proxy_command_is_set
session!(host: "1.2.3.4", port: 1234)
socket.stubs(:peer_ip).returns(Net::SSH::Transport::PacketStream::PROXY_COMMAND_HOST_IP)
assert_equal "[1.2.3.4]:1234", session.host_as_string
end
def test_close_should_cleanup_and_close_socket
session!
socket.expects(:cleanup)
socket.expects(:close)
session.close
end
def test_service_request_should_return_buffer
assert_equal "\005\000\000\000\004sftp", session.service_request('sftp').to_s
end
def test_rekey_when_kex_is_pending_should_do_nothing
algorithms.stubs(pending?: true)
algorithms.expects(:rekey!).never
session.rekey!
end
def test_rekey_when_no_kex_is_pending_should_initiate_rekey_and_block_until_it_completes
algorithms.stubs(pending?: false)
algorithms.expects(:rekey!)
session.expects(:wait).yields
algorithms.expects(:initialized?).returns(true)
session.rekey!
end
def test_rekey_as_needed_when_kex_is_pending_should_do_nothing
session!
algorithms.stubs(pending?: true)
socket.expects(:if_needs_rekey?).never
session.rekey_as_needed
end
def test_rekey_as_needed_when_no_kex_is_pending_and_no_rekey_is_needed_should_do_nothing
session!
algorithms.stubs(pending?: false)
socket.stubs(if_needs_rekey?: false)
session.expects(:rekey!).never
session.rekey_as_needed
end
def test_rekey_as_needed_when_no_kex_is_pending_and_rekey_is_needed_should_initiate_rekey_and_block
session!
algorithms.stubs(pending?: false)
socket.expects(:if_needs_rekey?).yields
session.expects(:rekey!)
session.rekey_as_needed
end
def test_peer_should_return_hash_of_info_about_peer
session!
socket.stubs(peer_ip: "1.2.3.4")
assert_equal({ ip: "1.2.3.4", port: TEST_PORT, host: TEST_HOST, canonized: "net.ssh.test,1.2.3.4" }, session.peer)
end
def test_next_message_should_block_until_next_message_is_available
session.expects(:poll_message).with(:block)
session.next_message
end
def test_poll_message_should_query_next_packet_using_the_given_blocking_parameter
session!
socket.expects(:next_packet).with(:blocking_parameter, nil).returns(nil)
session.poll_message(:blocking_parameter)
end
def test_poll_message_should_query_next_packet_using_the_timeout_option
session!(timeout: 7)
socket.expects(:next_packet).with(:nonblock, 7).returns(nil)
session.poll_message
end
def test_poll_message_should_default_to_non_blocking
session!
socket.expects(:next_packet).with(:nonblock, nil).returns(nil)
session.poll_message
end
def test_poll_message_should_silently_handle_disconnect_packets
session!
socket.expects(:next_packet).returns(P(:byte, DISCONNECT, :long, 1, :string, "testing", :string, ""))
assert_raises(Net::SSH::Disconnect) { session.poll_message }
end
def test_poll_message_should_silently_handle_ignore_packets
session!
socket.expects(:next_packet).times(2).returns(P(:byte, IGNORE, :string, "test"), nil)
assert_nil session.poll_message
end
def test_poll_message_should_silently_handle_unimplemented_packets
session!
socket.expects(:next_packet).times(2).returns(P(:byte, UNIMPLEMENTED, :long, 15), nil)
assert_nil session.poll_message
end
def test_poll_message_should_silently_handle_debug_packets_with_always_display
session!
socket.expects(:next_packet).times(2).returns(P(:byte, DEBUG, :bool, true, :string, "testing", :string, ""), nil)
assert_nil session.poll_message
end
def test_poll_message_should_silently_handle_debug_packets_without_always_display
session!
socket.expects(:next_packet).times(2).returns(P(:byte, DEBUG, :bool, false, :string, "testing", :string, ""), nil)
assert_nil session.poll_message
end
def test_poll_message_should_silently_handle_kexinit_packets
session!
packet = P(:byte, KEXINIT, :raw, "lasdfalksdjfa;slkdfja;slkfjsdfaklsjdfa;df")
socket.expects(:next_packet).times(2).returns(packet, nil)
algorithms.expects(:accept_kexinit).with(packet)
assert_nil session.poll_message
end
def test_poll_message_should_return_other_packets
session!
packet = P(:byte, SERVICE_ACCEPT, :string, "test")
socket.expects(:next_packet).returns(packet)
assert_equal packet, session.poll_message
end
def test_poll_message_should_enqueue_packets_when_algorithm_disallows_packet
session!
packet = P(:byte, SERVICE_ACCEPT, :string, "test")
algorithms.stubs(:allow?).with(packet).returns(false)
socket.expects(:next_packet).times(2).returns(packet, nil)
assert_nil session.poll_message
assert_equal [packet], session.queue
end
def test_poll_message_should_read_from_queue_when_next_in_queue_is_allowed_and_consume_queue_is_true
session!
packet = P(:byte, SERVICE_ACCEPT, :string, "test")
session.push(packet)
socket.expects(:next_packet).never
assert_equal packet, session.poll_message
assert session.queue.empty?
end
def test_poll_message_should_not_read_from_queue_when_next_in_queue_is_not_allowed
session!
packet = P(:byte, SERVICE_ACCEPT, :string, "test")
algorithms.stubs(:allow?).with(packet).returns(false)
session.push(packet)
socket.expects(:next_packet).returns(nil)
assert_nil session.poll_message
assert_equal [packet], session.queue
end
def test_poll_message_should_not_read_from_queue_when_consume_queue_is_false
session!
packet = P(:byte, SERVICE_ACCEPT, :string, "test")
session.push(packet)
socket.expects(:next_packet).returns(nil)
assert_nil session.poll_message(:nonblock, false)
assert_equal [packet], session.queue
end
def test_wait_with_block_should_return_immediately_if_block_returns_truth
session.expects(:poll_message).never
session.wait { true }
end
def test_wait_should_not_consume_queue_on_reads
n = 0
session.expects(:poll_message).with(:nonblock, false).returns(nil)
session.wait { (n += 1) > 1 }
end
def test_wait_without_block_should_return_after_first_read
session.expects(:poll_message).returns(nil)
session.wait
end
def test_wait_should_enqueue_packets
session!
p1 = P(:byte, SERVICE_REQUEST, :string, "test")
p2 = P(:byte, SERVICE_ACCEPT, :string, "test")
socket.expects(:next_packet).times(2).returns(p1, p2)
n = 0
session.wait { (n += 1) > 2 }
assert_equal [p1, p2], session.queue
end
def test_push_should_enqueue_packet
packet = P(:byte, SERVICE_ACCEPT, :string, "test")
session.push(packet)
assert_equal [packet], session.queue
end
def test_send_message_should_delegate_to_socket
session!
packet = P(:byte, SERVICE_ACCEPT, :string, "test")
socket.expects(:send_packet).with(packet)
session.send_message(packet)
end
def test_enqueue_message_should_delegate_to_socket
session!
packet = P(:byte, SERVICE_ACCEPT, :string, "test")
socket.expects(:enqueue_packet).with(packet)
session.enqueue_message(packet)
end
def test_configure_client_should_pass_options_to_socket_client_state
session.configure_client compression: :standard
assert_equal :standard, socket.client.compression
end
def test_configure_server_should_pass_options_to_socket_server_state
session.configure_server compression: :standard
assert_equal :standard, socket.server.compression
end
def test_hint_should_set_hint_on_socket
assert !socket.hints[:authenticated]
session.hint :authenticated
assert socket.hints[:authenticated]
end
class TestLogger < Logger
def initialize
@strio = StringIO.new
super(@strio)
end
def messages
@strio.string
end
end
def test_log_correct_debug_with_proxy
logger = TestLogger.new
proxy = Net::SSH::Proxy::HTTP.new("")
session!(logger: logger, proxy: proxy)
assert_match "establishing connection to #{TEST_HOST}:#{TEST_PORT} through proxy", logger.messages
end
def test_log_correct_debug_without_proxy
logger = TestLogger.new
session!(logger: logger)
assert_match "establishing connection to #{TEST_HOST}:#{TEST_PORT}", logger.messages
end
private
def socket
@socket ||= stub("socket", hints: {})
end
def server_version
@server_version ||= stub("server_version")
end
def algorithms
@algorithms ||= stub("algorithms", initialized?: true, allow?: true, start: true)
end
def session(options = {})
@session ||= begin
host = options.delete(:host) || TEST_HOST
if (proxy = options[:proxy])
proxy.stubs("open").returns(socket)
else
Socket.stubs(:tcp).with(host, options[:port] || TEST_PORT, nil, nil, { connect_timeout: options[:timeout] }).returns(socket)
end
Net::SSH::Transport::ServerVersion.stubs(:new).returns(server_version)
Net::SSH::Transport::Algorithms.stubs(:new).returns(algorithms)
Net::SSH::Transport::Session.new(host, options)
end
end
# a simple alias to make the tests more self-documenting. the bang
# version makes it look more like the session is being instantiated
alias session! session
end
end
|