workosv2 2.15.0

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 (242) hide show
  1. checksums.yaml +7 -0
  2. data/.github/CODEOWNERS +5 -0
  3. data/.github/pull_request_template.md +11 -0
  4. data/.github/renovate.json +5 -0
  5. data/.gitignore +49 -0
  6. data/.rspec +1 -0
  7. data/.rubocop.yml +24 -0
  8. data/.ruby-version +1 -0
  9. data/.semaphore/rubygems.yml +24 -0
  10. data/.semaphore/semaphore.yml +51 -0
  11. data/Gemfile +5 -0
  12. data/Gemfile.lock +126 -0
  13. data/Gemfile.lock.old +127 -0
  14. data/LICENSE +21 -0
  15. data/README.md +53 -0
  16. data/bin/build +3 -0
  17. data/bin/console +3 -0
  18. data/bin/docs +5 -0
  19. data/bin/publish +3 -0
  20. data/bin/tapioca +29 -0
  21. data/codecov.yml +12 -0
  22. data/docs/WorkOS/APIError.html +160 -0
  23. data/docs/WorkOS/AuditLog.html +235 -0
  24. data/docs/WorkOS/AuditTrail.html +235 -0
  25. data/docs/WorkOS/AuthenticationError.html +160 -0
  26. data/docs/WorkOS/Base.html +287 -0
  27. data/docs/WorkOS/Client.html +504 -0
  28. data/docs/WorkOS/InvalidRequestError.html +160 -0
  29. data/docs/WorkOS/Profile.html +788 -0
  30. data/docs/WorkOS/RequestError.html +135 -0
  31. data/docs/WorkOS/SSO.html +691 -0
  32. data/docs/WorkOS/Types/ProfileStruct.html +135 -0
  33. data/docs/WorkOS/Types/Provider.html +135 -0
  34. data/docs/WorkOS/Types.html +128 -0
  35. data/docs/WorkOS/WorkOSError.html +447 -0
  36. data/docs/WorkOS.html +324 -0
  37. data/docs/class_list.html +51 -0
  38. data/docs/css/common.css +1 -0
  39. data/docs/css/full_list.css +58 -0
  40. data/docs/css/style.css +496 -0
  41. data/docs/file.README.html +252 -0
  42. data/docs/file_list.html +56 -0
  43. data/docs/frames.html +17 -0
  44. data/docs/index.html +250 -0
  45. data/docs/js/app.js +314 -0
  46. data/docs/js/full_list.js +216 -0
  47. data/docs/js/jquery.js +4 -0
  48. data/docs/method_list.html +267 -0
  49. data/docs/top-level-namespace.html +110 -0
  50. data/lib/workosv2/audit_log_export.rb +55 -0
  51. data/lib/workosv2/audit_logs.rb +114 -0
  52. data/lib/workosv2/audit_trail.rb +111 -0
  53. data/lib/workosv2/challenge.rb +55 -0
  54. data/lib/workosv2/client.rb +186 -0
  55. data/lib/workosv2/configuration.rb +17 -0
  56. data/lib/workosv2/connection.rb +66 -0
  57. data/lib/workosv2/deprecated_hash_wrapper.rb +76 -0
  58. data/lib/workosv2/directory.rb +65 -0
  59. data/lib/workosv2/directory_group.rb +68 -0
  60. data/lib/workosv2/directory_sync.rb +218 -0
  61. data/lib/workosv2/directory_user.rb +97 -0
  62. data/lib/workosv2/errors.rb +81 -0
  63. data/lib/workosv2/event.rb +51 -0
  64. data/lib/workosv2/events.rb +52 -0
  65. data/lib/workosv2/factor.rb +54 -0
  66. data/lib/workosv2/hash_provider.rb +19 -0
  67. data/lib/workosv2/mfa.rb +178 -0
  68. data/lib/workosv2/organization.rb +57 -0
  69. data/lib/workosv2/organizations.rb +188 -0
  70. data/lib/workosv2/passwordless.rb +85 -0
  71. data/lib/workosv2/portal.rb +66 -0
  72. data/lib/workosv2/profile.rb +76 -0
  73. data/lib/workosv2/profile_and_token.rb +29 -0
  74. data/lib/workosv2/sso.rb +297 -0
  75. data/lib/workosv2/types/audit_log_export_struct.rb +17 -0
  76. data/lib/workosv2/types/challenge_struct.rb +18 -0
  77. data/lib/workosv2/types/connection_struct.rb +20 -0
  78. data/lib/workosv2/types/directory_group_struct.rb +19 -0
  79. data/lib/workosv2/types/directory_struct.rb +19 -0
  80. data/lib/workosv2/types/directory_user_struct.rb +26 -0
  81. data/lib/workosv2/types/event_struct.rb +15 -0
  82. data/lib/workosv2/types/factor_struct.rb +18 -0
  83. data/lib/workosv2/types/intent_enum.rb +17 -0
  84. data/lib/workosv2/types/list_struct.rb +13 -0
  85. data/lib/workosv2/types/organization_struct.rb +17 -0
  86. data/lib/workosv2/types/passwordless_session_struct.rb +17 -0
  87. data/lib/workosv2/types/profile_struct.rb +21 -0
  88. data/lib/workosv2/types/provider_enum.rb +15 -0
  89. data/lib/workosv2/types/verify_challenge_struct.rb +13 -0
  90. data/lib/workosv2/types/webhook_struct.rb +15 -0
  91. data/lib/workosv2/types.rb +25 -0
  92. data/lib/workosv2/verify_challenge.rb +39 -0
  93. data/lib/workosv2/version.rb +6 -0
  94. data/lib/workosv2/webhook.rb +51 -0
  95. data/lib/workosv2/webhooks.rb +217 -0
  96. data/lib/workosv2.rb +79 -0
  97. data/sorbet/config +2 -0
  98. data/sorbet/rbi/gems/addressable@2.8.0.rbi +290 -0
  99. data/sorbet/rbi/gems/ast@2.4.2.rbi +54 -0
  100. data/sorbet/rbi/gems/codecov@0.2.12.rbi +55 -0
  101. data/sorbet/rbi/gems/coderay@1.1.3.rbi +8 -0
  102. data/sorbet/rbi/gems/crack@0.4.5.rbi +57 -0
  103. data/sorbet/rbi/gems/diff-lcs@1.4.4.rbi +185 -0
  104. data/sorbet/rbi/gems/docile@1.3.5.rbi +54 -0
  105. data/sorbet/rbi/gems/hashdiff@1.0.1.rbi +82 -0
  106. data/sorbet/rbi/gems/json@2.5.1.rbi +109 -0
  107. data/sorbet/rbi/gems/method_source@1.0.0.rbi +8 -0
  108. data/sorbet/rbi/gems/parallel@1.20.1.rbi +113 -0
  109. data/sorbet/rbi/gems/parser@3.0.1.0.rbi +1187 -0
  110. data/sorbet/rbi/gems/pry@0.14.2.rbi +8 -0
  111. data/sorbet/rbi/gems/public_suffix@4.0.6.rbi +146 -0
  112. data/sorbet/rbi/gems/rainbow@3.0.0.rbi +153 -0
  113. data/sorbet/rbi/gems/rake@13.0.3.rbi +807 -0
  114. data/sorbet/rbi/gems/rbi@0.0.16.rbi +2118 -0
  115. data/sorbet/rbi/gems/regexp_parser@2.1.1.rbi +1117 -0
  116. data/sorbet/rbi/gems/rexml@3.2.5.rbi +709 -0
  117. data/sorbet/rbi/gems/rspec-core@3.9.3.rbi +2467 -0
  118. data/sorbet/rbi/gems/rspec-expectations@3.9.4.rbi +1569 -0
  119. data/sorbet/rbi/gems/rspec-mocks@3.9.1.rbi +1493 -0
  120. data/sorbet/rbi/gems/rspec-support@3.9.4.rbi +511 -0
  121. data/sorbet/rbi/gems/rspec@3.9.0.rbi +38 -0
  122. data/sorbet/rbi/gems/rubocop-ast@1.4.1.rbi +1881 -0
  123. data/sorbet/rbi/gems/rubocop@0.93.1.rbi +11497 -0
  124. data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +405 -0
  125. data/sorbet/rbi/gems/simplecov-html@0.12.3.rbi +89 -0
  126. data/sorbet/rbi/gems/simplecov@0.21.2.rbi +577 -0
  127. data/sorbet/rbi/gems/simplecov_json_formatter@0.1.2.rbi +8 -0
  128. data/sorbet/rbi/gems/spoom@1.1.15.rbi +1549 -0
  129. data/sorbet/rbi/gems/tapioca@0.7.3.rbi +1718 -0
  130. data/sorbet/rbi/gems/thor@1.2.1.rbi +844 -0
  131. data/sorbet/rbi/gems/unicode-display_width@1.7.0.rbi +22 -0
  132. data/sorbet/rbi/gems/unparser@0.6.2.rbi +8 -0
  133. data/sorbet/rbi/gems/vcr@5.0.0.rbi +699 -0
  134. data/sorbet/rbi/gems/webmock@3.12.2.rbi +662 -0
  135. data/sorbet/rbi/gems/yard-sorbet@0.8.0.rbi +268 -0
  136. data/sorbet/rbi/gems/yard@0.9.26.rbi +4048 -0
  137. data/sorbet/tapioca/config.yml +13 -0
  138. data/sorbet/tapioca/require.rb +4 -0
  139. data/spec/lib/workos/audit_logs_spec.rb +151 -0
  140. data/spec/lib/workos/audit_trail_spec.rb +146 -0
  141. data/spec/lib/workos/configuration_spec.rb +61 -0
  142. data/spec/lib/workos/directory_sync_spec.rb +492 -0
  143. data/spec/lib/workos/directory_user_spec.rb +36 -0
  144. data/spec/lib/workos/event_spec.rb +88 -0
  145. data/spec/lib/workos/mfa_spec.rb +281 -0
  146. data/spec/lib/workos/organizations_spec.rb +257 -0
  147. data/spec/lib/workos/passwordless_spec.rb +77 -0
  148. data/spec/lib/workos/portal_spec.rb +87 -0
  149. data/spec/lib/workos/sso_spec.rb +650 -0
  150. data/spec/lib/workos/webhooks_spec.rb +236 -0
  151. data/spec/spec_helper.rb +56 -0
  152. data/spec/support/fixtures/vcr_cassettes/audit_logs/create_event.yml +59 -0
  153. data/spec/support/fixtures/vcr_cassettes/audit_logs/create_event_custom_idempotency_key.yml +60 -0
  154. data/spec/support/fixtures/vcr_cassettes/audit_logs/create_event_invalid.yml +59 -0
  155. data/spec/support/fixtures/vcr_cassettes/audit_logs/create_export.yml +76 -0
  156. data/spec/support/fixtures/vcr_cassettes/audit_logs/create_export_with_filters.yml +77 -0
  157. data/spec/support/fixtures/vcr_cassettes/audit_logs/get_export.yml +73 -0
  158. data/spec/support/fixtures/vcr_cassettes/audit_trail/create_event.yml +65 -0
  159. data/spec/support/fixtures/vcr_cassettes/audit_trail/create_event_custom_idempotency_key.yml +67 -0
  160. data/spec/support/fixtures/vcr_cassettes/audit_trail/create_event_invalid.yml +68 -0
  161. data/spec/support/fixtures/vcr_cassettes/audit_trail/create_events_duplicate_idempotency_key_and_payload.yml +131 -0
  162. data/spec/support/fixtures/vcr_cassettes/audit_trail/create_events_duplicate_idempotency_key_different_payload.yml +134 -0
  163. data/spec/support/fixtures/vcr_cassettes/audit_trail/get_events.yml +61 -0
  164. data/spec/support/fixtures/vcr_cassettes/base/execute_request_unauthenticated.yml +66 -0
  165. data/spec/support/fixtures/vcr_cassettes/directory_sync/delete_directory.yml +72 -0
  166. data/spec/support/fixtures/vcr_cassettes/directory_sync/get_directory_with_invalid_id.yml +83 -0
  167. data/spec/support/fixtures/vcr_cassettes/directory_sync/get_directory_with_valid_id.yml +84 -0
  168. data/spec/support/fixtures/vcr_cassettes/directory_sync/get_group.yml +80 -0
  169. data/spec/support/fixtures/vcr_cassettes/directory_sync/get_group_with_invalid_id.yml +62 -0
  170. data/spec/support/fixtures/vcr_cassettes/directory_sync/get_user.yml +83 -0
  171. data/spec/support/fixtures/vcr_cassettes/directory_sync/get_user_with_invalid_id.yml +62 -0
  172. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_after.yml +87 -0
  173. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_before.yml +89 -0
  174. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_domain.yml +84 -0
  175. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_limit.yml +85 -0
  176. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_no_options.yml +93 -0
  177. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_search.yml +85 -0
  178. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_groups/with_after.yml +90 -0
  179. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_groups/with_before.yml +90 -0
  180. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_groups/with_directory.yml +90 -0
  181. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_groups/with_limit.yml +84 -0
  182. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_groups/with_no_options.yml +84 -0
  183. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_groups/with_user.yml +82 -0
  184. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_after.yml +186 -0
  185. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_before.yml +88 -0
  186. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_directory.yml +194 -0
  187. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_group.yml +186 -0
  188. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_limit.yml +189 -0
  189. data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_no_options.yml +74 -0
  190. data/spec/support/fixtures/vcr_cassettes/events/list_events_with_after.yml +80 -0
  191. data/spec/support/fixtures/vcr_cassettes/events/list_events_with_event.yml +80 -0
  192. data/spec/support/fixtures/vcr_cassettes/events/list_events_with_no_options.yml +80 -0
  193. data/spec/support/fixtures/vcr_cassettes/events/list_events_with_range.yml +80 -0
  194. data/spec/support/fixtures/vcr_cassettes/mfa/challenge_factor_generic_valid.yml +82 -0
  195. data/spec/support/fixtures/vcr_cassettes/mfa/challenge_factor_sms_valid.yml +82 -0
  196. data/spec/support/fixtures/vcr_cassettes/mfa/challenge_factor_totp_valid.yml +82 -0
  197. data/spec/support/fixtures/vcr_cassettes/mfa/delete_factor.yml +80 -0
  198. data/spec/support/fixtures/vcr_cassettes/mfa/enroll_factor_generic_valid.yml +82 -0
  199. data/spec/support/fixtures/vcr_cassettes/mfa/enroll_factor_sms_valid.yml +82 -0
  200. data/spec/support/fixtures/vcr_cassettes/mfa/enroll_factor_totp_valid.yml +82 -0
  201. data/spec/support/fixtures/vcr_cassettes/mfa/get_factor_invalid.yml +82 -0
  202. data/spec/support/fixtures/vcr_cassettes/mfa/get_factor_valid.yml +82 -0
  203. data/spec/support/fixtures/vcr_cassettes/mfa/verify_challenge_generic_expired.yml +84 -0
  204. data/spec/support/fixtures/vcr_cassettes/mfa/verify_challenge_generic_invalid.yml +84 -0
  205. data/spec/support/fixtures/vcr_cassettes/mfa/verify_challenge_generic_valid.yml +82 -0
  206. data/spec/support/fixtures/vcr_cassettes/mfa/verify_challenge_generic_valid_is_false.yml +82 -0
  207. data/spec/support/fixtures/vcr_cassettes/organization/create.yml +84 -0
  208. data/spec/support/fixtures/vcr_cassettes/organization/create_invalid.yml +72 -0
  209. data/spec/support/fixtures/vcr_cassettes/organization/create_with_duplicate_idempotency_key_and_different_payload.yml +155 -0
  210. data/spec/support/fixtures/vcr_cassettes/organization/create_with_duplicate_idempotency_key_and_payload.yml +154 -0
  211. data/spec/support/fixtures/vcr_cassettes/organization/create_with_idempotency_key.yml +79 -0
  212. data/spec/support/fixtures/vcr_cassettes/organization/delete.yml +72 -0
  213. data/spec/support/fixtures/vcr_cassettes/organization/delete_invalid.yml +72 -0
  214. data/spec/support/fixtures/vcr_cassettes/organization/get.yml +84 -0
  215. data/spec/support/fixtures/vcr_cassettes/organization/get_invalid.yml +72 -0
  216. data/spec/support/fixtures/vcr_cassettes/organization/list.yml +87 -0
  217. data/spec/support/fixtures/vcr_cassettes/organization/update.yml +84 -0
  218. data/spec/support/fixtures/vcr_cassettes/passwordless/create_session.yml +72 -0
  219. data/spec/support/fixtures/vcr_cassettes/passwordless/create_session_invalid.yml +73 -0
  220. data/spec/support/fixtures/vcr_cassettes/passwordless/send_session.yml +72 -0
  221. data/spec/support/fixtures/vcr_cassettes/passwordless/send_session_invalid.yml +73 -0
  222. data/spec/support/fixtures/vcr_cassettes/portal/generate_link_audit_logs.yml +72 -0
  223. data/spec/support/fixtures/vcr_cassettes/portal/generate_link_dsync.yml +72 -0
  224. data/spec/support/fixtures/vcr_cassettes/portal/generate_link_invalid.yml +72 -0
  225. data/spec/support/fixtures/vcr_cassettes/portal/generate_link_sso.yml +72 -0
  226. data/spec/support/fixtures/vcr_cassettes/sso/delete_connection_with_invalid_id.yml +72 -0
  227. data/spec/support/fixtures/vcr_cassettes/sso/delete_connection_with_valid_id.yml +70 -0
  228. data/spec/support/fixtures/vcr_cassettes/sso/get_connection_with_invalid_id.yml +72 -0
  229. data/spec/support/fixtures/vcr_cassettes/sso/get_connection_with_valid_id.yml +86 -0
  230. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_after.yml +83 -0
  231. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_before.yml +86 -0
  232. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_connection_type.yml +90 -0
  233. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_domain.yml +86 -0
  234. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_limit.yml +83 -0
  235. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_no_options.yml +89 -0
  236. data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_organization_id.yml +86 -0
  237. data/spec/support/fixtures/vcr_cassettes/sso/profile.yml +74 -0
  238. data/spec/support/profile.txt +1 -0
  239. data/spec/support/shared_examples/client_spec.rb +30 -0
  240. data/spec/support/webhook_payload.txt +1 -0
  241. data/workosv2.gemspec +38 -0
  242. metadata +531 -0
@@ -0,0 +1,650 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ require 'securerandom'
5
+
6
+ describe WorkOSV2::SSO do
7
+ it_behaves_like 'client'
8
+
9
+ describe '.authorization_url' do
10
+ context 'with a domain' do
11
+ let(:args) do
12
+ {
13
+ domain: 'foo.com',
14
+ client_id: 'workos-proj-123',
15
+ redirect_uri: 'foo.com/auth/callback',
16
+ state: {
17
+ next_page: '/dashboard/edit',
18
+ }.to_s,
19
+ }
20
+ end
21
+ it 'returns a valid URL' do
22
+ authorization_url = described_class.authorization_url(**args)
23
+ expect(URI.parse(authorization_url)).to be_a URI
24
+ end
25
+
26
+ it 'returns the expected hostname' do
27
+ authorization_url = described_class.authorization_url(**args)
28
+
29
+ expect(URI.parse(authorization_url).host).to eq(WorkOSV2::API_HOSTNAME)
30
+ end
31
+
32
+ it 'returns the expected query string' do
33
+ authorization_url = described_class.authorization_url(**args)
34
+
35
+ expect(URI.parse(authorization_url).query).to eq(
36
+ 'client_id=workos-proj-123&redirect_uri=foo.com%2Fauth%2Fcallback' \
37
+ '&response_type=code&state=%7B%3Anext_page%3D%3E%22%2Fdashboard%2F' \
38
+ 'edit%22%7D&domain=foo.com',
39
+ )
40
+ end
41
+ end
42
+
43
+ context 'with a provider' do
44
+ let(:args) do
45
+ {
46
+ provider: 'GoogleOAuth',
47
+ client_id: 'workos-proj-123',
48
+ redirect_uri: 'foo.com/auth/callback',
49
+ state: {
50
+ next_page: '/dashboard/edit',
51
+ }.to_s,
52
+ }
53
+ end
54
+ it 'returns a valid URL' do
55
+ authorization_url = described_class.authorization_url(**args)
56
+
57
+ expect(URI.parse(authorization_url)).to be_a URI
58
+ end
59
+
60
+ it 'returns the expected hostname' do
61
+ authorization_url = described_class.authorization_url(**args)
62
+
63
+ expect(URI.parse(authorization_url).host).to eq(WorkOSV2::API_HOSTNAME)
64
+ end
65
+
66
+ it 'returns the expected query string' do
67
+ authorization_url = described_class.authorization_url(**args)
68
+
69
+ expect(URI.parse(authorization_url).query).to eq(
70
+ 'client_id=workos-proj-123&redirect_uri=foo.com%2Fauth%2Fcallback' \
71
+ '&response_type=code&state=%7B%3Anext_page%3D%3E%22%2Fdashboard%2F' \
72
+ 'edit%22%7D&provider=GoogleOAuth',
73
+ )
74
+ end
75
+ end
76
+
77
+ context 'with a connection' do
78
+ let(:args) do
79
+ {
80
+ connection: 'connection_123',
81
+ client_id: 'workos-proj-123',
82
+ redirect_uri: 'foo.com/auth/callback',
83
+ state: {
84
+ next_page: '/dashboard/edit',
85
+ }.to_s,
86
+ }
87
+ end
88
+ it 'returns a valid URL' do
89
+ authorization_url = described_class.authorization_url(**args)
90
+
91
+ expect(URI.parse(authorization_url)).to be_a URI
92
+ end
93
+
94
+ it 'returns the expected hostname' do
95
+ authorization_url = described_class.authorization_url(**args)
96
+
97
+ expect(URI.parse(authorization_url).host).to eq(WorkOSV2::API_HOSTNAME)
98
+ end
99
+
100
+ it 'returns the expected query string' do
101
+ authorization_url = described_class.authorization_url(**args)
102
+
103
+ expect(URI.parse(authorization_url).query).to eq(
104
+ 'client_id=workos-proj-123&redirect_uri=foo.com%2Fauth%2Fcallback' \
105
+ '&response_type=code&state=%7B%3Anext_page%3D%3E%22%2Fdashboard%2F' \
106
+ 'edit%22%7D&connection=connection_123',
107
+ )
108
+ end
109
+ end
110
+
111
+ context 'with a domain' do
112
+ let(:args) do
113
+ {
114
+ domain: 'foo.com',
115
+ client_id: 'workos-proj-123',
116
+ redirect_uri: 'foo.com/auth/callback',
117
+ state: {
118
+ next_page: '/dashboard/edit',
119
+ }.to_s,
120
+ }
121
+ end
122
+ it 'returns a valid URL' do
123
+ authorization_url = described_class.authorization_url(**args)
124
+
125
+ expect(URI.parse(authorization_url)).to be_a URI
126
+ end
127
+
128
+ it 'returns the expected hostname' do
129
+ authorization_url = described_class.authorization_url(**args)
130
+
131
+ expect(URI.parse(authorization_url).host).to eq(WorkOSV2::API_HOSTNAME)
132
+ end
133
+
134
+ it 'returns the expected query string' do
135
+ authorization_url = described_class.authorization_url(**args)
136
+
137
+ expect(URI.parse(authorization_url).query).to eq(
138
+ 'client_id=workos-proj-123&redirect_uri=foo.com%2Fauth%2Fcallback' \
139
+ '&response_type=code&state=%7B%3Anext_page%3D%3E%22%2Fdashboard%2F' \
140
+ 'edit%22%7D&domain=foo.com',
141
+ )
142
+ end
143
+ end
144
+
145
+ context 'with a domain_hint' do
146
+ let(:args) do
147
+ {
148
+ connection: 'connection_123',
149
+ domain_hint: 'foo.com',
150
+ client_id: 'workos-proj-123',
151
+ redirect_uri: 'foo.com/auth/callback',
152
+ state: {
153
+ next_page: '/dashboard/edit',
154
+ }.to_s,
155
+ }
156
+ end
157
+ it 'returns a valid URL' do
158
+ authorization_url = described_class.authorization_url(**args)
159
+
160
+ expect(URI.parse(authorization_url)).to be_a URI
161
+ end
162
+
163
+ it 'returns the expected hostname' do
164
+ authorization_url = described_class.authorization_url(**args)
165
+
166
+ expect(URI.parse(authorization_url).host).to eq(WorkOSV2::API_HOSTNAME)
167
+ end
168
+
169
+ it 'returns the expected query string' do
170
+ authorization_url = described_class.authorization_url(**args)
171
+
172
+ expect(URI.parse(authorization_url).query).to eq(
173
+ 'client_id=workos-proj-123&redirect_uri=foo.com%2Fauth%2Fcallback' \
174
+ '&response_type=code&state=%7B%3Anext_page%3D%3E%22%2Fdashboard%2' \
175
+ 'Fedit%22%7D&domain_hint=foo.com&connection=connection_123',
176
+ )
177
+ end
178
+ end
179
+
180
+ context 'with a login_hint' do
181
+ let(:args) do
182
+ {
183
+ connection: 'connection_123',
184
+ login_hint: 'foo@workos.com',
185
+ client_id: 'workos-proj-123',
186
+ redirect_uri: 'foo.com/auth/callback',
187
+ state: {
188
+ next_page: '/dashboard/edit',
189
+ }.to_s,
190
+ }
191
+ end
192
+ it 'returns a valid URL' do
193
+ authorization_url = described_class.authorization_url(**args)
194
+
195
+ expect(URI.parse(authorization_url)).to be_a URI
196
+ end
197
+
198
+ it 'returns the expected hostname' do
199
+ authorization_url = described_class.authorization_url(**args)
200
+
201
+ expect(URI.parse(authorization_url).host).to eq(WorkOSV2::API_HOSTNAME)
202
+ end
203
+
204
+ it 'returns the expected query string' do
205
+ authorization_url = described_class.authorization_url(**args)
206
+
207
+ expect(URI.parse(authorization_url).query).to eq(
208
+ 'client_id=workos-proj-123&redirect_uri=foo.com%2Fauth%2Fcallback' \
209
+ '&response_type=code&state=%7B%3Anext_page%3D%3E%22%2Fdashboard%2' \
210
+ 'Fedit%22%7D&login_hint=foo%40workos.com&connection=connection_123',
211
+ )
212
+ end
213
+ end
214
+
215
+ context 'with an organization' do
216
+ let(:args) do
217
+ {
218
+ organization: 'org_123',
219
+ client_id: 'workos-proj-123',
220
+ redirect_uri: 'foo.com/auth/callback',
221
+ state: {
222
+ next_page: '/dashboard/edit',
223
+ }.to_s,
224
+ }
225
+ end
226
+ it 'returns a valid URL' do
227
+ authorization_url = described_class.authorization_url(**args)
228
+
229
+ expect(URI.parse(authorization_url)).to be_a URI
230
+ end
231
+
232
+ it 'returns the expected hostname' do
233
+ authorization_url = described_class.authorization_url(**args)
234
+
235
+ expect(URI.parse(authorization_url).host).to eq(WorkOSV2::API_HOSTNAME)
236
+ end
237
+
238
+ it 'returns the expected query string' do
239
+ authorization_url = described_class.authorization_url(**args)
240
+
241
+ expect(URI.parse(authorization_url).query).to eq(
242
+ 'client_id=workos-proj-123&redirect_uri=foo.com%2Fauth%2Fcallback' \
243
+ '&response_type=code&state=%7B%3Anext_page%3D%3E%22%2Fdashboard%2F' \
244
+ 'edit%22%7D&organization=org_123',
245
+ )
246
+ end
247
+ end
248
+
249
+ context 'with neither connection, domain, provider, or organization' do
250
+ let(:args) do
251
+ {
252
+ client_id: 'workos-proj-123',
253
+ redirect_uri: 'foo.com/auth/callback',
254
+ state: {
255
+ next_page: '/dashboard/edit',
256
+ }.to_s,
257
+ }
258
+ end
259
+ it 'raises an error' do
260
+ expect do
261
+ described_class.authorization_url(**args)
262
+ end.to raise_error(
263
+ ArgumentError,
264
+ 'Either connection, domain, provider, or organization is required.',
265
+ )
266
+ end
267
+ end
268
+
269
+ context 'with an invalid provider' do
270
+ let(:args) do
271
+ {
272
+ provider: 'Okta',
273
+ client_id: 'workos-proj-123',
274
+ redirect_uri: 'foo.com/auth/callback',
275
+ state: {
276
+ next_page: '/dashboard/edit',
277
+ }.to_s,
278
+ }
279
+ end
280
+ it 'raises an error' do
281
+ expect do
282
+ described_class.authorization_url(**args)
283
+ end.to raise_error(
284
+ ArgumentError,
285
+ 'Okta is not a valid value. `provider` must be in ["GoogleOAuth", "MicrosoftOAuth"]',
286
+ )
287
+ end
288
+ end
289
+ end
290
+
291
+ describe '.get_profile' do
292
+ # rubocop:disable Metrics/BlockLength
293
+ it 'returns a profile' do
294
+ VCR.use_cassette 'sso/profile' do
295
+ profile = described_class.get_profile(access_token: 'access_token')
296
+
297
+ expectation = {
298
+ connection_id: 'conn_01E83FVYZHY7DM4S9503JHV0R5',
299
+ connection_type: 'GoogleOAuth',
300
+ email: 'bob.loblaw@workos.com',
301
+ first_name: 'Bob',
302
+ id: 'prof_01EEJTY9SZ1R350RB7B73SNBKF',
303
+ idp_id: '116485463307139932699',
304
+ last_name: 'Loblaw',
305
+ groups: nil,
306
+ organization_id: 'org_01FG53X8636WSNW2WEKB2C31ZB',
307
+ raw_attributes: {
308
+ email: 'bob.loblaw@workos.com',
309
+ family_name: 'Loblaw',
310
+ given_name: 'Bob',
311
+ hd: 'workos.com',
312
+ id: '116485463307139932699',
313
+ locale: 'en',
314
+ name: 'Bob Loblaw',
315
+ picture: 'https://lh3.googleusercontent.com/a-/AOh14GyO2hLlgZvteDQ3Ldi3_-RteZLya0hWH7247Cam=s96-c',
316
+ verified_email: true,
317
+ },
318
+ }
319
+ expect(profile.to_json).to eq(expectation)
320
+ end
321
+ end
322
+ # rubocop:enable Metrics/BlockLength
323
+ end
324
+
325
+ describe '.profile_and_token' do
326
+ let(:args) do
327
+ {
328
+ code: SecureRandom.hex(10),
329
+ client_id: 'workos-proj-123',
330
+ }
331
+ end
332
+
333
+ let(:request_body) do
334
+ {
335
+ client_id: args[:client_id],
336
+ client_secret: WorkOSV2.config.key,
337
+ code: args[:code],
338
+ grant_type: 'authorization_code',
339
+ }
340
+ end
341
+ let(:user_agent) { 'user-agent-string' }
342
+ let(:headers) { { 'User-Agent' => user_agent } }
343
+
344
+ before do
345
+ allow(described_class).to receive(:user_agent).and_return(user_agent)
346
+ end
347
+
348
+ context 'with a successful response' do
349
+ let(:response_body) { File.read("#{SPEC_ROOT}/support/profile.txt") }
350
+
351
+ before do
352
+ stub_request(:post, 'https://api.workos.com/sso/token').
353
+ with(headers: headers, body: request_body).
354
+ to_return(status: 200, body: response_body)
355
+ end
356
+
357
+ it 'includes the SDK Version header' do
358
+ described_class.profile_and_token(**args)
359
+
360
+ expect(a_request(:post, 'https://api.workos.com/sso/token').
361
+ with(headers: headers, body: request_body)).to have_been_made
362
+ end
363
+
364
+ it 'returns a WorkOSV2::ProfileAndToken' do
365
+ profile_and_token = described_class.profile_and_token(**args)
366
+ expect(profile_and_token).to be_a(WorkOSV2::ProfileAndToken)
367
+
368
+ expectation = {
369
+ connection_id: 'conn_01EMH8WAK20T42N2NBMNBCYHAG',
370
+ connection_type: 'OktaSAML',
371
+ email: 'demo@workos-okta.com',
372
+ first_name: 'WorkOSV2',
373
+ id: 'prof_01DRA1XNSJDZ19A31F183ECQW5',
374
+ idp_id: '00u1klkowm8EGah2H357',
375
+ last_name: 'Demo',
376
+ groups: %w[Admins Developers],
377
+ organization_id: 'org_01FG53X8636WSNW2WEKB2C31ZB',
378
+ raw_attributes: {
379
+ email: 'demo@workos-okta.com',
380
+ first_name: 'WorkOSV2',
381
+ id: 'prof_01DRA1XNSJDZ19A31F183ECQW5',
382
+ idp_id: '00u1klkowm8EGah2H357',
383
+ last_name: 'Demo',
384
+ groups: %w[Admins Developers],
385
+ },
386
+ }
387
+
388
+ expect(profile_and_token.access_token).to eq('01DVX6QBS3EG6FHY2ESAA5Q65X')
389
+ expect(profile_and_token.profile.to_json).to eq(expectation)
390
+ end
391
+ end
392
+
393
+ context 'with an unprocessable request' do
394
+ before do
395
+ stub_request(:post, 'https://api.workos.com/sso/token').
396
+ with(headers: headers, body: request_body).
397
+ to_return(
398
+ headers: { 'X-Request-ID' => 'request-id' },
399
+ status: 422,
400
+ body: { "error": 'some error', "error_description": 'some error description' }.to_json,
401
+ )
402
+ end
403
+
404
+ it 'raises an exception with request ID' do
405
+ expect do
406
+ described_class.profile_and_token(**args)
407
+ end.to raise_error(
408
+ WorkOSV2::APIError,
409
+ 'error: some error, error_description: some error description - request ID: request-id',
410
+ )
411
+ end
412
+ end
413
+
414
+ context 'with an expired code' do
415
+ before do
416
+ stub_request(:post, 'https://api.workos.com/sso/token').
417
+ with(body: request_body).
418
+ to_return(
419
+ status: 400,
420
+ headers: { 'X-Request-ID' => 'request-id' },
421
+ body: {
422
+ "error": 'invalid_grant',
423
+ "error_description": "The code '01DVX3C5Z367SFHR8QNDMK7V24' has expired or is invalid.",
424
+ }.to_json,
425
+ )
426
+ end
427
+
428
+ it 'raises an exception' do
429
+ expect do
430
+ described_class.profile_and_token(**args)
431
+ end.to raise_error(
432
+ WorkOSV2::APIError,
433
+ "error: invalid_grant, error_description: The code '01DVX3C5Z367SFHR8QNDMK7V24'" \
434
+ ' has expired or is invalid. - request ID: request-id',
435
+ )
436
+ end
437
+ end
438
+ end
439
+
440
+ describe '.list_connections' do
441
+ context 'with no options' do
442
+ it 'returns connections and metadata' do
443
+ expected_metadata = {
444
+ 'after' => nil,
445
+ 'before' => 'before_id',
446
+ }
447
+
448
+ VCR.use_cassette 'sso/list_connections/with_no_options' do
449
+ connections = described_class.list_connections
450
+
451
+ expect(connections.data.size).to eq(6)
452
+ expect(connections.list_metadata).to eq(expected_metadata)
453
+ end
454
+ end
455
+ end
456
+
457
+ context 'with connection_type option' do
458
+ it 'forms the proper request to the API' do
459
+ request_args = [
460
+ '/connections?connection_type=OktaSAML',
461
+ 'Content-Type' => 'application/json'
462
+ ]
463
+
464
+ expected_request = Net::HTTP::Get.new(*request_args)
465
+
466
+ expect(Net::HTTP::Get).to receive(:new).with(*request_args).
467
+ and_return(expected_request)
468
+
469
+ VCR.use_cassette 'sso/list_connections/with_connection_type' do
470
+ connections = described_class.list_connections(
471
+ connection_type: 'OktaSAML',
472
+ )
473
+
474
+ expect(connections.data.size).to eq(10)
475
+ expect(connections.data.first.connection_type).to eq('OktaSAML')
476
+ end
477
+ end
478
+ end
479
+
480
+ context 'with domain option' do
481
+ it 'forms the proper request to the API' do
482
+ request_args = [
483
+ '/connections?domain=foo-corp.com',
484
+ 'Content-Type' => 'application/json'
485
+ ]
486
+
487
+ expected_request = Net::HTTP::Get.new(*request_args)
488
+
489
+ expect(Net::HTTP::Get).to receive(:new).with(*request_args).
490
+ and_return(expected_request)
491
+
492
+ VCR.use_cassette 'sso/list_connections/with_domain' do
493
+ connections = described_class.list_connections(
494
+ domain: 'foo-corp.com',
495
+ )
496
+
497
+ expect(connections.data.size).to eq(1)
498
+ end
499
+ end
500
+ end
501
+
502
+ context 'with organization_id option' do
503
+ it 'forms the proper request to the API' do
504
+ request_args = [
505
+ '/connections?organization_id=org_01F9293WD2PDEEV4Y625XPZVG7',
506
+ 'Content-Type' => 'application/json'
507
+ ]
508
+
509
+ expected_request = Net::HTTP::Get.new(*request_args)
510
+
511
+ expect(Net::HTTP::Get).to receive(:new).with(*request_args).
512
+ and_return(expected_request)
513
+
514
+ VCR.use_cassette 'sso/list_connections/with_organization_id' do
515
+ connections = described_class.list_connections(
516
+ organization_id: 'org_01F9293WD2PDEEV4Y625XPZVG7',
517
+ )
518
+
519
+ expect(connections.data.size).to eq(1)
520
+ expect(connections.data.first.organization_id).to eq(
521
+ 'org_01F9293WD2PDEEV4Y625XPZVG7',
522
+ )
523
+ end
524
+ end
525
+ end
526
+
527
+ context 'with limit option' do
528
+ it 'forms the proper request to the API' do
529
+ request_args = [
530
+ '/connections?limit=2',
531
+ 'Content-Type' => 'application/json'
532
+ ]
533
+
534
+ expected_request = Net::HTTP::Get.new(*request_args)
535
+
536
+ expect(Net::HTTP::Get).to receive(:new).with(*request_args).
537
+ and_return(expected_request)
538
+
539
+ VCR.use_cassette 'sso/list_connections/with_limit' do
540
+ connections = described_class.list_connections(
541
+ limit: 2,
542
+ )
543
+
544
+ expect(connections.data.size).to eq(2)
545
+ end
546
+ end
547
+ end
548
+
549
+ context 'with before option' do
550
+ it 'forms the proper request to the API' do
551
+ request_args = [
552
+ '/connections?before=conn_01FA3WGCWPCCY1V2FGES2FDNP7',
553
+ 'Content-Type' => 'application/json'
554
+ ]
555
+
556
+ expected_request = Net::HTTP::Get.new(*request_args)
557
+
558
+ expect(Net::HTTP::Get).to receive(:new).with(*request_args).
559
+ and_return(expected_request)
560
+
561
+ VCR.use_cassette 'sso/list_connections/with_before' do
562
+ connections = described_class.list_connections(
563
+ before: 'conn_01FA3WGCWPCCY1V2FGES2FDNP7',
564
+ )
565
+
566
+ expect(connections.data.size).to eq(3)
567
+ end
568
+ end
569
+ end
570
+
571
+ context 'with after option' do
572
+ it 'forms the proper request to the API' do
573
+ request_args = [
574
+ '/connections?after=conn_01FA3WGCWPCCY1V2FGES2FDNP7',
575
+ 'Content-Type' => 'application/json'
576
+ ]
577
+
578
+ expected_request = Net::HTTP::Get.new(*request_args)
579
+
580
+ expect(Net::HTTP::Get).to receive(:new).with(*request_args).
581
+ and_return(expected_request)
582
+
583
+ VCR.use_cassette 'sso/list_connections/with_after' do
584
+ connections = described_class.list_connections(
585
+ after: 'conn_01FA3WGCWPCCY1V2FGES2FDNP7',
586
+ )
587
+
588
+ expect(connections.data.size).to eq(2)
589
+ end
590
+ end
591
+ end
592
+ end
593
+
594
+ describe '.get_connection' do
595
+ context 'with a valid id' do
596
+ it 'gets the connection details' do
597
+ VCR.use_cassette('sso/get_connection_with_valid_id') do
598
+ connection = WorkOSV2::SSO.get_connection(
599
+ id: 'conn_01FA3WGCWPCCY1V2FGES2FDNP7',
600
+ )
601
+
602
+ expect(connection.id).to eq('conn_01FA3WGCWPCCY1V2FGES2FDNP7')
603
+ expect(connection.connection_type).to eq('OktaSAML')
604
+ expect(connection.name).to eq('Foo Corp')
605
+ expect(connection.domains.first[:domain]).to eq('foo-corp.com')
606
+ end
607
+ end
608
+ end
609
+
610
+ context 'with an invalid id' do
611
+ it 'raises an error' do
612
+ VCR.use_cassette('sso/get_connection_with_invalid_id') do
613
+ expect do
614
+ WorkOSV2::SSO.get_connection(id: 'invalid')
615
+ end.to raise_error(
616
+ WorkOSV2::APIError,
617
+ 'Status 404, Not Found - request ID: ',
618
+ )
619
+ end
620
+ end
621
+ end
622
+ end
623
+
624
+ describe '.delete_connection' do
625
+ context 'with a valid id' do
626
+ it 'returns true' do
627
+ VCR.use_cassette('sso/delete_connection_with_valid_id') do
628
+ response = WorkOSV2::SSO.delete_connection(
629
+ id: 'conn_01EX55FRVN1V2PCA9YWTMZQMMQ',
630
+ )
631
+
632
+ expect(response).to be(true)
633
+ end
634
+ end
635
+ end
636
+
637
+ context 'with an invalid id' do
638
+ it 'returns false' do
639
+ VCR.use_cassette('sso/delete_connection_with_invalid_id') do
640
+ expect do
641
+ WorkOSV2::SSO.delete_connection(id: 'invalid')
642
+ end.to raise_error(
643
+ WorkOSV2::APIError,
644
+ 'Status 404, Not Found - request ID: ',
645
+ )
646
+ end
647
+ end
648
+ end
649
+ end
650
+ end