descope 1.0.6 → 1.0.7

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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yaml +2 -2
  3. data/.github/workflows/publish-gem.yaml +10 -3
  4. data/.gitignore +2 -0
  5. data/.ruby-version +1 -1
  6. data/Gemfile +7 -7
  7. data/Gemfile.lock +68 -55
  8. data/README.md +159 -51
  9. data/examples/ruby-on-rails-api/descope/Gemfile +8 -8
  10. data/examples/ruby-on-rails-api/descope/Gemfile.lock +1 -1
  11. data/examples/ruby-on-rails-api/descope/package-lock.json +187 -131
  12. data/examples/ruby-on-rails-api/descope/package.json +1 -1
  13. data/examples/ruby-on-rails-api/descope/yarn.lock +182 -84
  14. data/lib/descope/api/v1/auth/enchantedlink.rb +3 -1
  15. data/lib/descope/api/v1/auth/magiclink.rb +3 -1
  16. data/lib/descope/api/v1/auth/otp.rb +3 -1
  17. data/lib/descope/api/v1/auth/password.rb +6 -2
  18. data/lib/descope/api/v1/auth/totp.rb +3 -1
  19. data/lib/descope/api/v1/auth.rb +47 -12
  20. data/lib/descope/api/v1/management/common.rb +20 -5
  21. data/lib/descope/api/v1/management/sso_application.rb +236 -0
  22. data/lib/descope/api/v1/management/sso_settings.rb +2 -24
  23. data/lib/descope/api/v1/management/user.rb +151 -13
  24. data/lib/descope/api/v1/management.rb +2 -0
  25. data/lib/descope/api/v1/session.rb +37 -4
  26. data/lib/descope/mixins/common.rb +1 -0
  27. data/lib/descope/mixins/http.rb +60 -9
  28. data/lib/descope/mixins/initializer.rb +2 -1
  29. data/lib/descope/mixins/logging.rb +12 -4
  30. data/lib/descope/version.rb +1 -1
  31. data/spec/descope/api/v1/auth_spec.rb +29 -0
  32. data/spec/descope/api/v1/auth_token_extraction_spec.rb +126 -0
  33. data/spec/descope/api/v1/session_refresh_spec.rb +98 -0
  34. data/spec/factories/user.rb +1 -1
  35. data/spec/integration/lib.descope/api/v1/auth/enchantedlink_spec.rb +1 -1
  36. data/spec/integration/lib.descope/api/v1/auth/magiclink_spec.rb +1 -1
  37. data/spec/integration/lib.descope/api/v1/auth/otp_spec.rb +1 -1
  38. data/spec/integration/lib.descope/api/v1/auth/session_spec.rb +49 -0
  39. data/spec/integration/lib.descope/api/v1/auth/totp_spec.rb +1 -1
  40. data/spec/integration/lib.descope/api/v1/management/access_key_spec.rb +3 -0
  41. data/spec/integration/lib.descope/api/v1/management/audit_spec.rb +5 -3
  42. data/spec/integration/lib.descope/api/v1/management/authz_spec.rb +2 -0
  43. data/spec/integration/lib.descope/api/v1/management/flow_spec.rb +3 -1
  44. data/spec/integration/lib.descope/api/v1/management/permissions_spec.rb +4 -2
  45. data/spec/integration/lib.descope/api/v1/management/project_spec.rb +2 -0
  46. data/spec/integration/lib.descope/api/v1/management/roles_spec.rb +2 -0
  47. data/spec/integration/lib.descope/api/v1/management/user_spec.rb +55 -6
  48. data/spec/lib.descope/api/v1/auth/enchantedlink_spec.rb +11 -2
  49. data/spec/lib.descope/api/v1/auth/password_spec.rb +10 -1
  50. data/spec/lib.descope/api/v1/auth_spec.rb +167 -5
  51. data/spec/lib.descope/api/v1/cookie_domain_fix_integration_spec.rb +245 -0
  52. data/spec/lib.descope/api/v1/management/sso_application_spec.rb +217 -0
  53. data/spec/lib.descope/api/v1/management/sso_settings_spec.rb +2 -2
  54. data/spec/lib.descope/api/v1/management/user_spec.rb +134 -46
  55. data/spec/lib.descope/api/v1/session_spec.rb +119 -6
  56. data/spec/lib.descope/mixins/http_spec.rb +218 -0
  57. data/spec/support/client_config.rb +0 -1
  58. data/spec/support/utils.rb +6 -0
  59. metadata +13 -8
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Descope::Mixins::HTTP do
6
+ before(:all) do
7
+ dummy_instance = DummyClass.new
8
+ dummy_instance.extend(Descope::Mixins::HTTP)
9
+ @instance = dummy_instance
10
+ end
11
+
12
+ describe '#parse_cookie_value' do
13
+ it 'extracts cookie value from Set-Cookie header' do
14
+ cookie_header = 'DS=jwt_token_value; Path=/; Domain=example.com; HttpOnly; Secure'
15
+ result = @instance.parse_cookie_value(cookie_header, 'DS')
16
+ expect(result).to eq('jwt_token_value')
17
+ end
18
+
19
+ it 'extracts cookie value with complex JWT token' do
20
+ jwt_token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5kZXNjb3BlLmNvbS9QMmFiY2RlMTIzNDUifQ.signature'
21
+ cookie_header = "DSR=#{jwt_token}; Path=/; Domain=dev.example.com; HttpOnly; Secure; Max-Age=2592000"
22
+ result = @instance.parse_cookie_value(cookie_header, 'DSR')
23
+ expect(result).to eq(jwt_token)
24
+ end
25
+
26
+ it 'returns nil when cookie name is not found' do
27
+ cookie_header = 'OTHER=value; Path=/; Domain=example.com'
28
+ result = @instance.parse_cookie_value(cookie_header, 'DS')
29
+ expect(result).to be_nil
30
+ end
31
+
32
+ it 'handles cookie value with special characters' do
33
+ cookie_header = 'DS=token.with-special_chars123; Path=/; Domain=example.com'
34
+ result = @instance.parse_cookie_value(cookie_header, 'DS')
35
+ expect(result).to eq('token.with-special_chars123')
36
+ end
37
+
38
+ it 'handles cookie header with spaces around value' do
39
+ cookie_header = 'DS= spaced_token ; Path=/; Domain=example.com'
40
+ result = @instance.parse_cookie_value(cookie_header, 'DS')
41
+ expect(result).to eq('spaced_token')
42
+ end
43
+ end
44
+
45
+ describe '#safe_parse_json with cookie handling' do
46
+ let(:mock_body) do
47
+ {
48
+ 'userId' => 'test123',
49
+ 'cookieExpiration' => 1640704758,
50
+ 'cookieDomain' => 'dev.example.com'
51
+ }.to_json
52
+ end
53
+
54
+ context 'when RestClient cookies are available (same domain)' do
55
+ it 'uses RestClient cookies when available' do
56
+ mock_cookies = {
57
+ 'DS' => 'session_token_from_restclient',
58
+ 'DSR' => 'refresh_token_from_restclient'
59
+ }
60
+
61
+ result = @instance.safe_parse_json(mock_body, cookies: mock_cookies, headers: {})
62
+
63
+ expect(result['cookies']).to eq(mock_cookies)
64
+ expect(result['cookies']['DS']).to eq('session_token_from_restclient')
65
+ expect(result['cookies']['DSR']).to eq('refresh_token_from_restclient')
66
+ end
67
+
68
+ it 'handles only refresh token in RestClient cookies' do
69
+ mock_cookies = { 'DSR' => 'refresh_token_only' }
70
+
71
+ result = @instance.safe_parse_json(mock_body, cookies: mock_cookies, headers: {})
72
+
73
+ expect(result['cookies']).to eq(mock_cookies)
74
+ expect(result['cookies']['DSR']).to eq('refresh_token_only')
75
+ expect(result['cookies']['DS']).to be_nil
76
+ end
77
+ end
78
+
79
+ context 'when RestClient cookies are empty (custom domain)' do
80
+ let(:set_cookie_headers) do
81
+ [
82
+ 'DS=session_jwt_token; Path=/; Domain=dev.example.com; HttpOnly; Secure; SameSite=None',
83
+ 'DSR=refresh_jwt_token; Path=/; Domain=dev.example.com; HttpOnly; Secure; SameSite=None; Max-Age=2592000'
84
+ ]
85
+ end
86
+
87
+ it 'parses cookies from Set-Cookie headers when RestClient cookies are empty' do
88
+ mock_headers = { 'set-cookie' => set_cookie_headers }
89
+
90
+ result = @instance.safe_parse_json(mock_body, cookies: {}, headers: mock_headers)
91
+
92
+ expect(result['cookies']).to_not be_nil
93
+ expect(result['cookies']['DS']).to eq('session_jwt_token')
94
+ expect(result['cookies']['DSR']).to eq('refresh_jwt_token')
95
+ end
96
+
97
+ it 'parses cookies from Set-Cookie header when headers is a string' do
98
+ mock_headers = { 'Set-Cookie' => set_cookie_headers.first }
99
+
100
+ result = @instance.safe_parse_json(mock_body, cookies: {}, headers: mock_headers)
101
+
102
+ expect(result['cookies']).to_not be_nil
103
+ expect(result['cookies']['DS']).to eq('session_jwt_token')
104
+ end
105
+
106
+ it 'handles case-insensitive Set-Cookie header names' do
107
+ mock_headers = { 'Set-Cookie' => set_cookie_headers }
108
+
109
+ result = @instance.safe_parse_json(mock_body, cookies: {}, headers: mock_headers)
110
+
111
+ expect(result['cookies']['DS']).to eq('session_jwt_token')
112
+ expect(result['cookies']['DSR']).to eq('refresh_jwt_token')
113
+ end
114
+
115
+ it 'handles complex JWT tokens in Set-Cookie headers' do
116
+ jwt_session = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5kZXNjb3BlLmNvbSJ9.session_sig'
117
+ jwt_refresh = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5kZXNjb3BlLmNvbSJ9.refresh_sig'
118
+
119
+ complex_headers = [
120
+ "DS=#{jwt_session}; Path=/; Domain=custom.example.com; HttpOnly; Secure; SameSite=None",
121
+ "DSR=#{jwt_refresh}; Path=/; Domain=custom.example.com; HttpOnly; Secure; SameSite=None; Max-Age=2592000"
122
+ ]
123
+ mock_headers = { 'set-cookie' => complex_headers }
124
+
125
+ result = @instance.safe_parse_json(mock_body, cookies: {}, headers: mock_headers)
126
+
127
+ expect(result['cookies']['DS']).to eq(jwt_session)
128
+ expect(result['cookies']['DSR']).to eq(jwt_refresh)
129
+ end
130
+
131
+ it 'ignores non-Descope cookies in Set-Cookie headers' do
132
+ mixed_headers = [
133
+ 'DS=session_token; Path=/; Domain=dev.example.com; HttpOnly',
134
+ 'CLOUDFLARE_SESSION=cf_token; Path=/; Domain=.example.com; HttpOnly',
135
+ 'DSR=refresh_token; Path=/; Domain=dev.example.com; HttpOnly'
136
+ ]
137
+ mock_headers = { 'set-cookie' => mixed_headers }
138
+
139
+ result = @instance.safe_parse_json(mock_body, cookies: {}, headers: mock_headers)
140
+
141
+ expect(result['cookies']['DS']).to eq('session_token')
142
+ expect(result['cookies']['DSR']).to eq('refresh_token')
143
+ expect(result['cookies']['CLOUDFLARE_SESSION']).to be_nil
144
+ end
145
+ end
146
+
147
+ context 'when no cookies are available anywhere' do
148
+ it 'does not add cookies key to response' do
149
+ result = @instance.safe_parse_json(mock_body, cookies: {}, headers: {})
150
+
151
+ expect(result).not_to have_key('cookies')
152
+ end
153
+
154
+ it 'handles missing Set-Cookie headers gracefully' do
155
+ mock_headers = { 'content-type' => 'application/json' }
156
+
157
+ result = @instance.safe_parse_json(mock_body, cookies: {}, headers: mock_headers)
158
+
159
+ expect(result).not_to have_key('cookies')
160
+ expect(result['userId']).to eq('test123')
161
+ end
162
+
163
+ it 'handles nil headers gracefully' do
164
+ result = @instance.safe_parse_json(mock_body, cookies: {}, headers: nil)
165
+
166
+ expect(result).not_to have_key('cookies')
167
+ expect(result['userId']).to eq('test123')
168
+ end
169
+ end
170
+
171
+ context 'edge cases' do
172
+ it 'handles malformed Set-Cookie headers' do
173
+ malformed_headers = [
174
+ 'MALFORMED_COOKIE_NO_EQUALS',
175
+ 'DS=; Path=/; Domain=example.com', # Empty value
176
+ '=value_without_name; Path=/', # No name
177
+ ]
178
+ mock_headers = { 'set-cookie' => malformed_headers }
179
+
180
+ result = @instance.safe_parse_json(mock_body, cookies: {}, headers: mock_headers)
181
+
182
+ # Should handle gracefully and not crash
183
+ expect(result['userId']).to eq('test123')
184
+ end
185
+
186
+ it 'prefers RestClient cookies over Set-Cookie headers when both available' do
187
+ mock_cookies = { 'DS' => 'restclient_token' }
188
+ set_cookie_headers = ['DS=header_token; Path=/; Domain=example.com']
189
+ mock_headers = { 'set-cookie' => set_cookie_headers }
190
+
191
+ result = @instance.safe_parse_json(mock_body, cookies: mock_cookies, headers: mock_headers)
192
+
193
+ # Should prefer RestClient cookies
194
+ expect(result['cookies']['DS']).to eq('restclient_token')
195
+ end
196
+ end
197
+ end
198
+
199
+ describe 'integration with request method' do
200
+ it 'passes headers parameter to safe_parse_json' do
201
+ # Mock RestClient response with custom domain cookies
202
+ mock_response = double('response')
203
+ allow(mock_response).to receive(:code).and_return(200)
204
+ allow(mock_response).to receive(:body).and_return('{"success": true}')
205
+ allow(mock_response).to receive(:cookies).and_return({})
206
+ allow(mock_response).to receive(:headers).and_return({
207
+ 'set-cookie' => ['DS=test_token; Domain=custom.example.com']
208
+ })
209
+
210
+ allow(@instance).to receive(:call).and_return(mock_response)
211
+
212
+ result = @instance.request(:get, '/test', {}, {})
213
+
214
+ expect(result['cookies']).to_not be_nil
215
+ expect(result['cookies']['DS']).to eq('test_token')
216
+ end
217
+ end
218
+ end
@@ -5,7 +5,6 @@ module Configuration
5
5
  module_function
6
6
 
7
7
  def config
8
- raise 'DESCOPE_MANAGEMENT_KEY is not set' if ENV['DESCOPE_MANAGEMENT_KEY'].nil?
9
8
  raise 'DESCOPE_PROJECT_ID is not set' if ENV['DESCOPE_PROJECT_ID'].nil?
10
9
 
11
10
  {
@@ -29,4 +29,10 @@ module SpecUtils
29
29
  value.each { |v| deep_stringify_keys(v) if v.is_a? Hash } if value.is_a? Array
30
30
  end
31
31
  end
32
+
33
+ def build_prefix
34
+ # Use GITHUB_RUN_NUMBER as the primary identifier, fall back to a timestamp if not available
35
+ prefix = ENV['GITHUB_RUN_NUMBER'] || ENV['GITHUB_RUN_ID']
36
+ prefix ? "build#{prefix}-" : "local-#{Time.now.to_i}-"
37
+ end
32
38
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: descope
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 1.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Descope Inc.
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-04-17 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: addressable
@@ -277,6 +276,7 @@ files:
277
276
  - lib/descope/api/v1/management/project.rb
278
277
  - lib/descope/api/v1/management/role.rb
279
278
  - lib/descope/api/v1/management/scim.rb
279
+ - lib/descope/api/v1/management/sso_application.rb
280
280
  - lib/descope/api/v1/management/sso_settings.rb
281
281
  - lib/descope/api/v1/management/tenant.rb
282
282
  - lib/descope/api/v1/management/user.rb
@@ -294,11 +294,15 @@ files:
294
294
  - lib/descope_client.rb
295
295
  - release-please-config.json
296
296
  - renovate.json
297
+ - spec/descope/api/v1/auth_spec.rb
298
+ - spec/descope/api/v1/auth_token_extraction_spec.rb
299
+ - spec/descope/api/v1/session_refresh_spec.rb
297
300
  - spec/factories/user.rb
298
301
  - spec/integration/lib.descope/api/v1/auth/enchantedlink_spec.rb
299
302
  - spec/integration/lib.descope/api/v1/auth/magiclink_spec.rb
300
303
  - spec/integration/lib.descope/api/v1/auth/otp_spec.rb
301
304
  - spec/integration/lib.descope/api/v1/auth/password_spec.rb
305
+ - spec/integration/lib.descope/api/v1/auth/session_spec.rb
302
306
  - spec/integration/lib.descope/api/v1/auth/totp_spec.rb
303
307
  - spec/integration/lib.descope/api/v1/management/access_key_spec.rb
304
308
  - spec/integration/lib.descope/api/v1/management/audit_spec.rb
@@ -316,6 +320,7 @@ files:
316
320
  - spec/lib.descope/api/v1/auth/saml_spec.rb
317
321
  - spec/lib.descope/api/v1/auth/totp_spec.rb
318
322
  - spec/lib.descope/api/v1/auth_spec.rb
323
+ - spec/lib.descope/api/v1/cookie_domain_fix_integration_spec.rb
319
324
  - spec/lib.descope/api/v1/management/access_key_spec.rb
320
325
  - spec/lib.descope/api/v1/management/audit_spec.rb
321
326
  - spec/lib.descope/api/v1/management/authz_spec.rb
@@ -325,11 +330,13 @@ files:
325
330
  - spec/lib.descope/api/v1/management/project_spec.rb
326
331
  - spec/lib.descope/api/v1/management/role_spec.rb
327
332
  - spec/lib.descope/api/v1/management/scim_spec.rb
333
+ - spec/lib.descope/api/v1/management/sso_application_spec.rb
328
334
  - spec/lib.descope/api/v1/management/sso_settings_spec.rb
329
335
  - spec/lib.descope/api/v1/management/tenant_spec.rb
330
336
  - spec/lib.descope/api/v1/management/user_spec.rb
331
337
  - spec/lib.descope/api/v1/session_spec.rb
332
338
  - spec/lib.descope/client_spec.rb
339
+ - spec/lib.descope/mixins/http_spec.rb
333
340
  - spec/spec_helper.rb
334
341
  - spec/support/client_config.rb
335
342
  - spec/support/dummy_class.rb
@@ -339,10 +346,9 @@ licenses:
339
346
  - MIT
340
347
  metadata:
341
348
  bug_tracker_uri: https://github.com/descope/descope-ruby-sdk/issues
342
- changelog_uri: https://github.com/descope/descope-ruby-sdk/releases/tag/1.0.6
349
+ changelog_uri: https://github.com/descope/descope-ruby-sdk/releases/tag/1.0.7
343
350
  documentation_uri: https://docs.descope.com
344
- source_code_uri: https://github.com/descope/descope-ruby-sdk/tree/1.0.6
345
- post_install_message:
351
+ source_code_uri: https://github.com/descope/descope-ruby-sdk/tree/1.0.7
346
352
  rdoc_options: []
347
353
  require_paths:
348
354
  - lib
@@ -357,8 +363,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
357
363
  - !ruby/object:Gem::Version
358
364
  version: '3.5'
359
365
  requirements: []
360
- rubygems_version: 3.5.3
361
- signing_key:
366
+ rubygems_version: 3.6.9
362
367
  specification_version: 4
363
368
  summary: Descope Ruby API Client
364
369
  test_files: []