aspera-cli 4.13.0 → 4.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +81 -7
  4. data/CONTRIBUTING.md +22 -6
  5. data/README.md +2038 -1080
  6. data/bin/ascli +18 -9
  7. data/bin/asession +12 -14
  8. data/examples/dascli +1 -1
  9. data/examples/proxy.pac +1 -1
  10. data/examples/rubyc +24 -0
  11. data/lib/aspera/aoc.rb +219 -159
  12. data/lib/aspera/ascmd.rb +25 -14
  13. data/lib/aspera/cli/basic_auth_plugin.rb +12 -9
  14. data/lib/aspera/cli/error.rb +17 -0
  15. data/lib/aspera/cli/extended_value.rb +47 -12
  16. data/lib/aspera/cli/formatter.rb +260 -179
  17. data/lib/aspera/cli/hints.rb +80 -0
  18. data/lib/aspera/cli/main.rb +104 -156
  19. data/lib/aspera/cli/manager.rb +259 -209
  20. data/lib/aspera/cli/plugin.rb +123 -63
  21. data/lib/aspera/cli/plugins/alee.rb +2 -3
  22. data/lib/aspera/cli/plugins/aoc.rb +341 -261
  23. data/lib/aspera/cli/plugins/ats.rb +22 -21
  24. data/lib/aspera/cli/plugins/bss.rb +5 -5
  25. data/lib/aspera/cli/plugins/config.rb +578 -627
  26. data/lib/aspera/cli/plugins/console.rb +44 -6
  27. data/lib/aspera/cli/plugins/cos.rb +15 -17
  28. data/lib/aspera/cli/plugins/faspex.rb +114 -100
  29. data/lib/aspera/cli/plugins/faspex5.rb +411 -264
  30. data/lib/aspera/cli/plugins/node.rb +354 -259
  31. data/lib/aspera/cli/plugins/orchestrator.rb +61 -29
  32. data/lib/aspera/cli/plugins/preview.rb +82 -90
  33. data/lib/aspera/cli/plugins/server.rb +79 -32
  34. data/lib/aspera/cli/plugins/shares.rb +55 -42
  35. data/lib/aspera/cli/sync_actions.rb +68 -0
  36. data/lib/aspera/cli/transfer_agent.rb +66 -73
  37. data/lib/aspera/cli/transfer_progress.rb +74 -0
  38. data/lib/aspera/cli/version.rb +1 -1
  39. data/lib/aspera/colors.rb +12 -8
  40. data/lib/aspera/command_line_builder.rb +14 -11
  41. data/lib/aspera/cos_node.rb +3 -2
  42. data/lib/aspera/data/6 +0 -0
  43. data/lib/aspera/environment.rb +24 -9
  44. data/lib/aspera/fasp/agent_aspera.rb +126 -0
  45. data/lib/aspera/fasp/agent_base.rb +31 -77
  46. data/lib/aspera/fasp/agent_connect.rb +25 -21
  47. data/lib/aspera/fasp/agent_direct.rb +89 -103
  48. data/lib/aspera/fasp/agent_httpgw.rb +231 -149
  49. data/lib/aspera/fasp/agent_node.rb +41 -34
  50. data/lib/aspera/fasp/agent_trsdk.rb +75 -32
  51. data/lib/aspera/fasp/error_info.rb +4 -2
  52. data/lib/aspera/fasp/faux_file.rb +52 -0
  53. data/lib/aspera/fasp/installation.rb +53 -195
  54. data/lib/aspera/fasp/management.rb +244 -0
  55. data/lib/aspera/fasp/parameters.rb +71 -37
  56. data/lib/aspera/fasp/parameters.yaml +76 -8
  57. data/lib/aspera/fasp/products.rb +162 -0
  58. data/lib/aspera/fasp/resume_policy.rb +3 -3
  59. data/lib/aspera/fasp/transfer_spec.rb +7 -6
  60. data/lib/aspera/fasp/uri.rb +26 -24
  61. data/lib/aspera/faspex_gw.rb +2 -2
  62. data/lib/aspera/faspex_postproc.rb +2 -2
  63. data/lib/aspera/hash_ext.rb +14 -4
  64. data/lib/aspera/json_rpc.rb +49 -0
  65. data/lib/aspera/keychain/macos_security.rb +13 -13
  66. data/lib/aspera/line_logger.rb +23 -0
  67. data/lib/aspera/log.rb +58 -16
  68. data/lib/aspera/node.rb +157 -92
  69. data/lib/aspera/oauth.rb +37 -19
  70. data/lib/aspera/open_application.rb +4 -4
  71. data/lib/aspera/persistency_action_once.rb +1 -1
  72. data/lib/aspera/persistency_folder.rb +2 -2
  73. data/lib/aspera/preview/file_types.rb +4 -2
  74. data/lib/aspera/preview/generator.rb +22 -35
  75. data/lib/aspera/preview/options.rb +2 -0
  76. data/lib/aspera/preview/terminal.rb +73 -16
  77. data/lib/aspera/preview/utils.rb +21 -28
  78. data/lib/aspera/proxy_auto_config.js +2 -2
  79. data/lib/aspera/rest.rb +136 -68
  80. data/lib/aspera/rest_call_error.rb +1 -1
  81. data/lib/aspera/rest_error_analyzer.rb +15 -14
  82. data/lib/aspera/rest_errors_aspera.rb +37 -34
  83. data/lib/aspera/secret_hider.rb +18 -15
  84. data/lib/aspera/ssh.rb +5 -2
  85. data/lib/aspera/sync.rb +127 -119
  86. data/lib/aspera/temp_file_manager.rb +10 -3
  87. data/lib/aspera/web_auth.rb +10 -7
  88. data/lib/aspera/web_server_simple.rb +9 -4
  89. data.tar.gz.sig +0 -0
  90. metadata +34 -17
  91. metadata.gz.sig +0 -0
  92. data/docs/test_env.conf +0 -186
  93. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  94. data/lib/aspera/cli/listener/logger.rb +0 -22
  95. data/lib/aspera/cli/listener/progress.rb +0 -50
  96. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  97. data/lib/aspera/cli/plugins/sync.rb +0 -44
  98. data/lib/aspera/data/7 +0 -0
  99. data/lib/aspera/fasp/listener.rb +0 -13
@@ -3,45 +3,49 @@
3
3
  require 'aspera/fasp/agent_base'
4
4
  require 'aspera/fasp/installation'
5
5
  require 'json'
6
+ require 'uri'
6
7
 
7
8
  module Aspera
8
9
  module Fasp
9
10
  class AgentTrsdk < Aspera::Fasp::AgentBase
10
11
  DEFAULT_OPTIONS = {
11
- address: '127.0.0.1',
12
- port: 55_002
12
+ url: 'grpc://127.0.0.1:0',
13
+ external: false,
14
+ keep: false
13
15
  }.freeze
14
16
  private_constant :DEFAULT_OPTIONS
15
17
 
16
18
  # options come from transfer_info
17
- def initialize(user_opts)
18
- raise "expecting Hash (or nil), but have #{user_opts.class}" unless user_opts.nil? || user_opts.is_a?(Hash)
19
- # set default options and override if specified
20
- options = DEFAULT_OPTIONS.dup
21
- user_opts&.each do |k, v|
22
- raise "Unknown local agent parameter: #{k}, expect one of #{DEFAULT_OPTIONS.keys.map(&:to_s).join(',')}" unless DEFAULT_OPTIONS.key?(k)
23
- options[k] = v
24
- end
25
- Log.log.debug{"options= #{options}"}
26
- super()
19
+ def initialize(user_opts={})
20
+ super(user_opts)
21
+ @options = AgentBase.options(default: DEFAULT_OPTIONS, options: user_opts)
22
+ daemon_uri = URI.parse(@options[:url])
23
+ raise Fasp::Error, "invalid url #{@options[:url]}" unless daemon_uri.scheme.eql?('grpc')
24
+ Log.log.debug{Log.dump(:agent_options, @options)}
27
25
  # load and create SDK stub
28
26
  $LOAD_PATH.unshift(Installation.instance.sdk_ruby_folder)
29
27
  require 'transfer_services_pb'
30
- @transfer_client = Transfersdk::TransferService::Stub.new("#{options[:address]}:#{options[:port]}", :this_channel_is_insecure)
28
+ # it stays
29
+ @daemon_pid = nil
31
30
  begin
31
+ @transfer_client = Transfersdk::TransferService::Stub.new("#{daemon_uri.host}:#{daemon_uri.port}", :this_channel_is_insecure)
32
32
  get_info_response = @transfer_client.get_info(Transfersdk::InstanceInfoRequest.new)
33
33
  Log.log.debug{"daemon info: #{get_info_response}"}
34
+ Log.log.warn{'attached to existing daemon'} unless @options[:external] || @options[:keep]
35
+ at_exit{shutdown}
34
36
  rescue GRPC::Unavailable
35
- Log.log.warn('no daemon present, starting daemon...')
37
+ raise if @options[:external]
38
+ raise "daemon started with PID #{@daemon_pid}, but connection failed to #{daemon_uri}}" unless @daemon_pid.nil?
39
+ Log.log.warn('no daemon present, starting daemon...') if @options[:external]
36
40
  # location of daemon binary
37
41
  bin_folder = File.realpath(File.join(Installation.instance.sdk_ruby_folder, '..'))
38
42
  # config file and logs are created in same folder
39
- conf_file = File.join(bin_folder, 'sdk.conf')
43
+ generated_config_file_path = File.join(bin_folder, 'sdk.conf')
40
44
  log_base = File.join(bin_folder, 'transferd')
41
45
  # create a config file for daemon
42
46
  config = {
43
- address: options[:address],
44
- port: options[:port],
47
+ address: daemon_uri.host,
48
+ port: daemon_uri.port,
45
49
  fasp_runtime: {
46
50
  use_embedded: false,
47
51
  user_defined: {
@@ -50,10 +54,30 @@ module Aspera
50
54
  }
51
55
  }
52
56
  }
53
- File.write(conf_file, config.to_json)
54
- trd_pid = Process.spawn(Installation.instance.path(:transferd), '--config', conf_file, out: "#{log_base}.out", err: "#{log_base}.err")
55
- Process.detach(trd_pid)
56
- sleep(2.0)
57
+ File.write(generated_config_file_path, config.to_json)
58
+ @daemon_pid = Process.spawn(Installation.instance.path(:transferd), '--config', generated_config_file_path, out: "#{log_base}.out", err: "#{log_base}.err")
59
+ begin
60
+ # wait for process to initialize
61
+ Timeout.timeout(2.0) do
62
+ _, status = Process.wait2(@daemon_pid)
63
+ raise "transfer daemon exited with status #{status.exitstatus}. Check files: #{log_base}.out #{log_base}.err"
64
+ end
65
+ rescue Timeout::Error
66
+ nil
67
+ end
68
+ Log.log.debug{"daemon started with pid #{@daemon_pid}"}
69
+ Process.detach(@daemon_pid) if @options[:keep]
70
+ if daemon_uri.port.eql?(0)
71
+ # if port is zero, a dynamic port was created, get it
72
+ File.open("#{log_base}.out", 'r') do |file|
73
+ file.each_line do |line|
74
+ if (m = line.match(/Info: API Server: Listening on ([^:]+):(\d+) /))
75
+ daemon_uri.port = m[2].to_i
76
+ # no "break" , need to keep last one
77
+ end
78
+ end
79
+ end
80
+ end
57
81
  retry
58
82
  end
59
83
  end
@@ -72,25 +96,34 @@ module Aspera
72
96
  end
73
97
 
74
98
  def wait_for_transfers_completion
75
- started = false
99
+ # set to true when we know the total size of the transfer
100
+ session_started = false
101
+ bytes_expected = nil
76
102
  # monitor transfer status
77
103
  @transfer_client.monitor_transfers(Transfersdk::RegistrationRequest.new(transferId: [@transfer_id])) do |response|
78
- Log.dump(:response, response.to_h)
104
+ Log.log.debug{Log.dump(:response, response.to_h)}
79
105
  # Log.log.debug{"#{response.sessionInfo.preTransferBytes} #{response.transferInfo.bytesTransferred}"}
80
106
  case response.status
81
107
  when :RUNNING
82
- if !started && !response.sessionInfo.preTransferBytes.eql?(0)
83
- notify_begin(@transfer_id, response.sessionInfo.preTransferBytes)
84
- started = true
85
- elsif started
86
- notify_progress(@transfer_id, response.transferInfo.bytesTransferred)
108
+ if !session_started
109
+ notify_progress(session_id: @transfer_id, type: :session_start)
110
+ session_started = true
87
111
  end
88
- when :FAILED, :COMPLETED, :CANCELED
89
- notify_end(@transfer_id)
90
- raise Fasp::Error, JSON.parse(response.message)['Description'] unless :COMPLETED.eql?(response.status)
112
+ if bytes_expected.nil? &&
113
+ !response.sessionInfo.preTransferBytes.eql?(0)
114
+ bytes_expected = response.sessionInfo.preTransferBytes
115
+ notify_progress(type: :session_size, session_id: @transfer_id, info: bytes_expected)
116
+ end
117
+ notify_progress(type: :transfer, session_id: @transfer_id, info: response.transferInfo.bytesTransferred)
118
+ when :COMPLETED
119
+ notify_progress(type: :transfer, session_id: @transfer_id, info: bytes_expected) if bytes_expected
120
+ notify_progress(type: :end, session_id: @transfer_id)
91
121
  break
122
+ when :FAILED, :CANCELED
123
+ notify_progress(type: :end, session_id: @transfer_id)
124
+ raise Fasp::Error, JSON.parse(response.message)['Description']
92
125
  when :QUEUED, :UNKNOWN_STATUS, :PAUSED, :ORPHANED
93
- # ignore
126
+ notify_progress(session_id: nil, type: :pre_start, info: response.status.to_s.downcase)
94
127
  else
95
128
  Log.log.error{"unknown status#{response.status}"}
96
129
  end
@@ -98,6 +131,16 @@ module Aspera
98
131
  # TODO: return status
99
132
  return []
100
133
  end
134
+
135
+ def shutdown
136
+ if !@options[:keep] && !@daemon_pid.nil?
137
+ Log.log.debug("stopping daemon #{@daemon_pid}")
138
+ Process.kill('INT', @daemon_pid)
139
+ _, status = Process.wait2(@daemon_pid)
140
+ Log.log.debug("daemon stopped #{status}")
141
+ @daemon_pid = nil
142
+ end
143
+ end
101
144
  end
102
145
  end
103
146
  end
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # cspell:words mnemo PROTO RCVR NOLIC PMTU BRTT VLINK BWMEAS SSEAR FIPS
4
+
3
5
  module Aspera
4
6
  module Fasp
5
7
  # from https://www.google.com/search?q=FASP+error+codes
6
- # Note that the fact that an error is retryable is not internally defined by protocol, it's client-side responsibility
8
+ # Note that the fact that an error is retry-able is not internally defined by protocol, it's client-side responsibility
7
9
  # rubocop:disable Layout/MultilineHashKeyLineBreaks
8
10
  # rubocop:disable Layout/FirstHashElementLineBreak
9
11
  ERROR_INFO = {
10
- # id retryable mnemo message additional info
12
+ # id retry-able mnemo message additional info
11
13
  1 => { r: false, c: 'FASP_PROTO', m: 'Generic fasp(tm) protocol error', a: 'fasp(tm) error'},
12
14
  2 => { r: false, c: 'ASCP', m: 'Generic SCP error', a: 'ASCP error'},
13
15
  3 => { r: false, c: 'AMBIGUOUS_TARGET', m: 'Target incorrectly specified', a: 'Ambiguous target'},
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aspera
4
+ module Fasp
5
+ # generates a pseudo file stream
6
+ class FauxFile
7
+ # marker for faux file
8
+ PREFIX = 'faux:///'
9
+ # size suffix
10
+ SUFFIX = %w[k m g t p e]
11
+ class << self
12
+ def open(name)
13
+ return nil unless name.start_with?(PREFIX)
14
+ parts = name[PREFIX.length..-1].split('?')
15
+ raise 'Format: #{PREFIX}<file path>?<size>' unless parts.length.eql?(2)
16
+ raise "Format: <integer>[#{SUFFIX.join(',')}]" unless (m = parts[1].downcase.match(/^(\d+)([#{SUFFIX.join('')}])$/))
17
+ size = m[1].to_i
18
+ suffix = m[2]
19
+ SUFFIX.each do |s|
20
+ size *= 1024
21
+ break if s.eql?(suffix)
22
+ end
23
+ return FauxFile.new(parts[0], size)
24
+ end
25
+ end
26
+ attr_reader :path, :size
27
+
28
+ def initialize(path, size)
29
+ @path = path
30
+ @size = size
31
+ @offset = 0
32
+ # we cache large chunks, anyway most of them will be the same size
33
+ @chunk_by_size = {}
34
+ end
35
+
36
+ def read(chunk_size)
37
+ return nil if eof?
38
+ bytes_to_read = [chunk_size, @size - @offset].min
39
+ @offset += bytes_to_read
40
+ @chunk_by_size[bytes_to_read] = "\x00" * bytes_to_read unless @chunk_by_size.key?(bytes_to_read)
41
+ return @chunk_by_size[bytes_to_read]
42
+ end
43
+
44
+ def close
45
+ end
46
+
47
+ def eof?
48
+ return @offset >= @size
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'English'
4
- require 'singleton'
5
- require 'aspera/log'
3
+ # cspell:ignore protobuf ckpt
6
4
  require 'aspera/environment'
7
5
  require 'aspera/data_repository'
6
+ require 'aspera/fasp/products'
7
+ require 'aspera/log'
8
+ require 'aspera/web_server_simple'
9
+ require 'English'
10
+ require 'singleton'
8
11
  require 'xmlsimple'
9
12
  require 'zlib'
10
13
  require 'base64'
@@ -13,24 +16,18 @@ require 'openssl'
13
16
 
14
17
  module Aspera
15
18
  module Fasp
16
- # Singleton that tells where to find ascp and other local resources (keys..) , using the "path(symb)" method.
19
+ # Singleton that tells where to find ascp and other local resources (keys..) , using the "path(:name)" method.
17
20
  # It is used by object : AgentDirect to find necessary resources
18
- # By default it takes the first Aspera product found specified in product_locations
21
+ # By default it takes the first Aspera product found
19
22
  # but the user can specify ascp location by calling:
20
23
  # Installation.instance.use_ascp_from_product(product_name)
21
24
  # or
22
25
  # Installation.instance.ascp_path=""
23
26
  class Installation
24
27
  include Singleton
25
- # known product names
26
- PRODUCT_CONNECT = 'Aspera Connect'
27
- PRODUCT_CLI_V1 = 'Aspera CLI'
28
- PRODUCT_DRIVE = 'Aspera Drive'
29
- PRODUCT_ENTSRV = 'Enterprise Server'
30
28
  # protobuf generated files from sdk
31
29
  EXT_RUBY_PROTOBUF = '_pb.rb'
32
30
  RB_SDK_FOLDER = 'lib'
33
- ONE_YEAR_SECONDS = 365 * 24 * 60 * 60
34
31
  DEFAULT_ASPERA_CONF = <<~END_OF_CONFIG_FILE
35
32
  <?xml version='1.0' encoding='UTF-8'?>
36
33
  <CONF version="2">
@@ -42,9 +39,9 @@ module Aspera
42
39
  </default>
43
40
  </CONF>
44
41
  END_OF_CONFIG_FILE
45
- DUMMY_CERT_INFO = '/C=US/ST=California/L=Emeryville/O=Aspera Inc./OU=Corporate/CN=Aspera Inc./emailAddress=info@asperasoft.com'
46
- private_constant :PRODUCT_CONNECT, :PRODUCT_CLI_V1, :PRODUCT_DRIVE, :PRODUCT_ENTSRV, :EXT_RUBY_PROTOBUF, :RB_SDK_FOLDER,
47
- :ONE_YEAR_SECONDS, :DEFAULT_ASPERA_CONF, :DUMMY_CERT_INFO
42
+ # all ascp files (in SDK)
43
+ FILES = %i[ascp ascp4 transferd ssh_private_dsa ssh_private_rsa aspera_license aspera_conf fallback_certificate fallback_private_key].freeze
44
+ private_constant :EXT_RUBY_PROTOBUF, :RB_SDK_FOLDER, :DEFAULT_ASPERA_CONF, :FILES
48
45
  # set ascp executable path
49
46
  def ascp_path=(v)
50
47
  @path_to_ascp = v
@@ -56,7 +53,7 @@ module Aspera
56
53
 
57
54
  def sdk_ruby_folder
58
55
  ruby_pb_folder = File.join(sdk_folder, RB_SDK_FOLDER)
59
- FileUtils.mkdir_p(ruby_pb_folder) unless Dir.exist?(ruby_pb_folder)
56
+ FileUtils.mkdir_p(ruby_pb_folder)
60
57
  return ruby_pb_folder
61
58
  end
62
59
 
@@ -73,60 +70,39 @@ module Aspera
73
70
  # @return the path to folder where SDK is installed
74
71
  def sdk_folder
75
72
  raise 'SDK path was ot initialized' if @sdk_dir.nil?
76
- FileUtils.mkdir_p(@sdk_dir) unless Dir.exist?(@sdk_dir)
73
+ FileUtils.mkdir_p(@sdk_dir)
77
74
  @sdk_dir
78
75
  end
79
76
 
80
77
  # find ascp in named product (use value : FIRST_FOUND='FIRST' to just use first one)
81
- # or select one from installed_products()
78
+ # or select one from Products.installed_products()
82
79
  def use_ascp_from_product(product_name)
83
80
  if product_name.eql?(FIRST_FOUND)
84
- pl = installed_products.first
81
+ pl = Products.installed_products.first
85
82
  raise "no FASP installation found\nPlease check manual on how to install FASP." if pl.nil?
86
83
  else
87
- pl = installed_products.find{|i|i[:name].eql?(product_name)}
84
+ pl = Products.installed_products.find{|i|i[:name].eql?(product_name)}
88
85
  raise "no such product installed: #{product_name}" if pl.nil?
89
86
  end
90
87
  self.ascp_path = pl[:ascp_path]
91
88
  Log.log.debug{"ascp_path=#{@path_to_ascp}"}
92
89
  end
93
90
 
94
- # @return the list of installed products in format of product_locations
95
- def installed_products
96
- if @found_products.nil?
97
- scan_locations = product_locations.clone
98
- # add SDK as first search path
99
- scan_locations.unshift({
100
- expected: 'SDK',
101
- app_root: sdk_folder,
102
- sub_bin: ''
103
- })
104
- # search installed products: with ascp
105
- @found_products = scan_locations.select! do |item|
106
- # skip if not main folder
107
- next false unless Dir.exist?(item[:app_root])
108
- Log.log.debug{"Found #{item[:app_root]}"}
109
- sub_bin = item[:sub_bin] || BIN_SUBFOLDER
110
- item[:ascp_path] = File.join(item[:app_root], sub_bin, ascp_filename)
111
- # skip if no ascp
112
- next false unless File.exist?(item[:ascp_path])
113
- # read info from product info file if present
114
- product_info_file = "#{item[:app_root]}/#{PRODUCT_INFO}"
115
- if File.exist?(product_info_file)
116
- res_s = XmlSimple.xml_in(File.read(product_info_file), {'ForceArray' => false})
117
- item[:name] = res_s['name']
118
- item[:version] = res_s['version']
119
- else
120
- item[:name] = item[:expected]
91
+ # @return [Hash] with key = file name (String), and value = path to file
92
+ def file_paths
93
+ return FILES.each_with_object({}) do |v, m|
94
+ m[v.to_s] =
95
+ begin
96
+ path(v)
97
+ rescue => e
98
+ e.message
121
99
  end
122
- true # select this version
123
- end
124
100
  end
125
- return @found_products
126
101
  end
127
102
 
128
- # all ascp files (in SDK)
129
- FILES = %i[ascp ascp4 ssh_bypass_key_dsa ssh_bypass_key_rsa aspera_license aspera_conf fallback_cert fallback_key].freeze
103
+ def check_or_create_sdk_file(filename, force: false, &block)
104
+ return Environment.write_file_restricted(File.join(sdk_folder, filename), force: force, mode: 0o644, &block)
105
+ end
130
106
 
131
107
  # get path of one resource file of currently activated product
132
108
  # keys and certs are generated locally... (they are well known values, arch. independent)
@@ -139,40 +115,32 @@ module Aspera
139
115
  file = file.gsub('ascp', 'ascp4') if k.eql?(:ascp4)
140
116
  when :transferd
141
117
  file = transferd_filepath
142
- when :ssh_bypass_key_dsa
143
- file = Environment.write_file_restricted(File.join(sdk_folder, 'aspera_bypass_dsa.pem')) {get_key('dsa', 1)}
144
- when :ssh_bypass_key_rsa
145
- file = Environment.write_file_restricted(File.join(sdk_folder, 'aspera_bypass_rsa.pem')) {get_key('rsa', 2)}
118
+ when :ssh_private_dsa, :ssh_private_rsa
119
+ # assume last 3 letters are type
120
+ type = k.to_s[-3..-1]
121
+ file = check_or_create_sdk_file("aspera_bypass_#{type}.pem") do
122
+ # generate PEM from DER
123
+ OpenSSL::PKey.const_get(type.upcase).new(DataRepository.instance.data(type.eql?('dsa') ? 1 : 2)).to_pem
124
+ end
146
125
  when :aspera_license
147
- file = Environment.write_file_restricted(File.join(sdk_folder, 'aspera-license')) do
148
- clear = [
149
- Zlib::Inflate.inflate(DataRepository.instance.data(6)),
150
- "==SIGNATURE==\n",
151
- Base64.strict_encode64(DataRepository.instance.data(7))
152
- ]
153
- Base64.strict_encode64(clear.join)
126
+ file = check_or_create_sdk_file('aspera-license') do
127
+ Zlib::Inflate.inflate(DataRepository.instance.data(6))
154
128
  end
155
129
  when :aspera_conf
156
- file = Environment.write_file_restricted(File.join(sdk_folder, 'aspera.conf')) {DEFAULT_ASPERA_CONF}
157
- when :fallback_cert, :fallback_key
158
- file_key = File.join(sdk_folder, 'aspera_fallback_key.pem')
130
+ file = check_or_create_sdk_file('aspera.conf') {DEFAULT_ASPERA_CONF}
131
+ when :fallback_certificate, :fallback_private_key
132
+ file_key = File.join(sdk_folder, 'aspera_fallback_cert_private_key.pem')
159
133
  file_cert = File.join(sdk_folder, 'aspera_fallback_cert.pem')
160
134
  if !File.exist?(file_key) || !File.exist?(file_cert)
161
135
  require 'openssl'
162
136
  # create new self signed certificate for http fallback
163
- private_key = OpenSSL::PKey::RSA.new(1024)
164
137
  cert = OpenSSL::X509::Certificate.new
165
- cert.subject = cert.issuer = OpenSSL::X509::Name.parse(DUMMY_CERT_INFO)
166
- cert.not_before = Time.now
167
- cert.not_after = Time.now + ONE_YEAR_SECONDS
168
- cert.public_key = private_key.public_key
169
- cert.serial = 0x0
170
- cert.version = 2
171
- cert.sign(private_key, OpenSSL::Digest.new('SHA1'))
172
- Environment.write_file_restricted(file_key, force: true) {private_key.to_pem}
173
- Environment.write_file_restricted(file_cert, force: true) {cert.to_pem}
138
+ private_key = OpenSSL::PKey::RSA.new(4096)
139
+ WebServerSimple.fill_self_signed_cert(cert, private_key)
140
+ check_or_create_sdk_file('aspera_fallback_cert_private_key.pem', force: true) {private_key.to_pem}
141
+ check_or_create_sdk_file('aspera_fallback_cert.pem', force: true) {cert.to_pem}
174
142
  end
175
- file = k.eql?(:fallback_cert) ? file_cert : file_key
143
+ file = k.eql?(:fallback_certificate) ? file_cert : file_key
176
144
  else
177
145
  raise "INTERNAL ERROR: #{k}"
178
146
  end
@@ -180,33 +148,13 @@ module Aspera
180
148
  return file
181
149
  end
182
150
 
183
- # @return the file path of local connect where API's URI can be read
184
- def connect_uri
185
- connect = get_product_folders(PRODUCT_CONNECT)
186
- folder = File.join(connect[:run_root], VAR_RUN_SUBFOLDER)
187
- ['', 's'].each do |ext|
188
- uri_file = File.join(folder, "http#{ext}.uri")
189
- Log.log.debug{"checking connect port file: #{uri_file}"}
190
- if File.exist?(uri_file)
191
- return File.open(uri_file, &:gets).strip
192
- end
193
- end
194
- raise "no connect uri file found in #{folder}"
195
- end
196
-
197
- # @ return path to configuration file of aspera CLI
198
- def cli_conf_file
199
- connect = get_product_folders(PRODUCT_CLI_V1)
200
- return File.join(connect[:app_root], BIN_SUBFOLDER, '.aspera_cli_conf')
201
- end
202
-
203
151
  # default bypass key phrase
204
- def bypass_pass
152
+ def ssh_cert_uuid
205
153
  return format('%08x-%04x-%04x-%04x-%04x%08x', *DataRepository.instance.data(3).unpack('NnnnnN'))
206
154
  end
207
155
 
208
- def bypass_keys
209
- return %i[ssh_bypass_key_dsa ssh_bypass_key_rsa].map{|i|Installation.instance.path(i)}
156
+ def aspera_token_ssh_key_paths
157
+ return %i[ssh_private_dsa ssh_private_rsa].map{|i|Installation.instance.path(i)}
210
158
  end
211
159
 
212
160
  # use in plugin `config`
@@ -268,123 +216,33 @@ module Aspera
268
216
  # ensure license file are generated so that ascp invocation for version works
269
217
  path(:aspera_license)
270
218
  path(:aspera_conf)
271
- ascp_path = File.join(sdk_folder, ascp_filename)
272
- raise "No #{ascp_filename} found in SDK archive" unless File.exist?(ascp_path)
219
+ ascp_file = Products.ascp_filename
220
+ ascp_path = File.join(sdk_folder, ascp_file)
221
+ raise "No #{ascp_file} found in SDK archive" unless File.exist?(ascp_path)
273
222
  Environment.restrict_file_access(ascp_path, mode: 0o755)
274
223
  Environment.restrict_file_access(ascp_path.gsub('ascp', 'ascp4'), mode: 0o755)
275
- ascp_version = get_ascp_version(File.join(sdk_folder, ascp_filename))
224
+ ascp_version = get_ascp_version(ascp_path)
276
225
  trd_path = transferd_filepath
277
226
  Log.log.warn{"No #{trd_path} in SDK archive"} unless File.exist?(trd_path)
278
227
  Environment.restrict_file_access(trd_path, mode: 0o755) if File.exist?(trd_path)
279
228
  transferd_version = get_exe_version(trd_path, 'version')
280
229
  sdk_version = transferd_version || ascp_version
281
- File.write(File.join(sdk_folder, PRODUCT_INFO), "<product><name>IBM Aspera SDK</name><version>#{sdk_version}</version></product>")
230
+ File.write(File.join(sdk_folder, Products::INFO_META_FILE), "<product><name>IBM Aspera SDK</name><version>#{sdk_version}</version></product>")
282
231
  return sdk_version
283
232
  end
284
233
 
285
234
  private
286
235
 
287
- BIN_SUBFOLDER = 'bin'
288
- ETC_SUBFOLDER = 'etc'
289
- VAR_RUN_SUBFOLDER = File.join('var', 'run')
290
- # product information manifest: XML (part of aspera product)
291
- PRODUCT_INFO = 'product-info.mf'
292
236
  # policy for product selection
293
237
  FIRST_FOUND = 'FIRST'
294
238
 
295
- private_constant :BIN_SUBFOLDER, :ETC_SUBFOLDER, :VAR_RUN_SUBFOLDER, :PRODUCT_INFO
296
-
297
239
  def initialize
298
240
  @path_to_ascp = nil
299
241
  @sdk_dir = nil
300
- @found_products = nil
301
- end
302
-
303
- # @return folder paths for specified applications
304
- # @param name Connect or CLI
305
- def get_product_folders(name)
306
- found = installed_products.select{|i|i[:expected].eql?(name) || i[:name].eql?(name)}
307
- raise "Product: #{name} not found, please install." if found.empty?
308
- return found.first
309
- end
310
-
311
- # filename for ascp with optional extension (Windows)
312
- def ascp_filename
313
- return 'ascp' + Environment.exe_extension
314
242
  end
315
243
 
316
244
  def transferd_filepath
317
- return File.join(sdk_folder, 'asperatransferd' + Environment.exe_extension)
318
- end
319
-
320
- # @return product folders depending on OS fields
321
- # :expected M app name is taken from the manifest if present, else defaults to this value
322
- # :app_root M main folder for the application
323
- # :log_root O location of log files (Linux uses syslog)
324
- # :run_root O only for Connect Client, location of http port file
325
- # :sub_bin O subfolder with executables, default : bin
326
- def product_locations
327
- case Aspera::Environment.os
328
- when Aspera::Environment::OS_WINDOWS; return [{
329
- expected: PRODUCT_CONNECT,
330
- app_root: File.join(ENV['LOCALAPPDATA'], 'Programs', 'Aspera', 'Aspera Connect'),
331
- log_root: File.join(ENV['LOCALAPPDATA'], 'Aspera', 'Aspera Connect', 'var', 'log'),
332
- run_root: File.join(ENV['LOCALAPPDATA'], 'Aspera', 'Aspera Connect')
333
- }, {
334
- expected: PRODUCT_CLI_V1,
335
- app_root: File.join('C:', 'Program Files', 'Aspera', 'cli'),
336
- log_root: File.join('C:', 'Program Files', 'Aspera', 'cli', 'var', 'log')
337
- }, {
338
- expected: PRODUCT_ENTSRV,
339
- app_root: File.join('C:', 'Program Files', 'Aspera', 'Enterprise Server'),
340
- log_root: File.join('C:', 'Program Files', 'Aspera', 'Enterprise Server', 'var', 'log')
341
- }]
342
- when Aspera::Environment::OS_X; return [{
343
- expected: PRODUCT_CONNECT,
344
- app_root: File.join(Dir.home, 'Applications', 'Aspera Connect.app'),
345
- log_root: File.join(Dir.home, 'Library', 'Logs', 'Aspera_Connect'),
346
- run_root: File.join(Dir.home, 'Library', 'Application Support', 'Aspera', 'Aspera Connect'),
347
- sub_bin: File.join('Contents', 'Resources')
348
- }, {
349
- expected: PRODUCT_CONNECT,
350
- app_root: File.join('', 'Applications', 'Aspera Connect.app'),
351
- log_root: File.join(Dir.home, 'Library', 'Logs', 'Aspera_Connect'),
352
- run_root: File.join(Dir.home, 'Library', 'Application Support', 'Aspera', 'Aspera Connect'),
353
- sub_bin: File.join('Contents', 'Resources')
354
- }, {
355
- expected: PRODUCT_CLI_V1,
356
- app_root: File.join(Dir.home, 'Applications', 'Aspera CLI'),
357
- log_root: File.join(Dir.home, 'Library', 'Logs', 'Aspera')
358
- }, {
359
- expected: PRODUCT_ENTSRV,
360
- app_root: File.join('', 'Library', 'Aspera'),
361
- log_root: File.join(Dir.home, 'Library', 'Logs', 'Aspera')
362
- }, {
363
- expected: PRODUCT_DRIVE,
364
- app_root: File.join('', 'Applications', 'Aspera Drive.app'),
365
- log_root: File.join(Dir.home, 'Library', 'Logs', 'Aspera_Drive'),
366
- sub_bin: File.join('Contents', 'Resources')
367
- }]
368
- else; return [{ # other: Linux and Unix family
369
- expected: PRODUCT_CONNECT,
370
- app_root: File.join(Dir.home, '.aspera', 'connect'),
371
- run_root: File.join(Dir.home, '.aspera', 'connect')
372
- }, {
373
- expected: PRODUCT_CLI_V1,
374
- app_root: File.join(Dir.home, '.aspera', 'cli')
375
- }, {
376
- expected: PRODUCT_ENTSRV,
377
- app_root: File.join('', 'opt', 'aspera')
378
- }]
379
- end
380
- end
381
-
382
- # @return a standard bypass key
383
- # @param type rsa or dsa
384
- # @param id in repository 1 for dsa, 2 for rsa
385
- def get_key(type, id)
386
- # generate PEM from DER
387
- OpenSSL::PKey.const_get(type.upcase).new(DataRepository.instance.data(id)).to_pem
245
+ return File.join(sdk_folder, 'asperatransferd' + Environment.exe_extension) # cspell:disable-line
388
246
  end
389
247
  end # Installation
390
248
  end