workos 1.3.0 → 1.6.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/Gemfile.lock +3 -3
- data/lib/workos/client.rb +3 -6
- data/lib/workos/connection.rb +9 -1
- data/lib/workos/directory.rb +9 -1
- 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 +2 -0
- 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/provider_enum.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 +21 -19
- 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 +17 -13
- 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 +3 -3
- data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_before.yml +3 -3
- data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_domain.yml +1 -1
- data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_limit.yml +2 -2
- data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_no_options.yml +3 -3
- data/spec/support/fixtures/vcr_cassettes/directory_sync/list_directories/with_search.yml +1 -1
- 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
|
@@ -282,7 +284,7 @@ describe WorkOS::DirectorySync do
|
|
282
284
|
context 'with directory option' do
|
283
285
|
it 'forms the proper request to the API' do
|
284
286
|
request_args = [
|
285
|
-
'/directory_users?directory=
|
287
|
+
'/directory_users?directory=directory_01FAZYMST676QMTFN1DDJZZX87',
|
286
288
|
'Content-Type' => 'application/json'
|
287
289
|
]
|
288
290
|
|
@@ -293,10 +295,10 @@ describe WorkOS::DirectorySync do
|
|
293
295
|
|
294
296
|
VCR.use_cassette 'directory_sync/list_users/with_directory' do
|
295
297
|
users = described_class.list_users(
|
296
|
-
directory: '
|
298
|
+
directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
|
297
299
|
)
|
298
300
|
|
299
|
-
expect(users.data.size).to eq(
|
301
|
+
expect(users.data.size).to eq(4)
|
300
302
|
end
|
301
303
|
end
|
302
304
|
end
|
@@ -304,7 +306,7 @@ describe WorkOS::DirectorySync do
|
|
304
306
|
context 'with group option' do
|
305
307
|
it 'forms the proper request to the API' do
|
306
308
|
request_args = [
|
307
|
-
'/directory_users?group=
|
309
|
+
'/directory_users?group=directory_group_01FBXGP79EJAYKW0WS9JCK1V6E',
|
308
310
|
'Content-Type' => 'application/json'
|
309
311
|
]
|
310
312
|
|
@@ -315,10 +317,10 @@ describe WorkOS::DirectorySync do
|
|
315
317
|
|
316
318
|
VCR.use_cassette 'directory_sync/list_users/with_group' do
|
317
319
|
users = described_class.list_users(
|
318
|
-
group: '
|
320
|
+
group: 'directory_group_01FBXGP79EJAYKW0WS9JCK1V6E',
|
319
321
|
)
|
320
322
|
|
321
|
-
expect(users.data.size).to eq(
|
323
|
+
expect(users.data.size).to eq(1)
|
322
324
|
end
|
323
325
|
end
|
324
326
|
end
|
@@ -326,8 +328,8 @@ describe WorkOS::DirectorySync do
|
|
326
328
|
context 'with the before option' do
|
327
329
|
it 'forms the proper request to the API' do
|
328
330
|
request_args = [
|
329
|
-
'/directory_users?before=
|
330
|
-
'directory=
|
331
|
+
'/directory_users?before=directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF&'\
|
332
|
+
'directory=directory_01FAZYMST676QMTFN1DDJZZX87',
|
331
333
|
'Content-Type' => 'application/json'
|
332
334
|
]
|
333
335
|
|
@@ -338,8 +340,8 @@ describe WorkOS::DirectorySync do
|
|
338
340
|
|
339
341
|
VCR.use_cassette 'directory_sync/list_users/with_before' do
|
340
342
|
users = described_class.list_users(
|
341
|
-
before: '
|
342
|
-
directory: '
|
343
|
+
before: 'directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF',
|
344
|
+
directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
|
343
345
|
)
|
344
346
|
|
345
347
|
expect(users.data.size).to eq(2)
|
@@ -350,8 +352,8 @@ describe WorkOS::DirectorySync do
|
|
350
352
|
context 'with the after option' do
|
351
353
|
it 'forms the proper request to the API' do
|
352
354
|
request_args = [
|
353
|
-
'/directory_users?after=
|
354
|
-
'directory=
|
355
|
+
'/directory_users?after=directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF&' \
|
356
|
+
'directory=directory_01FAZYMST676QMTFN1DDJZZX87',
|
355
357
|
'Content-Type' => 'application/json'
|
356
358
|
]
|
357
359
|
|
@@ -362,11 +364,11 @@ describe WorkOS::DirectorySync do
|
|
362
364
|
|
363
365
|
VCR.use_cassette 'directory_sync/list_users/with_after' do
|
364
366
|
users = described_class.list_users(
|
365
|
-
after: '
|
366
|
-
directory: '
|
367
|
+
after: 'directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF',
|
368
|
+
directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
|
367
369
|
)
|
368
370
|
|
369
|
-
expect(users.data.size).to eq(
|
371
|
+
expect(users.data.size).to eq(1)
|
370
372
|
end
|
371
373
|
end
|
372
374
|
end
|
@@ -375,7 +377,7 @@ describe WorkOS::DirectorySync do
|
|
375
377
|
it 'forms the proper request to the API' do
|
376
378
|
request_args = [
|
377
379
|
'/directory_users?limit=2&' \
|
378
|
-
'directory=
|
380
|
+
'directory=directory_01FAZYMST676QMTFN1DDJZZX87',
|
379
381
|
'Content-Type' => 'application/json'
|
380
382
|
]
|
381
383
|
|
@@ -387,7 +389,7 @@ describe WorkOS::DirectorySync do
|
|
387
389
|
VCR.use_cassette 'directory_sync/list_users/with_limit' do
|
388
390
|
users = described_class.list_users(
|
389
391
|
limit: 2,
|
390
|
-
directory: '
|
392
|
+
directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
|
391
393
|
)
|
392
394
|
|
393
395
|
expect(users.data.size).to eq(2)
|
@@ -425,10 +427,10 @@ describe WorkOS::DirectorySync do
|
|
425
427
|
it 'returns a user' do
|
426
428
|
VCR.use_cassette('directory_sync/get_user') do
|
427
429
|
user = WorkOS::DirectorySync.get_user(
|
428
|
-
'
|
430
|
+
'directory_user_01FAZYNPC8M0HRYTKFP2GNX852',
|
429
431
|
)
|
430
432
|
|
431
|
-
expect(user['first_name']).to eq('
|
433
|
+
expect(user['first_name']).to eq('Logan')
|
432
434
|
end
|
433
435
|
end
|
434
436
|
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
|
@@ -143,7 +145,7 @@ describe WorkOS::SSO do
|
|
143
145
|
described_class.authorization_url(**args)
|
144
146
|
end.to raise_error(
|
145
147
|
ArgumentError,
|
146
|
-
'Okta is not a valid value. `provider` must be in ["GoogleOAuth"]',
|
148
|
+
'Okta is not a valid value. `provider` must be in ["GoogleOAuth", "MicrosoftOAuth"]',
|
147
149
|
)
|
148
150
|
end
|
149
151
|
end
|
@@ -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
|
data/spec/spec_helper.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
http_interactions:
|
3
3
|
- request:
|
4
4
|
method: get
|
5
|
-
uri: https://api.workos.com/directory_users/
|
5
|
+
uri: https://api.workos.com/directory_users/directory_user_01FAZYNPC8M0HRYTKFP2GNX852
|
6
6
|
body:
|
7
7
|
encoding: US-ASCII
|
8
8
|
string: ''
|
@@ -14,7 +14,7 @@ http_interactions:
|
|
14
14
|
Accept:
|
15
15
|
- "*/*"
|
16
16
|
User-Agent:
|
17
|
-
- WorkOS; ruby/
|
17
|
+
- WorkOS; ruby/3.0.1; x86_64-darwin19; v1.4.0
|
18
18
|
Authorization:
|
19
19
|
- Bearer <API_KEY>
|
20
20
|
response:
|
@@ -22,16 +22,26 @@ http_interactions:
|
|
22
22
|
code: 200
|
23
23
|
message: OK
|
24
24
|
headers:
|
25
|
-
|
26
|
-
-
|
25
|
+
Date:
|
26
|
+
- Mon, 09 Aug 2021 17:36:13 GMT
|
27
|
+
Content-Type:
|
28
|
+
- application/json; charset=utf-8
|
29
|
+
Transfer-Encoding:
|
30
|
+
- chunked
|
27
31
|
Connection:
|
28
32
|
- keep-alive
|
29
33
|
Vary:
|
30
34
|
- Origin, Accept-Encoding
|
31
35
|
Access-Control-Allow-Credentials:
|
32
36
|
- 'true'
|
37
|
+
Content-Security-Policy:
|
38
|
+
- 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self''
|
39
|
+
https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src
|
40
|
+
''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests'
|
33
41
|
X-Dns-Prefetch-Control:
|
34
42
|
- 'off'
|
43
|
+
Expect-Ct:
|
44
|
+
- max-age=0
|
35
45
|
X-Frame-Options:
|
36
46
|
- SAMEORIGIN
|
37
47
|
Strict-Transport-Security:
|
@@ -40,23 +50,37 @@ http_interactions:
|
|
40
50
|
- noopen
|
41
51
|
X-Content-Type-Options:
|
42
52
|
- nosniff
|
53
|
+
X-Permitted-Cross-Domain-Policies:
|
54
|
+
- none
|
55
|
+
Referrer-Policy:
|
56
|
+
- no-referrer
|
43
57
|
X-Xss-Protection:
|
44
|
-
-
|
58
|
+
- '0'
|
45
59
|
X-Request-Id:
|
46
|
-
-
|
47
|
-
Content-Type:
|
48
|
-
- application/json; charset=utf-8
|
49
|
-
Content-Length:
|
50
|
-
- '368'
|
60
|
+
- 59862449-73e5-4dfd-93ab-3764b1917801
|
51
61
|
Etag:
|
52
|
-
- W/"
|
53
|
-
Date:
|
54
|
-
- Thu, 30 Apr 2020 04:43:14 GMT
|
62
|
+
- W/"4c3-Ikxt2N0fUuSxCjv+RdYvW8W9Xwo"
|
55
63
|
Via:
|
56
64
|
- 1.1 vegur
|
65
|
+
Cf-Cache-Status:
|
66
|
+
- DYNAMIC
|
67
|
+
Report-To:
|
68
|
+
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=m6%2FJ3BJ75VMwSOtfDQXjt%2FoL29FI%2Bv5VswhZzg6LVkOQi7nyI19Sks%2FkDGCDrSQ%2FMtyU6DI4OFWR9RB1I04IGdhehsY2oPGugIj%2BhHMiJdQEcE6vPAsuaF1HyVnXGvMgRYdurEW1Jr7rSYBWeA%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
69
|
+
Nel:
|
70
|
+
- '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}'
|
71
|
+
Server:
|
72
|
+
- cloudflare
|
73
|
+
Cf-Ray:
|
74
|
+
- 67c2bed0ebe6c7e6-DFW
|
75
|
+
Alt-Svc:
|
76
|
+
- h3-27=":443"; ma=86400, h3-28=":443"; ma=86400, h3-29=":443"; ma=86400, h3=":443";
|
77
|
+
ma=86400
|
57
78
|
body:
|
58
|
-
encoding:
|
59
|
-
string: '{"
|
79
|
+
encoding: ASCII-8BIT
|
80
|
+
string: '{"object":"directory_user","id":"directory_user_01FAZYNPC8M0HRYTKFP2GNX852","directory_id":"directory_01FAZYMST676QMTFN1DDJZZX87","idp_id":"6092c280a3f1e19ef6d8cef8","username":"logan@workos.com","emails":[{"primary":true,"value":"logan@workos.com"}],"first_name":"Logan","last_name":"Gingerich","state":"active","raw_attributes":{"id":"6092c280a3f1e19ef6d8cef8","name":"Logan
|
81
|
+
Paul Gingerich","teams":["5f696c8e9a63a60e965aaca8"],"spokeId":null,"lastName":"Gingerich","createdAt":"2021-05-05T16:06:24+0000","firstName":"Logan","updatedAt":"2021-07-19T19:17:52+0000","workEmail":"logan@workos.com","department":"Infra","departmentId":"5f27ada9a5e9bc0001a0ae4a"},"custom_attributes":{"department":"Infra"},"groups":[{"object":"directory_group","id":"directory_group_01FAZYNN1NZWMBRAXXDSTB5NFH","directory_id":"directory_01FAZYMST676QMTFN1DDJZZX87","name":"Department
|
82
|
+
- Infra","raw_attributes":{"id":"5f27ada9a5e9bc0001a0ae4a","name":"Infra","parent":"5f27ada9a5e9bc0001a0ae48"}},{"object":"directory_group","id":"directory_group_01FAZYNNQ4HQ6EBF29MPTH7VKB","directory_id":"directory_01FAZYMST676QMTFN1DDJZZX87","name":"Team
|
83
|
+
- Platform","raw_attributes":{"id":"5f696c8e9a63a60e965aaca8","name":"Platform","parent":null}}]}'
|
60
84
|
http_version:
|
61
|
-
recorded_at:
|
85
|
+
recorded_at: Mon, 09 Aug 2021 17:36:13 GMT
|
62
86
|
recorded_with: VCR 5.0.0
|
@@ -67,9 +67,9 @@ http_interactions:
|
|
67
67
|
body:
|
68
68
|
encoding: UTF-8
|
69
69
|
string: '{"object":"list","listMetadata":{"before":null,"after":null},"data":[{"object":"directory","id":"directory_01F7796W20KW0CXEQQEYENT0ZC","organization_id":"org_01EZDF20TZEJXKPSX2BJRN6TV6","name":"Bamboo
|
70
|
-
Test","external_key":"rPzV4pdpbaUiKsc6","type":"bamboohr","state":"unlinked","domain":"foo-corp.com"},{"object":"directory","id":"directory_01F5ZY7XVQZ3DRYEZTH1EPA8BS","organization_id":"org_01EZDF20TZEJXKPSX2BJRN6TV6","name":"Foo
|
71
|
-
Corp","external_key":"qV4eyK99QGUaYYa0","type":"okta scim v2.0","state":"linked","domain":"foo-corp.com"},{"object":"directory","id":"directory_01F5XHH1QHX6C2F0Z6WG9YPGCJ","organization_id":"org_01F29YJ068E52HGEB8ZQGC9MJG","name":"Example
|
72
|
-
Azure SCIM","external_key":"YDKJvbWHKKg66cSk","type":"azure scim v2.0","state":"linked","domain":"example.com"}]}'
|
70
|
+
Test","external_key":"rPzV4pdpbaUiKsc6","type":"bamboohr","state":"unlinked","domain":"foo-corp.com", "created_at":"2021-07-02T19:15:39.556Z","updated_at":"2021-07-02T19:31:28.499Z"},{"object":"directory","id":"directory_01F5ZY7XVQZ3DRYEZTH1EPA8BS","organization_id":"org_01EZDF20TZEJXKPSX2BJRN6TV6","name":"Foo
|
71
|
+
Corp","external_key":"qV4eyK99QGUaYYa0","type":"okta scim v2.0","state":"linked","domain":"foo-corp.com", "created_at":"2021-07-02T19:15:39.556Z","updated_at":"2021-07-02T19:31:28.499Z"},{"object":"directory","id":"directory_01F5XHH1QHX6C2F0Z6WG9YPGCJ","organization_id":"org_01F29YJ068E52HGEB8ZQGC9MJG","name":"Example
|
72
|
+
Azure SCIM","external_key":"YDKJvbWHKKg66cSk","type":"azure scim v2.0","state":"linked","domain":"example.com", "created_at":"2021-07-02T19:15:39.556Z","updated_at":"2021-07-02T19:31:28.499Z"}]}'
|
73
73
|
http_version:
|
74
74
|
recorded_at: Mon, 07 Jun 2021 17:55:30 GMT
|
75
75
|
recorded_with: VCR 5.0.0
|