workato-connector-sdk 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b958407bc907daf631e2bd91ecb5c002f911906e3bf8a88c00aedd612fe80bbe
4
- data.tar.gz: 1445969d82d648667e1de62809722625d06e40a30bb4bec565f56f1b7ce113da
3
+ metadata.gz: 0d849d4b677b749ece945ac694dc692e30ec1dee7d78c70e601a5e3b54d8c9ab
4
+ data.tar.gz: 17b87e09bee6d75ca84e8a8332effacb20350056df7995ccbce4f71f41679cc3
5
5
  SHA512:
6
- metadata.gz: 4ad8b4ef4ed273ad65573acb5a4e45dd1e0772b24a4de82cc4c40a3fe0a926c8f93f067eab0df2a6f23b0f94317f2d4bcaad588d203f9323417d7a973084a1fe
7
- data.tar.gz: 879463eea7f3e0b9ef01ce1a5019eafbc8af18610306541ff97f6b62520869e0db8d1de1c0a61b9bb3c233c0932dc780e1ec04fd61704350ece80e9fbf9c764d
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 |_, _, exception, new_settings|
88
+ Workato::Connector::Sdk::Operation.on_settings_updated = lambda do |message, new_settings|
89
89
  $stdout.pause if verbose?
90
90
  say('')
91
- say("Refresh token triggered on response \"#{exception}\"")
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,7 +6,7 @@ module Workato
6
6
  module Dsl
7
7
  module Error
8
8
  def error(message)
9
- raise message
9
+ raise Sdk::RuntimeError, message
10
10
  end
11
11
  end
12
12
  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 < RuntimeError
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
@@ -81,7 +81,7 @@ module Workato
81
81
 
82
82
  settings.merge!(new_settings)
83
83
 
84
- on_settings_updated&.call(http_body, http_code, exception, settings)
84
+ on_settings_updated&.call("Refresh token triggered on response \"#{exception}\"", settings)
85
85
 
86
86
  settings
87
87
  end
@@ -3,7 +3,7 @@
3
3
  module Workato
4
4
  module Connector
5
5
  module Sdk
6
- VERSION = '0.3.0'
6
+ VERSION = '0.4.0'
7
7
  end
8
8
  end
9
9
  end
@@ -117,6 +117,10 @@ module Workato
117
117
  ::ERB::Util.url_encode(self)
118
118
  end
119
119
 
120
+ def decode_url
121
+ CGI.unescape(self)
122
+ end
123
+
120
124
  def decode_urlsafe_base64
121
125
  Extension::Binary.new(Base64.urlsafe_decode64(self))
122
126
  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.3.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: 2021-12-14 00:00:00.000000000 Z
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