aspera-cli 4.16.0 → 4.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +50 -19
  4. data/CONTRIBUTING.md +3 -1
  5. data/README.md +965 -793
  6. data/bin/asession +29 -21
  7. data/lib/aspera/{fasp/agent_alpha.rb → agent/alpha.rb} +26 -25
  8. data/lib/aspera/{fasp/agent_base.rb → agent/base.rb} +15 -12
  9. data/lib/aspera/{fasp/agent_connect.rb → agent/connect.rb} +13 -11
  10. data/lib/aspera/{fasp/agent_direct.rb → agent/direct.rb} +49 -53
  11. data/lib/aspera/{fasp/agent_httpgw.rb → agent/httpgw.rb} +20 -19
  12. data/lib/aspera/{fasp/agent_node.rb → agent/node.rb} +20 -33
  13. data/lib/aspera/{fasp/agent_trsdk.rb → agent/trsdk.rb} +11 -11
  14. data/lib/aspera/api/aoc.rb +586 -0
  15. data/lib/aspera/api/ats.rb +46 -0
  16. data/lib/aspera/api/cos_node.rb +95 -0
  17. data/lib/aspera/api/node.rb +344 -0
  18. data/lib/aspera/ascmd.rb +46 -10
  19. data/lib/aspera/{fasp → ascp}/installation.rb +5 -5
  20. data/lib/aspera/{fasp → ascp}/management.rb +3 -8
  21. data/lib/aspera/{fasp → ascp}/products.rb +1 -1
  22. data/lib/aspera/assert.rb +30 -30
  23. data/lib/aspera/cli/basic_auth_plugin.rb +11 -10
  24. data/lib/aspera/cli/extended_value.rb +1 -1
  25. data/lib/aspera/cli/formatter.rb +13 -13
  26. data/lib/aspera/cli/hints.rb +5 -5
  27. data/lib/aspera/cli/main.rb +35 -28
  28. data/lib/aspera/cli/manager.rb +25 -24
  29. data/lib/aspera/cli/plugin.rb +22 -15
  30. data/lib/aspera/cli/plugin_factory.rb +61 -0
  31. data/lib/aspera/cli/plugins/alee.rb +7 -7
  32. data/lib/aspera/cli/plugins/aoc.rb +83 -77
  33. data/lib/aspera/cli/plugins/ats.rb +32 -33
  34. data/lib/aspera/cli/plugins/bss.rb +3 -4
  35. data/lib/aspera/cli/plugins/config.rb +169 -186
  36. data/lib/aspera/cli/plugins/console.rb +8 -6
  37. data/lib/aspera/cli/plugins/cos.rb +19 -18
  38. data/lib/aspera/cli/plugins/faspex.rb +61 -54
  39. data/lib/aspera/cli/plugins/faspex5.rb +150 -103
  40. data/lib/aspera/cli/plugins/node.rb +68 -73
  41. data/lib/aspera/cli/plugins/orchestrator.rb +34 -44
  42. data/lib/aspera/cli/plugins/preview.rb +31 -31
  43. data/lib/aspera/cli/plugins/server.rb +31 -33
  44. data/lib/aspera/cli/plugins/shares.rb +13 -11
  45. data/lib/aspera/cli/sync_actions.rb +8 -8
  46. data/lib/aspera/cli/transfer_agent.rb +32 -19
  47. data/lib/aspera/cli/transfer_progress.rb +1 -1
  48. data/lib/aspera/cli/version.rb +1 -1
  49. data/lib/aspera/colors.rb +5 -0
  50. data/lib/aspera/command_line_builder.rb +14 -14
  51. data/lib/aspera/coverage.rb +1 -2
  52. data/lib/aspera/data_repository.rb +1 -1
  53. data/lib/aspera/environment.rb +2 -3
  54. data/lib/aspera/faspex_gw.rb +5 -6
  55. data/lib/aspera/faspex_postproc.rb +1 -1
  56. data/lib/aspera/id_generator.rb +2 -2
  57. data/lib/aspera/json_rpc.rb +5 -5
  58. data/lib/aspera/keychain/encrypted_hash.rb +6 -6
  59. data/lib/aspera/keychain/macos_security.rb +27 -22
  60. data/lib/aspera/log.rb +2 -2
  61. data/lib/aspera/nagios.rb +3 -3
  62. data/lib/aspera/node_simulator.rb +5 -6
  63. data/lib/aspera/oauth/base.rb +143 -0
  64. data/lib/aspera/oauth/factory.rb +124 -0
  65. data/lib/aspera/oauth/generic.rb +34 -0
  66. data/lib/aspera/oauth/jwt.rb +51 -0
  67. data/lib/aspera/oauth/url_json.rb +31 -0
  68. data/lib/aspera/oauth/web.rb +50 -0
  69. data/lib/aspera/oauth.rb +5 -331
  70. data/lib/aspera/open_application.rb +7 -7
  71. data/lib/aspera/persistency_action_once.rb +4 -4
  72. data/lib/aspera/persistency_folder.rb +2 -2
  73. data/lib/aspera/preview/generator.rb +5 -5
  74. data/lib/aspera/preview/terminal.rb +3 -2
  75. data/lib/aspera/preview/utils.rb +3 -3
  76. data/lib/aspera/proxy_auto_config.rb +4 -4
  77. data/lib/aspera/rest.rb +175 -144
  78. data/lib/aspera/rest_errors_aspera.rb +3 -3
  79. data/lib/aspera/resumer.rb +77 -0
  80. data/lib/aspera/ssh.rb +6 -1
  81. data/lib/aspera/{fasp → transfer}/error.rb +3 -3
  82. data/lib/aspera/{fasp → transfer}/error_info.rb +1 -1
  83. data/lib/aspera/{fasp → transfer}/faux_file.rb +1 -1
  84. data/lib/aspera/{fasp → transfer}/parameters.rb +58 -89
  85. data/lib/aspera/{fasp/transfer_spec.rb → transfer/spec.rb} +18 -16
  86. data/lib/aspera/{fasp/parameters.yaml → transfer/spec.yaml} +4 -99
  87. data/lib/aspera/{fasp → transfer}/sync.rb +32 -32
  88. data/lib/aspera/{fasp → transfer}/uri.rb +9 -8
  89. data/lib/aspera/web_server_simple.rb +11 -3
  90. data.tar.gz.sig +0 -0
  91. metadata +36 -63
  92. metadata.gz.sig +0 -0
  93. data/lib/aspera/aoc.rb +0 -601
  94. data/lib/aspera/ats_api.rb +0 -47
  95. data/lib/aspera/cos_node.rb +0 -94
  96. data/lib/aspera/fasp/resume_policy.rb +0 -79
  97. data/lib/aspera/node.rb +0 -339
@@ -29,20 +29,20 @@ module Aspera
29
29
  # Called by provider of definition before constructor of this class so that params_definition has all mandatory fields
30
30
  def normalize_description(full_description)
31
31
  full_description.each do |name, options|
32
- assert_type(options, Hash){name}
32
+ Aspera.assert_type(options, Hash){name}
33
33
  unsupported_keys = options.keys - OPTIONS_KEYS
34
- assert(unsupported_keys.empty?){"Unsupported definition keys: #{unsupported_keys}"}
35
- assert(options.key?(:cli)){"Missing key: cli for #{name}"}
36
- assert_type(options[:cli], Hash){'Key: cli'}
37
- assert(options[:cli].key?(:type)){'Missing key: cli.type'}
38
- assert_values(options[:cli][:type], CLI_OPTION_TYPES){"Unsupported processing type for #{name}"}
34
+ Aspera.assert(unsupported_keys.empty?){"Unsupported definition keys: #{unsupported_keys}"}
35
+ Aspera.assert(options.key?(:cli)){"Missing key: cli for #{name}"}
36
+ Aspera.assert_type(options[:cli], Hash){'Key: cli'}
37
+ Aspera.assert(options[:cli].key?(:type)){'Missing key: cli.type'}
38
+ Aspera.assert_values(options[:cli][:type], CLI_OPTION_TYPES){"Unsupported processing type for #{name}"}
39
39
  # by default : optional
40
40
  options[:mandatory] ||= false
41
41
  options[:desc] ||= ''
42
42
  options[:desc] = "DEPRECATED: #{options[:deprecation]}\n#{options[:desc]}" if options.key?(:deprecation)
43
43
  cli = options[:cli]
44
44
  unsupported_cli_keys = cli.keys - CLI_KEYS
45
- assert(unsupported_cli_keys.empty?){"Unsupported cli keys: #{unsupported_cli_keys}"}
45
+ Aspera.assert(unsupported_cli_keys.empty?){"Unsupported cli keys: #{unsupported_cli_keys}"}
46
46
  # by default : string, unless it's without arg
47
47
  options[:accepted_types] ||= options[:cli][:type].eql?(:opt_without_arg) ? :bool : :string
48
48
  # single type is placed in array
@@ -111,7 +111,7 @@ module Aspera
111
111
  end
112
112
  processing_type = read ? :get_value : options[:cli][:type]
113
113
  # check mandatory parameter (nil is valid value)
114
- raise Fasp::Error, "Missing mandatory parameter: #{name}" if options[:mandatory] && !@param_hash.key?(name)
114
+ raise Transfer::Error, "Missing mandatory parameter: #{name}" if options[:mandatory] && !@param_hash.key?(name)
115
115
  parameter_value = @param_hash[name]
116
116
  # no default setting
117
117
  # parameter_value=options[:default] if parameter_value.nil? and options.has_key?(:default)
@@ -123,11 +123,11 @@ module Aspera
123
123
  when :hash then Hash
124
124
  when :int then Integer
125
125
  when :bool then [TrueClass, FalseClass]
126
- else error_unexpected_value(type_symbol)
126
+ else Aspera.error_unexpected_value(type_symbol)
127
127
  end
128
128
  end.flatten
129
129
  # check that value is of expected type
130
- raise Fasp::Error, "#{name} is : #{parameter_value.class} (#{parameter_value}), shall be #{options[:accepted_types]}, " \
130
+ raise Transfer::Error, "#{name} is : #{parameter_value.class} (#{parameter_value}), shall be #{options[:accepted_types]}, " \
131
131
  unless parameter_value.nil? || expected_classes.include?(parameter_value.class)
132
132
  # special processing will be requested with type get_value
133
133
  @used_param_names.push(name) unless processing_type.eql?(:special)
@@ -149,10 +149,10 @@ module Aspera
149
149
  # :convert has name of class and encoding method
150
150
  conversion_class, conversion_method = options[:cli][:convert].split('.')
151
151
  converted_value = Kernel.const_get(conversion_class).send(conversion_method, parameter_value)
152
- raise Fasp::Error, "unsupported #{name}: #{parameter_value}" if converted_value.nil?
152
+ raise Transfer::Error, "unsupported #{name}: #{parameter_value}" if converted_value.nil?
153
153
  parameter_value = converted_value
154
154
  when NilClass
155
- else error_unexpected_value(options[:cli][:convert].class)
155
+ else Aspera.error_unexpected_value(options[:cli][:convert].class)
156
156
  end
157
157
 
158
158
  case processing_type
@@ -161,14 +161,14 @@ module Aspera
161
161
  when :ignore, :special # ignore this parameter or process later
162
162
  return
163
163
  when :envvar # set in env var
164
- assert(options[:cli].key?(:variable)){'missing key: cli.variable'}
164
+ Aspera.assert(options[:cli].key?(:variable)){'missing key: cli.variable'}
165
165
  @result[:env][options[:cli][:variable]] = parameter_value
166
166
  when :opt_without_arg # if present and true : just add option without value
167
167
  add_param = false
168
168
  case parameter_value
169
169
  when false then nil # nothing to put on command line, no creation by default
170
170
  when true then add_param = true
171
- else error_unexpected_value(parameter_value){name}
171
+ else Aspera.error_unexpected_value(parameter_value){name}
172
172
  end
173
173
  add_param = !add_param if options[:add_on_false]
174
174
  add_command_line_options([options[:cli][:switch]]) if add_param
@@ -4,8 +4,7 @@
4
4
  if ENV.key?('ENABLE_COVERAGE')
5
5
  require 'simplecov'
6
6
  require 'securerandom'
7
- # compute gem source root based on this script location, assuming it is in bin/
8
- # use dirname instead of gsub, in case folder separator is not /
7
+ # compute development top folder based on this source location
9
8
  development_root = 3.times.inject(File.realpath(__FILE__)) { |p, _| File.dirname(p) }
10
9
  SimpleCov.root(development_root)
11
10
  SimpleCov.enable_for_subprocesses if SimpleCov.respond_to?(:enable_for_subprocesses)
@@ -30,7 +30,7 @@ module Aspera
30
30
  return format('%08x-%04x-%04x-%04x-%04x%08x', *raw_data.unpack('NnnnnN'))
31
31
  when :'aspera.global-cli-client', :'aspera.drive'
32
32
  return Base64.urlsafe_encode64(raw_data)
33
- else error_unexpected_value(name)
33
+ else Aspera.error_unexpected_value(name)
34
34
  end
35
35
  end
36
36
 
@@ -56,8 +56,7 @@ module Aspera
56
56
  when /s390/
57
57
  return CPU_S390
58
58
  when /arm/, /aarch64/
59
- # arm on mac has rosetta 2
60
- return CPU_X86_64 if os.eql?(OS_X)
59
+ return CPU_ARM64
61
60
  end
62
61
  raise "Unknown CPU: #{RbConfig::CONFIG['host_cpu']}"
63
62
  end
@@ -90,7 +89,7 @@ module Aspera
90
89
 
91
90
  # value is provided in block
92
91
  def write_file_restricted(path, force: false, mode: nil)
93
- assert(block_given?, exception_class: Aspera::InternalError)
92
+ Aspera.assert(block_given?, exception_class: Aspera::InternalError)
94
93
  if force || !File.exist?(path)
95
94
  # Windows may give error
96
95
  File.unlink(path) rescue nil
@@ -8,12 +8,11 @@ require 'json'
8
8
  module Aspera
9
9
  # Simulate the Faspex 4 /send API and creates a package on Aspera on Cloud or Faspex 5
10
10
  class Faspex4GWServlet < WEBrick::HTTPServlet::AbstractServlet
11
- # @param app_api [Aspera::AoC]
11
+ # @param app_api
12
12
  # @param app_context [String]
13
13
  def initialize(server, app_api, app_context)
14
- assert_values(app_api.class.name, ['Aspera::AoC', 'Aspera::Rest'])
14
+ Aspera.assert_values(app_api.class.name, ['Aspera::Api::AoC', 'Aspera::Rest'])
15
15
  super(server)
16
- # typed: Aspera::AoC
17
16
  @app_api = app_api
18
17
  @app_context = app_context
19
18
  end
@@ -51,7 +50,7 @@ module Aspera
51
50
  operation: 'POST',
52
51
  subpath: "packages/#{package['id']}/transfer_spec/upload",
53
52
  headers: {'Accept' => 'application/json'},
54
- url_params: {transfer_type: Aspera::Cli::Plugins::Faspex5::TRANSFER_CONNECT},
53
+ url_params: {transfer_type: Cli::Plugins::Faspex5::TRANSFER_CONNECT},
55
54
  json_params: {paths: [{'destination'=>'/'}]}
56
55
  )[:data]
57
56
  transfer_spec.delete('authentication')
@@ -72,11 +71,11 @@ module Aspera
72
71
  # compare string, as class is not yet known here
73
72
  faspex_package_create_result =
74
73
  case @app_api.class.name
75
- when 'Aspera::AoC'
74
+ when 'Aspera::Api::AoC'
76
75
  faspex4_send_to_aoc(faspex_pkg_parameters)
77
76
  when 'Aspera::Rest'
78
77
  faspex4_send_to_faspex5(faspex_pkg_parameters)
79
- else error_unexpected_value(@app_api.class.name)
78
+ else Aspera.error_unexpected_value(@app_api.class.name)
80
79
  end
81
80
  Log.log.info{"faspex_package_create_result=#{faspex_package_create_result}"}
82
81
  response.status = 200
@@ -12,7 +12,7 @@ module Aspera
12
12
  class Faspex4PostProcServlet < WEBrick::HTTPServlet::AbstractServlet
13
13
  ALLOWED_PARAMETERS = %i[root script_folder fail_on_error timeout_seconds].freeze
14
14
  def initialize(server, parameters)
15
- assert_type(parameters, Hash)
15
+ Aspera.assert_type(parameters, Hash)
16
16
  @parameters = parameters.symbolize_keys
17
17
  Log.log.debug{Log.dump(:post_proc_parameters, @parameters)}
18
18
  raise "unexpected key in parameters config: only: #{ALLOWED_PARAMETERS.join(', ')}" if @parameters.keys.any?{|k|!ALLOWED_PARAMETERS.include?(k)}
@@ -17,10 +17,10 @@ module Aspera
17
17
  i.is_a?(String) && i.start_with?('https://') ? URI.parse(i).host : i.to_s
18
18
  end.join(ID_SEPARATOR)
19
19
  end
20
- assert_type(object_id, String)
20
+ Aspera.assert_type(object_id, String)
21
21
  return object_id
22
22
  .gsub(WINDOWS_PROTECTED_CHAR, PROTECTED_CHAR_REPLACE) # remove windows forbidden chars
23
- .gsub('.', PROTECTED_CHAR_REPLACE) # keep dot for extension only (nicer)
23
+ .gsub('.', PROTECTED_CHAR_REPLACE) # keep dot for extension only (nicer)
24
24
  .downcase
25
25
  end
26
26
  end
@@ -35,11 +35,11 @@ module Aspera
35
35
  params: args,
36
36
  id: @request_id += 1
37
37
  })[:data]
38
- assert_type(data, Hash){'response'}
39
- assert(data['jsonrpc'] == JSON_RPC_VERSION){'bad version in response'}
40
- assert(data.key?('id')){'missing id in response'}
41
- assert(!(data.key?('error') && data.key?('result'))){'both error and response'}
42
- assert(
38
+ Aspera.assert_type(data, Hash){'response'}
39
+ Aspera.assert(data['jsonrpc'] == JSON_RPC_VERSION){'bad version in response'}
40
+ Aspera.assert(data.key?('id')){'missing id in response'}
41
+ Aspera.assert(!(data.key?('error') && data.key?('result'))){'both error and response'}
42
+ Aspera.assert(
43
43
  !data.key?('error') ||
44
44
  data['error'].is_a?(Hash) &&
45
45
  data['error']['code'].is_a?(Integer) &&
@@ -17,7 +17,7 @@ module Aspera
17
17
  CONTENT_KEYS = %i[label username password url description].freeze
18
18
  FILE_KEYS = %w[version type cipher data].sort.freeze
19
19
  def initialize(path, current_password)
20
- assert_type(path, String){'path to vault file'}
20
+ Aspera.assert_type(path, String){'path to vault file'}
21
21
  @path = path
22
22
  @all_secrets = {}
23
23
  vault_encrypted_data = nil
@@ -25,7 +25,7 @@ module Aspera
25
25
  vault_file = File.read(@path)
26
26
  if vault_file.start_with?('---')
27
27
  vault_info = YAML.parse(vault_file).to_ruby
28
- assert(vault_info.keys.sort == FILE_KEYS){'Invalid vault file'}
28
+ Aspera.assert(vault_info.keys.sort == FILE_KEYS){'Invalid vault file'}
29
29
  @cipher_name = vault_info['cipher']
30
30
  vault_encrypted_data = vault_info['data']
31
31
  else
@@ -65,11 +65,11 @@ module Aspera
65
65
  # set a secret
66
66
  # @param options [Hash] with keys :label, :username, :password, :url, :description
67
67
  def set(options)
68
- assert_type(options, Hash){'options'}
68
+ Aspera.assert_type(options, Hash){'options'}
69
69
  unsupported = options.keys - CONTENT_KEYS
70
- assert(unsupported.empty?){"unsupported options: #{unsupported}"}
70
+ Aspera.assert(unsupported.empty?){"unsupported options: #{unsupported}"}
71
71
  options.each_pair do |k, v|
72
- assert_type(v, String){k.to_s}
72
+ Aspera.assert_type(v, String){k.to_s}
73
73
  end
74
74
  label = options.delete(:label)
75
75
  raise "secret #{label} already exist, delete first" if @all_secrets.key?(label)
@@ -94,7 +94,7 @@ module Aspera
94
94
  end
95
95
 
96
96
  def get(label:, exception: true)
97
- assert(@all_secrets.key?(label)){"Label not found: #{label}"} if exception
97
+ Aspera.assert(@all_secrets.key?(label)){"Label not found: #{label}"} if exception
98
98
  result = @all_secrets[label].clone
99
99
  result[:label] = label if result.is_a?(Hash)
100
100
  return result
@@ -4,6 +4,7 @@
4
4
  require 'aspera/cli/info'
5
5
  require 'aspera/log'
6
6
  require 'aspera/assert'
7
+ require 'open3'
7
8
 
8
9
  # enhance the gem to support other key chains
9
10
  module Aspera
@@ -11,6 +12,8 @@ module Aspera
11
12
  module MacosSecurity
12
13
  # keychain based on macOS keychain, using `security` command line
13
14
  class Keychain
15
+ # https://www.unix.com/man-page/osx/1/security/
16
+ SECURITY_UTILITY = 'security'
14
17
  DOMAINS = %i[user system common dynamic].freeze
15
18
  LIST_OPTIONS = {
16
19
  domain: :c
@@ -38,25 +41,27 @@ module Aspera
38
41
  url = options&.delete(:url)
39
42
  if !url.nil?
40
43
  uri = URI.parse(url)
41
- assert(uri.scheme.eql?('https')){'only https'}
44
+ Aspera.assert(uri.scheme.eql?('https')){'only https'}
42
45
  options[:protocol] = 'htps' # cspell: disable-line
43
46
  raise 'host required in URL' if uri.host.nil?
44
47
  options[:server] = uri.host
45
48
  options[:path] = uri.path unless ['', '/'].include?(uri.path)
46
49
  options[:port] = uri.port unless uri.port.eql?(443) && !url.include?(':443/')
47
50
  end
48
- cmd = ['security', command]
51
+ command_line = [SECURITY_UTILITY, command]
49
52
  options&.each do |k, v|
50
- assert(supported.key?(k)){"unknown option: #{k}"}
53
+ Aspera.assert(supported.key?(k)){"unknown option: #{k}"}
51
54
  next if v.nil?
52
- cmd.push("-#{supported[k]}")
53
- cmd.push(v.shellescape) unless v.empty?
55
+ command_line.push("-#{supported[k]}")
56
+ command_line.push(v.shellescape) unless v.empty?
54
57
  end
55
- cmd.push(last_opt) unless last_opt.nil?
56
- Log.log.debug{"executing>>#{cmd.join(' ')}"}
57
- result = %x(#{cmd.join(' ')} 2>&1)
58
- Log.log.debug{"result>>[#{result}]"}
59
- return result
58
+ command_line.push(last_opt) unless last_opt.nil?
59
+ Log.log.debug{"executing>>#{command_line.join(' ')}"}
60
+ stdout, stderr, status = Open3.capture3(*command_line)
61
+ Log.log.debug{"status=#{status}, stderr=#{stderr}"}
62
+ Log.log.trace1{"stdout=#{stdout}"}
63
+ raise "#{SECURITY_UTILITY} failed: #{status.exitstatus} : #{stderr}" unless status.success?
64
+ return stdout
60
65
  end
61
66
 
62
67
  def key_chains(output)
@@ -72,7 +77,7 @@ module Aspera
72
77
  end
73
78
 
74
79
  def list(options={})
75
- assert_values(options[:domain], DOMAINS, exception_class: ArgumentError){'domain'} unless options[:domain].nil?
80
+ Aspera.assert_values(options[:domain], DOMAINS, exception_class: ArgumentError){'domain'} unless options[:domain].nil?
76
81
  key_chains(execute('list-key_chains', options, LIST_OPTIONS))
77
82
  end
78
83
 
@@ -91,15 +96,15 @@ module Aspera
91
96
  end
92
97
 
93
98
  def password(operation, pass_type, options)
94
- assert_values(operation, %i[add find delete]){'operation'}
95
- assert_values(pass_type, %i[generic internet]){'pass_type'}
96
- assert_type(options, Hash)
99
+ Aspera.assert_values(operation, %i[add find delete]){'operation'}
100
+ Aspera.assert_values(pass_type, %i[generic internet]){'pass_type'}
101
+ Aspera.assert_type(options, Hash)
97
102
  missing = (operation.eql?(:add) ? %i[account service password] : %i[label]) - options.keys
98
- assert(missing.empty?){"missing options: #{missing}"}
103
+ Aspera.assert(missing.empty?){"missing options: #{missing}"}
99
104
  options[:getpass] = '' if operation.eql?(:find)
100
105
  output = self.class.execute("#{operation}-#{pass_type}-password", options, ADD_PASS_OPTIONS, @path)
101
106
  raise output.gsub(/^.*: /, '') if output.start_with?('security: ')
102
- return nil unless operation.eql?(:find)
107
+ return unless operation.eql?(:find)
103
108
  attributes = {}
104
109
  output.split("\n").each do |line|
105
110
  case line
@@ -129,18 +134,18 @@ module Aspera
129
134
  end
130
135
 
131
136
  def set(options)
132
- assert_type(options, Hash){'options'}
137
+ Aspera.assert_type(options, Hash){'options'}
133
138
  unsupported = options.keys - %i[label username password url description]
134
- assert(unsupported.empty?){"unsupported options: #{unsupported}"}
139
+ Aspera.assert(unsupported.empty?){"unsupported options: #{unsupported}"}
135
140
  @keychain.password(
136
141
  :add, :generic, service: options[:label],
137
142
  account: options[:username] || 'none', password: options[:password], comment: options[:description])
138
143
  end
139
144
 
140
145
  def get(options)
141
- assert_type(options, Hash){'options'}
146
+ Aspera.assert_type(options, Hash){'options'}
142
147
  unsupported = options.keys - %i[label]
143
- assert(unsupported.empty?){"unsupported options: #{unsupported}"}
148
+ Aspera.assert(unsupported.empty?){"unsupported options: #{unsupported}"}
144
149
  info = @keychain.password(:find, :generic, label: options[:label])
145
150
  raise 'not found' if info.nil?
146
151
  result = options.clone
@@ -155,9 +160,9 @@ module Aspera
155
160
  end
156
161
 
157
162
  def delete(options)
158
- assert_type(options, Hash){'options'}
163
+ Aspera.assert_type(options, Hash){'options'}
159
164
  unsupported = options.keys - %i[label]
160
- assert(unsupported.empty?){"unsupported options: #{unsupported}"}
165
+ Aspera.assert(unsupported.empty?){"unsupported options: #{unsupported}"}
161
166
  raise 'delete not implemented, use macos keychain app'
162
167
  end
163
168
  end
data/lib/aspera/log.rb CHANGED
@@ -14,7 +14,7 @@ class Logger
14
14
  TRACE_MAX = 2
15
15
  # add custom level to logger severity
16
16
  module Severity
17
- 1.upto(TRACE_MAX).each { |level| const_set("TRACE#{level}", - level)}
17
+ 1.upto(TRACE_MAX).each { |level| const_set(:"TRACE#{level}", - level)}
18
18
  end
19
19
  # quick access to label
20
20
  SEVERITY_LABEL = Severity.constants.each_with_object({}) { |name, hash| hash[Severity.const_get(name)] = name}
@@ -99,7 +99,7 @@ module Aspera
99
99
  Logger::Severity.constants.each do |name|
100
100
  return name.downcase.to_sym if @logger.level.eql?(Logger::Severity.const_get(name))
101
101
  end
102
- error_unexpected_value(@logger.level){'log level'}
102
+ Aspera.error_unexpected_value(@logger.level){'log level'}
103
103
  end
104
104
 
105
105
  # change underlying logger, but keep log level
data/lib/aspera/nagios.rb CHANGED
@@ -23,10 +23,10 @@ module Aspera
23
23
  class << self
24
24
  # process results of a analysis and display status and exit with code
25
25
  def process(data)
26
- assert_type(data, Array)
27
- assert(!data.empty?){'data is empty'}
26
+ Aspera.assert_type(data, Array)
27
+ Aspera.assert(!data.empty?){'data is empty'}
28
28
  %w[status component message].each do |c|
29
- assert(data.first.key?(c)){"result must have #{c}"}
29
+ Aspera.assert(data.first.key?(c)){"result must have #{c}"}
30
30
  end
31
31
  res_errors = data.reject{|s|s['status'].eql?('ok')}
32
32
  # keep only errors in case of problem, other ok are assumed so
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'aspera/ascp/installation'
4
+ require 'aspera/agent/direct'
3
5
  require 'aspera/log'
4
- require 'aspera/fasp/installation'
5
- require 'aspera/fasp/agent_direct'
6
6
  require 'webrick'
7
7
  require 'json'
8
8
 
@@ -11,12 +11,12 @@ module Aspera
11
11
  class NodeSimulatorServlet < WEBrick::HTTPServlet::AbstractServlet
12
12
  PATH_TRANSFERS = '/ops/transfers'
13
13
  PATH_ONE_TRANSFER = %r{/ops/transfers/(.+)$}
14
- # @param app_api [Aspera::AoC]
14
+ # @param app_api [Api::AoC]
15
15
  # @param app_context [String]
16
16
  def initialize(server, credentials, transfer)
17
17
  super(server)
18
18
  @credentials = credentials
19
- @xfer_manager = Aspera::Fasp::AgentDirect.new
19
+ @xfer_manager = Agent::Direct.new
20
20
  end
21
21
 
22
22
  def do_POST(request, response)
@@ -27,7 +27,6 @@ module Aspera
27
27
  result = session[:ts].clone
28
28
  result['id'] = job_id
29
29
  set_json_response(response, result)
30
- Log.log.debug{">>> transfer started: #{job_id}"}
31
30
  else
32
31
  set_json_response(response, [{error: 'Bad request'}], code: 400)
33
32
  end
@@ -36,7 +35,7 @@ module Aspera
36
35
  def do_GET(request, response)
37
36
  case request.path
38
37
  when '/info'
39
- info = Aspera::Fasp::Installation.instance.ascp_info
38
+ info = Ascp::Installation.instance.ascp_info
40
39
  set_json_response(response, {
41
40
  application: 'node',
42
41
  current_time: Time.now.utc.iso8601(0),
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aspera/oauth/factory'
4
+ require 'aspera/log'
5
+ require 'aspera/assert'
6
+ require 'aspera/id_generator'
7
+ require 'date'
8
+
9
+ module Aspera
10
+ module OAuth
11
+ # Implement OAuth 2 for the REST client and generate a bearer token
12
+ # call get_authorization() to get a token.
13
+ # bearer tokens are kept in memory and also in a file cache for later re-use
14
+ # if a token is expired (api returns 4xx), call again get_authorization(refresh: true)
15
+ # https://tools.ietf.org/html/rfc6749
16
+ class Base
17
+ # scope can be modified after creation
18
+ attr_writer :scope
19
+
20
+ # [M]=mandatory [D]=has default value [O]=Optional/nil
21
+ # @param base_url [M] URL of authentication API
22
+ # @param auth [O] basic auth parameters
23
+ # @param client_id [O]
24
+ # @param client_secret [O]
25
+ # @param scope [O]
26
+ # @param path_token [D] API end point to create a token
27
+ # @param token_field [D] field in result that contains the token
28
+ def initialize(
29
+ base_url:,
30
+ auth: nil,
31
+ client_id: nil,
32
+ client_secret: nil,
33
+ scope: nil,
34
+ path_token: 'token', # default endpoint for /token to generate token
35
+ token_field: 'access_token' # field with token in result of call to path_token
36
+ )
37
+ Aspera.assert_type(base_url, String)
38
+ Aspera.assert(respond_to?(:create_token), 'create_token method must be defined', exception_class: InternalError)
39
+ @base_url = base_url
40
+ @path_token = path_token
41
+ @token_field = token_field
42
+ @client_id = client_id
43
+ @client_secret = client_secret
44
+ @scope = scope
45
+ @identifiers = []
46
+ @identifiers.push(auth[:username]) if auth.is_a?(Hash) && auth.key?(:username)
47
+ # this is the OAuth API
48
+ @api = Rest.new(
49
+ base_url: @base_url,
50
+ redirect_max: 2,
51
+ auth: auth)
52
+ end
53
+
54
+ # helper method to create token as per RFC
55
+ def create_token_call(www_params)
56
+ Log.log.debug{'Generating a new token'.bg_green}
57
+ return @api.call(
58
+ operation: 'POST',
59
+ subpath: @path_token,
60
+ headers: {'Accept' => 'application/json'},
61
+ www_body_params: www_params)
62
+ end
63
+
64
+ # @return Hash with optional general parameters
65
+ def optional_scope_client_id(add_secret: false)
66
+ call_params = {}
67
+ call_params[:scope] = @scope unless @scope.nil?
68
+ call_params[:client_id] = @client_id unless @client_id.nil?
69
+ call_params[:client_secret] = @client_secret if add_secret && !@client_id.nil?
70
+ return call_params
71
+ end
72
+
73
+ # OAuth v2 token generation
74
+ # @param use_refresh_token set to true to force refresh or re-generation (if previous failed)
75
+ def get_authorization(use_refresh_token: false, use_cache: true)
76
+ # generate token unique identifier for persistency (memory/disk cache)
77
+ token_id = IdGenerator.from_list(Factory.id(
78
+ @base_url,
79
+ @grant_method,
80
+ @identifiers,
81
+ @scope
82
+ ))
83
+
84
+ # get token_data from cache (or nil), token_data is what is returned by /token
85
+ token_data = Factory.instance.persist_mgr.get(token_id) if use_cache
86
+ token_data = JSON.parse(token_data) unless token_data.nil?
87
+ # Optional optimization: check if node token is expired based on decoded content then force refresh if close enough
88
+ # might help in case the transfer agent cannot refresh himself
89
+ # `direct` agent is equipped with refresh code
90
+ if !use_refresh_token && !token_data.nil?
91
+ decoded_token = OAuth::Factory.instance.decode_token(token_data[@token_field])
92
+ Log.log.debug{Log.dump('decoded_token', decoded_token)} unless decoded_token.nil?
93
+ if decoded_token.is_a?(Hash)
94
+ expires_at_sec =
95
+ if decoded_token['expires_at'].is_a?(String) then DateTime.parse(decoded_token['expires_at']).to_time
96
+ elsif decoded_token['exp'].is_a?(Integer) then Time.at(decoded_token['exp'])
97
+ end
98
+ # force refresh if we see a token too close from expiration
99
+ use_refresh_token = true if expires_at_sec.is_a?(Time) && (expires_at_sec - Time.now) < OAuth::Factory.instance.globals[:token_expiration_guard_sec]
100
+ Log.log.debug{"Expiration: #{expires_at_sec} / #{use_refresh_token}"}
101
+ end
102
+ end
103
+
104
+ # an API was already called, but failed, we need to regenerate or refresh
105
+ if use_refresh_token
106
+ if token_data.is_a?(Hash) && token_data.key?('refresh_token') && !token_data['refresh_token'].eql?('not_supported')
107
+ # save possible refresh token, before deleting the cache
108
+ refresh_token = token_data['refresh_token']
109
+ end
110
+ # delete cache
111
+ Factory.instance.persist_mgr.delete(token_id)
112
+ token_data = nil
113
+ # lets try the existing refresh token
114
+ if !refresh_token.nil?
115
+ Log.log.info{"refresh=[#{refresh_token}]".bg_green}
116
+ # try to refresh
117
+ # note: AoC admin token has no refresh, and lives by default 1800secs
118
+ resp = create_token_call(optional_scope_client_id.merge(grant_type: 'refresh_token', refresh_token: refresh_token))
119
+ if resp[:http].code.start_with?('2')
120
+ # save only if success
121
+ json_data = resp[:http].body
122
+ token_data = JSON.parse(json_data)
123
+ Factory.instance.persist_mgr.put(token_id, json_data)
124
+ else
125
+ Log.log.debug{"refresh failed: #{resp[:http].body}".bg_red}
126
+ end
127
+ end
128
+ end
129
+
130
+ # no cache, nor refresh: generate a token
131
+ if token_data.nil?
132
+ resp = create_token
133
+ json_data = resp[:http].body
134
+ token_data = JSON.parse(json_data)
135
+ Factory.instance.persist_mgr.put(token_id, json_data)
136
+ end # if ! in_cache
137
+ Aspera.assert(token_data.key?(@token_field)){"API error: No such field in answer: #{@token_field}"}
138
+ # ok we shall have a token here
139
+ return OAuth::Factory.bearer_build(token_data[@token_field])
140
+ end
141
+ end # OAuth
142
+ end
143
+ end