aspera-cli 4.9.0 → 4.11.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.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +20 -0
  4. data/CHANGELOG.md +509 -0
  5. data/CONTRIBUTING.md +118 -0
  6. data/README.md +1241 -916
  7. data/bin/ascli +4 -4
  8. data/bin/asession +11 -11
  9. data/docs/test_env.conf +32 -21
  10. data/examples/aoc.rb +4 -4
  11. data/examples/dascli +16 -9
  12. data/examples/faspex4.rb +8 -8
  13. data/examples/node.rb +12 -12
  14. data/examples/server.rb +10 -10
  15. data/lib/aspera/aoc.rb +273 -266
  16. data/lib/aspera/ascmd.rb +56 -54
  17. data/lib/aspera/ats_api.rb +4 -4
  18. data/lib/aspera/cli/basic_auth_plugin.rb +15 -12
  19. data/lib/aspera/cli/extended_value.rb +5 -5
  20. data/lib/aspera/cli/formater.rb +64 -64
  21. data/lib/aspera/cli/info.rb +2 -2
  22. data/lib/aspera/cli/listener/line_dump.rb +1 -1
  23. data/lib/aspera/cli/listener/logger.rb +1 -1
  24. data/lib/aspera/cli/listener/progress.rb +5 -6
  25. data/lib/aspera/cli/listener/progress_multi.rb +14 -19
  26. data/lib/aspera/cli/main.rb +66 -67
  27. data/lib/aspera/cli/manager.rb +112 -110
  28. data/lib/aspera/cli/plugin.rb +57 -36
  29. data/lib/aspera/cli/plugins/alee.rb +4 -4
  30. data/lib/aspera/cli/plugins/aoc.rb +309 -670
  31. data/lib/aspera/cli/plugins/ats.rb +44 -46
  32. data/lib/aspera/cli/plugins/bss.rb +10 -10
  33. data/lib/aspera/cli/plugins/config.rb +497 -378
  34. data/lib/aspera/cli/plugins/console.rb +12 -12
  35. data/lib/aspera/cli/plugins/cos.rb +18 -20
  36. data/lib/aspera/cli/plugins/faspex.rb +112 -114
  37. data/lib/aspera/cli/plugins/faspex5.rb +71 -46
  38. data/lib/aspera/cli/plugins/node.rb +379 -283
  39. data/lib/aspera/cli/plugins/orchestrator.rb +46 -46
  40. data/lib/aspera/cli/plugins/preview.rb +122 -114
  41. data/lib/aspera/cli/plugins/server.rb +137 -83
  42. data/lib/aspera/cli/plugins/shares.rb +30 -29
  43. data/lib/aspera/cli/plugins/sync.rb +13 -33
  44. data/lib/aspera/cli/transfer_agent.rb +60 -59
  45. data/lib/aspera/cli/version.rb +1 -1
  46. data/lib/aspera/colors.rb +3 -3
  47. data/lib/aspera/command_line_builder.rb +27 -27
  48. data/lib/aspera/cos_node.rb +22 -20
  49. data/lib/aspera/data_repository.rb +1 -1
  50. data/lib/aspera/environment.rb +35 -15
  51. data/lib/aspera/fasp/agent_base.rb +15 -15
  52. data/lib/aspera/fasp/agent_connect.rb +23 -21
  53. data/lib/aspera/fasp/agent_direct.rb +66 -64
  54. data/lib/aspera/fasp/agent_httpgw.rb +141 -78
  55. data/lib/aspera/fasp/agent_node.rb +23 -21
  56. data/lib/aspera/fasp/agent_trsdk.rb +20 -20
  57. data/lib/aspera/fasp/error.rb +3 -2
  58. data/lib/aspera/fasp/error_info.rb +11 -8
  59. data/lib/aspera/fasp/installation.rb +79 -79
  60. data/lib/aspera/fasp/listener.rb +1 -1
  61. data/lib/aspera/fasp/parameters.rb +86 -71
  62. data/lib/aspera/fasp/parameters.yaml +7 -4
  63. data/lib/aspera/fasp/resume_policy.rb +8 -8
  64. data/lib/aspera/fasp/transfer_spec.rb +35 -2
  65. data/lib/aspera/fasp/uri.rb +7 -7
  66. data/lib/aspera/faspex_gw.rb +7 -5
  67. data/lib/aspera/hash_ext.rb +3 -3
  68. data/lib/aspera/id_generator.rb +5 -5
  69. data/lib/aspera/keychain/encrypted_hash.rb +38 -105
  70. data/lib/aspera/keychain/macos_security.rb +128 -57
  71. data/lib/aspera/log.rb +7 -7
  72. data/lib/aspera/nagios.rb +19 -18
  73. data/lib/aspera/node.rb +209 -35
  74. data/lib/aspera/oauth.rb +37 -36
  75. data/lib/aspera/open_application.rb +19 -11
  76. data/lib/aspera/persistency_action_once.rb +4 -4
  77. data/lib/aspera/persistency_folder.rb +16 -15
  78. data/lib/aspera/preview/file_types.rb +8 -8
  79. data/lib/aspera/preview/generator.rb +67 -67
  80. data/lib/aspera/preview/utils.rb +27 -27
  81. data/lib/aspera/proxy_auto_config.js +41 -41
  82. data/lib/aspera/proxy_auto_config.rb +21 -14
  83. data/lib/aspera/rest.rb +72 -67
  84. data/lib/aspera/rest_call_error.rb +2 -1
  85. data/lib/aspera/rest_error_analyzer.rb +18 -17
  86. data/lib/aspera/rest_errors_aspera.rb +16 -16
  87. data/lib/aspera/secret_hider.rb +15 -13
  88. data/lib/aspera/ssh.rb +11 -10
  89. data/lib/aspera/sync.rb +158 -44
  90. data/lib/aspera/temp_file_manager.rb +2 -2
  91. data/lib/aspera/uri_reader.rb +4 -4
  92. data/lib/aspera/web_auth.rb +14 -13
  93. data.tar.gz.sig +0 -0
  94. metadata +11 -36
  95. metadata.gz.sig +0 -0
@@ -4,6 +4,7 @@ require 'aspera/cli/basic_auth_plugin'
4
4
  require 'aspera/cli/extended_value'
5
5
  require 'aspera/cli/version'
6
6
  require 'aspera/cli/formater'
7
+ require 'aspera/cli/info'
7
8
  require 'aspera/fasp/installation'
8
9
  require 'aspera/fasp/parameters'
9
10
  require 'aspera/fasp/transfer_spec'
@@ -27,7 +28,7 @@ module Aspera
27
28
  module Cli
28
29
  module Plugins
29
30
  # manage the CLI config file
30
- class Config < Plugin
31
+ class Config < Aspera::Cli::Plugin
31
32
  # folder in $HOME for application files (config, cache)
32
33
  ASPERA_HOME_FOLDER_NAME = '.aspera'
33
34
  # default config file
@@ -37,7 +38,6 @@ module Aspera
37
38
  CONF_PRESET_VERSION = 'version'
38
39
  CONF_PRESET_DEFAULT = 'default'
39
40
  CONF_PRESET_GLOBAL = 'global_common_defaults'
40
- CONF_PRESET_SECRETS = 'default_secrets' # pragma: allowlist secret
41
41
  CONF_PLUGIN_SYM = :config # Plugins::Config.name.split('::').last.downcase.to_sym
42
42
  CONF_GLOBAL_SYM = :config
43
43
  # old tool name
@@ -62,24 +62,45 @@ module Aspera
62
62
  EMAIL_TEST_TEMPLATE = <<~END_OF_TEMPLATE
63
63
  From: <%=from_name%> <<%=from_email%>>
64
64
  To: <<%=to%>>
65
- Subject: Amelia email test
65
+ Subject: #{GEM_NAME} email test
66
66
 
67
- It worked !
67
+ This email was sent to test #{PROGRAM_NAME}.
68
68
  END_OF_TEMPLATE
69
69
  # special extended values
70
70
  EXTV_INCLUDE_PRESETS = :incps
71
71
  EXTV_PRESET = :preset
72
+ EXTV_VAULT = :vault
72
73
  PRESET_DIG_SEPARATOR = '.'
73
74
  DEFAULT_CHECK_NEW_VERSION_DAYS = 7
74
75
  DEFAULT_PRIV_KEY_FILENAME = 'aspera_aoc_key' # pragma: allowlist secret
75
76
  DEFAULT_PRIVKEY_LENGTH = 4096
76
- private_constant :DEFAULT_CONFIG_FILENAME,:CONF_PRESET_CONFIG,:CONF_PRESET_VERSION,:CONF_PRESET_DEFAULT,
77
- :CONF_PRESET_GLOBAL,:PROGRAM_NAME_V1,:PROGRAM_NAME_V2,:DEFAULT_REDIRECT,:ASPERA_PLUGINS_FOLDERNAME,
78
- :RUBY_FILE_EXT,:AOC_COMMAND_V1,:AOC_COMMAND_V2,:AOC_COMMAND_V3,:AOC_COMMAND_CURRENT,:DEMO,
79
- :TRANSFER_SDK_ARCHIVE_URL,:AOC_PATH_API_CLIENTS,:DEMO_SERVER_PRESET,:EMAIL_TEST_TEMPLATE,:EXTV_INCLUDE_PRESETS,
80
- :EXTV_PRESET,:DEFAULT_CHECK_NEW_VERSION_DAYS,:DEFAULT_PRIV_KEY_FILENAME,:SERVER_COMMAND,:CONF_PRESET_SECRETS,
77
+ private_constant :DEFAULT_CONFIG_FILENAME,
78
+ :CONF_PRESET_CONFIG,
79
+ :CONF_PRESET_VERSION,
80
+ :CONF_PRESET_DEFAULT,
81
+ :CONF_PRESET_GLOBAL,
82
+ :PROGRAM_NAME_V1,
83
+ :PROGRAM_NAME_V2,
84
+ :DEFAULT_REDIRECT,
85
+ :ASPERA_PLUGINS_FOLDERNAME,
86
+ :RUBY_FILE_EXT,
87
+ :AOC_COMMAND_V1,
88
+ :AOC_COMMAND_V2,
89
+ :AOC_COMMAND_V3,
90
+ :AOC_COMMAND_CURRENT,
91
+ :DEMO,
92
+ :TRANSFER_SDK_ARCHIVE_URL,
93
+ :AOC_PATH_API_CLIENTS,
94
+ :DEMO_SERVER_PRESET,
95
+ :EMAIL_TEST_TEMPLATE,
96
+ :EXTV_INCLUDE_PRESETS,
97
+ :EXTV_PRESET,
98
+ :EXTV_VAULT,
99
+ :DEFAULT_CHECK_NEW_VERSION_DAYS,
100
+ :DEFAULT_PRIV_KEY_FILENAME,
101
+ :SERVER_COMMAND,
81
102
  :PRESET_DIG_SEPARATOR
82
- def initialize(env,params)
103
+ def initialize(env, params)
83
104
  raise 'env and params must be Hash' unless env.is_a?(Hash) && params.is_a?(Hash)
84
105
  raise 'missing param' unless %i[name help version gem].sort.eql?(params.keys.sort)
85
106
  super(env)
@@ -91,60 +112,70 @@ module Aspera
91
112
  @config_presets = nil
92
113
  @connect_versions = nil
93
114
  @vault = nil
94
- @conf_file_default = File.join(@main_folder,DEFAULT_CONFIG_FILENAME)
115
+ @conf_file_default = File.join(@main_folder, DEFAULT_CONFIG_FILENAME)
95
116
  @option_config_file = @conf_file_default
96
117
  @pac_exec = nil
97
- Log.log.debug("#{@info[:name]} folder: #{@main_folder}")
118
+ Log.log.debug{"#{@info[:name]} folder: #{@main_folder}"}
98
119
  # set folder for FASP SDK
99
120
  add_plugin_lookup_folder(self.class.gem_plugins_folder)
100
- add_plugin_lookup_folder(File.join(@main_folder,ASPERA_PLUGINS_FOLDERNAME))
121
+ add_plugin_lookup_folder(File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME))
101
122
  # do file parameter first
102
- options.set_obj_attr(:config_file,self,:option_config_file)
103
- options.add_opt_simple(:config_file,"read parameters from file in YAML format, current=#{@option_config_file}")
123
+ options.set_obj_attr(:config_file, self, :option_config_file)
124
+ options.add_opt_simple(:config_file, "read parameters from file in YAML format, current=#{@option_config_file}")
104
125
  options.parse_options!
105
- # read correct file
126
+ # read correct file (set @config_presets)
106
127
  read_config_file
107
128
  # add preset handler (needed for smtp)
108
- ExtendedValue.instance.set_handler(EXTV_PRESET,:reader,lambda{|v|preset_by_name(v)})
109
- ExtendedValue.instance.set_handler(EXTV_INCLUDE_PRESETS,:decoder,lambda{|v|expanded_with_preset_includes(v)})
129
+ ExtendedValue.instance.set_handler(EXTV_PRESET, :reader, lambda{|v|preset_by_name(v)})
130
+ ExtendedValue.instance.set_handler(EXTV_INCLUDE_PRESETS, :decoder, lambda{|v|expanded_with_preset_includes(v)})
131
+ ExtendedValue.instance.set_handler(EXTV_VAULT, :decoder, lambda{|v|vault_value(v)})
110
132
  # load defaults before it can be overriden
111
133
  add_plugin_default_preset(CONF_GLOBAL_SYM)
112
134
  options.parse_options!
113
- options.set_obj_attr(:ascp_path,Fasp::Installation.instance,:ascp_path)
114
- options.set_obj_attr(:sdk_folder,Fasp::Installation.instance,:sdk_folder)
115
- options.set_obj_attr(:use_product,self,:option_use_product)
116
- options.set_obj_attr(:preset,self,:option_preset)
117
- options.set_obj_attr(:plugin_folder,self,:option_plugin_folder)
118
- options.add_opt_switch(:no_default,'-N','do not load default configuration for plugin') { @use_plugin_defaults = false }
119
- options.add_opt_boolean(:override,'Wizard: override existing value')
120
- options.add_opt_boolean(:use_generic_client,'Wizard: AoC: use global or org specific jwt client id')
121
- options.add_opt_boolean(:default,'Wizard: set as default configuration for specified plugin (also: update)')
122
- options.add_opt_boolean(:test_mode,'Wizard: skip private key check step')
123
- options.add_opt_simple(:preset,'-PVALUE','load the named option preset from current config file')
124
- options.add_opt_simple(:pkeypath,'Wizard: path to private key for JWT')
125
- options.add_opt_simple(:ascp_path,'path to ascp')
126
- options.add_opt_simple(:use_product,'use ascp from specified product')
127
- options.add_opt_simple(:smtp,'smtp configuration (extended value: hash)')
128
- options.add_opt_simple(:fpac,'proxy auto configuration script')
129
- options.add_opt_simple(:secret,'default secret')
130
- options.add_opt_simple(:secrets,'secret vault')
131
- options.add_opt_simple(:sdk_url,'URL to get SDK')
132
- options.add_opt_simple(:sdk_folder,'SDK folder path')
133
- options.add_opt_simple(:notif_to,'email recipient for notification of transfers')
134
- options.add_opt_simple(:notif_template,'email ERB template for notification of transfers')
135
- options.add_opt_simple(:version_check_days,Integer,'period in days to check new version (zero to disable)')
136
- options.add_opt_simple(:plugin_folder,'folder where to find additional plugins')
137
- options.set_option(:use_generic_client,true)
138
- options.set_option(:test_mode,false)
139
- options.set_option(:default,true)
140
- options.set_option(:version_check_days,DEFAULT_CHECK_NEW_VERSION_DAYS)
141
- options.set_option(:sdk_url,TRANSFER_SDK_ARCHIVE_URL)
142
- options.set_option(:sdk_folder,File.join(@main_folder,'sdk'))
143
- options.set_option(:override,:no)
135
+ options.set_obj_attr(:ascp_path, Fasp::Installation.instance, :ascp_path)
136
+ options.set_obj_attr(:sdk_folder, Fasp::Installation.instance, :sdk_folder)
137
+ options.set_obj_attr(:use_product, self, :option_use_product)
138
+ options.set_obj_attr(:preset, self, :option_preset)
139
+ options.set_obj_attr(:plugin_folder, self, :option_plugin_folder)
140
+ options.add_opt_switch(:no_default, '-N', 'do not load default configuration for plugin') { @use_plugin_defaults = false }
141
+ options.add_opt_boolean(:override, 'Wizard: override existing value')
142
+ options.add_opt_boolean(:use_generic_client, 'Wizard: AoC: use global or org specific jwt client id')
143
+ options.add_opt_boolean(:default, 'Wizard: set as default configuration for specified plugin (also: update)')
144
+ options.add_opt_boolean(:test_mode, 'Wizard: skip private key check step')
145
+ options.add_opt_simple(:preset, '-PVALUE', 'load the named option preset from current config file')
146
+ options.add_opt_simple(:pkeypath, 'Wizard: path to private key for JWT')
147
+ options.add_opt_simple(:ascp_path, 'Path to ascp')
148
+ options.add_opt_simple(:use_product, 'Use ascp from specified product')
149
+ options.add_opt_simple(:smtp, 'SMTP configuration (extended value: hash)')
150
+ options.add_opt_simple(:fpac, 'Proxy auto configuration script')
151
+ options.add_opt_simple(:proxy_credentials, 'HTTP proxy credentials (Array with user and password)')
152
+ options.add_opt_simple(:secret, 'Secret for access keys')
153
+ options.add_opt_simple(:vault, 'Vault for secrets')
154
+ options.add_opt_simple(:vault_password, 'Vault password')
155
+ options.add_opt_simple(:sdk_url, 'URL to get SDK')
156
+ options.add_opt_simple(:sdk_folder, 'SDK folder path')
157
+ options.add_opt_simple(:notif_to, 'Email recipient for notification of transfers')
158
+ options.add_opt_simple(:notif_template, 'Email ERB template for notification of transfers')
159
+ options.add_opt_simple(:version_check_days, Integer, 'Period in days to check new version (zero to disable)')
160
+ options.add_opt_simple(:plugin_folder, 'Folder where to find additional plugins')
161
+ options.set_option(:use_generic_client, true)
162
+ options.set_option(:test_mode, false)
163
+ options.set_option(:default, true)
164
+ options.set_option(:version_check_days, DEFAULT_CHECK_NEW_VERSION_DAYS)
165
+ options.set_option(:sdk_url, TRANSFER_SDK_ARCHIVE_URL)
166
+ options.set_option(:sdk_folder, File.join(@main_folder, 'sdk'))
167
+ options.set_option(:override, :no)
144
168
  options.parse_options!
145
169
  pac_script = options.get_option(:fpac)
146
170
  # create PAC executor
147
171
  @pac_exec = Aspera::ProxyAutoConfig.new(pac_script).register_uri_generic unless pac_script.nil?
172
+ proxy_creds = options.get_option(:proxy_credentials)
173
+ if !proxy_creds.nil?
174
+ raise CliBadArgument, 'proxy credentials shall be an array (#{proxy_creds.class})' unless proxy_creds.is_a?(Array)
175
+ raise CliBadArgument, 'proxy credentials shall have two elements (#{proxy_creds.length})' unless proxy_creds.length.eql?(2)
176
+ @pac_exec.proxy_user = Rest.proxy_user = proxy_creds[0]
177
+ @pac_exec.proxy_pass = Rest.proxy_pass = proxy_creds[1]
178
+ end
148
179
  end
149
180
 
150
181
  # env var name to override the app's main folder
@@ -159,8 +190,8 @@ module Aspera
159
190
  # if env var undefined or empty
160
191
  if app_folder.nil? || app_folder.empty?
161
192
  user_home_folder = Dir.home
162
- raise CliError,"Home folder does not exist: #{user_home_folder}. Check your user environment or use #{conf_dir_env_var}." unless Dir.exist?(user_home_folder)
163
- app_folder = File.join(user_home_folder,ASPERA_HOME_FOLDER_NAME,@info[:name])
193
+ raise CliError, "Home folder does not exist: #{user_home_folder}. Check your user environment or use #{conf_dir_env_var}." unless Dir.exist?(user_home_folder)
194
+ app_folder = File.join(user_home_folder, ASPERA_HOME_FOLDER_NAME, @info[:name])
164
195
  end
165
196
  return app_folder
166
197
  end
@@ -174,8 +205,10 @@ module Aspera
174
205
  '0'
175
206
  end
176
207
  if Gem::Version.new(Environment.ruby_version) < Gem::Version.new(RUBY_FUTURE_MINIMUM_VERSION)
177
- Log.log.warn("Note that a future version will require Ruby version #{RUBY_FUTURE_MINIMUM_VERSION} at minimum, "\
178
- "you are using #{Environment.ruby_version}")
208
+ Log.log.warn do
209
+ "Note that a future version will require Ruby version #{RUBY_FUTURE_MINIMUM_VERSION} at minimum, "\
210
+ "you are using #{Environment.ruby_version}"
211
+ end
179
212
  end
180
213
  return {
181
214
  name: @info[:gem],
@@ -187,8 +220,8 @@ module Aspera
187
220
 
188
221
  def periodic_check_newer_gem_version
189
222
  # get verification period
190
- delay_days = options.get_option(:version_check_days,is_type: :mandatory)
191
- Log.log.info("check days: #{delay_days}")
223
+ delay_days = options.get_option(:version_check_days, is_type: :mandatory)
224
+ Log.log.info{"check days: #{delay_days}"}
192
225
  # check only if not zero day
193
226
  return if delay_days.eql?(0)
194
227
  # get last date from persistency
@@ -206,28 +239,29 @@ module Aspera
206
239
  # negative value will force check
207
240
  -1
208
241
  end
209
- Log.log.debug("days elapsed: #{last_check_days}")
242
+ Log.log.debug{"days elapsed: #{last_check_days}"}
210
243
  return if last_check_days < delay_days
211
244
  # generate timestamp
212
245
  last_check_array[0] = current_date.strftime('%Y/%m/%d')
213
246
  check_date_persist.save
214
247
  # compare this version and the one on internet
215
248
  check_data = check_gem_version
216
- Log.log.warn("A new version is available: #{check_data[:latest]}. "\
217
- "You have #{check_data[:current]}. Upgrade with: gem update #{check_data[:name]}") if check_data[:need_update]
249
+ Log.log.warn do
250
+ "A new version is available: #{check_data[:latest]}. You have #{check_data[:current]}. Upgrade with: gem update #{check_data[:name]}"
251
+ end if check_data[:need_update]
218
252
  end
219
253
 
220
254
  # retrieve structure from cloud (CDN) with all versions available
221
255
  def connect_versions
222
256
  if @connect_versions.nil?
223
257
  api_connect_cdn = Rest.new({base_url: CONNECT_WEB_URL})
224
- javascript = api_connect_cdn.call({operation: 'GET',subpath: CONNECT_VERSIONS})
258
+ javascript = api_connect_cdn.call({operation: 'GET', subpath: CONNECT_VERSIONS})
225
259
  # get result on one line
226
- connect_versions_javascript = javascript[:http].body.gsub(/\r?\n\s*/,'')
227
- Log.log.debug("javascript=[\n#{connect_versions_javascript}\n]")
260
+ connect_versions_javascript = javascript[:http].body.gsub(/\r?\n\s*/, '')
261
+ Log.log.debug{"javascript=[\n#{connect_versions_javascript}\n]"}
228
262
  # get javascript object only
229
263
  found = connect_versions_javascript.match(/^.*? = (.*);/)
230
- raise CliError,'Problen when getting connect versions from internet' if found.nil?
264
+ raise CliError, 'Problen when getting connect versions from internet' if found.nil?
231
265
  alldata = JSON.parse(found[1])
232
266
  @connect_versions = alldata['entries']
233
267
  end
@@ -240,20 +274,20 @@ module Aspera
240
274
  # @param plugin_name_sym : symbol for plugin name
241
275
  def add_plugin_default_preset(plugin_name_sym)
242
276
  default_config_name = get_plugin_default_config_name(plugin_name_sym)
243
- Log.log.debug("add_plugin_default_preset:#{plugin_name_sym}:#{default_config_name}")
244
- options.add_option_preset(preset_by_name(default_config_name),op: :unshift) unless default_config_name.nil?
277
+ Log.log.debug{"add_plugin_default_preset:#{plugin_name_sym}:#{default_config_name}"}
278
+ options.add_option_preset(preset_by_name(default_config_name), op: :unshift) unless default_config_name.nil?
245
279
  return nil
246
280
  end
247
281
 
248
282
  private
249
283
 
250
- def generate_rsa_private_key(private_key_path,length)
284
+ def generate_rsa_private_key(private_key_path, length)
251
285
  require 'openssl'
252
286
  priv_key = OpenSSL::PKey::RSA.new(length)
253
- File.write(private_key_path,priv_key.to_s)
254
- File.chmod(0400,private_key_path)
255
- File.write(private_key_path + '.pub',priv_key.public_key.to_s)
256
- File.chmod(0400,private_key_path + '.pub')
287
+ File.write(private_key_path, priv_key.to_s)
288
+ File.write(private_key_path + '.pub', priv_key.public_key.to_s)
289
+ Environment.restrict_file_access(private_key_path)
290
+ Environment.restrict_file_access(private_key_path + '.pub')
257
291
  nil
258
292
  end
259
293
 
@@ -272,7 +306,7 @@ module Aspera
272
306
  # @return main folder where code is, i.e. .../lib
273
307
  # go up as many times as englobing modules (not counting class, as it is a file)
274
308
  def gem_src_root
275
- File.expand_path(module_full_name.gsub('::','/').gsub(%r{[^/]+},'..'),gem_plugins_folder)
309
+ File.expand_path(module_full_name.gsub('::', '/').gsub(%r{[^/]+}, '..'), gem_plugins_folder)
276
310
  end
277
311
 
278
312
  # instanciate a plugin
@@ -286,7 +320,7 @@ module Aspera
286
320
  # set parameter and value in global config
287
321
  # creates one if none already created
288
322
  # @return preset name that contains global default
289
- def set_global_default(key,value)
323
+ def set_global_default(key, value)
290
324
  # get default preset if it exists
291
325
  global_default_preset_name = get_plugin_default_config_name(CONF_GLOBAL_SYM)
292
326
  if global_default_preset_name.nil?
@@ -312,17 +346,17 @@ module Aspera
312
346
  # @param config_name name of the preset in config file
313
347
  # @param include_path used to detect and avoid include loops
314
348
  def preset_by_name(config_name, include_path=[])
315
- raise CliError,'loop in include' if include_path.include?(config_name)
349
+ raise CliError, 'loop in include' if include_path.include?(config_name)
316
350
  include_path = include_path.clone # avoid messing up if there are multiple branches
317
351
  current = @config_presets
318
352
  config_name.split(PRESET_DIG_SEPARATOR).each do |name|
319
- raise CliError,"not a Hash: #{include_path} (#{current.class})" unless current.is_a?(Hash)
353
+ raise CliError, "Expecting Hash for subkey: #{include_path} (#{current.class})" unless current.is_a?(Hash)
320
354
  include_path.push(name)
321
355
  current = current[name]
322
- raise CliError,"no such config preset: #{include_path}" if nil?
356
+ raise CliError, "No such config preset: #{include_path}" if current.nil?
323
357
  end
324
358
  case current
325
- when Hash then return expanded_with_preset_includes(current,include_path)
359
+ when Hash then return expanded_with_preset_includes(current, include_path)
326
360
  when String then return ExtendedValue.instance.evaluate(current)
327
361
  else return current
328
362
  end
@@ -332,8 +366,8 @@ module Aspera
332
366
  # @param hash_val
333
367
  # @param include_path to avoid inclusion loop
334
368
  def expanded_with_preset_includes(hash_val, include_path=[])
335
- raise CliError,"#{EXTV_INCLUDE_PRESETS} requires a Hash, have #{hash_val.class}" unless hash_val.is_a?(Hash)
336
- if hash_val.has_key?(EXTV_INCLUDE_PRESETS)
369
+ raise CliError, "#{EXTV_INCLUDE_PRESETS} requires a Hash, have #{hash_val.class}" unless hash_val.is_a?(Hash)
370
+ if hash_val.key?(EXTV_INCLUDE_PRESETS)
337
371
  memory = hash_val.clone
338
372
  includes = memory[EXTV_INCLUDE_PRESETS]
339
373
  memory.delete(EXTV_INCLUDE_PRESETS)
@@ -341,7 +375,7 @@ module Aspera
341
375
  raise "#{EXTV_INCLUDE_PRESETS} must be an Array" unless includes.is_a?(Array)
342
376
  raise "#{EXTV_INCLUDE_PRESETS} must contain names" unless includes.map(&:class).uniq.eql?([String])
343
377
  includes.each do |preset_name|
344
- hash_val.merge!(preset_by_name(preset_name,include_path))
378
+ hash_val.merge!(preset_by_name(preset_name, include_path))
345
379
  end
346
380
  hash_val.merge!(memory)
347
381
  end
@@ -381,121 +415,122 @@ module Aspera
381
415
  end
382
416
  end
383
417
 
384
- def convert_preset_path(old_name,new_name,files_to_copy)
385
- old_subpath = File.join('',ASPERA_HOME_FOLDER_NAME,old_name,'')
386
- new_subpath = File.join('',ASPERA_HOME_FOLDER_NAME,new_name,'')
418
+ def convert_preset_path(old_name, new_name, files_to_copy)
419
+ old_subpath = File.join('', ASPERA_HOME_FOLDER_NAME, old_name, '')
420
+ new_subpath = File.join('', ASPERA_HOME_FOLDER_NAME, new_name, '')
387
421
  # convert possible keys located in config folder
388
422
  @config_presets.values.select{|p|p.is_a?(Hash)}.each do |preset|
389
423
  preset.values.select{|v|v.is_a?(String) && v.include?(old_subpath)}.each do |value|
390
424
  old_val = value.clone
391
- included_path = File.expand_path(old_val.gsub(/^@file:/,''))
425
+ included_path = File.expand_path(old_val.gsub(/^@file:/, ''))
392
426
  files_to_copy.push(included_path) unless files_to_copy.include?(included_path) || !File.exist?(included_path)
393
- value.gsub!(old_subpath,new_subpath)
394
- Log.log.warn("Converted config value: #{old_val} -> #{value}")
427
+ value.gsub!(old_subpath, new_subpath)
428
+ Log.log.warn{"Converted config value: #{old_val} -> #{value}"}
395
429
  end
396
430
  end
397
431
  end
398
432
 
399
- def convert_preset_plugin_name(old_name,new_name)
433
+ def convert_preset_plugin_name(old_name, new_name)
400
434
  default_preset = @config_presets[CONF_PRESET_DEFAULT]
401
- return unless default_preset.is_a?(Hash) && default_preset.has_key?(old_name)
435
+ return unless default_preset.is_a?(Hash) && default_preset.key?(old_name)
402
436
  default_preset[new_name] = default_preset[old_name]
403
437
  default_preset.delete(old_name)
404
- Log.log.warn("Converted plugin default: #{old_name} -> #{new_name}")
438
+ Log.log.warn{"Converted plugin default: #{old_name} -> #{new_name}"}
405
439
  end
406
440
 
407
441
  # read config file and validate format
408
442
  # tries to convert from older version if possible and required
409
443
  def read_config_file
410
- begin
411
- Log.log.debug("config file is: #{@option_config_file}".red)
412
- conf_file_v1 = File.join(Dir.home,ASPERA_HOME_FOLDER_NAME,PROGRAM_NAME_V1,DEFAULT_CONFIG_FILENAME)
413
- conf_file_v2 = File.join(Dir.home,ASPERA_HOME_FOLDER_NAME,PROGRAM_NAME_V2,DEFAULT_CONFIG_FILENAME)
414
- # files search for configuration, by default the one given by user
415
- search_files = [@option_config_file]
416
- # if default file, then also look for older versions
417
- search_files.push(conf_file_v2,conf_file_v1) if @option_config_file.eql?(@conf_file_default)
418
- # find first existing file (or nil)
419
- conf_file_to_load = search_files.find{|f| File.exist?(f)}
420
- # require save if old version of file
421
- save_required = !@option_config_file.eql?(conf_file_to_load)
422
- # if no file found, create default config
423
- if conf_file_to_load.nil?
424
- Log.log.warn("No config file found. Creating empty configuration file: #{@option_config_file}")
425
- @config_presets = {CONF_PRESET_CONFIG => {CONF_PRESET_VERSION => @info[:version]}}
426
- else
427
- Log.log.debug("loading #{@option_config_file}")
428
- @config_presets = YAML.load_file(conf_file_to_load)
429
- end
430
- files_to_copy = []
431
- Log.log.debug("Available_presets: #{@config_presets}")
432
- raise 'Expecting YAML Hash' unless @config_presets.is_a?(Hash)
433
- # check there is at least the config section
434
- if !@config_presets.has_key?(CONF_PRESET_CONFIG)
435
- raise "Cannot find key: #{CONF_PRESET_CONFIG}"
436
- end
437
- version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]
438
- if version.nil?
439
- raise 'No version found in config section.'
440
- end
441
- # oldest compatible conf file format, update to latest version when an incompatible change is made
442
- # check compatibility of version of conf file
443
- config_tested_version = '0.4.5'
444
- if Gem::Version.new(version) < Gem::Version.new(config_tested_version)
445
- raise "Unsupported config file version #{version}. Expecting min version #{config_tested_version}"
446
- end
447
- config_tested_version = '0.6.15'
448
- if Gem::Version.new(version) < Gem::Version.new(config_tested_version)
449
- convert_preset_plugin_name(AOC_COMMAND_V1,AOC_COMMAND_V2)
450
- version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = config_tested_version
451
- save_required = true
452
- end
453
- config_tested_version = '0.8.10'
454
- if Gem::Version.new(version) <= Gem::Version.new(config_tested_version)
455
- convert_preset_path(PROGRAM_NAME_V1,PROGRAM_NAME_V2,files_to_copy)
456
- version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = config_tested_version
457
- save_required = true
458
- end
459
- config_tested_version = '1.0'
460
- if Gem::Version.new(version) <= Gem::Version.new(config_tested_version)
461
- convert_preset_plugin_name(AOC_COMMAND_V2,AOC_COMMAND_V3)
462
- convert_preset_path(PROGRAM_NAME_V2,@info[:name],files_to_copy)
463
- version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = config_tested_version
464
- save_required = true
465
- end
466
- Log.log.debug("conf version: #{version}")
467
- # Place new compatibility code here
468
- if save_required
469
- Log.log.warn('Saving automatic conversion.')
470
- @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = @info[:version]
471
- save_presets_to_config_file
472
- Log.log.warn('Copying referenced files')
473
- files_to_copy.each do |file|
474
- FileUtils.cp(file,@main_folder)
475
- Log.log.warn("..#{file} -> #{@main_folder}")
476
- end
444
+ Log.log.debug{"config file is: #{@option_config_file}".red}
445
+ conf_file_v1 = File.join(Dir.home, ASPERA_HOME_FOLDER_NAME, PROGRAM_NAME_V1, DEFAULT_CONFIG_FILENAME)
446
+ conf_file_v2 = File.join(Dir.home, ASPERA_HOME_FOLDER_NAME, PROGRAM_NAME_V2, DEFAULT_CONFIG_FILENAME)
447
+ # files search for configuration, by default the one given by user
448
+ search_files = [@option_config_file]
449
+ # if default file, then also look for older versions
450
+ search_files.push(conf_file_v2, conf_file_v1) if @option_config_file.eql?(@conf_file_default)
451
+ # find first existing file (or nil)
452
+ conf_file_to_load = search_files.find{|f| File.exist?(f)}
453
+ # require save if old version of file
454
+ save_required = !@option_config_file.eql?(conf_file_to_load)
455
+ # if no file found, create default config
456
+ if conf_file_to_load.nil?
457
+ Log.log.warn{"No config file found. Creating empty configuration file: #{@option_config_file}"}
458
+ @config_presets = {CONF_PRESET_CONFIG => {CONF_PRESET_VERSION => @info[:version]}}
459
+ else
460
+ Log.log.debug{"loading #{@option_config_file}"}
461
+ @config_presets = YAML.load_file(conf_file_to_load)
462
+ end
463
+ files_to_copy = []
464
+ Log.log.debug{"Available_presets: #{@config_presets}"}
465
+ raise 'Expecting YAML Hash' unless @config_presets.is_a?(Hash)
466
+ # check there is at least the config section
467
+ if !@config_presets.key?(CONF_PRESET_CONFIG)
468
+ raise "Cannot find key: #{CONF_PRESET_CONFIG}"
469
+ end
470
+ version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]
471
+ if version.nil?
472
+ raise 'No version found in config section.'
473
+ end
474
+ # oldest compatible conf file format, update to latest version when an incompatible change is made
475
+ # check compatibility of version of conf file
476
+ config_tested_version = '0.4.5'
477
+ if Gem::Version.new(version) < Gem::Version.new(config_tested_version)
478
+ raise "Unsupported config file version #{version}. Expecting min version #{config_tested_version}"
479
+ end
480
+ config_tested_version = '0.6.15'
481
+ if Gem::Version.new(version) < Gem::Version.new(config_tested_version)
482
+ convert_preset_plugin_name(AOC_COMMAND_V1, AOC_COMMAND_V2)
483
+ version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = config_tested_version
484
+ save_required = true
485
+ end
486
+ config_tested_version = '0.8.10'
487
+ if Gem::Version.new(version) <= Gem::Version.new(config_tested_version)
488
+ convert_preset_path(PROGRAM_NAME_V1, PROGRAM_NAME_V2, files_to_copy)
489
+ version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = config_tested_version
490
+ save_required = true
491
+ end
492
+ config_tested_version = '1.0'
493
+ if Gem::Version.new(version) <= Gem::Version.new(config_tested_version)
494
+ convert_preset_plugin_name(AOC_COMMAND_V2, AOC_COMMAND_V3)
495
+ convert_preset_path(PROGRAM_NAME_V2, @info[:name], files_to_copy)
496
+ version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = config_tested_version
497
+ save_required = true
498
+ end
499
+ Log.log.debug{"conf version: #{version}"}
500
+ # Place new compatibility code here
501
+ if save_required
502
+ Log.log.warn('Saving automatic conversion.')
503
+ @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = @info[:version]
504
+ save_presets_to_config_file
505
+ Log.log.warn('Copying referenced files')
506
+ files_to_copy.each do |file|
507
+ FileUtils.cp(file, @main_folder)
508
+ Log.log.warn{"..#{file} -> #{@main_folder}"}
477
509
  end
478
- rescue Psych::SyntaxError => e
479
- Log.log.error('YAML error in config file')
480
- raise e
481
- rescue StandardError => e
482
- Log.log.debug("-> #{e}")
510
+ end
511
+ rescue Psych::SyntaxError => e
512
+ Log.log.error('YAML error in config file')
513
+ raise e
514
+ rescue StandardError => e
515
+ Log.log.debug{"-> #{e.class.name} : #{e}"}
516
+ if File.exist?(@option_config_file)
517
+ # then there is a problem with that file.
483
518
  new_name = "#{@option_config_file}.pre#{@info[:version]}.manual_conversion_needed"
484
- File.rename(@option_config_file,new_name)
485
- Log.log.warn("Renamed config file to #{new_name}.")
519
+ File.rename(@option_config_file, new_name)
520
+ Log.log.warn{"Renamed config file to #{new_name}."}
486
521
  Log.log.warn('Manual Conversion is required. Next time, a new empty file will be created.')
487
- raise CliError,e.to_s
488
522
  end
523
+ raise CliError, e.to_s
489
524
  end
490
525
 
491
526
  # find plugins in defined paths
492
527
  def add_plugins_from_lookup_folders
493
528
  @plugin_lookup_folders.each do |folder|
494
529
  next unless File.directory?(folder)
495
- #TODO: add gem root to load path ? and require short folder ?
496
- #$LOAD_PATH.push(folder) if i[:add_path]
530
+ # TODO: add gem root to load path ? and require short folder ?
531
+ # $LOAD_PATH.push(folder) if i[:add_path]
497
532
  Dir.entries(folder).select{|file|file.end_with?(RUBY_FILE_EXT)}.each do |source|
498
- add_plugin_info(File.join(folder,source))
533
+ add_plugin_info(File.join(folder, source))
499
534
  end
500
535
  end
501
536
  end
@@ -506,17 +541,17 @@ module Aspera
506
541
 
507
542
  def add_plugin_info(path)
508
543
  raise "ERROR: plugin path must end with #{RUBY_FILE_EXT}" if !path.end_with?(RUBY_FILE_EXT)
509
- plugin_symbol = File.basename(path,RUBY_FILE_EXT).to_sym
510
- req = path.gsub(/#{RUBY_FILE_EXT}$/o,'')
511
- if @plugins.has_key?(plugin_symbol)
512
- Log.log.warn("skipping plugin already registered: #{plugin_symbol}")
544
+ plugin_symbol = File.basename(path, RUBY_FILE_EXT).to_sym
545
+ req = path.gsub(/#{RUBY_FILE_EXT}$/o, '')
546
+ if @plugins.key?(plugin_symbol)
547
+ Log.log.warn{"skipping plugin already registered: #{plugin_symbol}"}
513
548
  return
514
549
  end
515
- @plugins[plugin_symbol] = {source: path,require_stanza: req}
550
+ @plugins[plugin_symbol] = {source: path, require_stanza: req}
516
551
  end
517
552
 
518
553
  def identify_plugin_for_url(url)
519
- plugins.each do |plugin_name_sym,plugin_info|
554
+ plugins.each do |plugin_name_sym, plugin_info|
520
555
  # no detection for internal plugin
521
556
  next if plugin_name_sym.eql?(CONF_PLUGIN_SYM)
522
557
  # load plugin class
@@ -532,19 +567,19 @@ module Aspera
532
567
  Log.log.warn(e.message)
533
568
  Log.log.warn('Use option --insecure=yes to ignore certificate') if e.message.include?('cert')
534
569
  rescue StandardError => e
535
- Log.log.debug("Cannot detect #{plugin_name_sym} : #{e.class}/#{e.message}")
570
+ Log.log.debug{"Cannot detect #{plugin_name_sym} : #{e.class}/#{e.message}"}
536
571
  end
537
572
  # second try : is there a redirect ?
538
573
  if detection_info.nil?
539
574
  begin
540
575
  # TODO: check if redirect ?
541
- new_url = Rest.new(base_url: url).call(operation: 'GET',subpath: '',redirect_max: 1)[:http].uri.to_s
576
+ new_url = Rest.new(base_url: url).call(operation: 'GET', subpath: '', redirect_max: 1)[:http].uri.to_s
542
577
  unless url.eql?(new_url)
543
578
  detection_info = c.detect(new_url)
544
579
  current_url = new_url
545
580
  end
546
581
  rescue StandardError => e
547
- Log.log.debug("Cannot detect #{plugin_name_sym} : #{e.message}")
582
+ Log.log.debug{"Cannot detect #{plugin_name_sym} : #{e.message}"}
548
583
  end
549
584
  end
550
585
  return detection_info.merge(product: plugin_name_sym, url: current_url) unless detection_info.nil?
@@ -557,7 +592,7 @@ module Aspera
557
592
  if %i[info version].include?(command)
558
593
  connect_id = options.get_next_argument('id or title')
559
594
  one_res = connect_versions.find{|i|i['id'].eql?(connect_id) || i['title'].eql?(connect_id)}
560
- raise CliNoSuchId.new(:connect,connect_id) if one_res.nil?
595
+ raise CliNoSuchId.new(:connect, connect_id) if one_res.nil?
561
596
  end
562
597
  case command
563
598
  when :list
@@ -578,11 +613,11 @@ module Aspera
578
613
  return {type: :object_list, data: all_links}
579
614
  when :download
580
615
  folder_dest = transfer.destination_folder(Fasp::TransferSpec::DIRECTION_RECEIVE)
581
- #folder_dest=self.options.get_next_argument('destination folder')
616
+ # folder_dest=self.options.get_next_argument('destination folder')
582
617
  api_connect_cdn = Rest.new({base_url: CONNECT_WEB_URL})
583
618
  fileurl = one_link['href']
584
- filename = fileurl.gsub(%r{.*/},'')
585
- api_connect_cdn.call({operation: 'GET',subpath: fileurl,save_to_file: File.join(folder_dest,filename)})
619
+ filename = fileurl.gsub(%r{.*/}, '')
620
+ api_connect_cdn.call({operation: 'GET', subpath: fileurl, save_to_file: File.join(folder_dest, filename)})
586
621
  return Main.result_status("Downloaded: #{filename}")
587
622
  when :open
588
623
  OpenApplication.instance.uri(one_link['href'])
@@ -600,12 +635,12 @@ module Aspera
600
635
  ascp_path = options.get_next_argument('path to ascp')
601
636
  ascp_version = Fasp::Installation.instance.get_ascp_version(ascp_path)
602
637
  self.format.display_status("ascp version: #{ascp_version}")
603
- preset_name = set_global_default(:ascp_path,ascp_path)
638
+ preset_name = set_global_default(:ascp_path, ascp_path)
604
639
  return Main.result_status("Saved to default global preset #{preset_name}")
605
640
  when :show # shows files used
606
641
  return {type: :status, data: Fasp::Installation.instance.path(:ascp)}
607
642
  when :info # shows files used
608
- data = Fasp::Installation::FILES.each_with_object({}) do |v,m|
643
+ data = Fasp::Installation::FILES.each_with_object({}) do |v, m|
609
644
  m[v.to_s] =
610
645
  begin
611
646
  Fasp::Installation.instance.path(v)
@@ -614,20 +649,26 @@ module Aspera
614
649
  end
615
650
  end
616
651
  # read PATHs from ascp directly, and pvcl modules as well
617
- Open3.popen3(Fasp::Installation.instance.path(:ascp),'-DDL-') do |_stdin, _stdout, stderr, thread|
652
+ Open3.popen3(Fasp::Installation.instance.path(:ascp), '-DDL-') do |_stdin, _stdout, stderr, thread|
618
653
  last_line = ''
619
654
  while (line = stderr.gets)
620
655
  line.chomp!
621
656
  last_line = line
622
657
  case line
623
- when %r{^DBG Path ([^ ]+) (dir|file) +: (.*)$} then data[Regexp.last_match(1)] = Regexp.last_match(3)
624
- when %r{^DBG Added module group:"([^"]+)" name:"([^"]+)", version:"([^"]+)" interface:"([^"]+)"$} then data[Regexp.last_match(2)] = Regexp.last_match(4)
625
- when %r{^DBG License result \(/license/(\S+)\): (.+)$} then data[Regexp.last_match(1)] = Regexp.last_match(2)
626
- when %r{^LOG (.+) version ([0-9.]+)$} then data['product_name'] = Regexp.last_match(1);data['product_version'] = Regexp.last_match(2)
627
- when %r{^LOG Initializing FASP version ([^,]+),} then data['ascp_version'] = Regexp.last_match(1)
658
+ when /^DBG Path ([^ ]+) (dir|file) +: (.*)$/
659
+ data[Regexp.last_match(1)] = Regexp.last_match(3)
660
+ when /^DBG Added module group:"([^"]+)" name:"([^"]+)", version:"([^"]+)" interface:"([^"]+)"$/
661
+ data[Regexp.last_match(2)] = "#{Regexp.last_match(4)} #{Regexp.last_match(1)} v#{Regexp.last_match(3)}"
662
+ when %r{^DBG License result \(/license/(\S+)\): (.+)$}
663
+ data[Regexp.last_match(1)] = Regexp.last_match(2)
664
+ when /^LOG (.+) version ([0-9.]+)$/
665
+ data['product_name'] = Regexp.last_match(1)
666
+ data['product_version'] = Regexp.last_match(2)
667
+ when /^LOG Initializing FASP version ([^,]+),/
668
+ data['ascp_version'] = Regexp.last_match(1)
628
669
  end
629
670
  end
630
- if !thread.value.exitstatus.eql?(1) && !data.has_key?('root')
671
+ if !thread.value.exitstatus.eql?(1) && !data.key?('root')
631
672
  raise last_line
632
673
  end
633
674
  end
@@ -641,21 +682,21 @@ module Aspera
641
682
  when :use
642
683
  default_product = options.get_next_argument('product name')
643
684
  Fasp::Installation.instance.use_ascp_from_product(default_product)
644
- preset_name = set_global_default(:ascp_path,Fasp::Installation.instance.path(:ascp))
685
+ preset_name = set_global_default(:ascp_path, Fasp::Installation.instance.path(:ascp))
645
686
  return Main.result_status("Saved to default global preset #{preset_name}")
646
687
  end
647
688
  when :install
648
- v = Fasp::Installation.instance.install_sdk(options.get_option(:sdk_url,is_type: :mandatory))
689
+ v = Fasp::Installation.instance.install_sdk(options.get_option(:sdk_url, is_type: :mandatory))
649
690
  return Main.result_status("Installed version #{v}")
650
691
  when :spec
651
692
  return {
652
693
  type: :object_list,
653
694
  data: Fasp::Parameters.man_table,
654
- fields: ['name','type',Fasp::Parameters::SUPPORTED_AGENTS_SHORT.map(&:to_s),'description'].flatten
695
+ fields: %w[name type].concat(Fasp::Parameters::SUPPORTED_AGENTS_SHORT.map(&:to_s), %w[description])
655
696
  }
656
697
  when :errors
657
698
  error_data = []
658
- Fasp::ERROR_INFO.each_pair do |code,prop|
699
+ Fasp::ERROR_INFO.each_pair do |code, prop|
659
700
  error_data.push(code: code, mnemonic: prop[:c], retry: prop[:r], info: prop[:a])
660
701
  end
661
702
  return {type: :object_list, data: error_data}
@@ -664,112 +705,168 @@ module Aspera
664
705
  end
665
706
 
666
707
  # legacy actions available globally
667
- PRESET_GBL_ACTIONS = %i[list overview].freeze
708
+ PRESET_GBL_ACTIONS = %i[list overview lookup secure].freeze
668
709
  # require existing preset
669
710
  PRESET_EXST_ACTIONS = %i[show delete get unset].freeze
670
711
  # require id
671
- PRESET_INSTANCE_ACTIONS = [PRESET_EXST_ACTIONS,%i[initialize update ask set]].flatten.freeze
672
- PRESET_ALL_ACTIONS = [PRESET_GBL_ACTIONS,PRESET_INSTANCE_ACTIONS].flatten.freeze
712
+ PRESET_INSTANCE_ACTIONS = %i[initialize update ask set].concat(PRESET_EXST_ACTIONS).freeze
713
+ PRESET_ALL_ACTIONS = [].concat(PRESET_GBL_ACTIONS, PRESET_INSTANCE_ACTIONS).freeze
673
714
 
674
- def execute_file_action(action,config_name)
715
+ def execute_preset(action: nil, name: nil)
675
716
  action = options.get_next_command(PRESET_ALL_ACTIONS) if action.nil?
676
- config_name = instance_identifier if config_name.nil? && PRESET_INSTANCE_ACTIONS.include?(action)
717
+ name = instance_identifier if name.nil? && PRESET_INSTANCE_ACTIONS.include?(action)
677
718
  # those operations require existing option
678
- raise "no such preset: #{config_name}" if PRESET_EXST_ACTIONS.include?(action) && !@config_presets.has_key?(config_name)
679
- selected_preset = @config_presets[config_name]
719
+ raise "no such preset: #{name}" if PRESET_EXST_ACTIONS.include?(action) && !@config_presets.key?(name)
720
+ selected_preset = @config_presets[name]
680
721
  case action
681
722
  when :list
682
723
  return {type: :value_list, data: @config_presets.keys, name: 'name'}
683
724
  when :overview
684
725
  return {type: :object_list, data: Formater.flatten_config_overview(@config_presets)}
685
726
  when :show
686
- raise "no such config: #{config_name}" if selected_preset.nil?
727
+ raise "no such config: #{name}" if selected_preset.nil?
687
728
  return {type: :single_object, data: selected_preset}
688
729
  when :delete
689
- @config_presets.delete(config_name)
730
+ @config_presets.delete(name)
690
731
  save_presets_to_config_file
691
- return Main.result_status("Deleted: #{config_name}")
732
+ return Main.result_status("Deleted: #{name}")
692
733
  when :get
693
734
  param_name = options.get_next_argument('parameter name')
694
735
  value = selected_preset[param_name]
695
- raise "no such option in preset #{config_name} : #{param_name}" if value.nil?
736
+ raise "no such option in preset #{name} : #{param_name}" if value.nil?
696
737
  case value
697
- when Numeric,String then return {type: :text, data: ExtendedValue.instance.evaluate(value.to_s)}
738
+ when Numeric, String then return {type: :text, data: ExtendedValue.instance.evaluate(value.to_s)}
698
739
  end
699
740
  return {type: :single_object, data: value}
700
741
  when :unset
701
742
  param_name = options.get_next_argument('parameter name')
702
743
  selected_preset.delete(param_name)
703
744
  save_presets_to_config_file
704
- return Main.result_status("Removed: #{config_name}: #{param_name}")
745
+ return Main.result_status("Removed: #{name}: #{param_name}")
705
746
  when :set
706
747
  param_name = options.get_next_argument('parameter name')
707
748
  param_value = options.get_next_argument('parameter value')
708
- if !@config_presets.has_key?(config_name)
709
- Log.log.debug("no such config name: #{config_name}, initializing")
710
- selected_preset = @config_presets[config_name] = {}
749
+ if !@config_presets.key?(name)
750
+ Log.log.debug{"no such config name: #{name}, initializing"}
751
+ selected_preset = @config_presets[name] = {}
711
752
  end
712
- if selected_preset.has_key?(param_name)
713
- Log.log.warn("overwriting value: #{selected_preset[param_name]}")
753
+ if selected_preset.key?(param_name)
754
+ Log.log.warn{"overwriting value: #{selected_preset[param_name]}"}
714
755
  end
715
756
  selected_preset[param_name] = param_value
716
757
  save_presets_to_config_file
717
- return Main.result_status("Updated: #{config_name}: #{param_name} <- #{param_value}")
758
+ return Main.result_status("Updated: #{name}: #{param_name} <- #{param_value}")
718
759
  when :initialize
719
- config_value = options.get_next_argument('extended value (Hash)')
720
- if @config_presets.has_key?(config_name)
721
- Log.log.warn("configuration already exists: #{config_name}, overwriting")
760
+ config_value = options.get_next_argument('extended value', type: Hash)
761
+ if @config_presets.key?(name)
762
+ Log.log.warn{"configuration already exists: #{name}, overwriting"}
722
763
  end
723
- @config_presets[config_name] = config_value
764
+ @config_presets[name] = config_value
724
765
  save_presets_to_config_file
725
766
  return Main.result_status("Modified: #{@option_config_file}")
726
767
  when :update
727
768
  # get unprocessed options
728
769
  theopts = options.get_options_table
729
- Log.log.debug("opts=#{theopts}")
730
- @config_presets[config_name] ||= {}
731
- @config_presets[config_name].merge!(theopts)
770
+ Log.log.debug{"opts=#{theopts}"}
771
+ @config_presets[name] ||= {}
772
+ @config_presets[name].merge!(theopts)
732
773
  # fix bug in 4.4 (creating key "true" in "default" preset)
733
774
  @config_presets[CONF_PRESET_DEFAULT].delete(true) if @config_presets[CONF_PRESET_DEFAULT].is_a?(Hash)
734
775
  save_presets_to_config_file
735
- return Main.result_status("Updated: #{config_name}")
776
+ return Main.result_status("Updated: #{name}")
736
777
  when :ask
737
778
  options.ask_missing_mandatory = :yes
738
- @config_presets[config_name] ||= {}
739
- options.get_next_argument('option names',expected: :multiple).each do |optionname|
740
- option_value = options.get_interactive(:option,optionname)
741
- @config_presets[config_name][optionname] = option_value
779
+ @config_presets[name] ||= {}
780
+ options.get_next_argument('option names', expected: :multiple).each do |optionname|
781
+ option_value = options.get_interactive(:option, optionname)
782
+ @config_presets[name][optionname] = option_value
742
783
  end
743
784
  save_presets_to_config_file
744
- return Main.result_status("Updated: #{config_name}")
785
+ return Main.result_status("Updated: #{name}")
786
+ when :lookup
787
+ BasicAuthPlugin.register_options(@agents)
788
+ url = options.get_option(:url, is_type: :mandatory)
789
+ user = options.get_option(:username, is_type: :mandatory)
790
+ result = lookup_preset(url: url, username: user)
791
+ raise 'no such config found' if result.nil?
792
+ return {type: :single_object, data: result}
793
+ when :secure
794
+ identifier = options.get_next_argument('config name', mandatory: false)
795
+ preset_names = identifier.nil? ? @config_presets.keys : [identifier]
796
+ secret_keywords = %w[password secret].freeze
797
+ preset_names.each do |pset_name|
798
+ preset = @config_presets[pset_name]
799
+ next unless preset.is_a?(Hash)
800
+ preset.each_key do |option_name|
801
+ secret_keywords.each do |keyword|
802
+ next unless option_name.end_with?(keyword)
803
+ vault_label = pset_name
804
+ incr = 0
805
+ until vault.get(label: vault_label, exception: false).nil?
806
+ vault_label = "#{pset_name}#{incr}"
807
+ incr += 1
808
+ end
809
+ to_set = {label: vault_label, password: preset[option_name]}
810
+ puts "need to encode #{pset_name}.#{option_name} -> #{vault_label} -> #{to_set}"
811
+ # to_copy=%i[]
812
+ vault.set(to_set)
813
+ preset[option_name] = "@vault:#{vault_label}.password"
814
+ end
815
+ end
816
+ end
817
+ return Main.result_status('Secrets secured in vault: Make sure to save the vault password securely.')
745
818
  end
746
819
  end
747
820
 
748
- ACTIONS = [PRESET_GBL_ACTIONS,%i[id preset open documentation genkey gem plugin flush_tokens echo wizard export_to_cli detect coffee
749
- ascp email_test smtp_settings proxy_check folder file check_update initdemo vault]].flatten.freeze
821
+ ACTIONS = %i[
822
+ id
823
+ preset
824
+ open
825
+ documentation
826
+ genkey
827
+ gem
828
+ plugin
829
+ flush_tokens
830
+ echo
831
+ wizard
832
+ export_to_cli
833
+ detect
834
+ coffee
835
+ ascp
836
+ email_test
837
+ smtp_settings
838
+ proxy_check
839
+ folder
840
+ file
841
+ check_update
842
+ initdemo
843
+ vault].concat(PRESET_GBL_ACTIONS).freeze
750
844
 
751
845
  # "config" plugin
752
846
  def execute_action
753
847
  action = options.get_next_command(ACTIONS)
754
848
  case action
755
849
  when *PRESET_GBL_ACTIONS # older syntax
756
- return execute_file_action(action,nil)
850
+ Log.log.warn{"This syntax is deprecated, use command: preset #{action}"}
851
+ return execute_preset(action: action)
757
852
  when :id # older syntax
758
- return execute_file_action(nil,options.get_next_argument('config name'))
853
+ identifier = options.get_next_argument('config name')
854
+ Log.log.warn{"This syntax is deprecated, use command: preset <verb> #{identifier}"}
855
+ return execute_preset(name: identifier)
759
856
  when :preset # newer syntax
760
- return execute_file_action(nil,nil)
857
+ return execute_preset
761
858
  when :open
762
- OpenApplication.instance.uri(@option_config_file.to_s) #file://
859
+ OpenApplication.editor(@option_config_file.to_s)
763
860
  return Main.result_nothing
764
861
  when :documentation
765
- section = options.get_next_argument('private key file path',mandatory: false)
862
+ section = options.get_next_argument('private key file path', mandatory: false)
766
863
  section = '#' + section unless section.nil?
767
864
  OpenApplication.instance.uri("#{@info[:help]}#{section}")
768
865
  return Main.result_nothing
769
866
  when :genkey # generate new rsa key
770
867
  private_key_path = options.get_next_argument('private key file path')
771
- private_key_length = options.get_next_argument('size in bits',mandatory: false) || DEFAULT_PRIVKEY_LENGTH
772
- generate_rsa_private_key(private_key_path,private_key_length)
868
+ private_key_length = options.get_next_argument('size in bits', mandatory: false) || DEFAULT_PRIVKEY_LENGTH
869
+ generate_rsa_private_key(private_key_path, private_key_length)
773
870
  return Main.result_status('Generated key: ' + private_key_path)
774
871
  when :echo # display the content of a value given on command line
775
872
  result = {type: :other_struct, data: options.get_next_argument('value')}
@@ -785,9 +882,9 @@ module Aspera
785
882
  when :list
786
883
  return {type: :object_list, data: @plugins.keys.map { |i| { 'plugin' => i.to_s, 'path' => @plugins[i][:source] } }, fields: %w[plugin path]}
787
884
  when :create
788
- plugin_name = options.get_next_argument('name',expected: :single).downcase
789
- plugin_folder = options.get_next_argument('folder',expected: :single,mandatory: false) || File.join(@main_folder,ASPERA_PLUGINS_FOLDERNAME)
790
- plugin_file = File.join(plugin_folder,"#{plugin_name}.rb")
885
+ plugin_name = options.get_next_argument('name', expected: :single).downcase
886
+ plugin_folder = options.get_next_argument('folder', expected: :single, mandatory: false) || File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME)
887
+ plugin_file = File.join(plugin_folder, "#{plugin_name}.rb")
791
888
  content = <<~END_OF_PLUGIN_CODE
792
889
  require 'aspera/cli/plugin'
793
890
  module Aspera
@@ -801,7 +898,7 @@ module Aspera
801
898
  end # Cli
802
899
  end # Aspera
803
900
  END_OF_PLUGIN_CODE
804
- File.write(plugin_file,content)
901
+ File.write(plugin_file, content)
805
902
  return Main.result_status("Created #{plugin_file}")
806
903
  end
807
904
  when :wizard
@@ -809,15 +906,15 @@ module Aspera
809
906
  options.ask_missing_mandatory = true
810
907
  # register url option
811
908
  BasicAuthPlugin.register_options(@agents)
812
- params={}
909
+ params = {}
813
910
  # get from option, or ask
814
- params[:instance_url] = options.get_option(:url,is_type: :mandatory)
911
+ params[:instance_url] = options.get_option(:url, is_type: :mandatory)
815
912
  # allow user to tell the preset name
816
913
  params[:preset_name] = options.get_option(:id)
817
914
  # allow user to specify type of application
818
915
  params[:application] = options.get_option(:value)
819
916
  params[:application] = params[:application].nil? ? identify_plugin_for_url(params[:instance_url])[:product] : params[:application].to_sym
820
- params[:plugin_name]=params[:application]
917
+ params[:plugin_name] = params[:application]
821
918
  params[:test_args] = '<replace per app>'
822
919
  case params[:application]
823
920
  when :faspex5
@@ -825,7 +922,7 @@ module Aspera
825
922
  when :aoc
826
923
  wizard_aoc(params)
827
924
  else
828
- raise CliBadArgument,"Supports only: aoc. Detected: #{params[:application]}"
925
+ raise CliBadArgument, "Supports only: aoc. Detected: #{params[:application]}"
829
926
  end # product
830
927
  if params[:option_default]
831
928
  self.format.display_status("Setting config preset as default for #{params[:plugin_name]}")
@@ -843,23 +940,23 @@ module Aspera
843
940
  add_plugin_default_preset(AOC_COMMAND_V3.to_sym)
844
941
  # instanciate AoC plugin
845
942
  self.class.plugin_class(AOC_COMMAND_CURRENT).new(@agents) # TODO: is this line needed ? get options ?
846
- url = options.get_option(:url,is_type: :mandatory)
943
+ url = options.get_option(:url, is_type: :mandatory)
847
944
  cli_conf_file = Fasp::Installation.instance.cli_conf_file
848
945
  data = JSON.parse(File.read(cli_conf_file))
849
- organization,instance_domain = AoC.parse_url(url)
946
+ organization, instance_domain = AoC.parse_url(url)
850
947
  key_basename = 'org_' + organization + '.pem'
851
- key_file = File.join(File.dirname(File.dirname(cli_conf_file)),'etc',key_basename)
852
- File.write(key_file,options.get_option(:private_key,is_type: :mandatory))
948
+ key_file = File.join(File.dirname(File.dirname(cli_conf_file)), 'etc', key_basename)
949
+ File.write(key_file, options.get_option(:private_key, is_type: :mandatory))
853
950
  new_conf = {
854
951
  'organization' => organization,
855
- 'hostname' => [organization,instance_domain].join('.'),
952
+ 'hostname' => [organization, instance_domain].join('.'),
856
953
  'privateKeyFilename' => key_basename,
857
- 'username' => options.get_option(:username,is_type: :mandatory)
954
+ 'username' => options.get_option(:username, is_type: :mandatory)
858
955
  }
859
956
  new_conf['clientId'] = options.get_option(:client_id)
860
957
  new_conf['clientSecret'] = options.get_option(:client_secret)
861
958
  if new_conf['clientId'].nil?
862
- new_conf['clientId'],new_conf['clientSecret'] = AoC.get_client_info
959
+ new_conf['clientId'], new_conf['clientSecret'] = AoC.get_client_info
863
960
  end
864
961
  entry = data['AoCAccounts'].find{|i|i['organization'].eql?(organization)}
865
962
  if entry.nil?
@@ -869,12 +966,12 @@ module Aspera
869
966
  self.format.display_status("Updating existing aoc entry: #{organization}")
870
967
  entry.merge!(new_conf)
871
968
  end
872
- File.write(cli_conf_file,JSON.pretty_generate(data))
969
+ File.write(cli_conf_file, JSON.pretty_generate(data))
873
970
  return Main.result_status("Updated: #{cli_conf_file}")
874
971
  when :detect
875
972
  # need url / username
876
973
  BasicAuthPlugin.register_options(@agents)
877
- return {type: :single_object, data: identify_plugin_for_url(options.get_option(:url,is_type: :mandatory))}
974
+ return {type: :single_object, data: identify_plugin_for_url(options.get_option(:url, is_type: :mandatory))}
878
975
  when :coffee
879
976
  OpenApplication.instance.uri('https://enjoyjava.com/wp-content/uploads/2018/01/How-to-make-strong-coffee.jpg')
880
977
  return Main.result_nothing
@@ -891,22 +988,22 @@ module Aspera
891
988
  when :file
892
989
  return Main.result_status(@option_config_file)
893
990
  when :email_test
894
- send_email_template({},EMAIL_TEST_TEMPLATE)
991
+ send_email_template(email_template_default: EMAIL_TEST_TEMPLATE)
895
992
  return Main.result_nothing
896
993
  when :smtp_settings
897
994
  return {type: :single_object, data: email_settings}
898
995
  when :proxy_check
899
996
  # ensure fpac was provided
900
- options.get_option(:fpac,is_type: :mandatory)
997
+ options.get_option(:fpac, is_type: :mandatory)
901
998
  server_url = options.get_next_argument('server url')
902
999
  return Main.result_status(@pac_exec.find_proxy_for_url(server_url))
903
1000
  when :check_update
904
1001
  return {type: :single_object, data: check_gem_version}
905
1002
  when :initdemo
906
- if @config_presets.has_key?(DEMO_SERVER_PRESET)
907
- Log.log.warn("Demo server preset already present: #{DEMO_SERVER_PRESET}")
1003
+ if @config_presets.key?(DEMO_SERVER_PRESET)
1004
+ Log.log.warn{"Demo server preset already present: #{DEMO_SERVER_PRESET}"}
908
1005
  else
909
- Log.log.info("Creating Demo server preset: #{DEMO_SERVER_PRESET}")
1006
+ Log.log.info{"Creating Demo server preset: #{DEMO_SERVER_PRESET}"}
910
1007
  @config_presets[DEMO_SERVER_PRESET] = {
911
1008
  'url' => 'ssh://' + DEMO + '.asperasoft.com:33001',
912
1009
  'username' => AOC_COMMAND_V2,
@@ -914,76 +1011,37 @@ module Aspera
914
1011
  }
915
1012
  end
916
1013
  @config_presets[CONF_PRESET_DEFAULT] ||= {}
917
- if @config_presets[CONF_PRESET_DEFAULT].has_key?(SERVER_COMMAND)
918
- Log.log.warn("Server default preset already set to: #{@config_presets[CONF_PRESET_DEFAULT][SERVER_COMMAND]}")
919
- Log.log.warn("Use #{DEMO_SERVER_PRESET} for demo: -P#{DEMO_SERVER_PRESET}") unless
1014
+ if @config_presets[CONF_PRESET_DEFAULT].key?(SERVER_COMMAND)
1015
+ Log.log.warn{"Server default preset already set to: #{@config_presets[CONF_PRESET_DEFAULT][SERVER_COMMAND]}"}
1016
+ Log.log.warn{"Use #{DEMO_SERVER_PRESET} for demo: -P#{DEMO_SERVER_PRESET}"} unless
920
1017
  DEMO_SERVER_PRESET.eql?(@config_presets[CONF_PRESET_DEFAULT][SERVER_COMMAND])
921
1018
  else
922
1019
  @config_presets[CONF_PRESET_DEFAULT][SERVER_COMMAND] = DEMO_SERVER_PRESET
923
- Log.log.info("Setting server default preset to : #{DEMO_SERVER_PRESET}")
1020
+ Log.log.info{"Setting server default preset to : #{DEMO_SERVER_PRESET}"}
924
1021
  end
925
1022
  save_presets_to_config_file
926
1023
  return Main.result_status('Done')
927
- when :vault
928
- command = options.get_next_command(%i[init list get set delete])
929
- case command
930
- when :init
931
- type = options.get_option(:value)
932
- case type
933
- when 'config',NilClass
934
- raise 'default secrets already exists' if @config_presets.has_key?(CONF_PRESET_SECRETS)
935
- @config_presets[CONF_PRESET_SECRETS] = {}
936
- set_global_default(:secrets,"@preset:#{CONF_PRESET_SECRETS}")
937
- else raise 'no such vault type'
938
- end
939
- return Main.result_status('Done')
940
- when :list
941
- return {type: :object_list, data: vault.list}
942
- when :set
943
- # register url option
944
- BasicAuthPlugin.register_options(@agents)
945
- username = options.get_option(:username,is_type: :mandatory)
946
- url = options.get_option(:url,is_type: :mandatory)
947
- description = options.get_option(:value)
948
- secret = options.get_next_argument('secret')
949
- vault.set(username: username, url: url, description: description, secret: secret)
950
- save_presets_to_config_file if vault.is_a?(Keychain::EncryptedHash)
951
- return Main.result_status('Done')
952
- when :get
953
- # register url option
954
- BasicAuthPlugin.register_options(@agents)
955
- username = options.get_option(:username,is_type: :mandatory)
956
- url = options.get_option(:url)
957
- result = vault.get(username: username, url: url)
958
- return {type: :single_object, data: result}
959
- when :delete
960
- # register url option
961
- BasicAuthPlugin.register_options(@agents)
962
- username = options.get_option(:username,is_type: :mandatory)
963
- url = options.get_option(:url)
964
- vault.delete(username: username, url: url)
965
- return Main.result_status('Done')
966
- end
1024
+ when :vault then execute_vault
967
1025
  else raise 'INTERNAL ERROR: wrong case'
968
1026
  end
969
1027
  end
970
1028
 
971
1029
  # @return email server setting with defaults if not defined
972
1030
  def email_settings
973
- smtp = options.get_option(:smtp,is_type: :mandatory)
1031
+ smtp = options.get_option(:smtp, is_type: :mandatory)
974
1032
  # change string keys into symbol keys
975
- smtp = smtp.keys.each_with_object({}){|v,m|m[v.to_sym] = smtp[v];}
1033
+ smtp = smtp.keys.each_with_object({}){|v, m|m[v.to_sym] = smtp[v]; }
976
1034
  # defaults
977
1035
  smtp[:tls] ||= true
978
1036
  smtp[:port] ||= smtp[:tls] ? 587 : 25
979
- smtp[:from_email] ||= smtp[:username] if smtp.has_key?(:username)
980
- smtp[:from_name] ||= smtp[:from_email].gsub(/@.*$/,'').gsub(/[^a-zA-Z]/,' ').capitalize if smtp.has_key?(:username)
981
- smtp[:domain] ||= smtp[:from_email].gsub(/^.*@/,'') if smtp.has_key?(:from_email)
1037
+ smtp[:from_email] ||= smtp[:username] if smtp.key?(:username)
1038
+ smtp[:from_name] ||= smtp[:from_email].gsub(/@.*$/, '').gsub(/[^a-zA-Z]/, ' ').capitalize if smtp.key?(:username)
1039
+ smtp[:domain] ||= smtp[:from_email].gsub(/^.*@/, '') if smtp.key?(:from_email)
982
1040
  # check minimum required
983
1041
  %i[server port domain].each do |n|
984
- raise "Missing smtp parameter: #{n}" unless smtp.has_key?(n)
1042
+ raise "Missing smtp parameter: #{n}" unless smtp.key?(n)
985
1043
  end
986
- Log.log.debug("smtp=#{smtp}")
1044
+ Log.log.debug{"smtp=#{smtp}"}
987
1045
  return smtp
988
1046
  end
989
1047
 
@@ -992,41 +1050,41 @@ module Aspera
992
1050
  Kernel.binding
993
1051
  end
994
1052
 
995
- def send_email_template(vars,email_template_default=nil)
996
- vars[:to] ||= options.get_option(:notif_to,is_type: :mandatory)
997
- notif_template = options.get_option(:notif_template,is_type: email_template_default.nil? ? :mandatory : :optional) || email_template_default
1053
+ def send_email_template(email_template_default: nil, values: {})
1054
+ values[:to] ||= options.get_option(:notif_to, is_type: :mandatory)
1055
+ notif_template = options.get_option(:notif_template, is_type: email_template_default.nil? ? :mandatory : :optional) || email_template_default
998
1056
  mail_conf = email_settings
999
- vars[:from_name] ||= mail_conf[:from_name]
1000
- vars[:from_email] ||= mail_conf[:from_email]
1057
+ values[:from_name] ||= mail_conf[:from_name]
1058
+ values[:from_email] ||= mail_conf[:from_email]
1001
1059
  %i[from_name from_email].each do |n|
1002
- raise "Missing email parameter: #{n}" unless vars.has_key?(n)
1060
+ raise "Missing email parameter: #{n}" unless values.key?(n)
1003
1061
  end
1004
1062
  start_options = [mail_conf[:domain]]
1005
- start_options.push(mail_conf[:username],mail_conf[:password],:login) if mail_conf.has_key?(:username) && mail_conf.has_key?(:password)
1006
- # create a binding with only variables defined in vars
1063
+ start_options.push(mail_conf[:username], mail_conf[:password], :login) if mail_conf.key?(:username) && mail_conf.key?(:password)
1064
+ # create a binding with only variables defined in values
1007
1065
  template_binding = empty_binding
1008
1066
  # add variables to binding
1009
- vars.each do |k,v|
1067
+ values.each do |k, v|
1010
1068
  raise "key (#{k.class}) must be Symbol" unless k.is_a?(Symbol)
1011
- template_binding.local_variable_set(k,v)
1069
+ template_binding.local_variable_set(k, v)
1012
1070
  end
1013
1071
  # execute template
1014
1072
  msg_with_headers = ERB.new(notif_template).result(template_binding)
1015
- Log.dump(:msg_with_headers,msg_with_headers)
1073
+ Log.dump(:msg_with_headers, msg_with_headers)
1016
1074
  smtp = Net::SMTP.new(mail_conf[:server], mail_conf[:port])
1017
1075
  smtp.enable_starttls if mail_conf[:tls]
1018
1076
  smtp.start(*start_options) do |smtp_session|
1019
- smtp_session.send_message(msg_with_headers, vars[:from_email], vars[:to])
1077
+ smtp_session.send_message(msg_with_headers, values[:from_email], values[:to])
1020
1078
  end
1021
1079
  end
1022
1080
 
1023
1081
  def save_presets_to_config_file
1024
1082
  raise 'no configuration loaded' if @config_presets.nil?
1025
1083
  FileUtils.mkdir_p(@main_folder) unless Dir.exist?(@main_folder)
1026
- File.chmod(0700,@main_folder)
1027
- Log.log.debug("Writing #{@option_config_file}")
1028
- File.write(@option_config_file,@config_presets.to_yaml)
1029
- File.chmod(0600,@option_config_file)
1084
+ Log.log.debug{"Writing #{@option_config_file}"}
1085
+ File.write(@option_config_file, @config_presets.to_yaml)
1086
+ Environment.restrict_file_access(@main_folder)
1087
+ Environment.restrict_file_access(@option_config_file)
1030
1088
  end
1031
1089
 
1032
1090
  # returns [String] name if config_presets has default
@@ -1037,53 +1095,114 @@ module Aspera
1037
1095
  Log.log.debug('skip default config')
1038
1096
  return nil
1039
1097
  end
1040
- if @config_presets.has_key?(CONF_PRESET_DEFAULT) &&
1041
- @config_presets[CONF_PRESET_DEFAULT].has_key?(plugin_sym.to_s)
1098
+ if @config_presets.key?(CONF_PRESET_DEFAULT) &&
1099
+ @config_presets[CONF_PRESET_DEFAULT].key?(plugin_sym.to_s)
1042
1100
  default_config_name = @config_presets[CONF_PRESET_DEFAULT][plugin_sym.to_s]
1043
- if !@config_presets.has_key?(default_config_name)
1044
- Log.log.error("Default config name [#{default_config_name}] specified for plugin [#{plugin_sym}], but it does not exist in config file.\n"\
1045
- 'Please fix the issue: either create preset with one parameter: '\
1046
- "(#{@info[:name]} config id #{default_config_name} init @json:'{}') or remove default (#{@info[:name]} config id default remove #{plugin_sym}).")
1101
+ if !@config_presets.key?(default_config_name)
1102
+ Log.log.error do
1103
+ "Default config name [#{default_config_name}] specified for plugin [#{plugin_sym}], but it does not exist in config file.\n"\
1104
+ 'Please fix the issue: either create preset with one parameter: '\
1105
+ "(#{@info[:name]} config id #{default_config_name} init @json:'{}') or remove default (#{@info[:name]} config id default remove #{plugin_sym})."
1106
+ end
1047
1107
  end
1048
- raise CliError,"Config name [#{default_config_name}] must be a hash, check config file." if !@config_presets[default_config_name].is_a?(Hash)
1108
+ raise CliError, "Config name [#{default_config_name}] must be a hash, check config file." if !@config_presets[default_config_name].is_a?(Hash)
1049
1109
  return default_config_name
1050
1110
  end
1051
1111
  return nil
1052
1112
  end # get_plugin_default_config_name
1053
1113
 
1114
+ ALLOWED_KEYS = %i[password username description].freeze
1115
+ def execute_vault
1116
+ command = options.get_next_command(%i[list show create delete password])
1117
+ case command
1118
+ when :list
1119
+ return {type: :object_list, data: vault.list}
1120
+ when :show
1121
+ return {type: :single_object, data: vault.get(label: options.get_next_argument('label'))}
1122
+ when :create
1123
+ label = options.get_next_argument('label')
1124
+ info = options.get_next_argument('info Hash')
1125
+ raise 'info must be Hash' unless info.is_a?(Hash)
1126
+ info = info.symbolize_keys
1127
+ info[:label] = label
1128
+ vault.set(info)
1129
+ return Main.result_status('Password added')
1130
+ when :delete
1131
+ vault.delete(label: options.get_next_argument('label'))
1132
+ return Main.result_status('Password deleted')
1133
+ when :password
1134
+ raise 'Vault does not support password change' unless vault.respond_to?(:password=)
1135
+ new_password = options.get_next_argument('new_password')
1136
+ vault.password = new_password
1137
+ vault.save
1138
+ return Main.result_status('Password updated')
1139
+ end
1140
+ end
1141
+
1142
+ def vault_value(name)
1143
+ m = name.match(/^(.+)\.(.+)$/)
1144
+ raise 'vault name shall match <name>.<param>' if m.nil?
1145
+ info = vault.get(label: m[1])
1146
+ # raise "no such vault entry: #{m[1]}" if info.nil?
1147
+ value = info[m[2].to_sym]
1148
+ raise "no such entry value: #{m[2]}" if value.nil?
1149
+ return value
1150
+ end
1151
+
1054
1152
  def vault
1055
1153
  if @vault.nil?
1056
- vault_info = options.get_option(:secrets)
1057
- case vault_info
1058
- when Hash
1059
- @vault = Keychain::EncryptedHash.new(vault_info)
1060
- when /^system/
1061
- name = vault_info.start_with?('system:') ? vault_info[7..-1] : nil
1154
+ vault_info = options.get_option(:vault) || {'type' => 'file', 'name' => 'vault.bin'}
1155
+ vault_password = options.get_option(:vault_password, is_type: :mandatory)
1156
+ raise 'vault must be Hash' unless vault_info.is_a?(Hash)
1157
+ vault_type = vault_info['type'] || 'file'
1158
+ vault_name = vault_info['name'] || (vault_type.eql?('file') ? 'vault.bin' : PROGRAM_NAME)
1159
+ case vault_type
1160
+ when 'file'
1161
+ # absolute_path? introduced in ruby 2.7
1162
+ vault_path = vault_name.eql?(File.absolute_path(vault_name)) ? vault_name : File.join(@main_folder, vault_name)
1163
+ @vault = Keychain::EncryptedHash.new(vault_path, vault_password)
1164
+ when 'system'
1062
1165
  case Environment.os
1063
1166
  when Environment::OS_X
1064
- @vault = Keychain::MacosSecurity.new(name)
1065
- when Environment::OS_WINDOWS,Environment::OS_LINUX,Environment::OS_AIX
1167
+ @vault = Keychain::MacosSystem.new(vault_name, vault_password)
1168
+ when Environment::OS_WINDOWS, Environment::OS_LINUX, Environment::OS_AIX
1066
1169
  raise 'not implemented'
1067
- else raise 'Error'
1170
+ else raise 'Error, OS not supported'
1068
1171
  end
1069
- when NilClass
1070
- # keep nil
1071
1172
  else
1072
- raise CliBadArgument,'secrets shall be Hash'
1173
+ raise CliBadArgument, "Unknown vault type: #{vault_type}"
1073
1174
  end
1074
1175
  end
1075
1176
  raise 'No vault defined' if @vault.nil?
1076
1177
  @vault
1077
1178
  end
1078
1179
 
1079
- def get_secret(options)
1080
- raise 'options shall be Hash' unless options.is_a?(Hash)
1081
- raise 'options shall have username' unless options.has_key?(:username)
1082
- secret = self.options.get_option(:secret)
1180
+ # version of URL without trailing "/" and removing default port
1181
+ def canonical_url(url)
1182
+ url.gsub(%r{/+$}, '').gsub(%r{^(https://[^/]+):443$}, '\1')
1183
+ end
1184
+
1185
+ def lookup_preset(url:, username:)
1186
+ # remove extra info to maximize match
1187
+ url = canonical_url(url)
1188
+ Log.log.debug{"Lookup preset for #{username}@#{url}"}
1189
+ @config_presets.each do |_k, v|
1190
+ next unless v.is_a?(Hash)
1191
+ conf_url = v['url'].is_a?(String) ? canonical_url(v['url']) : nil
1192
+ return v if conf_url.eql?(url) && v['username'].eql?(username)
1193
+ end
1194
+ nil
1195
+ end
1196
+
1197
+ def lookup_secret(url:, username:, mandatory: false)
1198
+ secret = options.get_option(:secret)
1083
1199
  if secret.nil?
1084
- secret = vault.get(options) rescue nil
1085
- # mandatory by default
1086
- raise "please provide secret for #{options[:username]}" if secret.nil? && (options[:mandatory].nil? || options[:mandatory])
1200
+ conf = lookup_preset(url: url, username: username)
1201
+ if conf.is_a?(Hash)
1202
+ Log.log.debug{"Found preset #{conf} with URL and username"}
1203
+ secret = conf['password']
1204
+ end
1205
+ raise "Please provide secret for #{username} using option: secret or by setting a preset for #{username}@#{url}." if secret.nil? && mandatory
1087
1206
  end
1088
1207
  return secret
1089
1208
  end
@@ -1093,33 +1212,33 @@ module Aspera
1093
1212
  params[:plugin_name] = AOC_COMMAND_CURRENT
1094
1213
  organization = AoC.parse_url(params[:instance_url]).first
1095
1214
  # if not defined by user, generate name
1096
- params[:preset_name] = [params[:application],organization].join('_') if params[:preset_name].nil?
1215
+ params[:preset_name] = [params[:application], organization].join('_') if params[:preset_name].nil?
1097
1216
  self.format.display_status("Preparing preset: #{params[:preset_name]}")
1098
1217
  # init defaults if necessary
1099
1218
  @config_presets[CONF_PRESET_DEFAULT] ||= {}
1100
- option_override = options.get_option(:override,is_type: :mandatory)
1101
- params[:option_default] = options.get_option(:default,is_type: :mandatory)
1102
- Log.log.error("override=#{option_override} -> #{option_override.class}")
1103
- raise CliError,"A default configuration already exists for plugin '#{params[:plugin_name]}' (use --override=yes or --default=no)" \
1104
- if !option_override && params[:option_default] && @config_presets[CONF_PRESET_DEFAULT].has_key?(params[:plugin_name])
1105
- raise CliError,"Preset already exists: #{params[:preset_name]} (use --override=yes or --id=<name>)" \
1106
- if !option_override && @config_presets.has_key?(params[:preset_name])
1219
+ option_override = options.get_option(:override, is_type: :mandatory)
1220
+ params[:option_default] = options.get_option(:default, is_type: :mandatory)
1221
+ Log.log.error{"override=#{option_override} -> #{option_override.class}"}
1222
+ raise CliError, "A default configuration already exists for plugin '#{params[:plugin_name]}' (use --override=yes or --default=no)" \
1223
+ if !option_override && params[:option_default] && @config_presets[CONF_PRESET_DEFAULT].key?(params[:plugin_name])
1224
+ raise CliError, "Preset already exists: #{params[:preset_name]} (use --override=yes or --id=<name>)" \
1225
+ if !option_override && @config_presets.key?(params[:preset_name])
1107
1226
  # lets see if path to priv key is provided
1108
1227
  private_key_path = options.get_option(:pkeypath)
1109
1228
  # give a chance to provide
1110
1229
  if private_key_path.nil?
1111
1230
  self.format.display_status('Please provide path to your private RSA key, or empty to generate one:')
1112
- private_key_path = options.get_option(:pkeypath,is_type: :mandatory).to_s
1231
+ private_key_path = options.get_option(:pkeypath, is_type: :mandatory).to_s
1113
1232
  end
1114
1233
  # else generate path
1115
1234
  if private_key_path.empty?
1116
- private_key_path = File.join(@main_folder,DEFAULT_PRIV_KEY_FILENAME)
1235
+ private_key_path = File.join(@main_folder, DEFAULT_PRIV_KEY_FILENAME)
1117
1236
  end
1118
1237
  if File.exist?(private_key_path)
1119
1238
  self.format.display_status('Using existing key:')
1120
1239
  else
1121
1240
  self.format.display_status("Generating #{DEFAULT_PRIVKEY_LENGTH} bit RSA key...")
1122
- generate_rsa_private_key(private_key_path,DEFAULT_PRIVKEY_LENGTH)
1241
+ generate_rsa_private_key(private_key_path, DEFAULT_PRIVKEY_LENGTH)
1123
1242
  self.format.display_status('Created:')
1124
1243
  end
1125
1244
  self.format.display_status(private_key_path)
@@ -1127,7 +1246,7 @@ module Aspera
1127
1246
  # declare command line options for AoC
1128
1247
  require 'aspera/cli/plugins/aoc'
1129
1248
  # make username mandatory for jwt, this triggers interactive input
1130
- options.get_option(:username,is_type: :mandatory)
1249
+ options.get_option(:username, is_type: :mandatory)
1131
1250
  # instanciate AoC plugin, so that command line options are known
1132
1251
  aoc_api = self.class.plugin_class(params[:plugin_name]).new(@agents.merge({skip_basic_auth_options: true, private_key_path: private_key_path})).aoc_api
1133
1252
  auto_set_pub_key = false
@@ -1146,7 +1265,7 @@ module Aspera
1146
1265
  end
1147
1266
  else
1148
1267
  self.format.display_status('Using organization specific client_id.')
1149
- if options.get_option(:client_id).nil? || options.get_option(:client_secret,is_type: :optional).nil?
1268
+ if options.get_option(:client_id).nil? || options.get_option(:client_secret, is_type: :optional).nil?
1150
1269
  self.format.display_status('Please login to your Aspera on Cloud instance.'.red)
1151
1270
  self.format.display_status('Go to: Apps->Admin->Organization->Integrations')
1152
1271
  self.format.display_status('Create or check if there is an existing integration named:')
@@ -1157,8 +1276,8 @@ module Aspera
1157
1276
  self.format.display_status('Please enter:'.red)
1158
1277
  end
1159
1278
  OpenApplication.instance.uri("#{params[:instance_url]}/#{AOC_PATH_API_CLIENTS}")
1160
- options.get_option(:client_id,is_type: :mandatory)
1161
- options.get_option(:client_secret,is_type: :mandatory)
1279
+ options.get_option(:client_id, is_type: :mandatory)
1280
+ options.get_option(:client_secret, is_type: :mandatory)
1162
1281
  use_browser_authentication = true
1163
1282
  end
1164
1283
  if use_browser_authentication
@@ -1171,13 +1290,13 @@ module Aspera
1171
1290
  end
1172
1291
  myself = aoc_api.read('self')[:data]
1173
1292
  if auto_set_pub_key
1174
- raise CliError,'Public key is already set in profile (use --override=yes)' unless myself['public_key'].empty? || option_override
1293
+ raise CliError, 'Public key is already set in profile (use --override=yes)' unless myself['public_key'].empty? || option_override
1175
1294
  self.format.display_status('Updating profile with new key')
1176
- aoc_api.update("users/#{myself['id']}",{'public_key' => pub_key_pem})
1295
+ aoc_api.update("users/#{myself['id']}", {'public_key' => pub_key_pem})
1177
1296
  end
1178
1297
  if auto_set_jwt
1179
1298
  self.format.display_status('Enabling JWT for client')
1180
- aoc_api.update("clients/#{options.get_option(:client_id)}",{'jwt_grant_enabled' => true,'explicit_authorization_required' => false})
1299
+ aoc_api.update("clients/#{options.get_option(:client_id)}", {'jwt_grant_enabled' => true, 'explicit_authorization_required' => false})
1181
1300
  end
1182
1301
  self.format.display_status("Creating new config preset: #{params[:preset_name]}")
1183
1302
  @config_presets[params[:preset_name]] = {
@@ -1197,34 +1316,34 @@ module Aspera
1197
1316
  def wizard_faspex5(params)
1198
1317
  self.format.display_status('Detected: Faspex v5'.bold)
1199
1318
  # if not defined by user, generate unique name
1200
- params[:preset_name] = [params[:application],URI.parse(params[:instance_url]).host.gsub(/[^a-z0-9.]/,'').split('.')].flatten.join('_') \
1319
+ params[:preset_name] = [params[:application]].concat(URI.parse(params[:instance_url]).host.gsub(/[^a-z0-9.]/, '').split('.')).join('_') \
1201
1320
  if params[:preset_name].nil?
1202
1321
  self.format.display_status("Preparing preset: #{params[:preset_name]}")
1203
1322
  # init defaults if necessary
1204
1323
  @config_presets[CONF_PRESET_DEFAULT] ||= {}
1205
- option_override = options.get_option(:override,is_type: :mandatory)
1206
- params[:option_default] = options.get_option(:default,is_type: :mandatory)
1207
- Log.log.error("override=#{option_override} -> #{option_override.class}")
1208
- raise CliError,"A default configuration already exists for plugin '#{params[:plugin_name]}' (use --override=yes or --default=no)" \
1209
- if !option_override && params[:option_default] && @config_presets[CONF_PRESET_DEFAULT].has_key?(params[:plugin_name])
1210
- raise CliError,"Preset already exists: #{params[:preset_name]} (use --override=yes or --id=<name>)" \
1211
- if !option_override && @config_presets.has_key?(params[:preset_name])
1324
+ option_override = options.get_option(:override, is_type: :mandatory)
1325
+ params[:option_default] = options.get_option(:default, is_type: :mandatory)
1326
+ Log.log.error{"override=#{option_override} -> #{option_override.class}"}
1327
+ raise CliError, "A default configuration already exists for plugin '#{params[:plugin_name]}' (use --override=yes or --default=no)" \
1328
+ if !option_override && params[:option_default] && @config_presets[CONF_PRESET_DEFAULT].key?(params[:plugin_name])
1329
+ raise CliError, "Preset already exists: #{params[:preset_name]} (use --override=yes or --id=<name>)" \
1330
+ if !option_override && @config_presets.key?(params[:preset_name])
1212
1331
  # lets see if path to priv key is provided
1213
1332
  private_key_path = options.get_option(:pkeypath)
1214
1333
  # give a chance to provide
1215
1334
  if private_key_path.nil?
1216
1335
  self.format.display_status('Please provide path to your private RSA key, or empty to generate one:')
1217
- private_key_path = options.get_option(:pkeypath,is_type: :mandatory).to_s
1336
+ private_key_path = options.get_option(:pkeypath, is_type: :mandatory).to_s
1218
1337
  end
1219
1338
  # else generate path
1220
1339
  if private_key_path.empty?
1221
- private_key_path = File.join(@main_folder,DEFAULT_PRIV_KEY_FILENAME)
1340
+ private_key_path = File.join(@main_folder, DEFAULT_PRIV_KEY_FILENAME)
1222
1341
  end
1223
1342
  if File.exist?(private_key_path)
1224
1343
  self.format.display_status('Using existing key:')
1225
1344
  else
1226
1345
  self.format.display_status("Generating #{DEFAULT_PRIVKEY_LENGTH} bit RSA key...")
1227
- generate_rsa_private_key(private_key_path,DEFAULT_PRIVKEY_LENGTH)
1346
+ generate_rsa_private_key(private_key_path, DEFAULT_PRIVKEY_LENGTH)
1228
1347
  self.format.display_status('Created:')
1229
1348
  end
1230
1349
  self.format.display_status(private_key_path)
@@ -1245,8 +1364,8 @@ module Aspera
1245
1364
  :username.to_s => options.get_option(:username),
1246
1365
  :auth.to_s => :jwt.to_s,
1247
1366
  :private_key.to_s => '@file:' + private_key_path,
1248
- :client_id.to_s => options.get_option(:client_id,is_type: :mandatory),
1249
- :client_secret.to_s => options.get_option(:client_secret,is_type: :mandatory)
1367
+ :client_id.to_s => options.get_option(:client_id, is_type: :mandatory),
1368
+ :client_secret.to_s => options.get_option(:client_secret, is_type: :mandatory)
1250
1369
  }
1251
1370
  params[:test_args] = "#{params[:plugin_name]} user profile show"
1252
1371
  end