workato-connector-sdk 0.3.0 → 0.4.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/lib/workato/cli/exec_command.rb +2 -2
- data/lib/workato/connector/sdk/dsl/aws.rb +225 -0
- data/lib/workato/connector/sdk/dsl/error.rb +1 -1
- data/lib/workato/connector/sdk/dsl/http.rb +19 -0
- data/lib/workato/connector/sdk/dsl/workato_code_lib.rb +17 -0
- data/lib/workato/connector/sdk/dsl.rb +2 -0
- data/lib/workato/connector/sdk/errors.rb +3 -1
- data/lib/workato/connector/sdk/operation.rb +1 -1
- data/lib/workato/connector/sdk/version.rb +1 -1
- data/lib/workato/extension/string.rb +4 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d849d4b677b749ece945ac694dc692e30ec1dee7d78c70e601a5e3b54d8c9ab
|
4
|
+
data.tar.gz: 17b87e09bee6d75ca84e8a8332effacb20350056df7995ccbce4f71f41679cc3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c89fc0971c2a247151bfdb08554e7479b1bb0a40e49d5da92c131ac698cdd2a6d0b7dded9d762d2fe853596e6a02a1986bcbe5127a72381d462fb9cc224caac
|
7
|
+
data.tar.gz: 53a8a4acbe3b23a46676327c87868682faf952a5d49ed2adb46f4b9193f7b55d5c45b1bde5d9a443ff043aa7402b5a5b4a981486e5b998d1b446b864bbc70eec
|
@@ -85,10 +85,10 @@ module Workato
|
|
85
85
|
)
|
86
86
|
@settings = settings_store.read
|
87
87
|
|
88
|
-
Workato::Connector::Sdk::Operation.on_settings_updated = lambda do |
|
88
|
+
Workato::Connector::Sdk::Operation.on_settings_updated = lambda do |message, new_settings|
|
89
89
|
$stdout.pause if verbose?
|
90
90
|
say('')
|
91
|
-
say(
|
91
|
+
say(message)
|
92
92
|
loop do
|
93
93
|
answer = ask('Updated settings file with new connection attributes? (Yes or No)').to_s.downcase
|
94
94
|
break if %w[n no].include?(answer)
|
@@ -0,0 +1,225 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Workato
|
4
|
+
module Connector
|
5
|
+
module Sdk
|
6
|
+
module Dsl
|
7
|
+
module AWS
|
8
|
+
TEMP_CREDENTIALS_REFRESH_TIMEOUT = 60 # seconds
|
9
|
+
|
10
|
+
DUMMY_AWS_IAM_EXTERNAL_ID = 'dummy-aws-iam-external-id'
|
11
|
+
DUMMY_AWS_WORKATO_ACCOUNT_ID = 'dummy-aws-workato-account-id'
|
12
|
+
|
13
|
+
AMAZON_ROLE_CLIENT_ID = ENV['AMAZON_ROLE_CLIENT_ID']
|
14
|
+
AMAZON_ROLE_CLIENT_KEY = ENV['AMAZON_ROLE_CLIENT_KEY']
|
15
|
+
AMAZON_ROLE_CLIENT_SECRET = ENV['AMAZON_ROLE_CLIENT_SECRET']
|
16
|
+
|
17
|
+
WWW_FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded; charset=utf-8'
|
18
|
+
|
19
|
+
def aws
|
20
|
+
AWS
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def generate_signature(connection:,
|
25
|
+
service:,
|
26
|
+
region:,
|
27
|
+
host: "#{service}.#{region}.amazonaws.com",
|
28
|
+
path: '/',
|
29
|
+
method: 'GET',
|
30
|
+
params: {},
|
31
|
+
headers: {},
|
32
|
+
payload: '')
|
33
|
+
|
34
|
+
credentials = if connection[:aws_assume_role].present?
|
35
|
+
role_based_auth(settings: connection)
|
36
|
+
else
|
37
|
+
{
|
38
|
+
access_key_id: connection[:aws_api_key],
|
39
|
+
secret_access_key: connection[:aws_secret_key]
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
url, headers = create_signature(
|
44
|
+
credentials: credentials,
|
45
|
+
service: service,
|
46
|
+
host: host,
|
47
|
+
region: region,
|
48
|
+
method: method,
|
49
|
+
path: path,
|
50
|
+
params: params,
|
51
|
+
headers: (headers || {}).with_indifferent_access,
|
52
|
+
payload: payload
|
53
|
+
)
|
54
|
+
|
55
|
+
{
|
56
|
+
url: url,
|
57
|
+
headers: headers
|
58
|
+
}.with_indifferent_access
|
59
|
+
end
|
60
|
+
|
61
|
+
def iam_external_id
|
62
|
+
settings[:aws_external_id] || DUMMY_AWS_IAM_EXTERNAL_ID
|
63
|
+
end
|
64
|
+
|
65
|
+
def workato_account_id
|
66
|
+
settings[:aws_workato_account_id] || AMAZON_ROLE_CLIENT_ID || DUMMY_AWS_WORKATO_ACCOUNT_ID
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def on_settings_updated
|
72
|
+
Workato::Connector::Sdk::Operation.on_settings_updated
|
73
|
+
end
|
74
|
+
|
75
|
+
def role_based_auth(settings:)
|
76
|
+
settings[:aws_external_id] ||= iam_external_id
|
77
|
+
temp_credentials = settings[:temp_credentials] || {}
|
78
|
+
|
79
|
+
# Refresh temp token that will expire within 60 seconds.
|
80
|
+
expiration = temp_credentials[:expiration]&.to_time(:utc)
|
81
|
+
if !expiration || expiration <= TEMP_CREDENTIALS_REFRESH_TIMEOUT.seconds.from_now
|
82
|
+
temp_credentials = refresh_temp_credentials(settings)
|
83
|
+
end
|
84
|
+
{
|
85
|
+
access_key_id: temp_credentials[:api_key],
|
86
|
+
secret_access_key: temp_credentials[:secret_key],
|
87
|
+
session_token: temp_credentials[:session_token]
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
def refresh_temp_credentials(settings)
|
92
|
+
sts_credentials = {
|
93
|
+
access_key_id: amazon_role_client_key(settings),
|
94
|
+
secret_access_key: amazon_role_client_secret(settings)
|
95
|
+
}
|
96
|
+
|
97
|
+
sts_params = {
|
98
|
+
'Version' => '2011-06-15',
|
99
|
+
'Action' => 'AssumeRole',
|
100
|
+
'RoleSessionName' => 'workato',
|
101
|
+
'RoleArn' => settings[:aws_assume_role],
|
102
|
+
'ExternalId' => settings[:aws_external_id].presence
|
103
|
+
}.compact
|
104
|
+
|
105
|
+
sts_auth_url, sts_auth_headers = create_signature(
|
106
|
+
credentials: sts_credentials,
|
107
|
+
params: sts_params,
|
108
|
+
service: 'sts',
|
109
|
+
host: 'sts.amazonaws.com',
|
110
|
+
region: 'us-east-1',
|
111
|
+
headers: {
|
112
|
+
'Accept' => 'application/xml',
|
113
|
+
'Content-Type' => WWW_FORM_CONTENT_TYPE
|
114
|
+
}
|
115
|
+
)
|
116
|
+
|
117
|
+
request_temp_credentials(url: sts_auth_url, headers: sts_auth_headers).tap do |temp_credentials|
|
118
|
+
update_settings(settings, temp_credentials)
|
119
|
+
end
|
120
|
+
rescue StandardError => e
|
121
|
+
raise e if settings[:aws_external_id].blank?
|
122
|
+
|
123
|
+
settings[:aws_external_id] = nil
|
124
|
+
retry
|
125
|
+
end
|
126
|
+
|
127
|
+
def update_settings(settings, temp_credentials)
|
128
|
+
settings.merge!(temp_credentials: temp_credentials)
|
129
|
+
Workato::Connector::Sdk::Operation.on_settings_updated&.call(
|
130
|
+
'Refresh AWS temporary credentials',
|
131
|
+
settings
|
132
|
+
)
|
133
|
+
end
|
134
|
+
|
135
|
+
def request_temp_credentials(url:, headers:)
|
136
|
+
response = RestClient::Request.execute(
|
137
|
+
url: url,
|
138
|
+
headers: headers,
|
139
|
+
method: :get
|
140
|
+
)
|
141
|
+
response = Workato::Connector::Sdk::Xml.parse_xml_to_hash(response.body)
|
142
|
+
|
143
|
+
temp_credentials = response.dig('AssumeRoleResponse', 0, 'AssumeRoleResult', 0, 'Credentials', 0)
|
144
|
+
{
|
145
|
+
session_token: temp_credentials.dig('SessionToken', 0, 'content!'),
|
146
|
+
api_key: temp_credentials.dig('AccessKeyId', 0, 'content!'),
|
147
|
+
secret_key: temp_credentials.dig('SecretAccessKey', 0, 'content!'),
|
148
|
+
expiration: temp_credentials.dig('Expiration', 0, 'content!')
|
149
|
+
}
|
150
|
+
end
|
151
|
+
|
152
|
+
def create_signature(credentials:,
|
153
|
+
service:,
|
154
|
+
host:,
|
155
|
+
region:,
|
156
|
+
path: '/',
|
157
|
+
method: 'GET',
|
158
|
+
params: {},
|
159
|
+
headers: {},
|
160
|
+
payload: '')
|
161
|
+
url = URI::HTTPS.build(host: host, path: path, query: params.presence.to_param&.gsub('+', '%20')).to_s
|
162
|
+
signer_options = {
|
163
|
+
service: service,
|
164
|
+
region: region,
|
165
|
+
access_key_id: amazon_role_client_key(credentials),
|
166
|
+
secret_access_key: amazon_role_client_secret(credentials),
|
167
|
+
session_token: credentials[:session_token]
|
168
|
+
}
|
169
|
+
|
170
|
+
apply_service_specific_options(service, headers, signer_options, payload)
|
171
|
+
|
172
|
+
signer = Aws::Sigv4::Signer.new(signer_options)
|
173
|
+
signature = signer.sign_request(http_method: method, url: url, headers: headers, body: payload)
|
174
|
+
|
175
|
+
headers_with_sig = merge_headers_with_sig_headers(headers, signature.headers)
|
176
|
+
headers_with_sig = headers_with_sig.transform_keys { |key| key.gsub(/\b[a-z]/, &:upcase) }
|
177
|
+
[url, headers_with_sig]
|
178
|
+
end
|
179
|
+
|
180
|
+
def apply_service_specific_options(service, headers, signer_options, payload)
|
181
|
+
accept_headers = headers.key?('Accept') || headers.key?('accept')
|
182
|
+
content_type = headers.key?('content-type') || headers.key?('Content-Type')
|
183
|
+
|
184
|
+
case service
|
185
|
+
when 'ec2'
|
186
|
+
signer_options[:apply_checksum_header] = false
|
187
|
+
|
188
|
+
headers.except!('Accept', 'Content-Type')
|
189
|
+
when 's3'
|
190
|
+
signer_options[:uri_escape_path] = false
|
191
|
+
|
192
|
+
headers['Accept'] = 'application/xml' unless accept_headers
|
193
|
+
headers['Content-Type'] = WWW_FORM_CONTENT_TYPE unless content_type
|
194
|
+
headers['X-Amz-Content-SHA256'] = 'UNSIGNED-PAYLOAD' if payload.blank?
|
195
|
+
when 'monitoring'
|
196
|
+
signer_options[:apply_checksum_header] = false
|
197
|
+
|
198
|
+
headers['Accept'] = 'application/json' unless accept_headers
|
199
|
+
headers['Content-Type'] = WWW_FORM_CONTENT_TYPE unless content_type
|
200
|
+
when 'lambda'
|
201
|
+
signer_options[:apply_checksum_header] = false
|
202
|
+
|
203
|
+
headers['Content-Type'] = WWW_FORM_CONTENT_TYPE unless content_type
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def merge_headers_with_sig_headers(headers, sig_headers)
|
208
|
+
headers_keys = headers.transform_keys { |key| key.to_s.downcase }
|
209
|
+
sig_headers_to_merge = sig_headers.reject { |key| headers_keys.include?(key.downcase) }
|
210
|
+
headers.merge(sig_headers_to_merge)
|
211
|
+
end
|
212
|
+
|
213
|
+
def amazon_role_client_key(settings)
|
214
|
+
settings[:access_key_id] || AMAZON_ROLE_CLIENT_KEY
|
215
|
+
end
|
216
|
+
|
217
|
+
def amazon_role_client_secret(settings)
|
218
|
+
settings[:secret_access_key] || AMAZON_ROLE_CLIENT_SECRET
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -6,6 +6,10 @@ module Workato
|
|
6
6
|
module Dsl
|
7
7
|
# https://docs.workato.com/developing-connectors/sdk/sdk-reference/http.html#http-methods
|
8
8
|
module HTTP
|
9
|
+
PARALLEL_SUCCESS_INDEX = 0
|
10
|
+
PARALLEL_RESULTS_INDEX = 1
|
11
|
+
PARALLEL_ERRORS_INDEX = 2
|
12
|
+
|
9
13
|
def get(url, params = {})
|
10
14
|
http_request(url, method: 'GET').params(params).response_format_json
|
11
15
|
end
|
@@ -42,6 +46,21 @@ module Workato
|
|
42
46
|
http_request(url, method: 'MOVE').payload(payload).format_json
|
43
47
|
end
|
44
48
|
|
49
|
+
def parallel(requests = [], threads: 1, rpm: nil, requests_per_period: nil, period: 1.minute.to_i) # rubocop:disable Lint/UnusedMethodArgument
|
50
|
+
requests.each.with_object([true, [], []]) do |request, result|
|
51
|
+
response = nil
|
52
|
+
exception = nil
|
53
|
+
begin
|
54
|
+
response = request.execute!
|
55
|
+
rescue RequestError, RuntimeError => e
|
56
|
+
exception = e.to_s
|
57
|
+
end
|
58
|
+
result[PARALLEL_SUCCESS_INDEX] &&= exception.nil?
|
59
|
+
result[PARALLEL_RESULTS_INDEX] << response
|
60
|
+
result[PARALLEL_ERRORS_INDEX] << exception
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
45
64
|
private
|
46
65
|
|
47
66
|
def http_request(url, method:)
|
@@ -10,6 +10,8 @@ module Workato
|
|
10
10
|
JWT_ALGORITHMS = %w[RS256 RS384 RS512].freeze
|
11
11
|
JWT_RSA_KEY_MIN_LENGTH = 2048
|
12
12
|
|
13
|
+
VERIFY_RCA_ALGORITHMS = %w[SHA SHA1 SHA224 SHA256 SHA384 SHA512].freeze
|
14
|
+
|
13
15
|
def workato
|
14
16
|
WorkatoCodeLib
|
15
17
|
end
|
@@ -38,6 +40,21 @@ module Workato
|
|
38
40
|
::JWT.encode(payload, rsa_private, algorithm, header_fields)
|
39
41
|
end
|
40
42
|
|
43
|
+
def verify_rsa(payload, certificate, signature, algorithm = 'SHA256')
|
44
|
+
algorithm = algorithm.to_s.upcase
|
45
|
+
unless VERIFY_RCA_ALGORITHMS.include?(algorithm)
|
46
|
+
raise "Unsupported signing method. Supports only #{VERIFY_RCA_ALGORITHMS.join(', ')}. Got: '#{algorithm}'" # rubocop:disable Layout/LineLength
|
47
|
+
end
|
48
|
+
|
49
|
+
cert = OpenSSL::X509::Certificate.new(certificate)
|
50
|
+
digest = OpenSSL::Digest.new(algorithm)
|
51
|
+
cert.public_key.verify(digest, signature, payload)
|
52
|
+
rescue OpenSSL::PKey::PKeyError
|
53
|
+
raise 'An error occurred during signature verification. Check arguments'
|
54
|
+
rescue OpenSSL::X509::CertificateError
|
55
|
+
raise 'Invalid certificate format'
|
56
|
+
end
|
57
|
+
|
41
58
|
def parse_yaml(yaml)
|
42
59
|
::Psych.safe_load(yaml)
|
43
60
|
rescue ::Psych::DisallowedClass => e
|
@@ -10,6 +10,7 @@ require_relative './dsl/lookup_table'
|
|
10
10
|
require_relative './dsl/workato_code_lib'
|
11
11
|
require_relative './dsl/workato_schema'
|
12
12
|
require_relative './dsl/time'
|
13
|
+
require_relative './dsl/aws'
|
13
14
|
|
14
15
|
module Workato
|
15
16
|
module Connector
|
@@ -21,6 +22,7 @@ module Workato
|
|
21
22
|
include LookupTable
|
22
23
|
include WorkatoCodeLib
|
23
24
|
include WorkatoSchema
|
25
|
+
include AWS
|
24
26
|
|
25
27
|
def sleep(seconds)
|
26
28
|
::Kernel.sleep(seconds.presence || 0)
|
@@ -9,6 +9,8 @@ module Workato
|
|
9
9
|
|
10
10
|
CustomRequestError = Class.new(StandardError)
|
11
11
|
|
12
|
+
RuntimeError = Class.new(StandardError)
|
13
|
+
|
12
14
|
class RequestError < StandardError
|
13
15
|
attr_reader :method,
|
14
16
|
:code,
|
@@ -22,7 +24,7 @@ module Workato
|
|
22
24
|
end
|
23
25
|
end
|
24
26
|
|
25
|
-
class NotImplementedError <
|
27
|
+
class NotImplementedError < StandardError
|
26
28
|
def initialize(msg = 'This part of Connector SDK is not implemented in workato-connector-sdk yet')
|
27
29
|
super
|
28
30
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: workato-connector-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pavel Abolmasov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '5.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: aws-sigv4
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.2.4
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.2.4
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: countries
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -374,6 +388,7 @@ files:
|
|
374
388
|
- lib/workato/connector/sdk/connector.rb
|
375
389
|
- lib/workato/connector/sdk/dsl.rb
|
376
390
|
- lib/workato/connector/sdk/dsl/account_property.rb
|
391
|
+
- lib/workato/connector/sdk/dsl/aws.rb
|
377
392
|
- lib/workato/connector/sdk/dsl/call.rb
|
378
393
|
- lib/workato/connector/sdk/dsl/error.rb
|
379
394
|
- lib/workato/connector/sdk/dsl/http.rb
|