workato-connector-sdk 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +57 -30
  3. data/exe/workato +12 -1
  4. data/lib/workato/cli/edit_command.rb +1 -3
  5. data/lib/workato/cli/exec_command.rb +21 -5
  6. data/lib/workato/cli/generate_command.rb +1 -2
  7. data/lib/workato/cli/generators/connector_generator.rb +4 -1
  8. data/lib/workato/cli/main.rb +50 -3
  9. data/lib/workato/cli/oauth2_command.rb +180 -0
  10. data/lib/workato/cli/push_command.rb +36 -24
  11. data/lib/workato/connector/sdk/action.rb +52 -4
  12. data/lib/workato/connector/sdk/connection.rb +144 -0
  13. data/lib/workato/connector/sdk/connector.rb +24 -9
  14. data/lib/workato/connector/sdk/dsl/time.rb +8 -1
  15. data/lib/workato/connector/sdk/dsl/workato_code_lib.rb +4 -4
  16. data/lib/workato/connector/sdk/errors.rb +13 -0
  17. data/lib/workato/connector/sdk/object_definitions.rb +10 -10
  18. data/lib/workato/connector/sdk/operation.rb +37 -39
  19. data/lib/workato/connector/sdk/request.rb +11 -7
  20. data/lib/workato/connector/sdk/schema/field/array.rb +25 -0
  21. data/lib/workato/connector/sdk/schema/field/convertors.rb +189 -0
  22. data/lib/workato/connector/sdk/schema/field/date.rb +28 -0
  23. data/lib/workato/connector/sdk/schema/field/date_time.rb +28 -0
  24. data/lib/workato/connector/sdk/schema/field/integer.rb +27 -0
  25. data/lib/workato/connector/sdk/schema/field/number.rb +27 -0
  26. data/lib/workato/connector/sdk/schema/field/object.rb +25 -0
  27. data/lib/workato/connector/sdk/schema/field/string.rb +26 -0
  28. data/lib/workato/connector/sdk/schema/type/time.rb +53 -0
  29. data/lib/workato/connector/sdk/schema/type/unicode_string.rb +22 -0
  30. data/lib/workato/connector/sdk/schema.rb +230 -0
  31. data/lib/workato/connector/sdk/settings.rb +6 -3
  32. data/lib/workato/connector/sdk/trigger.rb +25 -0
  33. data/lib/workato/connector/sdk/version.rb +1 -1
  34. data/lib/workato/connector/sdk.rb +1 -0
  35. data/lib/workato/extension/string.rb +16 -10
  36. data/lib/workato/web/app.rb +23 -0
  37. data/templates/Gemfile.erb +1 -0
  38. data/templates/spec/action_spec.rb.erb +7 -1
  39. data/templates/spec/trigger_spec.rb.erb +6 -0
  40. metadata +95 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fcda7a1bda0d422376d4f8345bb63c8e7016d62432cb7d5b8838689347f7f18d
4
- data.tar.gz: 8e9a38245c57162d41dbdc6ebd7385f464dc3807848decf02eb295b5a8a6d490
3
+ metadata.gz: b958407bc907daf631e2bd91ecb5c002f911906e3bf8a88c00aedd612fe80bbe
4
+ data.tar.gz: 1445969d82d648667e1de62809722625d06e40a30bb4bec565f56f1b7ce113da
5
5
  SHA512:
6
- metadata.gz: fdc43f6ba0e21396c896afc879883b145306a6f03a1f45701b734d066f6431d66b34dc105d5a79bbfcb3e7b1409a2caab954e04e62e1ae395bbd06632e8a8a3f
7
- data.tar.gz: 22fcdc88c6632b992481dd5f6b910ba8ec85b9ba72ea1a3a4345ee3d476aed7b534556b8ac8bc1cfae0f68b4f3be293328739d62d7cd248d371f1301f4e470e9
6
+ metadata.gz: 4ad8b4ef4ed273ad65573acb5a4e45dd1e0772b24a4de82cc4c40a3fe0a926c8f93f067eab0df2a6f23b0f94317f2d4bcaad588d203f9323417d7a973084a1fe
7
+ data.tar.gz: 879463eea7f3e0b9ef01ce1a5019eafbc8af18610306541ff97f6b62520869e0db8d1de1c0a61b9bb3c233c0932dc780e1ec04fd61704350ece80e9fbf9c764d
data/README.md CHANGED
@@ -15,6 +15,7 @@ This guide below showcases how you can do the following things:
15
15
  1. Install [RVM ("Ruby Version Manager")](http://rvm.io/) or a Ruby manager of your choice. You can find more at [here](https://www.ruby-lang.org/en/documentation/installation/)
16
16
  2. Choose between Ruby versions `2.4.10` `2.5.X` or `2.7.X`. Our preferred version is `2.7.X`.
17
17
  3. Verify you're running a valid ruby version. Do this by running either `ruby -v` or the commands within your version manager. i.e., `rvm current` if you have installed RVM.
18
+ 4. For Windows you need tzinfo-data gem installed as well. `gem install tzinfo-data`
18
19
 
19
20
  ```bash
20
21
  ruby -v
@@ -38,7 +39,8 @@ Commands:
38
39
  workato generate <SUBCOMMAND> # Generates code from template
39
40
  workato help [COMMAND] # Describe available commands or one specific command
40
41
  workato new <CONNECTOR_PATH> # Inits new connector folder
41
- workato push <FOLDER> # Upload and release connector's code
42
+ workato oauth2 # Implements OAuth Authorization Code flow
43
+ workato push # Upload and release connector's code
42
44
 
43
45
  Options:
44
46
  [--verbose], [--no-verbose]
@@ -124,11 +126,11 @@ You don't need to create this file. It'll be created later on. This file just ho
124
126
 
125
127
  ### 2.5 README.MD
126
128
  This file shows up on your Github project (or other Git software you use). Use it to document what your connector does! **Not created via `workato new <PATH>` commands.**
127
- When you use the `workato push <FOLDER>` command to sync your connector with your Workato workspace, this is the default file for your connector's description.
129
+ When you use the `workato push` command to sync your connector with your Workato workspace, this is the default file for your connector's description.
128
130
 
129
131
  ### 2.6 logo.png
130
132
  The logo of your connector. **Not created via `workato new <PATH>` commands.**
131
- When you use the `workato push <FOLDER>` command to sync your connector with your Workato workspace, this is the default file for your connector's logo.
133
+ When you use the `workato push` command to sync your connector with your Workato workspace, this is the default file for your connector's logo.
132
134
 
133
135
  ### 2.7 connector.rb
134
136
  Well, this is your actual connector code. This file should be a replica of your connector code in Workato.
@@ -223,7 +225,8 @@ Commands:
223
225
  workato generate <SUBCOMMAND> # Generates code from template
224
226
  workato help [COMMAND] # Describe available commands or one specific command
225
227
  workato new <CONNECTOR_PATH> # Inits new connector folder
226
- workato push <FOLDER> # Upload and release connector's code
228
+ workato oauth2 # Implements OAuth Authorization Code flow
229
+ workato push # Upload and release connector's code
227
230
 
228
231
  Options:
229
232
  [--verbose], [--no-verbose]
@@ -258,7 +261,6 @@ Edit encrypted file, e.g. settings.yaml.enc
258
261
  ### 3.3 workato exec
259
262
  ```
260
263
  workato help exec
261
-
262
264
  Usage:
263
265
  workato exec <PATH>
264
266
 
@@ -269,6 +271,7 @@ Options:
269
271
  -k, [--key=KEY] # Path to file with encrypt/decrypt key. NOTE: key from WORKATO_CONNECTOR_MASTER_KEY has higher priority
270
272
  -i, [--input=INPUT] # Path to file with input JSON
271
273
  [--closure=CLOSURE] # Path to file with next poll closure JSON
274
+ [--continue=CONTINUE] # Path to file with next multistep action continue closure JSON
272
275
  -a, [--args=ARGS] # Path to file with method arguments JSON
273
276
  [--extended-input-schema=EXTENDED_INPUT_SCHEMA] # Path to file with extended input schema definition JSON
274
277
  [--extended-output-schema=EXTENDED_OUTPUT_SCHEMA] # Path to file with extended output schema definition JSON
@@ -278,8 +281,11 @@ Options:
278
281
  [--webhook-headers=WEBHOOK_HEADERS] # Path to file with webhook headers JSON
279
282
  [--webhook-url=WEBHOOK_URL] # Webhook URL for automatic webhook subscription
280
283
  -o, [--output=OUTPUT] # Write output to JSON file
281
- [--debug], [--no-debug]
282
- [--verbose], [--no-verbose]
284
+ [--oauth2-code=OAUTH2_CODE] # OAuth2 code exchange to tokens pair
285
+ [--redirect-url=REDIRECT_URL] # OAuth2 callback url
286
+ [--refresh-token=REFRESH_TOKEN] # OAuth2 refresh token
287
+ [--debug], [--no-debug]
288
+ [--verbose], [--no-verbose]
283
289
 
284
290
  Description:
285
291
  The 'workato exec' executes connector's lambda block at <PATH>. Lambda's parameters can be provided if needed, see options part.
@@ -357,12 +363,36 @@ Please select default HTTP mocking behavior suitable for your project?
357
363
 
358
364
  - `simple` means your HTTP requests will be stored in plain text.
359
365
 
360
- ### 3.6 workato push
366
+ ### 3.6 workato oauth2
367
+ ```
368
+ workato help oauth2
369
+
370
+ Usage:
371
+ workato oauth2
372
+
373
+ Options:
374
+ -c, [--connector=CONNECTOR] # Path to connector source code
375
+ -s, [--settings=SETTINGS] # Path to plain or encrypted file with connection configs, passwords, tokens, secrets etc
376
+ -n, [--connection=CONNECTION] # Connection name if settings file contains multiple settings
377
+ -k, [--key=KEY] # Path to file with encrypt/decrypt key. NOTE: key from WORKATO_CONNECTOR_MASTER_KEY has higher priority
378
+ [--port=PORT] # Listen requests on specific port
379
+ # Default: 45555
380
+ [--ip=IP] # Listen requests on specific interface
381
+ # Default: 127.0.0.1
382
+ [--https], [--no-https] # Start HTTPS server using self-signed certificate
383
+ [--verbose], [--no-verbose]
384
+
385
+ Implements OAuth Authorization Code flow
386
+ ```
387
+
388
+ Use this to implement the OAuth2 Authorization code grant flow for applicable connectors. Applicable connectors are ones where the connection hash has `type: 'oauth2`. For more information, check out our guide on our [main docs site](https://docs.workato.com/developing-connectors/sdk/guides/authentication/oauth/auth-code.html#how-to-guide-oauth-2-0-authorization-code-variant).
389
+
390
+ ### 3.7 workato push
361
391
  ```
362
392
  workato help push
363
393
 
364
394
  Usage:
365
- workato push <FOLDER>
395
+ workato push
366
396
 
367
397
  Options:
368
398
  -t, [--title=TITLE] # Connector title on the Workato Platform
@@ -375,7 +405,8 @@ Options:
375
405
  [--environment=ENVIRONMENT] # Server to push connector code to
376
406
  # Default: live
377
407
  # Possible values: preview, preview-eu, live, live-eu
378
- [--verbose], [--no-verbose]
408
+ [--folder=FOLDER] # Folder ID if you what to push to folder other than Home
409
+ [--verbose], [--no-verbose]
379
410
 
380
411
  Upload and release connector's code
381
412
  ```
@@ -488,7 +519,7 @@ workato exec test #Output of the test: lambda function should be shown
488
519
  > *Note*: You may also see a intermediary command from the Gem asking if you'd like to refresh your access tokens. This is done when HTTP requests are made which have a response that triggers the `refresh_on` block. Selecting yes would cause the Gem to update your settings file with the latest auth credentials.
489
520
 
490
521
  ### 4.3 Example: Testing your connection on CLI - OAuth 2 - auth code grant flows
491
- For auth code grant flows, the Workato Gem doesn't simulate the browser popup so you'll need to supply the access token and refresh tokens directly!
522
+ For auth code grant flows, the Workato Gem allows you to simulate the OAuth2 flow using the `workato oauth2` command.
492
523
 
493
524
  ```ruby
494
525
  {
@@ -572,27 +603,24 @@ For auth code grant flows, the Workato Gem doesn't simulate the browser popup so
572
603
  and a `settings.yaml.enc` or `settings.yaml` file with the following details
573
604
 
574
605
  ```yaml
575
- My Valid Connection:
576
- access_token: "valid_access_token"
577
- refresh_token: "valid_refresh_token"
578
- user_key: "valid_user_key"
579
- client_id: "valid_client_id"
580
- client_secret: "valid_client_secret"
581
- Invalid Access Token:
582
- access_token: "invalid_access_token"
583
- refresh_token: "valid_refresh_token"
584
- user_key: "valid_user_key"
585
- client_id: "valid_client_id"
586
- client_secret: "valid_client_secret"
606
+ client_id: valid_client_id
607
+ client_secret: valid_client_secret
587
608
  ```
588
609
 
589
- You can now run the following commands to verify that the `test:` lambda function you have defined is working:
610
+ You can now run the following commands to go through the OAuth2 Authorization code flow which includes a browser popup.
590
611
  ```bash
591
- workato exec test --connection='My Valid Connection' #Output of the test: lambda function should be shown
592
- workato exec test --connection='Invalid Access Token' --verbose #You should see a see a set of request where the test is tried, access token receives 401 and the sequence to refresh the gem using the refresh key is used! This allows you to test how your connector refreshes a connection.
612
+ workato oauth2
593
613
  ```
594
614
 
595
- > *Note*: `--verbose` can be used to details everything, including the HTTP requests. You may also see a intermediary command from the Gem asking if you'd like to refresh your access tokens. This is done when HTTP requests are made which have a response that triggers the `refresh_on` block. Selecting yes would cause the Gem to update your settings file with the latest auth credentials.
615
+ https://user-images.githubusercontent.com/25265275/137942408-812fa6ad-353f-4ea2-bf37-f804e2ff7b04.mov
616
+
617
+
618
+ > *Note*: `--verbose` can be used to detail everything, including the HTTP requests.
619
+
620
+
621
+ Now after you've successfully gone through the flow, you may be use the same `workato exec test` command to verify you're applying your token properly in your requests! Depending on when you received your token, you may also see a intermediary command from the Gem asking if you'd like to refresh your access tokens (if it has expired). This is done when HTTP requests are made which have a response that triggers the `refresh_on` block. Selecting "Yes" would cause the Gem to update your settings file with the latest auth credentials.
622
+
623
+ Take note, you may also use `workato exec` to execute lambdas in your `authorization` hash like `acquire` and `refresh`. **That said, we highlight recommend you use `workato exec test` and `workato oauth2` which handle the updating of your `settings.yaml` file automatically.**
596
624
 
597
625
  ### Example: Testing a sample action on CLI
598
626
  Continuing from the previous example, let's take a look at a simple action and invoke the individual lambda functions.
@@ -631,7 +659,7 @@ Continuing from the previous example, let's take a look at a simple action and i
631
659
  end,
632
660
 
633
661
 
634
- execute: lambda do |connection, input, input_schema, output_schema|
662
+ execute: lambda do |connection, input, input_schema, output_schema, closure|
635
663
  get("/api/v2/customers",input)
636
664
  end,
637
665
 
@@ -1086,8 +1114,7 @@ jobs:
1086
1114
  # env:
1087
1115
  # WORKATO_API_EMAIL: ${{ secrets.WORKATO_DEV_ENVIRONMENT_API_EMAIL}}
1088
1116
  # WORKATO_API_TOKEN: ${{ secrets.WORKATO_DEV_ENVIRONMENT_API_TOKEN}}
1089
- # WORKATO_API_FOLDER: ${{ secrets.WORKATO_DEV_ENVIRONMENT_API_HOME_FOLDER}}
1090
- # run: workato push $WORKATO_API_FOLDER
1117
+ # run: workato push
1091
1118
  ```
1092
1119
 
1093
1120
  You may also add more Github actions for rubocop to automate this.
data/exe/workato CHANGED
@@ -2,4 +2,15 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'workato/cli/main'
5
- Workato::CLI::Main.start
5
+
6
+ begin
7
+ Workato::CLI::Main.start
8
+ rescue Workato::CLI::ExecCommand::DebugExceptionError => e
9
+ raise e.cause
10
+ rescue SystemExit, SignalException
11
+ raise
12
+ rescue Exception => e # rubocop:disable Lint/RescueException
13
+ puts ''
14
+ puts e.message
15
+ exit(false)
16
+ end
@@ -28,7 +28,7 @@ module Workato
28
28
 
29
29
  puts 'File encrypted and saved.'
30
30
  rescue ActiveSupport::MessageEncryptor::InvalidMessage
31
- puts "Couldn't decrypt #{encrypted_file_path}. Perhaps you passed the wrong key?"
31
+ raise "Couldn't decrypt #{encrypted_file_path}. Perhaps you passed the wrong key?"
32
32
  end
33
33
 
34
34
  private
@@ -63,8 +63,6 @@ module Workato
63
63
  yield
64
64
  rescue Interrupt
65
65
  puts 'Aborted changing file: nothing saved.'
66
- rescue ActiveSupport::EncryptedFile::MissingKeyError => e
67
- puts e.message
68
66
  end
69
67
  end
70
68
  end
@@ -1,13 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'thor'
4
- require 'ruby-progressbar'
5
4
 
6
5
  module Workato
7
6
  module CLI
8
7
  class ExecCommand
9
8
  include Thor::Shell
10
9
 
10
+ DebugExceptionError = Class.new(StandardError)
11
+
11
12
  def initialize(path:, options:)
12
13
  @path = path
13
14
  @options = options
@@ -55,10 +56,14 @@ module Workato
55
56
  extended_output_schema: from_json(options[:extended_output_schema]).presence || [],
56
57
  config_fields: from_json(options[:config_fields]),
57
58
  closure: from_json(options[:closure], parse_json_times: true).presence,
59
+ continue: from_json(options[:continue], parse_json_times: true),
58
60
  payload: from_json(options[:webhook_payload]),
59
61
  params: from_json(options[:webhook_params]),
60
62
  headers: from_json(options[:webhook_headers]),
61
63
  webhook_url: options[:webhook_url],
64
+ oauth2_code: options[:oauth2_code],
65
+ redirect_url: options[:redirect_url],
66
+ refresh_token: options[:refresh_token],
62
67
  recipe_id: SecureRandom.uuid
63
68
  }
64
69
  end
@@ -119,16 +124,25 @@ module Workato
119
124
  methods = path.split('.')
120
125
  method = methods.pop
121
126
  object = methods.inject(params[:connector]) { |obj, m| obj.public_send(m) }
127
+ output = invoke_method(object, method)
128
+ if output.respond_to?(:invoke)
129
+ invoke_method(output, :invoke)
130
+ else
131
+ output
132
+ end
133
+ rescue Exception => e # rubocop:disable Lint/RescueException
134
+ raise DebugExceptionError, e if options[:debug]
135
+
136
+ raise
137
+ end
138
+
139
+ def invoke_method(object, method)
122
140
  parameters = object.method(method).parameters.reject { |p| p[0] == :block }.map(&:second)
123
141
  args = params.values_at(*parameters)
124
142
  if parameters.last == :args
125
143
  args = args.take(args.length - 1) + Array.wrap(args.last).flatten(1)
126
144
  end
127
145
  object.public_send(method, *args)
128
- rescue Exception => e # rubocop:disable Lint/RescueException
129
- raise e if options[:debug]
130
-
131
- e.message
132
146
  end
133
147
 
134
148
  def show_output(output)
@@ -149,6 +163,8 @@ module Workato
149
163
  def with_progress
150
164
  return yield unless verbose?
151
165
 
166
+ require 'ruby-progressbar'
167
+
152
168
  say('')
153
169
 
154
170
  old_stdout = $stdout
@@ -70,8 +70,7 @@ module Workato
70
70
  def ensure_connector_source
71
71
  return if connector
72
72
 
73
- error "Can't find connector source code"
74
- exit
73
+ raise "Can't find connector source code"
75
74
  end
76
75
 
77
76
  def create_spec_file(type, name)
@@ -30,7 +30,7 @@ module Workato
30
30
  def create_spec_files
31
31
  template('.rspec.erb', '.rspec')
32
32
 
33
- @vcr_record_mode = ask(<<~HELP)
33
+ say(<<~HELP)
34
34
  Please select default HTTP mocking behavior suitable for your project?
35
35
 
36
36
  1 - secure. Cause an error to be raised for any unknown requests, all request recordings are encrypted.
@@ -39,8 +39,11 @@ module Workato
39
39
  Example: VCR_RECORD_MODE=once bundle exec rspec spec/actions/test_action_spec.rb
40
40
 
41
41
  2 - simple. Record new interaction if it is a new request, requests are stored as plain text and expose secret tokens.
42
+
42
43
  HELP
43
44
 
45
+ @vcr_record_mode = ask('Your choice:')
46
+
44
47
  MasterKeyGenerator.new.call if @vcr_record_mode == '1'
45
48
 
46
49
  template('spec/spec_helper.rb.erb', 'spec/spec_helper.rb')
@@ -6,6 +6,7 @@ require_relative './exec_command'
6
6
  require_relative './edit_command'
7
7
  require_relative './generate_command'
8
8
  require_relative './push_command'
9
+ require_relative './oauth2_command'
9
10
  require_relative './generators/connector_generator'
10
11
  require_relative './generators/master_key_generator'
11
12
 
@@ -42,6 +43,7 @@ module Workato
42
43
  "NOTE: key from #{Workato::Connector::Sdk::DEFAULT_MASTER_KEY_ENV} has higher priority"
43
44
  method_option :input, type: :string, aliases: '-i', desc: 'Path to file with input JSON'
44
45
  method_option :closure, type: :string, desc: 'Path to file with next poll closure JSON'
46
+ method_option :continue, type: :string, desc: 'Path to file with next multistep action continue closure JSON'
45
47
  method_option :args, type: :string, aliases: '-a', desc: 'Path to file with method arguments JSON'
46
48
  method_option :extended_input_schema, type: :string,
47
49
  desc: 'Path to file with extended input schema definition JSON'
@@ -53,6 +55,9 @@ module Workato
53
55
  method_option :webhook_headers, type: :string, desc: 'Path to file with webhook headers JSON'
54
56
  method_option :webhook_url, type: :string, desc: 'Webhook URL for automatic webhook subscription'
55
57
  method_option :output, type: :string, aliases: '-o', desc: 'Write output to JSON file'
58
+ method_option :oauth2_code, type: :string, desc: 'OAuth2 code exchange to tokens pair'
59
+ method_option :redirect_url, type: :string, desc: 'OAuth2 callback url'
60
+ method_option :refresh_token, type: :string, desc: 'OAuth2 refresh token'
56
61
 
57
62
  method_option :debug, type: :boolean
58
63
 
@@ -95,7 +100,7 @@ module Workato
95
100
  desc 'generate <SUBCOMMAND>', 'Generates code from template'
96
101
  subcommand('generate', GenerateCommand)
97
102
 
98
- desc 'push <FOLDER>', "Upload and release connector's code"
103
+ desc 'push', "Upload and release connector's code"
99
104
  method_option :title,
100
105
  type: :string,
101
106
  aliases: '-t',
@@ -130,10 +135,52 @@ module Workato
130
135
  enum: Workato::CLI::PushCommand::ENVIRONMENTS.keys,
131
136
  default: 'live',
132
137
  desc: 'Server to push connector code to'
138
+ method_option :folder,
139
+ type: :string,
140
+ desc: 'Folder ID if you what to push to folder other than Home'
133
141
 
134
- def push(folder)
142
+ def push
135
143
  PushCommand.new(
136
- folder: folder,
144
+ options: options
145
+ ).call
146
+ end
147
+
148
+ desc 'oauth2', 'Implements OAuth Authorization Code flow'
149
+
150
+ method_option :connector,
151
+ type: :string,
152
+ aliases: '-c',
153
+ desc: 'Path to connector source code',
154
+ lazy_default: Workato::Connector::Sdk::DEFAULT_CONNECTOR_PATH
155
+ method_option :settings,
156
+ type: :string,
157
+ aliases: '-s',
158
+ desc: 'Path to plain or encrypted file with connection configs, passwords, tokens, secrets etc',
159
+ lazy_default: Workato::Connector::Sdk::DEFAULT_ENCRYPTED_SETTINGS_PATH
160
+ method_option :connection,
161
+ type: :string,
162
+ aliases: '-n',
163
+ desc: 'Connection name if settings file contains multiple settings'
164
+ method_option :key,
165
+ type: :string,
166
+ aliases: '-k',
167
+ lazy_default: Workato::Connector::Sdk::DEFAULT_MASTER_KEY_PATH,
168
+ desc: 'Path to file with encrypt/decrypt key. '\
169
+ "NOTE: key from #{Workato::Connector::Sdk::DEFAULT_MASTER_KEY_ENV} has higher priority"
170
+ method_option :port,
171
+ type: :string,
172
+ desc: 'Listen requests on specific port',
173
+ default: Workato::CLI::OAuth2Command::DEFAULT_PORT
174
+ method_option :ip,
175
+ type: :string,
176
+ desc: 'Listen requests on specific interface',
177
+ default: Workato::CLI::OAuth2Command::DEFAULT_ADDRESS
178
+ method_option :https,
179
+ type: :boolean,
180
+ desc: 'Start HTTPS server using self-signed certificate'
181
+
182
+ def oauth2
183
+ OAuth2Command.new(
137
184
  options: options
138
185
  ).call
139
186
  end
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'workato/web/app'
4
+
5
+ module Workato
6
+ module CLI
7
+ class OAuth2Command
8
+ include Thor::Shell
9
+
10
+ AWAIT_CODE_TIMEOUT_INTERVAL = 180 # seconds
11
+ AWAIT_CODE_SLEEP_INTERVAL = 5 # seconds
12
+
13
+ DEFAULT_ADDRESS = '127.0.0.1'
14
+ DEFAULT_PORT = '45555'
15
+
16
+ def initialize(options: {})
17
+ @options = options
18
+ @port = options[:port] || DEFAULT_PORT
19
+ @https = options[:https].is_true?
20
+ @base_url = "#{https ? 'https' : 'http'}://localhost:#{port}"
21
+ @redirect_url = "#{base_url}#{Workato::Web::App::CALLBACK_PATH}"
22
+ end
23
+
24
+ def call
25
+ require_gems
26
+ start_webrick
27
+
28
+ say 'Local server is running. Allow following redirect_url in your OAuth2 provider:'
29
+ say "\n"
30
+ say redirect_url
31
+ say ''
32
+
33
+ say_status :success, "Open #{authorize_url} in browser"
34
+ Launchy.open(authorize_url) do |exception|
35
+ raise "Attempted to open #{authorize_url} and failed because #{exception}"
36
+ end
37
+
38
+ code = await_code
39
+ say_status :success, "Receive OAuth2 code=#{code}"
40
+
41
+ tokens = acquire_token(code)
42
+ say_status :success, 'Receive OAuth2 tokens'
43
+ jj tokens if verbose?
44
+
45
+ settings_store.update(tokens)
46
+ say_status :success, 'Update settings file'
47
+ rescue Timeout::Error
48
+ raise "Have not received callback from OAuth2 provider in #{AWAIT_CODE_TIMEOUT_INTERVAL} seconds. Aborting!"
49
+ rescue Errno::EADDRINUSE
50
+ raise "Port #{port} already in use. Try to use different port with --port=#{rand(10_000..65_664)}"
51
+ ensure
52
+ stop_webrick
53
+ end
54
+
55
+ private
56
+
57
+ attr_reader :https,
58
+ :base_url,
59
+ :redirect_url,
60
+ :port,
61
+ :options
62
+
63
+ def verbose?
64
+ !!options[:verbose]
65
+ end
66
+
67
+ def require_gems
68
+ require 'oauth2'
69
+ require 'launchy'
70
+ require 'rack'
71
+ end
72
+
73
+ def start_webrick
74
+ @thread = Thread.start do
75
+ Rack::Handler::WEBrick.run(
76
+ Workato::Web::App.new,
77
+ {
78
+ Port: port,
79
+ BindAddress: options[:ip] || DEFAULT_ADDRESS,
80
+ SSLEnable: https,
81
+ SSLVerifyClient: OpenSSL::SSL::VERIFY_NONE,
82
+ SSLCertName: [%w[CN localhost]]
83
+ }.tap do |server_options|
84
+ unless verbose?
85
+ server_options[:AccessLog] = []
86
+ server_options[:Logger] = WEBrick::Log.new($stderr, 0)
87
+ end
88
+ end
89
+ )
90
+ end
91
+ @thread.abort_on_exception = true
92
+ end
93
+
94
+ def stop_webrick
95
+ Rack::Handler::WEBrick.shutdown
96
+ @thread.exit
97
+ end
98
+
99
+ def client
100
+ @client ||= OAuth2::Client.new(
101
+ connector.connection.authorization.client_id(settings),
102
+ connector.connection.authorization.client_secret(settings),
103
+ site: connector.connection.base_uri(settings),
104
+ token_url: connector.connection.authorization.token_url(settings),
105
+ redirect_uri: redirect_url
106
+ )
107
+ end
108
+
109
+ def authorize_url
110
+ return @authorize_url if defined?(@authorize_url)
111
+
112
+ @authorize_url =
113
+ if (authorization_url = connector.connection.authorization.authorization_url(settings))
114
+ params = {
115
+ client_id: connector.connection.authorization.client_id(settings),
116
+ redirect_uri: redirect_url
117
+ }
118
+ uri = URI(authorization_url)
119
+ uri.query = params.with_indifferent_access.merge(Rack::Utils.parse_nested_query(uri.query || '')).to_param
120
+ uri.to_s
121
+ end
122
+ end
123
+
124
+ def settings_store
125
+ @settings_store ||= Workato::Connector::Sdk::Settings.new(
126
+ path: options[:settings],
127
+ name: options[:connection],
128
+ key_path: options[:key]
129
+ )
130
+ end
131
+
132
+ def settings
133
+ @settings ||= settings_store.read
134
+ end
135
+
136
+ def connector
137
+ @connector ||= Workato::Connector::Sdk::Connector.from_file(
138
+ options[:connector] || Workato::Connector::Sdk::DEFAULT_CONNECTOR_PATH
139
+ )
140
+ end
141
+
142
+ def await_code
143
+ code_uri = URI("#{base_url}#{Workato::Web::App::CODE_PATH}")
144
+
145
+ Timeout.timeout(AWAIT_CODE_TIMEOUT_INTERVAL) do
146
+ loop do
147
+ response = get(code_uri) rescue nil
148
+ break response if response.present?
149
+
150
+ sleep(AWAIT_CODE_SLEEP_INTERVAL)
151
+ end
152
+ end
153
+ end
154
+
155
+ def acquire_token(code)
156
+ if connector.source.dig(:connection, :authorization, :acquire)
157
+ tokens, _, extra_settings = connector.connection.authorization.acquire(settings, await_code, redirect_url)
158
+ tokens ||= {}
159
+ extra_settings ||= {}
160
+ extra_settings.merge(tokens)
161
+ else
162
+ client.auth_code.get_token(code).to_hash
163
+ end
164
+ end
165
+
166
+ def get(uri)
167
+ http = Net::HTTP.new(uri.host, uri.port)
168
+ if https
169
+ http.use_ssl = true
170
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
171
+ end
172
+
173
+ request = Net::HTTP::Get.new(uri.request_uri)
174
+
175
+ response = http.request(request)
176
+ response.body
177
+ end
178
+ end
179
+ end
180
+ end