idrac 0.1.38 → 0.1.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,717 @@
1
+ require 'faraday'
2
+ require 'base64'
3
+ require 'json'
4
+ require 'colorize'
5
+ require 'uri'
6
+ require 'logger'
7
+ require 'socket'
8
+
9
+ module IDRAC
10
+ class Session
11
+ attr_reader :host, :username, :password, :port, :use_ssl, :verify_ssl,
12
+ :x_auth_token, :session_location, :direct_mode, :auto_delete_sessions
13
+ attr_accessor :verbosity
14
+
15
+ include Debuggable
16
+
17
+ def initialize(client)
18
+ @client = client
19
+ @host = client.host
20
+ @username = client.username
21
+ @password = client.password
22
+ @port = client.port
23
+ @use_ssl = client.use_ssl
24
+ @verify_ssl = client.verify_ssl
25
+ @x_auth_token = nil
26
+ @session_location = nil
27
+ @direct_mode = client.direct_mode
28
+ @sessions_maxed = false
29
+ @auto_delete_sessions = client.auto_delete_sessions
30
+ @verbosity = client.respond_to?(:verbosity) ? client.verbosity : 0
31
+ end
32
+
33
+ def connection
34
+ @connection ||= Faraday.new(url: base_url, ssl: {
35
+ verify: verify_ssl
36
+ # Keep SSL settings minimal for cross-version compatibility
37
+ }) do |faraday|
38
+ faraday.request :multipart
39
+ faraday.request :url_encoded
40
+ faraday.adapter Faraday.default_adapter
41
+ # Add request/response logging
42
+ if @verbosity > 0
43
+ faraday.response :logger, Logger.new(STDOUT), bodies: @verbosity >= 2 do |logger|
44
+ logger.filter(/(Authorization: Basic )([^,\n]+)/, '\1[FILTERED]')
45
+ logger.filter(/(Password"=>"?)([^,"]+)/, '\1[FILTERED]')
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ # Force clear all sessions by directly using Basic Auth
52
+ def force_clear_sessions
53
+ debug "Attempting to force clear all sessions...", 0
54
+
55
+ max_retries = 3
56
+ retry_count = 0
57
+
58
+ while retry_count < max_retries
59
+ if delete_all_sessions_with_basic_auth
60
+ debug "Successfully cleared sessions using Basic Auth", 0, :green
61
+ return true
62
+ else
63
+ retry_count += 1
64
+ if retry_count < max_retries
65
+ # Exponential backoff
66
+ sleep_time = 2 ** retry_count
67
+ debug "Retrying session clear after #{sleep_time} seconds (attempt #{retry_count+1}/#{max_retries})", 0, :light_yellow
68
+ sleep(sleep_time)
69
+ else
70
+ debug "Failed to clear sessions after #{max_retries} attempts", 0, :red
71
+ return false
72
+ end
73
+ end
74
+ end
75
+
76
+ false
77
+ end
78
+
79
+ # Delete all sessions using Basic Authentication
80
+ def delete_all_sessions_with_basic_auth
81
+ debug "Attempting to delete all sessions using Basic Authentication...", 0
82
+
83
+ # First, get the list of sessions
84
+ sessions_url = determine_session_endpoint
85
+
86
+ begin
87
+ # Get the list of sessions
88
+ response = request_with_basic_auth(:get, sessions_url, nil, 'application/json')
89
+
90
+ if response.status != 200
91
+ debug "Failed to get sessions list: #{response.status} - #{response.body}", 0, :red
92
+ # If we received HTML error, assume we can't get sessions and try direct session deletion
93
+ if response.headers['content-type']&.include?('text/html') || response.body.to_s.include?('DOCTYPE html')
94
+ debug "Received HTML error response, trying direct session deletion", 0, :light_yellow
95
+ return try_delete_latest_sessions
96
+ end
97
+ return false
98
+ end
99
+
100
+ # Parse the response to get session IDs
101
+ begin
102
+ sessions_data = JSON.parse(response.body)
103
+
104
+ if sessions_data['Members'] && sessions_data['Members'].any?
105
+ debug "Found #{sessions_data['Members'].count} active sessions", 0, :light_yellow
106
+
107
+ # Delete each session
108
+ success = true
109
+ sessions_data['Members'].each do |session|
110
+ session_url = session['@odata.id']
111
+
112
+ # Skip if no URL
113
+ next unless session_url
114
+
115
+ # Delete the session
116
+ delete_response = request_with_basic_auth(:delete, session_url, nil, 'application/json')
117
+
118
+ if delete_response.status == 200 || delete_response.status == 204
119
+ debug "Successfully deleted session: #{session_url}", 1, :green
120
+ else
121
+ debug "Failed to delete session #{session_url}: #{delete_response.status}", 0, :red
122
+ success = false
123
+ end
124
+
125
+ # Small delay between deletions
126
+ sleep(1)
127
+ end
128
+
129
+ return success
130
+ else
131
+ debug "No active sessions found", 0, :light_yellow
132
+ return true
133
+ end
134
+ rescue JSON::ParserError => e
135
+ debug "Error parsing sessions response: #{e.message}", 0, :red
136
+ debug "Trying direct session deletion", 0, :light_yellow
137
+ return try_delete_latest_sessions
138
+ end
139
+ rescue => e
140
+ debug "Error during session deletion with Basic Auth: #{e.message}", 0, :red
141
+ debug "Trying direct session deletion", 0, :light_yellow
142
+ return try_delete_latest_sessions
143
+ end
144
+ end
145
+
146
+ # Try to delete sessions by direct URL when we can't list sessions
147
+ def try_delete_latest_sessions
148
+ # Try to delete sessions by direct URL when we can't list sessions
149
+ debug "Attempting to delete recent sessions directly...", 0
150
+ base_url = determine_session_endpoint
151
+ success = false
152
+
153
+ # Try session IDs 1-10 (common for iDRAC)
154
+ (1..10).each do |id|
155
+ session_url = "#{base_url}/#{id}"
156
+ begin
157
+ delete_response = request_with_basic_auth(:delete, session_url, nil, 'application/json')
158
+
159
+ if delete_response.status == 200 || delete_response.status == 204
160
+ debug "Successfully deleted session: #{session_url}", 1, :green
161
+ success = true
162
+ else
163
+ debug "Failed to delete session #{session_url}: #{delete_response.status}", 1, :red
164
+ end
165
+ rescue => e
166
+ debug "Error deleting session #{session_url}: #{e.message}", 1, :red
167
+ end
168
+
169
+ # Small delay between deletions
170
+ sleep(0.5)
171
+ end
172
+
173
+ return success
174
+ end
175
+
176
+ # Create a Redfish session
177
+ def create
178
+ # Skip if we're in direct mode
179
+ if @direct_mode
180
+ debug "Skipping Redfish session creation (direct mode)", 0, :light_yellow
181
+ return false
182
+ end
183
+
184
+ # Determine the correct session endpoint based on Redfish version
185
+ session_endpoint = determine_session_endpoint
186
+
187
+ payload = { "UserName" => username, "Password" => password }
188
+
189
+ debug "Attempting to create Redfish session at #{base_url}#{session_endpoint}", 0
190
+ debug "SSL verification: #{verify_ssl ? 'Enabled' : 'Disabled'}", 1
191
+ print_connection_debug_info if @verbosity >= 2
192
+
193
+ # Try creation methods in sequence
194
+ return true if create_session_with_content_type(session_endpoint, payload)
195
+ return true if create_session_with_basic_auth(session_endpoint, payload)
196
+ return true if handle_max_sessions_and_retry(session_endpoint, payload)
197
+ return true if create_session_with_form_urlencoded(session_endpoint, payload)
198
+
199
+ # If all attempts fail, switch to direct mode
200
+ @direct_mode = true
201
+ false
202
+ end
203
+
204
+ # Delete the Redfish session
205
+ def delete
206
+ return unless @x_auth_token && @session_location
207
+
208
+ begin
209
+ debug "Deleting Redfish session...", 0
210
+
211
+ # Use the X-Auth-Token for authentication
212
+ headers = { 'X-Auth-Token' => @x_auth_token }
213
+
214
+ response = connection.delete(@session_location) do |req|
215
+ req.headers.merge!(headers)
216
+ end
217
+
218
+ if response.status == 200 || response.status == 204
219
+ debug "Redfish session deleted successfully", 0, :green
220
+ @x_auth_token = nil
221
+ @session_location = nil
222
+ return true
223
+ else
224
+ debug "Failed to delete Redfish session: #{response.status} - #{response.body}", 0, :red
225
+ return false
226
+ end
227
+ rescue => e
228
+ debug "Error during Redfish session deletion: #{e.message}", 0, :red
229
+ return false
230
+ end
231
+ end
232
+
233
+ private
234
+
235
+ def base_url
236
+ protocol = use_ssl ? 'https' : 'http'
237
+ "#{protocol}://#{host}:#{port}"
238
+ end
239
+
240
+ def print_connection_debug_info
241
+ begin
242
+ debug "=== Connection Debug Info ===", 2, :yellow
243
+ debug "Host: #{host}, Port: #{port}, SSL: #{use_ssl}", 2
244
+ debug "Ruby version: #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}", 2
245
+
246
+ begin
247
+ debug "OpenSSL version: #{OpenSSL::OPENSSL_VERSION}", 2
248
+ rescue => e
249
+ debug "Could not determine OpenSSL version: #{e.message}", 2
250
+ end
251
+
252
+ # Test basic TCP connection first
253
+ begin
254
+ socket = TCPSocket.new(host, port)
255
+ debug "TCP connection successful", 2, :green
256
+ socket.close
257
+ rescue => e
258
+ debug "TCP connection failed: #{e.class.name}: #{e.message}", 2, :red
259
+ debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
260
+ end
261
+
262
+ # Try SSL connection if using SSL
263
+ if use_ssl
264
+ begin
265
+ tcp_client = TCPSocket.new(host, port)
266
+ ssl_context = OpenSSL::SSL::SSLContext.new
267
+ ssl_context.verify_mode = verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
268
+ ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_client, ssl_context)
269
+ ssl_client.connect
270
+ debug "SSL connection successful", 2, :green
271
+ debug "SSL protocol: #{ssl_client.ssl_version}", 2
272
+ debug "SSL cipher: #{ssl_client.cipher.join(', ')}", 2
273
+
274
+ if @verbosity >= 3
275
+ cert = ssl_client.peer_cert
276
+ if cert
277
+ debug "Server certificate:", 3
278
+ debug " Subject: #{cert.subject}", 3
279
+ debug " Issuer: #{cert.issuer}", 3
280
+ debug " Validity: #{cert.not_before} to #{cert.not_after}", 3
281
+ debug " Fingerprint: #{OpenSSL::Digest::SHA256.new(cert.to_der).to_s}", 3
282
+ else
283
+ debug "No server certificate available", 3, :yellow
284
+ end
285
+ end
286
+
287
+ ssl_client.close
288
+ tcp_client.close
289
+ rescue => e
290
+ debug "SSL connection failed: #{e.class.name}: #{e.message}", 2, :red
291
+ debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
292
+ end
293
+ end
294
+ debug "===========================", 2, :yellow
295
+ rescue => e
296
+ debug "Error during connection debugging: #{e.class.name}: #{e.message}", 2, :red
297
+ debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
298
+ end
299
+ end
300
+
301
+ def basic_auth_headers(content_type = 'application/json')
302
+ {
303
+ 'Authorization' => "Basic #{Base64.strict_encode64("#{username}:#{password}")}",
304
+ 'Content-Type' => content_type
305
+ }
306
+ end
307
+
308
+ def request_with_basic_auth(method, url, body = nil, content_type = 'application/json')
309
+ debug "Basic Auth request: #{method.to_s.upcase} #{url}", 1
310
+ debug "Request body size: #{body.to_s.size} bytes", 2 if body
311
+
312
+ connection.send(method, url) do |req|
313
+ req.headers.merge!(basic_auth_headers(content_type))
314
+ req.body = body if body
315
+ debug "Request headers: #{req.headers.reject { |k,v| k =~ /auth/i }.to_json}", 2
316
+ end
317
+ rescue Faraday::SSLError => e
318
+ debug "SSL Error in Basic Auth request: #{e.message}", 0, :red
319
+ debug "OpenSSL version: #{OpenSSL::OPENSSL_VERSION}", 1
320
+ debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
321
+ raise e
322
+ rescue => e
323
+ debug "Error during #{method} request with Basic Auth: #{e.class.name}: #{e.message}", 0, :red
324
+ debug e.backtrace.join("\n"), 2 if e.backtrace && @verbosity >= 2
325
+ raise e
326
+ end
327
+
328
+ def process_session_response(response)
329
+ if response.status == 201 || response.status == 200
330
+ @x_auth_token = response.headers['X-Auth-Token']
331
+ @session_location = response.headers['Location']
332
+ @sessions_maxed = false
333
+ true
334
+ else
335
+ false
336
+ end
337
+ end
338
+
339
+ def create_session_with_content_type(url, payload)
340
+ begin
341
+ debug "Creating session with Content-Type: application/json", 0
342
+
343
+ response = connection.post(url) do |req|
344
+ req.headers['Content-Type'] = 'application/json'
345
+ req.headers['Accept'] = 'application/json'
346
+ req.body = payload.to_json
347
+ debug "Request headers: #{req.headers.reject { |k,v| k =~ /auth/i }.to_json}", 2
348
+ debug "Request body: #{req.body}", 2
349
+ end
350
+
351
+ debug "Response status: #{response.status}", 1
352
+ debug "Response headers: #{response.headers.to_json}", 2
353
+ debug "Response body: #{response.body}", 2
354
+
355
+ if response.status == 405
356
+ debug "405 Method Not Allowed: Check if the endpoint supports POST requests and verify the request format.", 0, :red
357
+ return false
358
+ end
359
+
360
+ if process_session_response(response)
361
+ debug "Redfish session created successfully", 0, :green
362
+ return true
363
+ end
364
+
365
+ # If the response status is 415 (Unsupported Media Type), try with different Content-Type
366
+ if response.status == 415 || (response.body.to_s.include?("unsupported media type"))
367
+ debug "415 Unsupported Media Type, trying alternate content type", 0, :yellow
368
+
369
+ # Try with no content-type header, just the payload
370
+ alt_response = connection.post(url) do |req|
371
+ # No Content-Type header
372
+ req.headers['Accept'] = '*/*'
373
+ req.body = payload.to_json
374
+ end
375
+
376
+ if process_session_response(alt_response)
377
+ debug "Redfish session created successfully with alternate content type", 0, :green
378
+ return true
379
+ end
380
+ end
381
+ rescue Faraday::SSLError => e
382
+ debug "SSL Error: #{e.message}", 0, :red
383
+ debug "OpenSSL version: #{OpenSSL::OPENSSL_VERSION}", 1
384
+ debug "Connection URL: #{base_url}#{url}", 1
385
+ debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
386
+ return false
387
+ rescue => e
388
+ debug "First session creation attempt failed: #{e.class.name}: #{e.message}", 0, :light_red
389
+ debug e.backtrace.join("\n"), 2 if e.backtrace && @verbosity >= 2
390
+ end
391
+ false
392
+ end
393
+
394
+ def create_session_with_basic_auth(url, payload)
395
+ begin
396
+ debug "Creating session with Basic Auth", 0
397
+
398
+ # Try first with JSON format
399
+ response = request_with_basic_auth(:post, url, payload.to_json, 'application/json')
400
+
401
+ debug "Response status: #{response.status}", 1
402
+ debug "Response body size: #{response.body.to_s.size} bytes", 2
403
+
404
+ if @verbosity >= 2 || response.status >= 400
405
+ debug "Response body (first 500 chars): #{response.body.to_s[0..500]}", 2
406
+ end
407
+
408
+ if process_session_response(response)
409
+ debug "Redfish session created successfully with Basic Auth (JSON)", 0, :green
410
+ return true
411
+ end
412
+
413
+ # If that fails, try with form-urlencoded
414
+ if response.status == 415 || (response.body.to_s.include?("unsupported media type"))
415
+ debug "415 Unsupported Media Type with JSON, trying form-urlencoded", 0, :yellow
416
+
417
+ form_data = "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}"
418
+ form_response = request_with_basic_auth(:post, url, form_data, 'application/x-www-form-urlencoded')
419
+
420
+ if process_session_response(form_response)
421
+ debug "Redfish session created successfully with Basic Auth (form-urlencoded)", 0, :green
422
+ return true
423
+ elsif form_response.status == 400
424
+ # Check for maximum sessions error
425
+ if (form_response.body.include?("maximum number of user sessions") ||
426
+ form_response.body.include?("RAC0218") ||
427
+ form_response.body.include?("Internal Server Error"))
428
+ debug "Maximum sessions reached detected during session creation", 0, :light_red
429
+ @sessions_maxed = true
430
+ return false
431
+ end
432
+ end
433
+ elsif response.status == 400
434
+ # Check for maximum sessions error
435
+ if (response.body.include?("maximum number of user sessions") ||
436
+ response.body.include?("RAC0218") ||
437
+ response.body.include?("Internal Server Error"))
438
+ debug "Maximum sessions reached detected during session creation", 0, :light_red
439
+ @sessions_maxed = true
440
+ return false
441
+ end
442
+ end
443
+
444
+ # Try one more approach with no Content-Type header
445
+ debug "Trying Basic Auth with no Content-Type header", 0, :yellow
446
+ no_content_type_response = connection.post(url) do |req|
447
+ req.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
448
+ req.headers['Accept'] = '*/*'
449
+ req.body = payload.to_json
450
+ end
451
+
452
+ if process_session_response(no_content_type_response)
453
+ debug "Redfish session created successfully with Basic Auth (no content type)", 0, :green
454
+ return true
455
+ end
456
+
457
+ debug "Failed to create Redfish session: #{response.status} - #{response.body}", 0, :red
458
+ return false
459
+ rescue Faraday::SSLError => e
460
+ debug "SSL Error in Basic Auth request: #{e.message}", 0, :red
461
+ debug "OpenSSL version: #{OpenSSL::OPENSSL_VERSION}", 1
462
+ debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
463
+ return false
464
+ rescue => e
465
+ debug "Error during Redfish session creation with Basic Auth: #{e.class.name}: #{e.message}", 0, :red
466
+ debug e.backtrace.join("\n"), 2 if e.backtrace && @verbosity >= 2
467
+ return false
468
+ end
469
+ end
470
+
471
+ def handle_max_sessions_and_retry(url, payload)
472
+ return false unless @sessions_maxed
473
+
474
+ debug "Maximum sessions reached, attempting to clear sessions", 0
475
+ if @auto_delete_sessions
476
+ if force_clear_sessions
477
+ debug "Successfully cleared sessions, trying to create a new session", 0, :green
478
+
479
+ # Give the iDRAC a moment to process the session deletions
480
+ sleep(3)
481
+
482
+ # Try one more time after clearing with form-urlencoded
483
+ begin
484
+ response = connection.post(url) do |req|
485
+ req.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
486
+ req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
487
+ req.body = "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}"
488
+ end
489
+
490
+ if process_session_response(response)
491
+ debug "Redfish session created successfully after clearing sessions", 0, :green
492
+ return true
493
+ else
494
+ debug "Failed to create Redfish session after clearing sessions: #{response.status} - #{response.body}", 0, :red
495
+ # If still failing, try direct mode
496
+ debug "Falling back to direct mode", 0, :light_yellow
497
+ @direct_mode = true
498
+ return false
499
+ end
500
+ rescue => e
501
+ debug "Error during session creation after clearing: #{e.class.name}: #{e.message}", 0, :red
502
+ debug "Falling back to direct mode", 0, :light_yellow
503
+ @direct_mode = true
504
+ return false
505
+ end
506
+ else
507
+ debug "Failed to clear sessions, switching to direct mode", 0, :light_yellow
508
+ @direct_mode = true
509
+ return false
510
+ end
511
+ else
512
+ debug "Auto delete sessions is disabled, switching to direct mode", 0, :light_yellow
513
+ @direct_mode = true
514
+ return false
515
+ end
516
+ end
517
+
518
+ def create_session_with_form_urlencoded(url, payload)
519
+ # Only try with form-urlencoded if we had a 415 error previously
520
+ begin
521
+ debug "Trying with form-urlencoded content type", 0
522
+ debug "URL: #{base_url}#{url}", 1
523
+
524
+ # Try first without any authorization header
525
+ response = connection.post(url) do |req|
526
+ req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
527
+ req.headers['Accept'] = '*/*'
528
+ req.body = "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}"
529
+ debug "Request headers: #{req.headers.reject { |k,v| k =~ /auth/i }.to_json}", 2
530
+ end
531
+
532
+ debug "Response status: #{response.status}", 1
533
+ debug "Response headers: #{response.headers.to_json}", 2
534
+ debug "Response body: #{response.body}", 3
535
+
536
+ if process_session_response(response)
537
+ debug "Redfish session created successfully with form-urlencoded", 0, :green
538
+ return true
539
+ end
540
+
541
+ # If that fails, try with Basic Auth + form-urlencoded
542
+ debug "Trying form-urlencoded with Basic Auth", 0
543
+ auth_response = request_with_basic_auth(:post, url, "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}", 'application/x-www-form-urlencoded')
544
+
545
+ if process_session_response(auth_response)
546
+ debug "Redfish session created successfully with form-urlencoded + Basic Auth", 0, :green
547
+ return true
548
+ end
549
+
550
+ # Last resort: try with both headers (some iDRAC versions need this)
551
+ debug "Trying with both Content-Type headers", 0
552
+ both_response = connection.post(url) do |req|
553
+ req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
554
+ req.headers['Accept'] = 'application/json'
555
+ req.headers['X-Requested-With'] = 'XMLHttpRequest'
556
+ req.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
557
+ req.body = "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}"
558
+ end
559
+
560
+ if process_session_response(both_response)
561
+ debug "Redfish session created successfully with multiple content types", 0, :green
562
+ return true
563
+ else
564
+ debug "Failed with form-urlencoded too: #{response.status} - #{response.body}", 0, :red
565
+ return false
566
+ end
567
+ rescue Faraday::SSLError => e
568
+ debug "SSL Error in form-urlencoded request: #{e.message}", 0, :red
569
+ debug "OpenSSL version: #{OpenSSL::OPENSSL_VERSION}", 1
570
+ debug "Connection URL: #{base_url}#{url}", 1
571
+ debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
572
+ return false
573
+ rescue => e
574
+ debug "Error during form-urlencoded session creation: #{e.class.name}: #{e.message}", 0, :red
575
+ debug e.backtrace.join("\n"), 2 if e.backtrace && @verbosity >= 2
576
+ return false
577
+ end
578
+ end
579
+
580
+ # Determine the correct session endpoint based on Redfish version
581
+ def determine_session_endpoint
582
+ begin
583
+ debug "Checking Redfish version to determine session endpoint...", 1
584
+
585
+ response = connection.get('/redfish/v1') do |req|
586
+ req.headers['Accept'] = 'application/json'
587
+ end
588
+
589
+ if response.status == 200
590
+ begin
591
+ data = JSON.parse(response.body)
592
+ redfish_version = data['RedfishVersion']
593
+
594
+ if redfish_version
595
+ debug "Detected Redfish version: #{redfish_version}", 1
596
+
597
+ # For version 1.17.0 and below, use the /redfish/v1/Sessions endpoint
598
+ # For newer versions, use /redfish/v1/SessionService/Sessions
599
+ if Gem::Version.new(redfish_version) <= Gem::Version.new('1.17.0')
600
+ endpoint = '/redfish/v1/Sessions'
601
+ debug "Using endpoint #{endpoint} for Redfish version #{redfish_version}", 1
602
+ return endpoint
603
+ else
604
+ endpoint = '/redfish/v1/SessionService/Sessions'
605
+ debug "Using endpoint #{endpoint} for Redfish version #{redfish_version}", 1
606
+ return endpoint
607
+ end
608
+ end
609
+ rescue JSON::ParserError => e
610
+ debug "Error parsing Redfish version: #{e.message}", 0, :red
611
+ debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
612
+ rescue => e
613
+ debug "Error determining Redfish version: #{e.message}", 0, :red
614
+ debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
615
+ end
616
+ end
617
+ rescue => e
618
+ debug "Error checking Redfish version: #{e.message}", 0, :red
619
+ debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
620
+ end
621
+
622
+ # Default to /redfish/v1/Sessions if we can't determine version
623
+ default_endpoint = '/redfish/v1/Sessions'
624
+ debug "Defaulting to endpoint #{default_endpoint}", 0, :light_yellow
625
+ default_endpoint
626
+ end
627
+ end
628
+
629
+ # Module containing extracted session methods to be included in Client
630
+ module SessionMethods
631
+ def force_clear_sessions
632
+ debug = ->(msg, level=0, color=:light_cyan) {
633
+ verbosity = respond_to?(:verbosity) ? verbosity : 0
634
+ return unless verbosity >= level
635
+ msg = msg.send(color) if color && msg.respond_to?(color)
636
+ puts msg
637
+ }
638
+
639
+ debug.call "Attempting to force clear all sessions...", 0
640
+
641
+ if delete_all_sessions_with_basic_auth
642
+ debug.call "Successfully cleared sessions using Basic Auth", 0, :green
643
+ true
644
+ else
645
+ debug.call "Failed to clear sessions using Basic Auth", 0, :red
646
+ false
647
+ end
648
+ end
649
+
650
+ # Delete all sessions using Basic Authentication
651
+ def delete_all_sessions_with_basic_auth
652
+ debug = ->(msg, level=0, color=:light_cyan) {
653
+ verbosity = respond_to?(:verbosity) ? verbosity : 0
654
+ return unless verbosity >= level
655
+ msg = msg.send(color) if color && msg.respond_to?(color)
656
+ puts msg
657
+ }
658
+
659
+ debug.call "Attempting to delete all sessions using Basic Authentication...", 0
660
+
661
+ # First, get the list of sessions
662
+ sessions_url = session&.determine_session_endpoint || '/redfish/v1/Sessions'
663
+
664
+ begin
665
+ # Get the list of sessions
666
+ response = authenticated_request(:get, sessions_url)
667
+
668
+ if response.status != 200
669
+ debug.call "Failed to get sessions list: #{response.status} - #{response.body}", 0, :red
670
+ return false
671
+ end
672
+
673
+ # Parse the response to get session IDs
674
+ begin
675
+ sessions_data = JSON.parse(response.body)
676
+
677
+ if sessions_data['Members'] && sessions_data['Members'].any?
678
+ debug.call "Found #{sessions_data['Members'].count} active sessions", 0, :light_yellow
679
+
680
+ # Delete each session
681
+ success = true
682
+ sessions_data['Members'].each do |session|
683
+ session_url = session['@odata.id']
684
+
685
+ # Skip if no URL
686
+ next unless session_url
687
+
688
+ # Delete the session
689
+ delete_response = authenticated_request(:delete, session_url)
690
+
691
+ if delete_response.status == 200 || delete_response.status == 204
692
+ debug.call "Successfully deleted session: #{session_url}", 1, :green
693
+ else
694
+ debug.call "Failed to delete session #{session_url}: #{delete_response.status}", 0, :red
695
+ success = false
696
+ end
697
+
698
+ # Small delay between deletions
699
+ sleep(1)
700
+ end
701
+
702
+ return success
703
+ else
704
+ debug.call "No active sessions found", 0, :light_yellow
705
+ return true
706
+ end
707
+ rescue JSON::ParserError => e
708
+ debug.call "Error parsing sessions response: #{e.message}", 0, :red
709
+ return false
710
+ end
711
+ rescue => e
712
+ debug.call "Error during session deletion with Basic Auth: #{e.message}", 0, :red
713
+ return false
714
+ end
715
+ end
716
+ end
717
+ end