workos 1.4.0 → 1.6.1
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 +4 -4
- data/.rubocop.yml +1 -1
- data/.ruby-version +1 -1
- data/.semaphore/semaphore.yml +2 -2
- data/Gemfile.lock +3 -3
- data/lib/workos/client.rb +3 -6
- data/lib/workos/connection.rb +9 -1
- data/lib/workos/directory.rb +10 -2
- data/lib/workos/directory_user.rb +4 -1
- data/lib/workos/errors.rb +4 -0
- data/lib/workos/organization.rb +10 -1
- data/lib/workos/organizations.rb +18 -4
- data/lib/workos/profile.rb +7 -2
- data/lib/workos/types/connection_struct.rb +2 -0
- data/lib/workos/types/directory_struct.rb +3 -1
- data/lib/workos/types/directory_user_struct.rb +1 -0
- data/lib/workos/types/organization_struct.rb +3 -0
- data/lib/workos/types/profile_struct.rb +1 -0
- data/lib/workos/types/webhook_struct.rb +14 -0
- data/lib/workos/types.rb +1 -0
- data/lib/workos/version.rb +1 -1
- data/lib/workos/webhook.rb +47 -0
- data/lib/workos/webhooks.rb +168 -0
- data/lib/workos.rb +3 -0
- data/spec/lib/workos/audit_trail_spec.rb +2 -0
- data/spec/lib/workos/directory_sync_spec.rb +32 -29
- data/spec/lib/workos/organizations_spec.rb +13 -11
- data/spec/lib/workos/passwordless_spec.rb +2 -0
- data/spec/lib/workos/portal_spec.rb +2 -0
- data/spec/lib/workos/sso_spec.rb +16 -12
- data/spec/lib/workos/webhooks_spec.rb +190 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/fixtures/vcr_cassettes/directory_sync/get_user.yml +40 -16
- data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_after.yml +34 -22
- data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_before.yml +36 -22
- data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_domain.yml +30 -19
- data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_limit.yml +31 -20
- data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_no_options.yml +39 -21
- data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_search.yml +32 -20
- data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_after.yml +128 -28
- data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_before.yml +31 -18
- data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_directory.yml +136 -35
- data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_group.yml +128 -18
- data/spec/support/fixtures/vcr_cassettes/directory_sync/list_users/with_limit.yml +131 -17
- data/spec/support/fixtures/vcr_cassettes/organization/create.yml +28 -16
- data/spec/support/fixtures/vcr_cassettes/organization/get.yml +27 -16
- data/spec/support/fixtures/vcr_cassettes/organization/list.yml +29 -14
- data/spec/support/fixtures/vcr_cassettes/organization/update.yml +27 -16
- data/spec/support/fixtures/vcr_cassettes/sso/get_connection_with_valid_id.yml +28 -16
- data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_after.yml +25 -15
- data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_before.yml +28 -15
- data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_connection_type.yml +31 -14
- data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_domain.yml +27 -13
- data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_limit.yml +24 -15
- data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_no_options.yml +30 -14
- data/spec/support/fixtures/vcr_cassettes/sso/list_connections/with_organization_id.yml +28 -14
- data/spec/support/fixtures/vcr_cassettes/sso/profile.yml +1 -1
- data/spec/support/profile.txt +1 -1
- data/spec/support/shared_examples/client_spec.rb +16 -0
- data/spec/support/webhook_payload.txt +1 -0
- metadata +12 -5
- data/spec/support/fixtures/vcr_cassettes/organization/update_invalid.yml +0 -73
@@ -2,6 +2,8 @@
|
|
2
2
|
# typed: false
|
3
3
|
|
4
4
|
describe WorkOS::DirectorySync do
|
5
|
+
it_behaves_like 'client'
|
6
|
+
|
5
7
|
describe '.list_directories' do
|
6
8
|
context 'with no options' do
|
7
9
|
it 'returns directories and metadata' do
|
@@ -13,7 +15,7 @@ describe WorkOS::DirectorySync do
|
|
13
15
|
VCR.use_cassette 'directory_sync/list_directories/with_no_options' do
|
14
16
|
directories = described_class.list_directories
|
15
17
|
|
16
|
-
expect(directories.data.size).to eq(
|
18
|
+
expect(directories.data.size).to eq(10)
|
17
19
|
expect(directories.list_metadata).to eq(expected_metadata)
|
18
20
|
end
|
19
21
|
end
|
@@ -44,7 +46,7 @@ describe WorkOS::DirectorySync do
|
|
44
46
|
context 'with search option' do
|
45
47
|
it 'forms the proper request to the API' do
|
46
48
|
request_args = [
|
47
|
-
'/directories?search=
|
49
|
+
'/directories?search=Testing',
|
48
50
|
'Content-Type' => 'application/json'
|
49
51
|
]
|
50
52
|
|
@@ -55,10 +57,11 @@ describe WorkOS::DirectorySync do
|
|
55
57
|
|
56
58
|
VCR.use_cassette 'directory_sync/list_directories/with_search' do
|
57
59
|
directories = described_class.list_directories(
|
58
|
-
search: '
|
60
|
+
search: 'Testing',
|
59
61
|
)
|
60
62
|
|
61
|
-
expect(directories.data.size).to eq(
|
63
|
+
expect(directories.data.size).to eq(2)
|
64
|
+
expect(directories.data[0].name).to include('Testing')
|
62
65
|
end
|
63
66
|
end
|
64
67
|
end
|
@@ -66,7 +69,7 @@ describe WorkOS::DirectorySync do
|
|
66
69
|
context 'with the before option' do
|
67
70
|
it 'forms the proper request to the API' do
|
68
71
|
request_args = [
|
69
|
-
'/directories?before=
|
72
|
+
'/directories?before=directory_01FGCPNV312FHFRCX0BYWHVSE1',
|
70
73
|
'Content-Type' => 'application/json'
|
71
74
|
]
|
72
75
|
|
@@ -77,10 +80,10 @@ describe WorkOS::DirectorySync do
|
|
77
80
|
|
78
81
|
VCR.use_cassette 'directory_sync/list_directories/with_before' do
|
79
82
|
directories = described_class.list_directories(
|
80
|
-
before: '
|
83
|
+
before: 'directory_01FGCPNV312FHFRCX0BYWHVSE1',
|
81
84
|
)
|
82
85
|
|
83
|
-
expect(directories.data.size).to eq(
|
86
|
+
expect(directories.data.size).to eq(6)
|
84
87
|
end
|
85
88
|
end
|
86
89
|
end
|
@@ -88,7 +91,7 @@ describe WorkOS::DirectorySync do
|
|
88
91
|
context 'with the after option' do
|
89
92
|
it 'forms the proper request to the API' do
|
90
93
|
request_args = [
|
91
|
-
'/directories?after=
|
94
|
+
'/directories?after=directory_01FGCPNV312FHFRCX0BYWHVSE1',
|
92
95
|
'Content-Type' => 'application/json'
|
93
96
|
]
|
94
97
|
|
@@ -98,9 +101,9 @@ describe WorkOS::DirectorySync do
|
|
98
101
|
and_return(expected_request)
|
99
102
|
|
100
103
|
VCR.use_cassette 'directory_sync/list_directories/with_after' do
|
101
|
-
directories = described_class.list_directories(after: '
|
104
|
+
directories = described_class.list_directories(after: 'directory_01FGCPNV312FHFRCX0BYWHVSE1')
|
102
105
|
|
103
|
-
expect(directories.data.size).to eq(
|
106
|
+
expect(directories.data.size).to eq(4)
|
104
107
|
end
|
105
108
|
end
|
106
109
|
end
|
@@ -282,7 +285,7 @@ describe WorkOS::DirectorySync do
|
|
282
285
|
context 'with directory option' do
|
283
286
|
it 'forms the proper request to the API' do
|
284
287
|
request_args = [
|
285
|
-
'/directory_users?directory=
|
288
|
+
'/directory_users?directory=directory_01FAZYMST676QMTFN1DDJZZX87',
|
286
289
|
'Content-Type' => 'application/json'
|
287
290
|
]
|
288
291
|
|
@@ -293,10 +296,10 @@ describe WorkOS::DirectorySync do
|
|
293
296
|
|
294
297
|
VCR.use_cassette 'directory_sync/list_users/with_directory' do
|
295
298
|
users = described_class.list_users(
|
296
|
-
directory: '
|
299
|
+
directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
|
297
300
|
)
|
298
301
|
|
299
|
-
expect(users.data.size).to eq(
|
302
|
+
expect(users.data.size).to eq(4)
|
300
303
|
end
|
301
304
|
end
|
302
305
|
end
|
@@ -304,7 +307,7 @@ describe WorkOS::DirectorySync do
|
|
304
307
|
context 'with group option' do
|
305
308
|
it 'forms the proper request to the API' do
|
306
309
|
request_args = [
|
307
|
-
'/directory_users?group=
|
310
|
+
'/directory_users?group=directory_group_01FBXGP79EJAYKW0WS9JCK1V6E',
|
308
311
|
'Content-Type' => 'application/json'
|
309
312
|
]
|
310
313
|
|
@@ -315,10 +318,10 @@ describe WorkOS::DirectorySync do
|
|
315
318
|
|
316
319
|
VCR.use_cassette 'directory_sync/list_users/with_group' do
|
317
320
|
users = described_class.list_users(
|
318
|
-
group: '
|
321
|
+
group: 'directory_group_01FBXGP79EJAYKW0WS9JCK1V6E',
|
319
322
|
)
|
320
323
|
|
321
|
-
expect(users.data.size).to eq(
|
324
|
+
expect(users.data.size).to eq(1)
|
322
325
|
end
|
323
326
|
end
|
324
327
|
end
|
@@ -326,8 +329,8 @@ describe WorkOS::DirectorySync do
|
|
326
329
|
context 'with the before option' do
|
327
330
|
it 'forms the proper request to the API' do
|
328
331
|
request_args = [
|
329
|
-
'/directory_users?before=
|
330
|
-
'directory=
|
332
|
+
'/directory_users?before=directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF&'\
|
333
|
+
'directory=directory_01FAZYMST676QMTFN1DDJZZX87',
|
331
334
|
'Content-Type' => 'application/json'
|
332
335
|
]
|
333
336
|
|
@@ -338,8 +341,8 @@ describe WorkOS::DirectorySync do
|
|
338
341
|
|
339
342
|
VCR.use_cassette 'directory_sync/list_users/with_before' do
|
340
343
|
users = described_class.list_users(
|
341
|
-
before: '
|
342
|
-
directory: '
|
344
|
+
before: 'directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF',
|
345
|
+
directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
|
343
346
|
)
|
344
347
|
|
345
348
|
expect(users.data.size).to eq(2)
|
@@ -350,8 +353,8 @@ describe WorkOS::DirectorySync do
|
|
350
353
|
context 'with the after option' do
|
351
354
|
it 'forms the proper request to the API' do
|
352
355
|
request_args = [
|
353
|
-
'/directory_users?after=
|
354
|
-
'directory=
|
356
|
+
'/directory_users?after=directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF&' \
|
357
|
+
'directory=directory_01FAZYMST676QMTFN1DDJZZX87',
|
355
358
|
'Content-Type' => 'application/json'
|
356
359
|
]
|
357
360
|
|
@@ -362,11 +365,11 @@ describe WorkOS::DirectorySync do
|
|
362
365
|
|
363
366
|
VCR.use_cassette 'directory_sync/list_users/with_after' do
|
364
367
|
users = described_class.list_users(
|
365
|
-
after: '
|
366
|
-
directory: '
|
368
|
+
after: 'directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF',
|
369
|
+
directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
|
367
370
|
)
|
368
371
|
|
369
|
-
expect(users.data.size).to eq(
|
372
|
+
expect(users.data.size).to eq(1)
|
370
373
|
end
|
371
374
|
end
|
372
375
|
end
|
@@ -375,7 +378,7 @@ describe WorkOS::DirectorySync do
|
|
375
378
|
it 'forms the proper request to the API' do
|
376
379
|
request_args = [
|
377
380
|
'/directory_users?limit=2&' \
|
378
|
-
'directory=
|
381
|
+
'directory=directory_01FAZYMST676QMTFN1DDJZZX87',
|
379
382
|
'Content-Type' => 'application/json'
|
380
383
|
]
|
381
384
|
|
@@ -387,7 +390,7 @@ describe WorkOS::DirectorySync do
|
|
387
390
|
VCR.use_cassette 'directory_sync/list_users/with_limit' do
|
388
391
|
users = described_class.list_users(
|
389
392
|
limit: 2,
|
390
|
-
directory: '
|
393
|
+
directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
|
391
394
|
)
|
392
395
|
|
393
396
|
expect(users.data.size).to eq(2)
|
@@ -425,10 +428,10 @@ describe WorkOS::DirectorySync do
|
|
425
428
|
it 'returns a user' do
|
426
429
|
VCR.use_cassette('directory_sync/get_user') do
|
427
430
|
user = WorkOS::DirectorySync.get_user(
|
428
|
-
'
|
431
|
+
'directory_user_01FAZYNPC8M0HRYTKFP2GNX852',
|
429
432
|
)
|
430
433
|
|
431
|
-
expect(user['first_name']).to eq('
|
434
|
+
expect(user['first_name']).to eq('Logan')
|
432
435
|
end
|
433
436
|
end
|
434
437
|
end
|
@@ -2,18 +2,20 @@
|
|
2
2
|
# typed: false
|
3
3
|
|
4
4
|
describe WorkOS::Organizations do
|
5
|
+
it_behaves_like 'client'
|
6
|
+
|
5
7
|
describe '.create_organization' do
|
6
8
|
context 'with valid payload' do
|
7
9
|
it 'creates an organization' do
|
8
10
|
VCR.use_cassette 'organization/create' do
|
9
11
|
organization = described_class.create_organization(
|
10
|
-
domains: ['example.
|
12
|
+
domains: ['example.io'],
|
11
13
|
name: 'Test Organization',
|
12
14
|
)
|
13
15
|
|
14
|
-
expect(organization.id).to eq('
|
16
|
+
expect(organization.id).to eq('org_01FCPEJXEZR4DSBA625YMGQT9N')
|
15
17
|
expect(organization.name).to eq('Test Organization')
|
16
|
-
expect(organization.domains.first[:domain]).to eq('example.
|
18
|
+
expect(organization.domains.first[:domain]).to eq('example.io')
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
@@ -46,7 +48,7 @@ describe WorkOS::Organizations do
|
|
46
48
|
VCR.use_cassette 'organization/list' do
|
47
49
|
organizations = described_class.list_organizations
|
48
50
|
|
49
|
-
expect(organizations.data.size).to eq(
|
51
|
+
expect(organizations.data.size).to eq(6)
|
50
52
|
expect(organizations.list_metadata).to eq(expected_metadata)
|
51
53
|
end
|
52
54
|
end
|
@@ -69,7 +71,7 @@ describe WorkOS::Organizations do
|
|
69
71
|
before: 'before-id',
|
70
72
|
)
|
71
73
|
|
72
|
-
expect(organizations.data.size).to eq(
|
74
|
+
expect(organizations.data.size).to eq(6)
|
73
75
|
end
|
74
76
|
end
|
75
77
|
end
|
@@ -89,7 +91,7 @@ describe WorkOS::Organizations do
|
|
89
91
|
VCR.use_cassette 'organization/list', match_requests_on: [:path] do
|
90
92
|
organizations = described_class.list_organizations(after: 'after-id')
|
91
93
|
|
92
|
-
expect(organizations.data.size).to eq(
|
94
|
+
expect(organizations.data.size).to eq(6)
|
93
95
|
end
|
94
96
|
end
|
95
97
|
end
|
@@ -109,7 +111,7 @@ describe WorkOS::Organizations do
|
|
109
111
|
VCR.use_cassette 'organization/list', match_requests_on: [:path] do
|
110
112
|
organizations = described_class.list_organizations(limit: 10)
|
111
113
|
|
112
|
-
expect(organizations.data.size).to eq(
|
114
|
+
expect(organizations.data.size).to eq(6)
|
113
115
|
end
|
114
116
|
end
|
115
117
|
end
|
@@ -120,10 +122,10 @@ describe WorkOS::Organizations do
|
|
120
122
|
it 'gets the organization details' do
|
121
123
|
VCR.use_cassette('organization/get') do
|
122
124
|
organization = described_class.get_organization(
|
123
|
-
id: '
|
125
|
+
id: 'org_01F9293WD2PDEEV4Y625XPZVG7',
|
124
126
|
)
|
125
127
|
|
126
|
-
expect(organization.id).to eq('
|
128
|
+
expect(organization.id).to eq('org_01F9293WD2PDEEV4Y625XPZVG7')
|
127
129
|
expect(organization.name).to eq('Foo Corp')
|
128
130
|
expect(organization.domains.first[:domain]).to eq('foo-corp.com')
|
129
131
|
end
|
@@ -149,12 +151,12 @@ describe WorkOS::Organizations do
|
|
149
151
|
it 'creates an organization' do
|
150
152
|
VCR.use_cassette 'organization/update' do
|
151
153
|
organization = described_class.update_organization(
|
152
|
-
organization: '
|
154
|
+
organization: 'org_01F6Q6TFP7RD2PF6J03ANNWDKV',
|
153
155
|
domains: ['example.me'],
|
154
156
|
name: 'Test Organization',
|
155
157
|
)
|
156
158
|
|
157
|
-
expect(organization.id).to eq('
|
159
|
+
expect(organization.id).to eq('org_01F6Q6TFP7RD2PF6J03ANNWDKV')
|
158
160
|
expect(organization.name).to eq('Test Organization')
|
159
161
|
expect(organization.domains.first[:domain]).to eq('example.me')
|
160
162
|
end
|
data/spec/lib/workos/sso_spec.rb
CHANGED
@@ -4,6 +4,8 @@
|
|
4
4
|
require 'securerandom'
|
5
5
|
|
6
6
|
describe WorkOS::SSO do
|
7
|
+
it_behaves_like 'client'
|
8
|
+
|
7
9
|
describe '.authorization_url' do
|
8
10
|
context 'with a domain' do
|
9
11
|
let(:args) do
|
@@ -162,6 +164,7 @@ describe WorkOS::SSO do
|
|
162
164
|
id: 'prof_01EEJTY9SZ1R350RB7B73SNBKF',
|
163
165
|
idp_id: '116485463307139932699',
|
164
166
|
last_name: 'Loblaw',
|
167
|
+
organization_id: 'org_01FG53X8636WSNW2WEKB2C31ZB',
|
165
168
|
raw_attributes: {
|
166
169
|
email: 'bob.loblaw@workos.com',
|
167
170
|
family_name: 'Loblaw',
|
@@ -231,6 +234,7 @@ describe WorkOS::SSO do
|
|
231
234
|
id: 'prof_01DRA1XNSJDZ19A31F183ECQW5',
|
232
235
|
idp_id: '00u1klkowm8EGah2H357',
|
233
236
|
last_name: 'Demo',
|
237
|
+
organization_id: 'org_01FG53X8636WSNW2WEKB2C31ZB',
|
234
238
|
raw_attributes: {
|
235
239
|
email: 'demo@workos-okta.com',
|
236
240
|
first_name: 'WorkOS',
|
@@ -303,7 +307,7 @@ describe WorkOS::SSO do
|
|
303
307
|
VCR.use_cassette 'sso/list_connections/with_no_options' do
|
304
308
|
connections = described_class.list_connections
|
305
309
|
|
306
|
-
expect(connections.data.size).to eq(
|
310
|
+
expect(connections.data.size).to eq(6)
|
307
311
|
expect(connections.list_metadata).to eq(expected_metadata)
|
308
312
|
end
|
309
313
|
end
|
@@ -326,7 +330,7 @@ describe WorkOS::SSO do
|
|
326
330
|
connection_type: 'OktaSAML',
|
327
331
|
)
|
328
332
|
|
329
|
-
expect(connections.data.size).to eq(
|
333
|
+
expect(connections.data.size).to eq(10)
|
330
334
|
expect(connections.data.first.connection_type).to eq('OktaSAML')
|
331
335
|
end
|
332
336
|
end
|
@@ -357,7 +361,7 @@ describe WorkOS::SSO do
|
|
357
361
|
context 'with organization_id option' do
|
358
362
|
it 'forms the proper request to the API' do
|
359
363
|
request_args = [
|
360
|
-
'/connections?organization_id=
|
364
|
+
'/connections?organization_id=org_01F9293WD2PDEEV4Y625XPZVG7',
|
361
365
|
'Content-Type' => 'application/json'
|
362
366
|
]
|
363
367
|
|
@@ -368,12 +372,12 @@ describe WorkOS::SSO do
|
|
368
372
|
|
369
373
|
VCR.use_cassette 'sso/list_connections/with_organization_id' do
|
370
374
|
connections = described_class.list_connections(
|
371
|
-
organization_id: '
|
375
|
+
organization_id: 'org_01F9293WD2PDEEV4Y625XPZVG7',
|
372
376
|
)
|
373
377
|
|
374
378
|
expect(connections.data.size).to eq(1)
|
375
379
|
expect(connections.data.first.organization_id).to eq(
|
376
|
-
'
|
380
|
+
'org_01F9293WD2PDEEV4Y625XPZVG7',
|
377
381
|
)
|
378
382
|
end
|
379
383
|
end
|
@@ -404,7 +408,7 @@ describe WorkOS::SSO do
|
|
404
408
|
context 'with before option' do
|
405
409
|
it 'forms the proper request to the API' do
|
406
410
|
request_args = [
|
407
|
-
'/connections?before=
|
411
|
+
'/connections?before=conn_01FA3WGCWPCCY1V2FGES2FDNP7',
|
408
412
|
'Content-Type' => 'application/json'
|
409
413
|
]
|
410
414
|
|
@@ -415,7 +419,7 @@ describe WorkOS::SSO do
|
|
415
419
|
|
416
420
|
VCR.use_cassette 'sso/list_connections/with_before' do
|
417
421
|
connections = described_class.list_connections(
|
418
|
-
before: '
|
422
|
+
before: 'conn_01FA3WGCWPCCY1V2FGES2FDNP7',
|
419
423
|
)
|
420
424
|
|
421
425
|
expect(connections.data.size).to eq(3)
|
@@ -426,7 +430,7 @@ describe WorkOS::SSO do
|
|
426
430
|
context 'with after option' do
|
427
431
|
it 'forms the proper request to the API' do
|
428
432
|
request_args = [
|
429
|
-
'/connections?after=
|
433
|
+
'/connections?after=conn_01FA3WGCWPCCY1V2FGES2FDNP7',
|
430
434
|
'Content-Type' => 'application/json'
|
431
435
|
]
|
432
436
|
|
@@ -437,10 +441,10 @@ describe WorkOS::SSO do
|
|
437
441
|
|
438
442
|
VCR.use_cassette 'sso/list_connections/with_after' do
|
439
443
|
connections = described_class.list_connections(
|
440
|
-
after: '
|
444
|
+
after: 'conn_01FA3WGCWPCCY1V2FGES2FDNP7',
|
441
445
|
)
|
442
446
|
|
443
|
-
expect(connections.data.size).to eq(
|
447
|
+
expect(connections.data.size).to eq(2)
|
444
448
|
end
|
445
449
|
end
|
446
450
|
end
|
@@ -451,10 +455,10 @@ describe WorkOS::SSO do
|
|
451
455
|
it 'gets the connection details' do
|
452
456
|
VCR.use_cassette('sso/get_connection_with_valid_id') do
|
453
457
|
connection = WorkOS::SSO.get_connection(
|
454
|
-
id: '
|
458
|
+
id: 'conn_01FA3WGCWPCCY1V2FGES2FDNP7',
|
455
459
|
)
|
456
460
|
|
457
|
-
expect(connection.id).to eq('
|
461
|
+
expect(connection.id).to eq('conn_01FA3WGCWPCCY1V2FGES2FDNP7')
|
458
462
|
expect(connection.connection_type).to eq('OktaSAML')
|
459
463
|
expect(connection.name).to eq('Foo Corp')
|
460
464
|
expect(connection.domains.first[:domain]).to eq('foo-corp.com')
|
@@ -0,0 +1,190 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: false
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
require 'openssl'
|
6
|
+
|
7
|
+
describe WorkOS::Webhooks do
|
8
|
+
describe '.construct_event' do
|
9
|
+
before(:each) do
|
10
|
+
@payload = File.read("#{SPEC_ROOT}/support/webhook_payload.txt")
|
11
|
+
@secret = 'secret'
|
12
|
+
@timestamp = Time.at(Time.now.to_i * 1000)
|
13
|
+
unhashed_string = "#{@timestamp.to_i}.#{@payload}"
|
14
|
+
digest = OpenSSL::Digest.new('sha256')
|
15
|
+
@signature_hash = OpenSSL::HMAC.hexdigest(digest, @secret, unhashed_string)
|
16
|
+
@expectation = {
|
17
|
+
id: 'directory_user_01FAEAJCR3ZBZ30D8BD1924TVG',
|
18
|
+
state: 'active',
|
19
|
+
emails: [{
|
20
|
+
type: 'work',
|
21
|
+
value: 'blair@foo-corp.com',
|
22
|
+
primary: true,
|
23
|
+
}],
|
24
|
+
idp_id: '00u1e8mutl6wlH3lL4x7',
|
25
|
+
object: 'directory_user',
|
26
|
+
username: 'blair@foo-corp.com',
|
27
|
+
last_name: 'Lunceford',
|
28
|
+
first_name: 'Blair',
|
29
|
+
directory_id: 'directory_01F9M7F68PZP8QXP8G7X5QRHS7',
|
30
|
+
raw_attributes: {
|
31
|
+
name: {
|
32
|
+
givenName: 'Blair',
|
33
|
+
familyName: 'Lunceford',
|
34
|
+
middleName: 'Elizabeth',
|
35
|
+
honorificPrefix: 'Ms.',
|
36
|
+
},
|
37
|
+
title: 'Developer Success Engineer',
|
38
|
+
active: true,
|
39
|
+
emails: [{
|
40
|
+
type: 'work',
|
41
|
+
value: 'blair@foo-corp.com',
|
42
|
+
primary: true,
|
43
|
+
}],
|
44
|
+
groups: [],
|
45
|
+
locale: 'en-US',
|
46
|
+
schemas: [
|
47
|
+
'urn:ietf:params:scim:schemas:core:2.0:User',
|
48
|
+
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
|
49
|
+
],
|
50
|
+
userName: 'blair@foo-corp.com',
|
51
|
+
addresses: [{
|
52
|
+
region: 'CO',
|
53
|
+
primary: true,
|
54
|
+
locality: 'Steamboat Springs',
|
55
|
+
postalCode: '80487',
|
56
|
+
}],
|
57
|
+
externalId: '00u1e8mutl6wlH3lL4x7',
|
58
|
+
displayName: 'Blair Lunceford',
|
59
|
+
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
|
60
|
+
manager: {
|
61
|
+
value: '2',
|
62
|
+
displayName: 'Kathleen Chung',
|
63
|
+
},
|
64
|
+
division: 'Engineering',
|
65
|
+
department: 'Customer Success',
|
66
|
+
},
|
67
|
+
},
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'with the correct payload, sig_header, and secret' do
|
72
|
+
it 'returns a webhook event' do
|
73
|
+
webhook = described_class.construct_event(
|
74
|
+
payload: @payload,
|
75
|
+
sig_header: "t=#{@timestamp.to_i}, v1=#{@signature_hash}",
|
76
|
+
secret: @secret,
|
77
|
+
)
|
78
|
+
|
79
|
+
expect(webhook.data).to eq(@expectation)
|
80
|
+
expect(webhook.event).to eq('dsync.user.created')
|
81
|
+
expect(webhook.id).to eq('wh_123')
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'with the correct payload, sig_header, secret, and tolerance' do
|
86
|
+
it 'returns a webhook event' do
|
87
|
+
webhook = described_class.construct_event(
|
88
|
+
payload: @payload,
|
89
|
+
sig_header: "t=#{@timestamp.to_i}, v1=#{@signature_hash}",
|
90
|
+
secret: @secret,
|
91
|
+
tolerance: 300,
|
92
|
+
)
|
93
|
+
|
94
|
+
expect(webhook.data).to eq(@expectation)
|
95
|
+
expect(webhook.event).to eq('dsync.user.created')
|
96
|
+
expect(webhook.id).to eq('wh_123')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'with an empty header' do
|
101
|
+
it 'raises an error' do
|
102
|
+
expect do
|
103
|
+
described_class.construct_event(
|
104
|
+
payload: @payload,
|
105
|
+
sig_header: '',
|
106
|
+
secret: @secret,
|
107
|
+
)
|
108
|
+
end.to raise_error(
|
109
|
+
WorkOS::SignatureVerificationError,
|
110
|
+
'Unable to extract timestamp and signature hash from header',
|
111
|
+
)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'with an empty signature hash' do
|
116
|
+
it 'raises an error' do
|
117
|
+
expect do
|
118
|
+
described_class.construct_event(
|
119
|
+
payload: @payload,
|
120
|
+
sig_header: "t=#{@timestamp.to_i}, v1=",
|
121
|
+
secret: @secret,
|
122
|
+
)
|
123
|
+
end.to raise_error(
|
124
|
+
WorkOS::SignatureVerificationError,
|
125
|
+
'No signature hash found with expected scheme v1',
|
126
|
+
)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'with an incorrect signature hash' do
|
131
|
+
it 'raises an error' do
|
132
|
+
expect do
|
133
|
+
described_class.construct_event(
|
134
|
+
payload: @payload,
|
135
|
+
sig_header: "t=#{@timestamp.to_i}, v1=99999",
|
136
|
+
secret: @secret,
|
137
|
+
)
|
138
|
+
end.to raise_error(
|
139
|
+
WorkOS::SignatureVerificationError,
|
140
|
+
'Signature hash does not match the expected signature hash for payload',
|
141
|
+
)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'with an incorrect payload' do
|
146
|
+
it 'raises an error' do
|
147
|
+
expect do
|
148
|
+
described_class.construct_event(
|
149
|
+
payload: 'invalid',
|
150
|
+
sig_header: "t=#{@timestamp.to_i}, v1=#{@signature_hash}",
|
151
|
+
secret: @secret,
|
152
|
+
)
|
153
|
+
end.to raise_error(
|
154
|
+
WorkOS::SignatureVerificationError,
|
155
|
+
'Signature hash does not match the expected signature hash for payload',
|
156
|
+
)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context 'with an incorrect webhook secret' do
|
161
|
+
it 'raises an error' do
|
162
|
+
expect do
|
163
|
+
described_class.construct_event(
|
164
|
+
payload: @payload,
|
165
|
+
sig_header: "t=#{@timestamp.to_i}, v1=#{@signature_hash}",
|
166
|
+
secret: 'invalid',
|
167
|
+
)
|
168
|
+
end.to raise_error(
|
169
|
+
WorkOS::SignatureVerificationError,
|
170
|
+
'Signature hash does not match the expected signature hash for payload',
|
171
|
+
)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context 'with a timestamp outside tolerance' do
|
176
|
+
it 'raises an error' do
|
177
|
+
expect do
|
178
|
+
described_class.construct_event(
|
179
|
+
payload: @payload,
|
180
|
+
sig_header: "t=9999, v1=#{@signature_hash}",
|
181
|
+
secret: @secret,
|
182
|
+
)
|
183
|
+
end.to raise_error(
|
184
|
+
WorkOS::SignatureVerificationError,
|
185
|
+
'Timestamp outside the tolerance zone',
|
186
|
+
)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|