conversant 1.0.16
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.
- checksums.yaml +7 -0
- data/.env.example +39 -0
- data/.gitignore +52 -0
- data/.gitlab-ci.yml +108 -0
- data/.rspec +3 -0
- data/.rubocop.yml +16 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +487 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +860 -0
- data/RELEASE.md +726 -0
- data/Rakefile +21 -0
- data/conversant.gemspec +49 -0
- data/examples/inheritance_integration.rb +348 -0
- data/examples/rails_initializer.rb +69 -0
- data/lib/conversant/configuration.rb +132 -0
- data/lib/conversant/v3/base.rb +47 -0
- data/lib/conversant/v3/http_client.rb +456 -0
- data/lib/conversant/v3/mixins/authentication.rb +221 -0
- data/lib/conversant/v3/services/authorization.rb +194 -0
- data/lib/conversant/v3/services/cdn/analytics.rb +483 -0
- data/lib/conversant/v3/services/cdn/audit.rb +71 -0
- data/lib/conversant/v3/services/cdn/business.rb +122 -0
- data/lib/conversant/v3/services/cdn/certificate.rb +180 -0
- data/lib/conversant/v3/services/cdn/dashboard.rb +109 -0
- data/lib/conversant/v3/services/cdn/domain.rb +223 -0
- data/lib/conversant/v3/services/cdn/monitoring.rb +65 -0
- data/lib/conversant/v3/services/cdn/partner/analytics.rb +233 -0
- data/lib/conversant/v3/services/cdn/partner.rb +60 -0
- data/lib/conversant/v3/services/cdn.rb +221 -0
- data/lib/conversant/v3/services/lms/dashboard.rb +99 -0
- data/lib/conversant/v3/services/lms/domain.rb +108 -0
- data/lib/conversant/v3/services/lms/job.rb +211 -0
- data/lib/conversant/v3/services/lms/partner/analytics.rb +266 -0
- data/lib/conversant/v3/services/lms/partner/business.rb +151 -0
- data/lib/conversant/v3/services/lms/partner/report.rb +170 -0
- data/lib/conversant/v3/services/lms/partner.rb +58 -0
- data/lib/conversant/v3/services/lms/preset.rb +57 -0
- data/lib/conversant/v3/services/lms.rb +173 -0
- data/lib/conversant/v3/services/oss/partner/analytics.rb +105 -0
- data/lib/conversant/v3/services/oss/partner.rb +48 -0
- data/lib/conversant/v3/services/oss.rb +128 -0
- data/lib/conversant/v3/services/portal/dashboard.rb +114 -0
- data/lib/conversant/v3/services/portal.rb +219 -0
- data/lib/conversant/v3/services/vms/analytics.rb +114 -0
- data/lib/conversant/v3/services/vms/business.rb +190 -0
- data/lib/conversant/v3/services/vms/partner/analytics.rb +133 -0
- data/lib/conversant/v3/services/vms/partner/business.rb +90 -0
- data/lib/conversant/v3/services/vms/partner.rb +57 -0
- data/lib/conversant/v3/services/vms/transcoding.rb +184 -0
- data/lib/conversant/v3/services/vms.rb +166 -0
- data/lib/conversant/v3.rb +36 -0
- data/lib/conversant/version.rb +5 -0
- data/lib/conversant.rb +108 -0
- data/publish.sh +107 -0
- data/sig/conversant/v3/services/authorization.rbs +34 -0
- data/sig/conversant/v3/services/cdn.rbs +123 -0
- data/sig/conversant/v3/services/lms.rbs +80 -0
- data/sig/conversant/v3/services/portal.rbs +22 -0
- data/sig/conversant/v3/services/vms.rbs +64 -0
- data/sig/conversant/v3.rbs +85 -0
- data/sig/conversant.rbs +37 -0
- metadata +267 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Conversant
|
|
4
|
+
module V3
|
|
5
|
+
# HTTP client module for handling SSO authentication and HTTP requests
|
|
6
|
+
#
|
|
7
|
+
# This module provides low-level HTTP operations including SSO login flow,
|
|
8
|
+
# cookie management, and authenticated request handling. It's included by
|
|
9
|
+
# Base class and used by all service classes for authentication.
|
|
10
|
+
#
|
|
11
|
+
# @example Using HttpClient in a service
|
|
12
|
+
# class MyService < Conversant::V3::Base
|
|
13
|
+
# include HttpClient
|
|
14
|
+
#
|
|
15
|
+
# def my_api_call
|
|
16
|
+
# sessions = authenticate
|
|
17
|
+
# request(:get, api_url, nil, headers)
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# @since 1.0.0
|
|
22
|
+
module HttpClient
|
|
23
|
+
# Default login URL for SSO authentication
|
|
24
|
+
LOGIN_URL = 'https://console.swiftfederation.com/'
|
|
25
|
+
|
|
26
|
+
# Redis key for storing portal SESSION cookie
|
|
27
|
+
PORTAL_SESSION_REDIS_KEY = 'CONVERSANT.V3.PORTAL.SESSION'
|
|
28
|
+
|
|
29
|
+
# Redis key for storing SSO_GW_SESSION2 cookie
|
|
30
|
+
SSO_GW_SESSION2_REDIS_KEY = 'CONVERSANT.V3.PORTAL.SSO_GW_SESSION2'
|
|
31
|
+
|
|
32
|
+
# Gets the thread-local cookie jar for storing cookies during authentication
|
|
33
|
+
#
|
|
34
|
+
# The cookie jar is used to maintain cookies across multiple HTTP requests
|
|
35
|
+
# during the SSO login flow. Each thread has its own isolated cookie jar.
|
|
36
|
+
#
|
|
37
|
+
# @return [Hash{String => String}] Cookie name-value pairs
|
|
38
|
+
#
|
|
39
|
+
# @example
|
|
40
|
+
# cookie_jar['SESSION'] = 'abc123'
|
|
41
|
+
# puts cookie_jar['SESSION'] # => 'abc123'
|
|
42
|
+
#
|
|
43
|
+
# @since 1.0.0
|
|
44
|
+
def cookie_jar
|
|
45
|
+
Thread.current[:conversant_cookie_jar] ||= {}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Sets the thread-local cookie jar
|
|
49
|
+
#
|
|
50
|
+
# @param value [Hash, nil] Cookie name-value pairs, or nil to clear
|
|
51
|
+
# @return [Hash, nil] The assigned value
|
|
52
|
+
#
|
|
53
|
+
# @example
|
|
54
|
+
# self.cookie_jar = { 'SESSION' => 'abc123' }
|
|
55
|
+
# self.cookie_jar = nil # Clear cookies
|
|
56
|
+
#
|
|
57
|
+
# @since 1.0.0
|
|
58
|
+
def cookie_jar=(value)
|
|
59
|
+
Thread.current[:conversant_cookie_jar] = value
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Logs a debug message if debug mode is enabled
|
|
63
|
+
#
|
|
64
|
+
# @param message [String] The message to log
|
|
65
|
+
# @return [void]
|
|
66
|
+
#
|
|
67
|
+
# @example
|
|
68
|
+
# debug_log "Starting authentication"
|
|
69
|
+
#
|
|
70
|
+
# @since 1.0.0
|
|
71
|
+
def debug_log(message)
|
|
72
|
+
return unless configuration.debug_mode
|
|
73
|
+
|
|
74
|
+
configuration.logger.debug "Conversant::V3 - #{message}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Performs SSO login to obtain root SESSION and SSO_GW_SESSION2 cookies
|
|
78
|
+
#
|
|
79
|
+
# Executes a multi-step SSO authentication flow:
|
|
80
|
+
# 1. Fetches the login page
|
|
81
|
+
# 2. Extracts the form action URL
|
|
82
|
+
# 3. Submits credentials
|
|
83
|
+
# 4. Extracts and caches session cookies
|
|
84
|
+
#
|
|
85
|
+
# Sessions are cached in Redis for reuse across all services and customers.
|
|
86
|
+
#
|
|
87
|
+
# @return [Hash{Symbol => String}, nil] Hash with :session and :sso_gw_session2 keys, or nil on failure
|
|
88
|
+
#
|
|
89
|
+
# @example
|
|
90
|
+
# sessions = sso_login
|
|
91
|
+
# if sessions
|
|
92
|
+
# puts "SESSION: #{sessions[:session]}"
|
|
93
|
+
# puts "SSO_GW_SESSION2: #{sessions[:sso_gw_session2]}"
|
|
94
|
+
# end
|
|
95
|
+
#
|
|
96
|
+
# @since 1.0.0
|
|
97
|
+
def sso_login
|
|
98
|
+
session_key = PORTAL_SESSION_REDIS_KEY
|
|
99
|
+
sso_key = SSO_GW_SESSION2_REDIS_KEY
|
|
100
|
+
|
|
101
|
+
# Check cache first
|
|
102
|
+
cached_session = redis.get(session_key)
|
|
103
|
+
cached_sso_gw = redis.get(sso_key)
|
|
104
|
+
|
|
105
|
+
if cached_session && cached_sso_gw
|
|
106
|
+
debug_log 'Using cached global SSO sessions'
|
|
107
|
+
return {
|
|
108
|
+
session: cached_session,
|
|
109
|
+
sso_gw_session2: cached_sso_gw
|
|
110
|
+
}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
begin
|
|
114
|
+
# Initialize thread-safe cookie jar
|
|
115
|
+
self.cookie_jar = {}
|
|
116
|
+
|
|
117
|
+
# Step 1: Get login page and extract form action
|
|
118
|
+
debug_log "[SSO Login - Step 1] Fetching login page: #{LOGIN_URL}"
|
|
119
|
+
login_page_response = http_get(LOGIN_URL)
|
|
120
|
+
return nil unless login_page_response
|
|
121
|
+
|
|
122
|
+
debug_log "Login page loaded, status: #{login_page_response[:status]}"
|
|
123
|
+
|
|
124
|
+
# Step 2: Parse login form to get action URL
|
|
125
|
+
form_action = extract_form_action(login_page_response[:body])
|
|
126
|
+
unless form_action
|
|
127
|
+
debug_log 'Could not extract form action URL from login page'
|
|
128
|
+
return nil
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
debug_log "[SSO Login - Step 2] Form action extracted: #{form_action}"
|
|
132
|
+
|
|
133
|
+
# Step 3: Submit credentials
|
|
134
|
+
form_data = URI.encode_www_form(
|
|
135
|
+
username: configuration.swiftserve_identifier_id,
|
|
136
|
+
password: configuration.swiftserve_identifier_hash,
|
|
137
|
+
credentialId: ''
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
debug_log '[SSO Login - Step 3] Submitting login form...'
|
|
141
|
+
login_response = http_post(form_action, form_data, {
|
|
142
|
+
'Content-Type' => 'application/x-www-form-urlencoded'
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
return nil unless login_response
|
|
146
|
+
|
|
147
|
+
debug_log "Login completed, status: #{login_response[:status]}"
|
|
148
|
+
|
|
149
|
+
# Step 4: Extract cookies
|
|
150
|
+
session_cookie = cookie_jar['SESSION']
|
|
151
|
+
sso_gw_session2_cookie = cookie_jar['SSO_GW_SESSION2']
|
|
152
|
+
|
|
153
|
+
if session_cookie && sso_gw_session2_cookie
|
|
154
|
+
# Cache the sessions globally
|
|
155
|
+
redis.set(session_key, session_cookie, ex: configuration.cache_ttl)
|
|
156
|
+
redis.set(sso_key, sso_gw_session2_cookie, ex: configuration.cache_ttl)
|
|
157
|
+
|
|
158
|
+
configuration.logger.info 'Conversant::V3 - SSO login successful, sessions cached'
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
session: session_cookie,
|
|
162
|
+
sso_gw_session2: sso_gw_session2_cookie
|
|
163
|
+
}
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
debug_log 'New SSO login failed - no valid sessions obtained'
|
|
167
|
+
nil
|
|
168
|
+
|
|
169
|
+
rescue StandardError => e
|
|
170
|
+
configuration.logger.error "Conversant::V3 - SSO login error: #{e.message}"
|
|
171
|
+
nil
|
|
172
|
+
ensure
|
|
173
|
+
self.cookie_jar = nil
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Authenticates and returns cached SSO sessions
|
|
178
|
+
#
|
|
179
|
+
# Wrapper around sso_login that ensures sessions are cached in Redis.
|
|
180
|
+
# This is the primary method used by service classes for authentication.
|
|
181
|
+
#
|
|
182
|
+
# @return [Hash{Symbol => String}, nil] Hash with :session and :sso_gw_session2 keys, or nil on failure
|
|
183
|
+
#
|
|
184
|
+
# @example
|
|
185
|
+
# sessions = authenticate
|
|
186
|
+
# if sessions
|
|
187
|
+
# headers['Cookie'] = "SESSION=#{sessions[:session]}; SSO_GW_SESSION2=#{sessions[:sso_gw_session2]}"
|
|
188
|
+
# end
|
|
189
|
+
#
|
|
190
|
+
# @since 1.0.0
|
|
191
|
+
def authenticate
|
|
192
|
+
result = sso_login
|
|
193
|
+
|
|
194
|
+
if result && result[:session]
|
|
195
|
+
redis.set(PORTAL_SESSION_REDIS_KEY, result[:session], ex: configuration.cache_ttl)
|
|
196
|
+
redis.set(SSO_GW_SESSION2_REDIS_KEY, result[:sso_gw_session2], ex: configuration.cache_ttl) if result[:sso_gw_session2]
|
|
197
|
+
result
|
|
198
|
+
else
|
|
199
|
+
nil
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Makes an authenticated HTTP request
|
|
204
|
+
#
|
|
205
|
+
# Handles JSON payload serialization and SSL verification based on configuration.
|
|
206
|
+
# All service classes use this method for making API calls.
|
|
207
|
+
#
|
|
208
|
+
# @param method [Symbol] HTTP method (:get, :post, :put, :delete, etc.)
|
|
209
|
+
# @param url [String] Full URL to request
|
|
210
|
+
# @param payload [Hash, String, nil] Request payload (auto-converted to JSON if Content-Type matches)
|
|
211
|
+
# @param headers [Hash{String => String}] HTTP headers
|
|
212
|
+
#
|
|
213
|
+
# @return [Array(Integer, RestClient::Response)] Status code and response object
|
|
214
|
+
#
|
|
215
|
+
# @example
|
|
216
|
+
# code, response = request(:get, 'https://api.example.com/data', nil, headers)
|
|
217
|
+
# if code == 200
|
|
218
|
+
# data = JSON.parse(response.body)
|
|
219
|
+
# end
|
|
220
|
+
#
|
|
221
|
+
# @since 1.0.0
|
|
222
|
+
def request(method, url, payload, headers)
|
|
223
|
+
debug_log "[Request] #{method} #{url}"
|
|
224
|
+
debug_log "[Request] Headers: #{headers.inspect}" if configuration.debug_mode
|
|
225
|
+
|
|
226
|
+
# Check Content-Type using string key (headers are now plain hash with string keys)
|
|
227
|
+
if headers&.[]('Content-Type') == configuration.default_content_type
|
|
228
|
+
payload = payload&.to_json
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
request = RestClient::Request.new(
|
|
232
|
+
method: method,
|
|
233
|
+
url: url,
|
|
234
|
+
payload: payload,
|
|
235
|
+
headers: headers,
|
|
236
|
+
verify_ssl: configuration.verify_ssl
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
response = request.execute do |response|
|
|
240
|
+
response
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
debug_log "[Response] Status: #{response.code}" if configuration.debug_mode
|
|
244
|
+
[response.code, response]
|
|
245
|
+
rescue StandardError => e
|
|
246
|
+
configuration.logger.error "Conversant::V3 - Request exception: #{e.message}"
|
|
247
|
+
[500, nil]
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
private
|
|
251
|
+
|
|
252
|
+
# Performs an HTTP GET request with cookie management
|
|
253
|
+
#
|
|
254
|
+
# Handles redirects automatically and updates the cookie jar from responses.
|
|
255
|
+
# Used internally during SSO login flow.
|
|
256
|
+
#
|
|
257
|
+
# @param url [String] URL to fetch
|
|
258
|
+
# @return [Hash{Symbol => untyped}, nil] Response hash with :status, :body, :headers keys, or nil on error
|
|
259
|
+
def http_get(url)
|
|
260
|
+
headers = {
|
|
261
|
+
'User-Agent' => configuration.default_ua,
|
|
262
|
+
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
headers = add_cookies_to_headers(headers)
|
|
266
|
+
|
|
267
|
+
response = RestClient::Request.execute(
|
|
268
|
+
method: :get,
|
|
269
|
+
url: url,
|
|
270
|
+
headers: headers,
|
|
271
|
+
verify_ssl: configuration.verify_ssl,
|
|
272
|
+
max_redirects: 10
|
|
273
|
+
) do |resp, request, result|
|
|
274
|
+
update_cookie_jar(resp)
|
|
275
|
+
|
|
276
|
+
case resp.code
|
|
277
|
+
when 200..299
|
|
278
|
+
resp
|
|
279
|
+
when 300..399
|
|
280
|
+
resp.follow_redirection
|
|
281
|
+
else
|
|
282
|
+
resp
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
update_cookie_jar(response)
|
|
287
|
+
|
|
288
|
+
{
|
|
289
|
+
status: response.code,
|
|
290
|
+
body: response.body,
|
|
291
|
+
headers: response.headers
|
|
292
|
+
}
|
|
293
|
+
rescue StandardError => e
|
|
294
|
+
configuration.logger.error "Conversant::V3 - HTTP GET error: #{e.message}"
|
|
295
|
+
nil
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Performs an HTTP POST request with cookie management
|
|
299
|
+
#
|
|
300
|
+
# Handles redirects automatically and updates the cookie jar from responses.
|
|
301
|
+
# Used internally during SSO login flow for form submissions.
|
|
302
|
+
#
|
|
303
|
+
# @param url [String] URL to post to
|
|
304
|
+
# @param payload [String] Request body
|
|
305
|
+
# @param extra_headers [Hash{String => String}] Additional headers to merge
|
|
306
|
+
# @return [Hash{Symbol => untyped}, nil] Response hash with :status, :body, :headers keys, or nil on error
|
|
307
|
+
def http_post(url, payload, extra_headers = {})
|
|
308
|
+
headers = {
|
|
309
|
+
'User-Agent' => configuration.default_ua,
|
|
310
|
+
'Accept' => '*/*'
|
|
311
|
+
}.merge(extra_headers)
|
|
312
|
+
|
|
313
|
+
headers = add_cookies_to_headers(headers)
|
|
314
|
+
|
|
315
|
+
response = RestClient::Request.execute(
|
|
316
|
+
method: :post,
|
|
317
|
+
url: url,
|
|
318
|
+
payload: payload,
|
|
319
|
+
headers: headers,
|
|
320
|
+
verify_ssl: configuration.verify_ssl,
|
|
321
|
+
max_redirects: 10
|
|
322
|
+
) do |resp, request, result|
|
|
323
|
+
update_cookie_jar(resp)
|
|
324
|
+
|
|
325
|
+
case resp.code
|
|
326
|
+
when 200..299
|
|
327
|
+
resp
|
|
328
|
+
when 300..399
|
|
329
|
+
location = resp.headers[:location]
|
|
330
|
+
if location
|
|
331
|
+
get_response = http_get(location)
|
|
332
|
+
if get_response && get_response[:status] == 200
|
|
333
|
+
OpenStruct.new(
|
|
334
|
+
code: get_response[:status],
|
|
335
|
+
body: get_response[:body],
|
|
336
|
+
headers: get_response[:headers]
|
|
337
|
+
)
|
|
338
|
+
else
|
|
339
|
+
resp.follow_redirection
|
|
340
|
+
end
|
|
341
|
+
else
|
|
342
|
+
resp.follow_redirection
|
|
343
|
+
end
|
|
344
|
+
else
|
|
345
|
+
resp
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
update_cookie_jar(response)
|
|
350
|
+
|
|
351
|
+
{
|
|
352
|
+
status: response.code,
|
|
353
|
+
body: response.body,
|
|
354
|
+
headers: response.headers
|
|
355
|
+
}
|
|
356
|
+
rescue StandardError => e
|
|
357
|
+
configuration.logger.error "Conversant::V3 - HTTP POST error: #{e.message}"
|
|
358
|
+
nil
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Adds cookies from cookie jar to request headers
|
|
362
|
+
#
|
|
363
|
+
# @param headers [Hash{String => String}] Request headers
|
|
364
|
+
# @return [Hash{String => String}] Headers with Cookie header added if cookies exist
|
|
365
|
+
def add_cookies_to_headers(headers)
|
|
366
|
+
return headers if cookie_jar.nil? || cookie_jar.empty?
|
|
367
|
+
|
|
368
|
+
cookie_string = cookie_jar.map { |name, value| "#{name}=#{value}" }.join('; ')
|
|
369
|
+
headers.merge('Cookie' => cookie_string)
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Updates cookie jar from HTTP response
|
|
373
|
+
#
|
|
374
|
+
# Extracts cookies from Set-Cookie headers and response.cookies hash.
|
|
375
|
+
#
|
|
376
|
+
# @param response [RestClient::Response] HTTP response object
|
|
377
|
+
# @return [void]
|
|
378
|
+
def update_cookie_jar(response)
|
|
379
|
+
return unless response&.headers
|
|
380
|
+
|
|
381
|
+
set_cookie_headers = response.headers[:set_cookie]
|
|
382
|
+
if set_cookie_headers
|
|
383
|
+
if set_cookie_headers.is_a?(Array)
|
|
384
|
+
set_cookie_headers.each { |cookie| parse_cookie_to_jar(cookie) }
|
|
385
|
+
elsif set_cookie_headers.is_a?(String)
|
|
386
|
+
parse_cookie_to_jar(set_cookie_headers)
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
if response.respond_to?(:cookies) && response.cookies.is_a?(Hash)
|
|
391
|
+
response.cookies.each do |name, value|
|
|
392
|
+
cookie_jar[name.to_s] = value.to_s
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# Parses a Set-Cookie header string and stores it in the cookie jar
|
|
398
|
+
#
|
|
399
|
+
# @param cookie_string [String] Set-Cookie header value
|
|
400
|
+
# @return [void]
|
|
401
|
+
def parse_cookie_to_jar(cookie_string)
|
|
402
|
+
return unless cookie_string
|
|
403
|
+
|
|
404
|
+
cookie_parts = cookie_string.split(';')
|
|
405
|
+
return if cookie_parts.empty?
|
|
406
|
+
|
|
407
|
+
first_part = cookie_parts.first
|
|
408
|
+
parts = first_part.split('=', 2)
|
|
409
|
+
return unless parts && parts.length == 2
|
|
410
|
+
|
|
411
|
+
name, value = parts
|
|
412
|
+
cookie_jar[name.strip] = value.strip
|
|
413
|
+
|
|
414
|
+
debug_log "Parsed cookie: #{name.strip}" if configuration.debug_mode
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Extracts form action URL from HTML login page
|
|
418
|
+
#
|
|
419
|
+
# Searches for the Keycloak login form action URL in the HTML.
|
|
420
|
+
#
|
|
421
|
+
# @param html_body [String] HTML content of login page
|
|
422
|
+
# @return [String, nil] Form action URL, or nil if not found
|
|
423
|
+
def extract_form_action(html_body)
|
|
424
|
+
return nil unless html_body
|
|
425
|
+
|
|
426
|
+
# Parse HTML to find form action URL
|
|
427
|
+
match = html_body.match(/form[^>]*id=["']kc-form-login["'][^>]*action=["']([^"']*)["']/)
|
|
428
|
+
if match
|
|
429
|
+
return CGI.unescapeHTML(match[1])
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Fallback: look for any form with action
|
|
433
|
+
match = html_body.match(/form[^>]*action=["']([^"']*)["']/)
|
|
434
|
+
if match
|
|
435
|
+
return CGI.unescapeHTML(match[1])
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
nil
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Returns the global Conversant configuration
|
|
442
|
+
#
|
|
443
|
+
# @return [Conversant::Configuration] Configuration instance
|
|
444
|
+
def configuration
|
|
445
|
+
Conversant.configuration
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# Returns the Redis client from configuration
|
|
449
|
+
#
|
|
450
|
+
# @return [Redis] Redis client instance
|
|
451
|
+
def redis
|
|
452
|
+
configuration.redis
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
end
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Conversant
|
|
4
|
+
module V3
|
|
5
|
+
module Mixins
|
|
6
|
+
# Authentication mixin for integrating V3 authentication into existing classes
|
|
7
|
+
#
|
|
8
|
+
# This mixin provides a bridge for existing classes to use Conversant V3
|
|
9
|
+
# authentication without fully adopting the gem's service architecture.
|
|
10
|
+
# It's designed for gradual migration and backward compatibility.
|
|
11
|
+
#
|
|
12
|
+
# @example Basic usage in existing class
|
|
13
|
+
# class MyExistingClass
|
|
14
|
+
# include Conversant::V3::Mixins::Authentication
|
|
15
|
+
# use_conversant_auth!
|
|
16
|
+
#
|
|
17
|
+
# def initialize(customer_id, type = 2)
|
|
18
|
+
# initialize_conversant_auth(customer_id, type)
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# def my_cdn_call
|
|
22
|
+
# headers = cdn_authorized_headers
|
|
23
|
+
# # Make API call with headers
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# @example Drop-in replacement for ConversantHttpClientV3
|
|
28
|
+
# module ConversantHttpClientV3
|
|
29
|
+
# def self.included(base)
|
|
30
|
+
# base.class_eval do
|
|
31
|
+
# include Conversant::V3::Mixins::Authentication
|
|
32
|
+
# use_conversant_auth!
|
|
33
|
+
# end
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# def authenticate
|
|
37
|
+
# conversant_authenticate
|
|
38
|
+
# end
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# @since 1.0.0
|
|
42
|
+
module Authentication
|
|
43
|
+
# Hook called when module is included
|
|
44
|
+
#
|
|
45
|
+
# @param base [Class] The class including this module
|
|
46
|
+
# @return [void]
|
|
47
|
+
def self.included(base)
|
|
48
|
+
base.extend(ClassMethods)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Class methods added when Authentication is included
|
|
52
|
+
module ClassMethods
|
|
53
|
+
# Enables Conversant authentication for the class
|
|
54
|
+
#
|
|
55
|
+
# Includes HttpClient module and sets up customer_id/type attributes.
|
|
56
|
+
# Call this in your class after including the Authentication mixin.
|
|
57
|
+
#
|
|
58
|
+
# @return [void]
|
|
59
|
+
#
|
|
60
|
+
# @example
|
|
61
|
+
# class MyService
|
|
62
|
+
# include Conversant::V3::Mixins::Authentication
|
|
63
|
+
# use_conversant_auth!
|
|
64
|
+
# end
|
|
65
|
+
def use_conversant_auth!
|
|
66
|
+
include Conversant::V3::HttpClient
|
|
67
|
+
attr_reader :customer_id, :type
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @return [Integer] Customer ID for API calls
|
|
72
|
+
attr_reader :customer_id
|
|
73
|
+
|
|
74
|
+
# @return [Integer] Customer type
|
|
75
|
+
attr_reader :type
|
|
76
|
+
|
|
77
|
+
# Initializes authentication with customer ID and type
|
|
78
|
+
#
|
|
79
|
+
# Call this from your class's initialize method to set up authentication.
|
|
80
|
+
#
|
|
81
|
+
# @param customer_id [Integer] Customer ID for API calls
|
|
82
|
+
# @param type [Integer] Customer type (default: 2)
|
|
83
|
+
# @return [void]
|
|
84
|
+
#
|
|
85
|
+
# @example
|
|
86
|
+
# def initialize(customer_id, type = 2)
|
|
87
|
+
# initialize_conversant_auth(customer_id, type)
|
|
88
|
+
# end
|
|
89
|
+
#
|
|
90
|
+
# @since 1.0.0
|
|
91
|
+
def initialize_conversant_auth(customer_id, type = 2)
|
|
92
|
+
@customer_id = customer_id # @type [Integer]
|
|
93
|
+
@type = type # @type [Integer]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Authenticates and returns SSO sessions
|
|
97
|
+
#
|
|
98
|
+
# Wrapper method for backward compatibility with existing code.
|
|
99
|
+
#
|
|
100
|
+
# @return [Hash{Symbol => String}, nil] Hash with :session and :sso_gw_session2 keys, or nil on failure
|
|
101
|
+
#
|
|
102
|
+
# @example
|
|
103
|
+
# sessions = conversant_authenticate
|
|
104
|
+
# if sessions
|
|
105
|
+
# puts sessions[:session]
|
|
106
|
+
# end
|
|
107
|
+
#
|
|
108
|
+
# @since 1.0.0
|
|
109
|
+
def conversant_authenticate
|
|
110
|
+
authenticate
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Returns authorized headers for Portal API calls
|
|
114
|
+
#
|
|
115
|
+
# Delegates to Portal service class which uses the Authorization module.
|
|
116
|
+
#
|
|
117
|
+
# @return [Hash{String => String}] Headers hash with 'Cookie' header containing SESSION and SSO_GW_SESSION2
|
|
118
|
+
#
|
|
119
|
+
# @raise [Conversant::AuthenticationError] if sessions cannot be obtained
|
|
120
|
+
#
|
|
121
|
+
# @example
|
|
122
|
+
# headers = portal_authorized_headers
|
|
123
|
+
# response = RestClient.get(url, headers)
|
|
124
|
+
#
|
|
125
|
+
# @since 1.0.0
|
|
126
|
+
def portal_authorized_headers
|
|
127
|
+
portal_service.send(:authorized_headers)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Returns authorized headers for CDN API calls
|
|
131
|
+
#
|
|
132
|
+
# Delegates to CDN service class which uses the Authorization module.
|
|
133
|
+
#
|
|
134
|
+
# @return [Hash{String => String}] Headers hash with 'Cookie' header containing SESSION and SSO_GW_SESSION2
|
|
135
|
+
#
|
|
136
|
+
# @example
|
|
137
|
+
# headers = cdn_authorized_headers
|
|
138
|
+
# response = RestClient.get(cdn_url, headers)
|
|
139
|
+
#
|
|
140
|
+
# @since 1.0.0
|
|
141
|
+
def cdn_authorized_headers
|
|
142
|
+
cdn_service.send(:authorized_headers)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Returns authorized headers for LMS API calls
|
|
146
|
+
#
|
|
147
|
+
# Delegates to LMS service class which uses the Authorization module.
|
|
148
|
+
#
|
|
149
|
+
# @return [Hash{String => String}] Headers hash with 'Cookie' header containing JSESSIONID and SSO_GW_SESSION2
|
|
150
|
+
#
|
|
151
|
+
# @example
|
|
152
|
+
# headers = lms_authorized_headers
|
|
153
|
+
# response = RestClient.get(lms_url, headers)
|
|
154
|
+
#
|
|
155
|
+
# @since 1.0.0
|
|
156
|
+
def lms_authorized_headers
|
|
157
|
+
lms_service.send(:authorized_headers)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Returns authorized headers for VMS API calls
|
|
161
|
+
#
|
|
162
|
+
# Delegates to VMS service class which uses the Authorization module.
|
|
163
|
+
#
|
|
164
|
+
# @return [Hash{String => String}] Headers hash with 'Cookie' header containing JSESSIONID and SSO_GW_SESSION2
|
|
165
|
+
#
|
|
166
|
+
# @example
|
|
167
|
+
# headers = vms_authorized_headers
|
|
168
|
+
# response = RestClient.get(vms_url, headers)
|
|
169
|
+
#
|
|
170
|
+
# @since 1.0.0
|
|
171
|
+
def vms_authorized_headers
|
|
172
|
+
vms_service.send(:authorized_headers)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
# Gets or creates Portal service instance
|
|
178
|
+
#
|
|
179
|
+
# @return [Conversant::V3::Services::Portal] Portal service instance
|
|
180
|
+
def portal_service
|
|
181
|
+
@portal_service ||= Conversant::V3.portal(@customer_id, @type)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Gets or creates CDN service instance
|
|
185
|
+
#
|
|
186
|
+
# @return [Conversant::V3::Services::CDN] CDN service instance
|
|
187
|
+
def cdn_service
|
|
188
|
+
@cdn_service ||= Conversant::V3.cdn(@customer_id, @type)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Gets or creates LMS service instance
|
|
192
|
+
#
|
|
193
|
+
# @return [Conversant::V3::Services::LMS] LMS service instance
|
|
194
|
+
def lms_service
|
|
195
|
+
@lms_service ||= Conversant::V3.lms(@customer_id, @type)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Gets or creates VMS service instance
|
|
199
|
+
#
|
|
200
|
+
# @return [Conversant::V3::Services::VMS] VMS service instance
|
|
201
|
+
def vms_service
|
|
202
|
+
@vms_service ||= Conversant::V3.vms(@customer_id, @type)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Returns the global Conversant configuration
|
|
206
|
+
#
|
|
207
|
+
# @return [Conversant::Configuration] Configuration instance
|
|
208
|
+
def configuration
|
|
209
|
+
Conversant.configuration
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Returns the Redis client from configuration
|
|
213
|
+
#
|
|
214
|
+
# @return [Redis] Redis client instance
|
|
215
|
+
def redis
|
|
216
|
+
configuration.redis
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|