workos 1.4.0 → 2.0.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/.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 +10 -2
- data/lib/workos/directory.rb +11 -3
- data/lib/workos/directory_sync.rb +29 -0
- 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 +3 -1
- data/lib/workos/types/directory_struct.rb +4 -2
- 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 +64 -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/{organization/update_invalid.yml → directory_sync/get_directory_with_invalid_id.yml} +35 -25
- data/spec/support/fixtures/vcr_cassettes/directory_sync/get_directory_with_valid_id.yml +84 -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 +16 -5
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: true
|
|
3
|
+
|
|
4
|
+
require 'openssl'
|
|
5
|
+
|
|
6
|
+
module WorkOS
|
|
7
|
+
# The Webhooks module provides convenience methods for working with the WorkOS webhooks.
|
|
8
|
+
# You'll need to extract the signature header and payload from the webhook request
|
|
9
|
+
# sig_header = request.headers['WorkOS-Signature']
|
|
10
|
+
# payload = request.body.read
|
|
11
|
+
#
|
|
12
|
+
# The secret is the Webhook Secret from your WorkOS Dashboard
|
|
13
|
+
# The tolerance is for the timestamp validation
|
|
14
|
+
#
|
|
15
|
+
module Webhooks
|
|
16
|
+
class << self
|
|
17
|
+
extend T::Sig
|
|
18
|
+
|
|
19
|
+
DEFAULT_TOLERANCE = 180
|
|
20
|
+
|
|
21
|
+
# Initializes an Event object from a JSON payload
|
|
22
|
+
# rubocop:disable Layout/LineLength
|
|
23
|
+
#
|
|
24
|
+
# @param [String] payload The payload from the webhook sent by WorkOS. This is the RAW_POST_DATA of the request.
|
|
25
|
+
# @param [String] sig_header The signature from the webhook sent by WorkOS.
|
|
26
|
+
# @param [String] secret The webhook secret from the WorkOS dashboard.
|
|
27
|
+
# @param [Integer] tolerance The time tolerance in seconds for the webhook.
|
|
28
|
+
#
|
|
29
|
+
# @example
|
|
30
|
+
# WorkOS::Webhooks.construct_event(
|
|
31
|
+
# payload: "{"id": "wh_123","data":{"id":"directory_user_01FAEAJCR3ZBZ30D8BD1924TVG","state":"active","emails":[{"type":"work","value":"blair@foo-corp.com","primary":true}],"idp_id":"00u1e8mutl6wlH3lL4x7","object":"directory_user","username":"blair@foo-corp.com","last_name":"Lunceford","first_name":"Blair","directory_id":"directory_01F9M7F68PZP8QXP8G7X5QRHS7","raw_attributes":{"name":{"givenName":"Blair","familyName":"Lunceford","middleName":"Elizabeth","honorificPrefix":"Ms."},"title":"Developer Success Engineer","active":true,"emails":[{"type":"work","value":"blair@foo-corp.com","primary":true}],"groups":[],"locale":"en-US","schemas":["urn:ietf:params:scim:schemas:core:2.0:User","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"],"userName":"blair@foo-corp.com","addresses":[{"region":"CO","primary":true,"locality":"Steamboat Springs","postalCode":"80487"}],"externalId":"00u1e8mutl6wlH3lL4x7","displayName":"Blair Lunceford","urn:ietf:params:scim:schemas:extension:enterprise:2.0:User":{"manager":{"value":"2","displayName":"Kathleen Chung"},"division":"Engineering","department":"Customer Success"}}},"event":"dsync.user.created"}",
|
|
32
|
+
# sig_header: 't=1626125972272, v1=80f7ab7efadc306eb5797c588cee9410da9be4416782b497bf1e1bf4175fb928',
|
|
33
|
+
# secret: 'LJlTiC19GmCKWs8AE0IaOQcos',
|
|
34
|
+
# )
|
|
35
|
+
#
|
|
36
|
+
# => #<WorkOS::Webhook:0x00007fa64b980910 @event="dsync.user.created", @data={:id=>"directory_user_01FAEAJCR3ZBZ30D8BD1924TVG", :state=>"active", :emails=>[{:type=>"work", :value=>"blair@foo-corp.com", :primary=>true}], :idp_id=>"00u1e8mutl6wlH3lL4x7", :object=>"directory_user", :username=>"blair@foo-corp.com", :last_name=>"Lunceford", :first_name=>"Blair", :directory_id=>"directory_01F9M7F68PZP8QXP8G7X5QRHS7", :raw_attributes=>{:name=>{:givenName=>"Blair", :familyName=>"Lunceford", :middleName=>"Elizabeth", :honorificPrefix=>"Ms."}, :title=>"Developer Success Engineer", :active=>true, :emails=>[{:type=>"work", :value=>"blair@foo-corp.com", :primary=>true}], :groups=>[], :locale=>"en-US", :schemas=>["urn:ietf:params:scim:schemas:core:2.0:User", "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"], :userName=>"blair@foo-corp.com", :addresses=>[{:region=>"CO", :primary=>true, :locality=>"Steamboat Springs", :postalCode=>"80487"}], :externalId=>"00u1e8mutl6wlH3lL4x7", :displayName=>"Blair Lunceford", :"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"=>{:manager=>{:value=>"2", :displayName=>"Kathleen Chung"}, :division=>"Engineering", :department=>"Customer Success"}}}>
|
|
37
|
+
#
|
|
38
|
+
# @return [WorkOS::Webhook]
|
|
39
|
+
# rubocop:enable Layout/LineLength
|
|
40
|
+
sig do
|
|
41
|
+
params(
|
|
42
|
+
payload: String,
|
|
43
|
+
sig_header: String,
|
|
44
|
+
secret: String,
|
|
45
|
+
tolerance: Integer,
|
|
46
|
+
).returns(WorkOS::Webhook)
|
|
47
|
+
end
|
|
48
|
+
def construct_event(
|
|
49
|
+
payload:,
|
|
50
|
+
sig_header:,
|
|
51
|
+
secret:,
|
|
52
|
+
tolerance: DEFAULT_TOLERANCE
|
|
53
|
+
)
|
|
54
|
+
verify_header(payload: payload, sig_header: sig_header, secret: secret, tolerance: tolerance)
|
|
55
|
+
WorkOS::Webhook.new(payload)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
sig do
|
|
61
|
+
params(
|
|
62
|
+
payload: String,
|
|
63
|
+
sig_header: String,
|
|
64
|
+
secret: String,
|
|
65
|
+
tolerance: Integer,
|
|
66
|
+
).returns(T::Boolean)
|
|
67
|
+
end
|
|
68
|
+
# rubocop:disable Metrics/MethodLength
|
|
69
|
+
def verify_header(
|
|
70
|
+
payload:,
|
|
71
|
+
sig_header:,
|
|
72
|
+
secret:,
|
|
73
|
+
tolerance: DEFAULT_TOLERANCE
|
|
74
|
+
)
|
|
75
|
+
begin
|
|
76
|
+
timestamp, signature_hash = get_timestamp_and_signature_hash(sig_header: sig_header)
|
|
77
|
+
rescue StandardError
|
|
78
|
+
raise WorkOS::SignatureVerificationError.new(
|
|
79
|
+
message: 'Unable to extract timestamp and signature hash from header',
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
if signature_hash.empty?
|
|
84
|
+
raise WorkOS::SignatureVerificationError.new(
|
|
85
|
+
message: 'No signature hash found with expected scheme v1',
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if timestamp < Time.now - tolerance
|
|
90
|
+
raise WorkOS::SignatureVerificationError.new(
|
|
91
|
+
message: 'Timestamp outside the tolerance zone',
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
expected_sig = compute_signature(timestamp: timestamp, payload: payload, secret: secret)
|
|
96
|
+
unless secure_compare(str_a: expected_sig, str_b: signature_hash)
|
|
97
|
+
raise WorkOS::SignatureVerificationError.new(
|
|
98
|
+
message: 'Signature hash does not match the expected signature hash for payload',
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
true
|
|
103
|
+
end
|
|
104
|
+
# rubocop:enable Metrics/MethodLength
|
|
105
|
+
|
|
106
|
+
sig do
|
|
107
|
+
params(
|
|
108
|
+
sig_header: String,
|
|
109
|
+
).returns(T::Array[T.untyped])
|
|
110
|
+
end
|
|
111
|
+
def get_timestamp_and_signature_hash(
|
|
112
|
+
sig_header:
|
|
113
|
+
)
|
|
114
|
+
timestamp, signature_hash = sig_header.split(', ')
|
|
115
|
+
|
|
116
|
+
if timestamp.nil? || signature_hash.nil?
|
|
117
|
+
raise WorkOS::SignatureVerificationError.new(
|
|
118
|
+
message: 'Unable to extract timestamp and signature hash from header',
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
timestamp = timestamp.sub('t=', '')
|
|
123
|
+
signature_hash = signature_hash.sub('v1=', '')
|
|
124
|
+
|
|
125
|
+
[Time.at(timestamp.to_i), signature_hash]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
sig do
|
|
129
|
+
params(
|
|
130
|
+
timestamp: Time,
|
|
131
|
+
payload: String,
|
|
132
|
+
secret: String,
|
|
133
|
+
).returns(String)
|
|
134
|
+
end
|
|
135
|
+
def compute_signature(
|
|
136
|
+
timestamp:,
|
|
137
|
+
payload:,
|
|
138
|
+
secret:
|
|
139
|
+
)
|
|
140
|
+
unhashed_string = "#{timestamp.to_i}.#{payload}"
|
|
141
|
+
digest = OpenSSL::Digest.new('sha256')
|
|
142
|
+
OpenSSL::HMAC.hexdigest(digest, secret, unhashed_string)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Constant time string comparison to prevent timing attacks
|
|
146
|
+
# Code borrowed from ActiveSupport
|
|
147
|
+
sig do
|
|
148
|
+
params(
|
|
149
|
+
str_a: String,
|
|
150
|
+
str_b: String,
|
|
151
|
+
).returns(T::Boolean)
|
|
152
|
+
end
|
|
153
|
+
def secure_compare(
|
|
154
|
+
str_a:,
|
|
155
|
+
str_b:
|
|
156
|
+
)
|
|
157
|
+
return false unless str_a.bytesize == str_b.bytesize
|
|
158
|
+
|
|
159
|
+
l = T.unsafe(str_a.unpack("C#{str_a.bytesize}"))
|
|
160
|
+
|
|
161
|
+
res = 0
|
|
162
|
+
str_b.each_byte { |byte| res |= byte ^ l.shift }
|
|
163
|
+
|
|
164
|
+
res.zero?
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
data/lib/workos.rb
CHANGED
|
@@ -42,11 +42,14 @@ module WorkOS
|
|
|
42
42
|
autoload :ProfileAndToken, 'workos/profile_and_token'
|
|
43
43
|
autoload :SSO, 'workos/sso'
|
|
44
44
|
autoload :DirectoryUser, 'workos/directory_user'
|
|
45
|
+
autoload :Webhook, 'workos/webhook'
|
|
46
|
+
autoload :Webhooks, 'workos/webhooks'
|
|
45
47
|
|
|
46
48
|
# Errors
|
|
47
49
|
autoload :APIError, 'workos/errors'
|
|
48
50
|
autoload :AuthenticationError, 'workos/errors'
|
|
49
51
|
autoload :InvalidRequestError, 'workos/errors'
|
|
52
|
+
autoload :SignatureVerificationError, 'workos/errors'
|
|
50
53
|
|
|
51
54
|
# Remove WORKOS_KEY at some point in the future. Keeping it here now for
|
|
52
55
|
# backwards compatibility.
|
|
@@ -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
|
|
@@ -140,6 +143,38 @@ describe WorkOS::DirectorySync do
|
|
|
140
143
|
end
|
|
141
144
|
end
|
|
142
145
|
|
|
146
|
+
describe '.get_directory' do
|
|
147
|
+
context 'with a valid id' do
|
|
148
|
+
it 'gets the directory details' do
|
|
149
|
+
VCR.use_cassette('directory_sync/get_directory_with_valid_id') do
|
|
150
|
+
directory = WorkOS::DirectorySync.get_directory(
|
|
151
|
+
id: 'directory_01FK17DWRHH7APAFXT5B52PV0W',
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
expect(directory.id).to eq('directory_01FK17DWRHH7APAFXT5B52PV0W')
|
|
155
|
+
expect(directory.name).to eq('Testing Active Attribute')
|
|
156
|
+
expect(directory.domain).to eq('example.me')
|
|
157
|
+
expect(directory.type).to eq('azure scim v2.0')
|
|
158
|
+
expect(directory.state).to eq('linked')
|
|
159
|
+
expect(directory.organization_id).to eq('org_01F6Q6TFP7RD2PF6J03ANNWDKV')
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
context 'with an invalid id' do
|
|
165
|
+
it 'raises an error' do
|
|
166
|
+
VCR.use_cassette('directory_sync/get_directory_with_invalid_id') do
|
|
167
|
+
expect do
|
|
168
|
+
WorkOS::DirectorySync.get_directory(id: 'invalid')
|
|
169
|
+
end.to raise_error(
|
|
170
|
+
WorkOS::APIError,
|
|
171
|
+
"Status 404, Directory not found: 'invalid'. - request ID: ",
|
|
172
|
+
)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
143
178
|
describe '.list_groups' do
|
|
144
179
|
context 'with no options' do
|
|
145
180
|
it 'raises an error' do
|
|
@@ -282,7 +317,7 @@ describe WorkOS::DirectorySync do
|
|
|
282
317
|
context 'with directory option' do
|
|
283
318
|
it 'forms the proper request to the API' do
|
|
284
319
|
request_args = [
|
|
285
|
-
'/directory_users?directory=
|
|
320
|
+
'/directory_users?directory=directory_01FAZYMST676QMTFN1DDJZZX87',
|
|
286
321
|
'Content-Type' => 'application/json'
|
|
287
322
|
]
|
|
288
323
|
|
|
@@ -293,10 +328,10 @@ describe WorkOS::DirectorySync do
|
|
|
293
328
|
|
|
294
329
|
VCR.use_cassette 'directory_sync/list_users/with_directory' do
|
|
295
330
|
users = described_class.list_users(
|
|
296
|
-
directory: '
|
|
331
|
+
directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
|
|
297
332
|
)
|
|
298
333
|
|
|
299
|
-
expect(users.data.size).to eq(
|
|
334
|
+
expect(users.data.size).to eq(4)
|
|
300
335
|
end
|
|
301
336
|
end
|
|
302
337
|
end
|
|
@@ -304,7 +339,7 @@ describe WorkOS::DirectorySync do
|
|
|
304
339
|
context 'with group option' do
|
|
305
340
|
it 'forms the proper request to the API' do
|
|
306
341
|
request_args = [
|
|
307
|
-
'/directory_users?group=
|
|
342
|
+
'/directory_users?group=directory_group_01FBXGP79EJAYKW0WS9JCK1V6E',
|
|
308
343
|
'Content-Type' => 'application/json'
|
|
309
344
|
]
|
|
310
345
|
|
|
@@ -315,10 +350,10 @@ describe WorkOS::DirectorySync do
|
|
|
315
350
|
|
|
316
351
|
VCR.use_cassette 'directory_sync/list_users/with_group' do
|
|
317
352
|
users = described_class.list_users(
|
|
318
|
-
group: '
|
|
353
|
+
group: 'directory_group_01FBXGP79EJAYKW0WS9JCK1V6E',
|
|
319
354
|
)
|
|
320
355
|
|
|
321
|
-
expect(users.data.size).to eq(
|
|
356
|
+
expect(users.data.size).to eq(1)
|
|
322
357
|
end
|
|
323
358
|
end
|
|
324
359
|
end
|
|
@@ -326,8 +361,8 @@ describe WorkOS::DirectorySync do
|
|
|
326
361
|
context 'with the before option' do
|
|
327
362
|
it 'forms the proper request to the API' do
|
|
328
363
|
request_args = [
|
|
329
|
-
'/directory_users?before=
|
|
330
|
-
'directory=
|
|
364
|
+
'/directory_users?before=directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF&'\
|
|
365
|
+
'directory=directory_01FAZYMST676QMTFN1DDJZZX87',
|
|
331
366
|
'Content-Type' => 'application/json'
|
|
332
367
|
]
|
|
333
368
|
|
|
@@ -338,8 +373,8 @@ describe WorkOS::DirectorySync do
|
|
|
338
373
|
|
|
339
374
|
VCR.use_cassette 'directory_sync/list_users/with_before' do
|
|
340
375
|
users = described_class.list_users(
|
|
341
|
-
before: '
|
|
342
|
-
directory: '
|
|
376
|
+
before: 'directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF',
|
|
377
|
+
directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
|
|
343
378
|
)
|
|
344
379
|
|
|
345
380
|
expect(users.data.size).to eq(2)
|
|
@@ -350,8 +385,8 @@ describe WorkOS::DirectorySync do
|
|
|
350
385
|
context 'with the after option' do
|
|
351
386
|
it 'forms the proper request to the API' do
|
|
352
387
|
request_args = [
|
|
353
|
-
'/directory_users?after=
|
|
354
|
-
'directory=
|
|
388
|
+
'/directory_users?after=directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF&' \
|
|
389
|
+
'directory=directory_01FAZYMST676QMTFN1DDJZZX87',
|
|
355
390
|
'Content-Type' => 'application/json'
|
|
356
391
|
]
|
|
357
392
|
|
|
@@ -362,11 +397,11 @@ describe WorkOS::DirectorySync do
|
|
|
362
397
|
|
|
363
398
|
VCR.use_cassette 'directory_sync/list_users/with_after' do
|
|
364
399
|
users = described_class.list_users(
|
|
365
|
-
after: '
|
|
366
|
-
directory: '
|
|
400
|
+
after: 'directory_user_01FAZYNPC8TJBP7Y2ERT51MGDF',
|
|
401
|
+
directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
|
|
367
402
|
)
|
|
368
403
|
|
|
369
|
-
expect(users.data.size).to eq(
|
|
404
|
+
expect(users.data.size).to eq(1)
|
|
370
405
|
end
|
|
371
406
|
end
|
|
372
407
|
end
|
|
@@ -375,7 +410,7 @@ describe WorkOS::DirectorySync do
|
|
|
375
410
|
it 'forms the proper request to the API' do
|
|
376
411
|
request_args = [
|
|
377
412
|
'/directory_users?limit=2&' \
|
|
378
|
-
'directory=
|
|
413
|
+
'directory=directory_01FAZYMST676QMTFN1DDJZZX87',
|
|
379
414
|
'Content-Type' => 'application/json'
|
|
380
415
|
]
|
|
381
416
|
|
|
@@ -387,7 +422,7 @@ describe WorkOS::DirectorySync do
|
|
|
387
422
|
VCR.use_cassette 'directory_sync/list_users/with_limit' do
|
|
388
423
|
users = described_class.list_users(
|
|
389
424
|
limit: 2,
|
|
390
|
-
directory: '
|
|
425
|
+
directory: 'directory_01FAZYMST676QMTFN1DDJZZX87',
|
|
391
426
|
)
|
|
392
427
|
|
|
393
428
|
expect(users.data.size).to eq(2)
|
|
@@ -425,10 +460,10 @@ describe WorkOS::DirectorySync do
|
|
|
425
460
|
it 'returns a user' do
|
|
426
461
|
VCR.use_cassette('directory_sync/get_user') do
|
|
427
462
|
user = WorkOS::DirectorySync.get_user(
|
|
428
|
-
'
|
|
463
|
+
'directory_user_01FAZYNPC8M0HRYTKFP2GNX852',
|
|
429
464
|
)
|
|
430
465
|
|
|
431
|
-
expect(user['first_name']).to eq('
|
|
466
|
+
expect(user['first_name']).to eq('Logan')
|
|
432
467
|
end
|
|
433
468
|
end
|
|
434
469
|
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')
|