idrac 0.1.40 → 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.
data/lib/idrac/session.rb CHANGED
@@ -3,11 +3,16 @@ require 'base64'
3
3
  require 'json'
4
4
  require 'colorize'
5
5
  require 'uri'
6
+ require 'logger'
7
+ require 'socket'
6
8
 
7
9
  module IDRAC
8
10
  class Session
9
11
  attr_reader :host, :username, :password, :port, :use_ssl, :verify_ssl,
10
12
  :x_auth_token, :session_location, :direct_mode, :auto_delete_sessions
13
+ attr_accessor :verbosity
14
+
15
+ include Debuggable
11
16
 
12
17
  def initialize(client)
13
18
  @client = client
@@ -22,42 +27,73 @@ module IDRAC
22
27
  @direct_mode = client.direct_mode
23
28
  @sessions_maxed = false
24
29
  @auto_delete_sessions = client.auto_delete_sessions
30
+ @verbosity = client.respond_to?(:verbosity) ? client.verbosity : 0
25
31
  end
26
32
 
27
33
  def connection
28
- @connection ||= Faraday.new(url: base_url, ssl: { verify: verify_ssl }) do |faraday|
34
+ @connection ||= Faraday.new(url: base_url, ssl: {
35
+ verify: verify_ssl
36
+ # Keep SSL settings minimal for cross-version compatibility
37
+ }) do |faraday|
29
38
  faraday.request :multipart
30
39
  faraday.request :url_encoded
31
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
32
48
  end
33
49
  end
34
50
 
35
51
  # Force clear all sessions by directly using Basic Auth
36
52
  def force_clear_sessions
37
- puts "Attempting to force clear all sessions...".light_cyan
53
+ debug "Attempting to force clear all sessions...", 0
38
54
 
39
- if delete_all_sessions_with_basic_auth
40
- puts "Successfully cleared sessions using Basic Auth".green
41
- true
42
- else
43
- puts "Failed to clear sessions using Basic Auth".red
44
- false
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
45
74
  end
75
+
76
+ false
46
77
  end
47
78
 
48
79
  # Delete all sessions using Basic Authentication
49
80
  def delete_all_sessions_with_basic_auth
50
- puts "Attempting to delete all sessions using Basic Authentication...".light_cyan
81
+ debug "Attempting to delete all sessions using Basic Authentication...", 0
51
82
 
52
83
  # First, get the list of sessions
53
- sessions_url = '/redfish/v1/SessionService/Sessions'
84
+ sessions_url = determine_session_endpoint
54
85
 
55
86
  begin
56
87
  # Get the list of sessions
57
- response = request_with_basic_auth(:get, sessions_url)
88
+ response = request_with_basic_auth(:get, sessions_url, nil, 'application/json')
58
89
 
59
90
  if response.status != 200
60
- puts "Failed to get sessions list: #{response.status} - #{response.body}".red
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
61
97
  return false
62
98
  end
63
99
 
@@ -66,7 +102,7 @@ module IDRAC
66
102
  sessions_data = JSON.parse(response.body)
67
103
 
68
104
  if sessions_data['Members'] && sessions_data['Members'].any?
69
- puts "Found #{sessions_data['Members'].count} active sessions".light_yellow
105
+ debug "Found #{sessions_data['Members'].count} active sessions", 0, :light_yellow
70
106
 
71
107
  # Delete each session
72
108
  success = true
@@ -77,12 +113,12 @@ module IDRAC
77
113
  next unless session_url
78
114
 
79
115
  # Delete the session
80
- delete_response = request_with_basic_auth(:delete, session_url)
116
+ delete_response = request_with_basic_auth(:delete, session_url, nil, 'application/json')
81
117
 
82
118
  if delete_response.status == 200 || delete_response.status == 204
83
- puts "Successfully deleted session: #{session_url}".green
119
+ debug "Successfully deleted session: #{session_url}", 1, :green
84
120
  else
85
- puts "Failed to delete session #{session_url}: #{delete_response.status}".red
121
+ debug "Failed to delete session #{session_url}: #{delete_response.status}", 0, :red
86
122
  success = false
87
123
  end
88
124
 
@@ -92,35 +128,73 @@ module IDRAC
92
128
 
93
129
  return success
94
130
  else
95
- puts "No active sessions found".light_yellow
131
+ debug "No active sessions found", 0, :light_yellow
96
132
  return true
97
133
  end
98
134
  rescue JSON::ParserError => e
99
- puts "Error parsing sessions response: #{e.message}".red.bold
100
- return false
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
101
138
  end
102
139
  rescue => e
103
- puts "Error during session deletion with Basic Auth: #{e.message}".red.bold
104
- return false
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
105
143
  end
106
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
107
175
 
108
176
  # Create a Redfish session
109
177
  def create
110
178
  # Skip if we're in direct mode
111
179
  if @direct_mode
112
- puts "Skipping Redfish session creation (direct mode)".light_yellow
180
+ debug "Skipping Redfish session creation (direct mode)", 0, :light_yellow
113
181
  return false
114
182
  end
115
183
 
116
- url = '/redfish/v1/SessionService/Sessions'
184
+ # Determine the correct session endpoint based on Redfish version
185
+ session_endpoint = determine_session_endpoint
186
+
117
187
  payload = { "UserName" => username, "Password" => password }
118
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
+
119
193
  # Try creation methods in sequence
120
- return true if create_session_with_content_type(url, payload)
121
- return true if create_session_with_basic_auth(url, payload)
122
- return true if handle_max_sessions_and_retry(url, payload)
123
- return true if create_session_with_form_urlencoded(url, payload)
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)
124
198
 
125
199
  # If all attempts fail, switch to direct mode
126
200
  @direct_mode = true
@@ -132,7 +206,7 @@ module IDRAC
132
206
  return unless @x_auth_token && @session_location
133
207
 
134
208
  begin
135
- puts "Deleting Redfish session...".light_cyan
209
+ debug "Deleting Redfish session...", 0
136
210
 
137
211
  # Use the X-Auth-Token for authentication
138
212
  headers = { 'X-Auth-Token' => @x_auth_token }
@@ -142,16 +216,16 @@ module IDRAC
142
216
  end
143
217
 
144
218
  if response.status == 200 || response.status == 204
145
- puts "Redfish session deleted successfully".green
219
+ debug "Redfish session deleted successfully", 0, :green
146
220
  @x_auth_token = nil
147
221
  @session_location = nil
148
222
  return true
149
223
  else
150
- puts "Failed to delete Redfish session: #{response.status} - #{response.body}".red
224
+ debug "Failed to delete Redfish session: #{response.status} - #{response.body}", 0, :red
151
225
  return false
152
226
  end
153
227
  rescue => e
154
- puts "Error during Redfish session deletion: #{e.message}".red.bold
228
+ debug "Error during Redfish session deletion: #{e.message}", 0, :red
155
229
  return false
156
230
  end
157
231
  end
@@ -163,20 +237,91 @@ module IDRAC
163
237
  "#{protocol}://#{host}:#{port}"
164
238
  end
165
239
 
166
- def basic_auth_headers
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')
167
302
  {
168
303
  'Authorization' => "Basic #{Base64.strict_encode64("#{username}:#{password}")}",
169
- 'Content-Type' => 'application/json'
304
+ 'Content-Type' => content_type
170
305
  }
171
306
  end
172
307
 
173
- def request_with_basic_auth(method, url, body = nil)
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
+
174
312
  connection.send(method, url) do |req|
175
- req.headers.merge!(basic_auth_headers)
313
+ req.headers.merge!(basic_auth_headers(content_type))
176
314
  req.body = body if body
315
+ debug "Request headers: #{req.headers.reject { |k,v| k =~ /auth/i }.to_json}", 2
177
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
178
322
  rescue => e
179
- puts "Error during #{method} request with Basic Auth: #{e.message}".red.bold
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
180
325
  raise e
181
326
  end
182
327
 
@@ -193,61 +338,179 @@ module IDRAC
193
338
 
194
339
  def create_session_with_content_type(url, payload)
195
340
  begin
341
+ debug "Creating session with Content-Type: application/json", 0
342
+
196
343
  response = connection.post(url) do |req|
197
344
  req.headers['Content-Type'] = 'application/json'
345
+ req.headers['Accept'] = 'application/json'
198
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
199
358
  end
200
359
 
201
360
  if process_session_response(response)
202
- puts "Redfish session created successfully".green
361
+ debug "Redfish session created successfully", 0, :green
203
362
  return true
204
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
205
387
  rescue => e
206
- puts "First session creation attempt failed: #{e.message}".light_red
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
207
390
  end
208
391
  false
209
392
  end
210
393
 
211
394
  def create_session_with_basic_auth(url, payload)
212
395
  begin
213
- response = request_with_basic_auth(:post, url, payload.to_json)
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
214
407
 
215
408
  if process_session_response(response)
216
- puts "Redfish session created successfully with Basic Auth".green
409
+ debug "Redfish session created successfully with Basic Auth (JSON)", 0, :green
217
410
  return true
218
- elsif response.status == 400 && response.body.include?("maximum number of user sessions")
219
- puts "Maximum sessions reached during Redfish session creation".light_red
220
- @sessions_maxed = true
221
- return false
222
- else
223
- puts "Failed to create Redfish session: #{response.status} - #{response.body}".red
224
- return false
225
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
226
464
  rescue => e
227
- puts "Error during Redfish session creation with Basic Auth: #{e.message}".red.bold
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
228
467
  return false
229
468
  end
230
469
  end
231
470
 
232
471
  def handle_max_sessions_and_retry(url, payload)
233
- return false unless @sessions_maxed && @auto_delete_sessions
472
+ return false unless @sessions_maxed
234
473
 
235
- puts "Auto-delete sessions is enabled, attempting to clear sessions".light_cyan
236
- if force_clear_sessions
237
- puts "Successfully cleared sessions, trying to create a new session".green
238
-
239
- # Try one more time after clearing
240
- response = request_with_basic_auth(:post, url, payload.to_json)
241
-
242
- if process_session_response(response)
243
- puts "Redfish session created successfully after clearing sessions".green
244
- return true
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
245
506
  else
246
- puts "Failed to create Redfish session after clearing sessions: #{response.status} - #{response.body}".red
507
+ debug "Failed to clear sessions, switching to direct mode", 0, :light_yellow
508
+ @direct_mode = true
247
509
  return false
248
510
  end
249
511
  else
250
- puts "Failed to clear sessions, switching to direct mode".light_yellow
512
+ debug "Auto delete sessions is disabled, switching to direct mode", 0, :light_yellow
513
+ @direct_mode = true
251
514
  return false
252
515
  end
253
516
  end
@@ -255,22 +518,198 @@ module IDRAC
255
518
  def create_session_with_form_urlencoded(url, payload)
256
519
  # Only try with form-urlencoded if we had a 415 error previously
257
520
  begin
258
- puts "Trying with form-urlencoded content type".light_cyan
521
+ debug "Trying with form-urlencoded content type", 0
522
+ debug "URL: #{base_url}#{url}", 1
523
+
524
+ # Try first without any authorization header
259
525
  response = connection.post(url) do |req|
260
526
  req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
261
- req.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
527
+ req.headers['Accept'] = '*/*'
262
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
263
530
  end
264
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
+
265
536
  if process_session_response(response)
266
- puts "Redfish session created successfully with form-urlencoded".green
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
267
562
  return true
268
563
  else
269
- puts "Failed with form-urlencoded too: #{response.status} - #{response.body}".red
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
270
709
  return false
271
710
  end
272
711
  rescue => e
273
- puts "Error during form-urlencoded session creation: #{e.message}".red.bold
712
+ debug.call "Error during session deletion with Basic Auth: #{e.message}", 0, :red
274
713
  return false
275
714
  end
276
715
  end