aspera-cli 4.25.0.pre → 4.25.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 (63) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +23 -17
  4. data/CONTRIBUTING.md +119 -47
  5. data/README.md +325 -239
  6. data/lib/aspera/agent/direct.rb +14 -12
  7. data/lib/aspera/agent/factory.rb +9 -6
  8. data/lib/aspera/agent/transferd.rb +8 -8
  9. data/lib/aspera/api/aoc.rb +33 -24
  10. data/lib/aspera/api/ats.rb +1 -0
  11. data/lib/aspera/api/faspex.rb +11 -5
  12. data/lib/aspera/ascmd.rb +1 -1
  13. data/lib/aspera/ascp/installation.rb +7 -7
  14. data/lib/aspera/ascp/management.rb +9 -5
  15. data/lib/aspera/assert.rb +3 -3
  16. data/lib/aspera/cli/extended_value.rb +10 -2
  17. data/lib/aspera/cli/formatter.rb +15 -62
  18. data/lib/aspera/cli/manager.rb +9 -43
  19. data/lib/aspera/cli/plugins/aoc.rb +71 -66
  20. data/lib/aspera/cli/plugins/ats.rb +30 -36
  21. data/lib/aspera/cli/plugins/base.rb +11 -6
  22. data/lib/aspera/cli/plugins/config.rb +21 -16
  23. data/lib/aspera/cli/plugins/console.rb +2 -1
  24. data/lib/aspera/cli/plugins/faspex.rb +7 -4
  25. data/lib/aspera/cli/plugins/faspex5.rb +12 -9
  26. data/lib/aspera/cli/plugins/faspio.rb +5 -2
  27. data/lib/aspera/cli/plugins/httpgw.rb +2 -1
  28. data/lib/aspera/cli/plugins/node.rb +10 -6
  29. data/lib/aspera/cli/plugins/oauth.rb +12 -11
  30. data/lib/aspera/cli/plugins/orchestrator.rb +2 -1
  31. data/lib/aspera/cli/plugins/preview.rb +2 -2
  32. data/lib/aspera/cli/plugins/server.rb +3 -2
  33. data/lib/aspera/cli/plugins/shares.rb +59 -20
  34. data/lib/aspera/cli/transfer_agent.rb +1 -2
  35. data/lib/aspera/cli/version.rb +1 -1
  36. data/lib/aspera/command_line_builder.rb +5 -5
  37. data/lib/aspera/coverage.rb +5 -1
  38. data/lib/aspera/dot_container.rb +108 -0
  39. data/lib/aspera/environment.rb +69 -89
  40. data/lib/aspera/faspex_postproc.rb +1 -1
  41. data/lib/aspera/id_generator.rb +7 -10
  42. data/lib/aspera/keychain/macos_security.rb +2 -2
  43. data/lib/aspera/log.rb +2 -1
  44. data/lib/aspera/oauth/base.rb +25 -38
  45. data/lib/aspera/oauth/factory.rb +5 -6
  46. data/lib/aspera/oauth/generic.rb +1 -1
  47. data/lib/aspera/oauth/jwt.rb +1 -1
  48. data/lib/aspera/oauth/url_json.rb +4 -3
  49. data/lib/aspera/oauth/web.rb +2 -2
  50. data/lib/aspera/preview/file_types.rb +1 -1
  51. data/lib/aspera/preview/terminal.rb +95 -29
  52. data/lib/aspera/preview/utils.rb +6 -5
  53. data/lib/aspera/rest.rb +5 -2
  54. data/lib/aspera/ssh.rb +6 -5
  55. data/lib/aspera/sync/conf.schema.yaml +2 -2
  56. data/lib/aspera/sync/operations.rb +3 -3
  57. data/lib/aspera/transfer/parameters.rb +6 -6
  58. data/lib/aspera/transfer/spec.schema.yaml +4 -4
  59. data/lib/aspera/transfer/spec_doc.rb +11 -21
  60. data/lib/aspera/uri_reader.rb +17 -3
  61. data.tar.gz.sig +0 -0
  62. metadata +17 -2
  63. metadata.gz.sig +0 -0
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aspera/assert'
4
+
5
+ module Aspera
6
+ # Convert dotted-path to/from nested Hash/Array container
7
+ class DotContainer
8
+ class << self
9
+ # Insert extended value `value` into struct `result` at `path`
10
+ # @param path [String] Dotted path in container
11
+ # @param value [String] Last value to insert
12
+ # @param result [NilClass, Hash, Array] current value
13
+ # @return [Hash, Array]
14
+ def dotted_to_container(path, value, result = nil)
15
+ # Typed keys
16
+ keys = path.split(OPTION_DOTTED_SEPARATOR).map{ |k| int_or_string(k)}
17
+ # Create, or re-use first level container
18
+ current = (result ||= new_hash_or_array_from_key(keys.first))
19
+ # walk the path, and create sub-containers if necessary
20
+ keys.each_cons(2) do |k, next_k|
21
+ array_requires_integer_index!(current, k)
22
+ current = (current[k] ||= new_hash_or_array_from_key(next_k))
23
+ end
24
+ # Assign value at last index
25
+ array_requires_integer_index!(current, keys.last)
26
+ current[keys.last] = value
27
+ result
28
+ end
29
+
30
+ private
31
+
32
+ # Convert `String` to `Integer`, or keep `String` if not `Integer`
33
+ def int_or_string(value)
34
+ Integer(value, exception: false) || value
35
+ end
36
+
37
+ # Assert that if `container` is an `Array`, then `index` is an `Integer`
38
+ # @param container [Hash, Array]
39
+ # @param index [String, Integer]
40
+ def array_requires_integer_index!(container, index)
41
+ Aspera.assert(container.is_a?(Hash) || index.is_a?(Integer)){'Using String index when Integer index used previously'}
42
+ end
43
+
44
+ # Create a new `Hash` or `Array` depending on type of `key`
45
+ def new_hash_or_array_from_key(key)
46
+ key.is_a?(Integer) ? [] : {}
47
+ end
48
+ end
49
+
50
+ # @param [Hash,Array] Container object
51
+ def initialize(container)
52
+ Aspera.assert_type(container, Hash)
53
+ # tail (pop,push) contains the next element to display
54
+ # elements are [path, value]
55
+ @stack = container.empty? ? [] : [[[], container]]
56
+ end
57
+
58
+ # Convert nested Hash/Array container to dotted-path Hash
59
+ # @return [Hash] Dotted-path Hash
60
+ def to_dotted
61
+ result = {}
62
+ until @stack.empty?
63
+ path, current = @stack.pop
64
+ to_insert = nil
65
+ # empty things are left intact
66
+ if current.respond_to?(:empty?) && current.empty?
67
+ to_insert = current
68
+ else
69
+ case current
70
+ when Hash
71
+ add_elements(path, current)
72
+ when Array
73
+ # Array has no nested structures -> list of Strings
74
+ if current.none?{ |i| i.is_a?(Array) || i.is_a?(Hash)}
75
+ to_insert = current.map(&:to_s)
76
+ # Array of Hashes with only 'name' keys -> list of Strings
77
+ elsif current.all?{ |i| i.is_a?(Hash) && i.keys == ['name']}
78
+ to_insert = current.map{ |i| i['name']}
79
+ # Array of Hashes with only 'name' and 'value' keys -> Hash of key/values
80
+ elsif current.all?{ |i| i.is_a?(Hash) && i.keys.sort == %w[name value]}
81
+ add_elements(path, current.each_with_object({}){ |i, h| h[i['name']] = i['value']})
82
+ else
83
+ add_elements(path, current.each_with_index.map{ |v, i| [i, v]})
84
+ end
85
+ else
86
+ to_insert = current
87
+ end
88
+ end
89
+ result[path.map(&:to_s).join(OPTION_DOTTED_SEPARATOR)] = to_insert unless to_insert.nil?
90
+ end
91
+ result
92
+ end
93
+
94
+ private
95
+
96
+ # Add elements of enumerator to the @stack, in reverse order
97
+ def add_elements(path, enum)
98
+ enum.reverse_each do |key, value|
99
+ @stack.push([path + [key], value])
100
+ end
101
+ nil
102
+ end
103
+
104
+ # "."
105
+ OPTION_DOTTED_SEPARATOR = '.'
106
+ private_constant :OPTION_DOTTED_SEPARATOR
107
+ end
108
+ end
@@ -42,105 +42,85 @@ module Aspera
42
42
 
43
43
  RB_EXT = '.rb'
44
44
 
45
+ PROCESS_MODES = %i[execute background capture].freeze
46
+
45
47
  class << self
46
48
  def ruby_version
47
49
  return RbConfig::CONFIG['RUBY_PROGRAM_VERSION']
48
50
  end
49
51
 
50
- # empty variable binding for secure eval
52
+ # Empty variable binding for secure eval
51
53
  def empty_binding
52
54
  return Kernel.binding
53
55
  end
54
56
 
55
- # secure execution of Ruby code
57
+ # Secure execution of Ruby code
58
+ # @param code [String] Ruby code to execute
59
+ # @param file [String] File name for error reporting
60
+ # @param line [Integer] Line number for error reporting
56
61
  def secure_eval(code, file, line)
57
62
  Kernel.send('lave'.reverse, code, empty_binding, file, line)
58
63
  end
59
64
 
60
- # Generate log line for external program with arguments
61
- # @param exec [String] Path to executable
62
- # @param args [Array, nil] Arguments
63
- # @param env [Hash, nil] Environment variables
64
- # @return [String] log line with environment, program and arguments
65
- def log_spawn(exec:, args: nil, env: nil)
66
- [
67
- 'execute:'.red,
68
- env&.map{ |k, v| "#{k}=#{Shellwords.shellescape(v)}"},
69
- Shellwords.shellescape(exec),
70
- args&.map{ |a| Shellwords.shellescape(a)}
71
- ].compact.flatten.join(' ')
72
- end
73
-
74
- # Start process in background
75
- # caller can call Process.wait on returned value
76
- # @param exec [String] Path to executable
77
- # @param args [Array, nil] Arguments for executable
78
- # @param env [Hash, nil] Environment variables
79
- # @param kwargs [Hash] Options for `Process.spawn`
80
- # @return [String] PID of process
81
- # @raise [Exception] if problem
82
- def secure_spawn(exec:, args: nil, env: nil, **kwargs)
83
- Aspera.assert_type(exec, String)
84
- Aspera.assert_type(args, Array, NilClass)
85
- Aspera.assert_type(env, Hash, NilClass)
86
- Log.log.debug{log_spawn(exec: exec, args: args, env: env)}
87
- spawn_args = []
88
- spawn_args.push(env) unless env.nil?
89
- spawn_args.push([exec, exec])
90
- spawn_args.concat(args) unless args.nil?
91
- kwargs[:close_others] = true unless kwargs.key?(:close_others)
92
- # Start separate process in background
93
- pid = Process.spawn(*spawn_args, **kwargs)
94
- Log.dump(:pid, pid)
95
- return pid
65
+ # Build argv for Process.spawn / Kernel.system (no shell)
66
+ # @param cmd [Array] Command and arguments
67
+ # @param kwargs [Hash] Additional arguments to `secure_execute`
68
+ def build_spawn_argv(cmd, kwargs)
69
+ env = kwargs.delete(:env)
70
+ argv = []
71
+ argv << env if env
72
+ argv << [cmd.first, cmd.first] # no shell, preserve argv[0]
73
+ argv.concat(cmd.drop(1))
74
+ argv
96
75
  end
97
76
 
98
- # Start process (not in shell) and wait for completion.
99
- # By default, sets `exception: true` in `kwargs`
100
- # @param exec [String] Path to executable
101
- # @param args [Array, nil] Arguments
102
- # @param env [Hash, nil] Environment variables
103
- # @param kwargs [Hash] Arguments for `Kernel.system`
104
- # @return `nil`
105
- # @raise [RuntimeError] if problem
106
- def secure_execute(exec:, args: nil, env: nil, **kwargs)
107
- Aspera.assert_type(exec, String)
108
- Aspera.assert_type(args, Array, NilClass)
109
- Aspera.assert_type(env, Hash, NilClass)
110
- Log.log.debug{log_spawn(exec: exec, args: args, env: env)}
111
- Log.dump(:kwargs, kwargs, level: :trace1)
112
- spawn_args = []
113
- spawn_args.push(env) unless env.nil?
114
- # Ensure no shell expansion
115
- spawn_args.push([exec, exec])
116
- spawn_args.concat(args) unless args.nil?
117
- # By default: exception on error
118
- kwargs[:exception] = true unless kwargs.key?(:exception)
119
- # Start in separate process
120
- Kernel.system(*spawn_args, **kwargs)
121
- nil
122
- end
123
-
124
- # Execute process and capture stdout
125
- # @param exec [String] path to executable
126
- # @param args [Array] arguments to executable
127
- # @param kwargs [Hash] options to Open3.capture3
128
- # @return stdout of executable or raise exception
129
- def secure_capture(exec:, args: [], env: nil, exception: true, **kwargs)
130
- Aspera.assert_type(exec, String)
131
- Aspera.assert_type(args, Array)
132
- Log.log.debug{log_spawn(exec: exec, args: args, env: env)}
133
- Log.dump(:kwargs, kwargs, level: :trace2)
134
- # Log.dump(:ENV, ENV.to_h, level: :trace1)
135
- capture_args = []
136
- capture_args.push(env) unless env.nil?
137
- capture_args.push(exec)
138
- capture_args.concat(args)
139
- stdout, stderr, status = Open3.capture3(*capture_args, **kwargs)
140
- Log.log.debug{"status=#{status}, stderr=#{stderr}"}
141
- Log.log.trace1{"stdout=#{stdout}"}
142
- raise "process failed: #{status.exitstatus} (#{stderr})" if !status.success? && exception
143
- return stdout
77
+ # Execute a process securely (no shell)
78
+ # mode:
79
+ # :execute -> Kernel.system, return nil
80
+ # :background -> Process.spawn, return pid
81
+ # :capture -> Open3.capture3, return stdout
82
+ # @param cmd [Array] Command and arguments
83
+ # @param env [Hash, nil] Environment variables
84
+ # @param mode [Symbol] Execution mode (see above)
85
+ # @param kwargs [Hash] Additional arguments to underlying method, includes:
86
+ # :exception [Boolean] for :capture mode, raise error if process fails
87
+ # :close_others [Boolean] for :background mode
88
+ # :env [Hash] for :execute mode
89
+ def secure_execute(*cmd, mode: :execute, **kwargs)
90
+ cmd = cmd.map(&:to_s)
91
+ Aspera.assert(cmd.size.positive?, type: ArgumentError){'executable must be present'}
92
+ Aspera.assert_values(mode, PROCESS_MODES, type: ArgumentError){'mode'}
93
+ Log.log.debug do
94
+ parts = [mode.to_s, 'command:']
95
+ kwargs[:env]&.each{ |k, v| parts << "#{k}=#{Shellwords.shellescape(v.to_s)}"}
96
+ cmd.each{ |a| parts << Shellwords.shellescape(a)}
97
+ parts.join(' ')
98
+ end
99
+ case mode
100
+ when :execute
101
+ # https://docs.ruby-lang.org/en/master/Kernel.html#method-i-system
102
+ # https://docs.ruby-lang.org/en/master/Process.html#module-Process-label-Execution+Options
103
+ kwargs[:exception] = true unless kwargs.key?(:exception)
104
+ Kernel.system(*build_spawn_argv(cmd, kwargs), **kwargs)
105
+ when :background
106
+ # https://docs.ruby-lang.org/en/master/Process.html#method-c-spawn
107
+ # https://docs.ruby-lang.org/en/master/Process.html#module-Process-label-Execution+Options
108
+ kwargs[:close_others] = true unless kwargs.key?(:close_others)
109
+ pid = Process.spawn(*build_spawn_argv(cmd, kwargs), **kwargs)
110
+ Log.dump(:pid, pid)
111
+ pid
112
+ when :capture
113
+ # https://docs.ruby-lang.org/en/master/Open3.html#method-c-capture3
114
+ # https://docs.ruby-lang.org/en/master/Process.html#module-Process-label-Execution+Options
115
+ argv = [kwargs.delete(:env)].compact + cmd
116
+ exception = kwargs.delete(:exception){true}
117
+ stdout, stderr, status = Open3.capture3(*argv, **kwargs)
118
+ Log.log.debug{"status=#{status}, stderr=#{stderr}"}
119
+ Log.log.trace1{"stdout=#{stdout}"}
120
+ raise "Process failed: #{status.exitstatus} (#{stderr})" if exception && !status.success?
121
+ stdout
122
+ else Aspera.error_unreachable_line
123
+ end
144
124
  end
145
125
 
146
126
  # Write content to a file, with restricted access
@@ -273,9 +253,9 @@ module Aspera
273
253
  # @param uri [String] the URI to open
274
254
  def open_uri_graphical(uri)
275
255
  case @os
276
- when Environment::OS_MACOS then return self.class.secure_execute(exec: 'open', args: [uri.to_s])
277
- when Environment::OS_WINDOWS then return self.class.secure_execute(exec: 'start', args: ['explorer', %Q{"#{uri}"}])
278
- when Environment::OS_LINUX then return self.class.secure_execute(exec: 'xdg-open', args: [uri.to_s])
256
+ when Environment::OS_MACOS then return self.class.secure_execute('open', uri.to_s)
257
+ when Environment::OS_WINDOWS then return self.class.secure_execute('start', 'explorer', %Q{"#{uri}"})
258
+ when Environment::OS_LINUX then return self.class.secure_execute('xdg-open', uri.to_s)
279
259
  else Assert.error_unexpected_value(os){'no graphical open method'}
280
260
  end
281
261
  end
@@ -283,9 +263,9 @@ module Aspera
283
263
  # open a file in an editor
284
264
  def open_editor(file_path)
285
265
  if ENV.key?('EDITOR')
286
- self.class.secure_execute(exec: ENV['EDITOR'], args: [file_path.to_s])
266
+ self.class.secure_execute(ENV['EDITOR'], file_path.to_s)
287
267
  elsif @os.eql?(Environment::OS_WINDOWS)
288
- self.class.secure_execute(exec: 'notepad.exe', args: [%Q{"#{file_path}"}])
268
+ self.class.secure_execute('notepad.exe', %Q{"#{file_path}"})
289
269
  else
290
270
  open_uri_graphical(file_path.to_s)
291
271
  end
@@ -50,7 +50,7 @@ module Aspera
50
50
  Log.dump(:webhook_parameters, webhook_parameters)
51
51
  # env expects only strings
52
52
  environment = webhook_parameters.each_with_object({}){ |(k, v), h| h[k] = v.to_s}
53
- post_proc_pid = Environment.secure_spawn(env: environment, exec: script_path)
53
+ post_proc_pid = Environment.secure_execute(script_path, mode: :background, env: environment)
54
54
  Timeout.timeout(@parameters[:timeout_seconds]) do
55
55
  # "wait" for process to avoid zombie
56
56
  Process.wait(post_proc_pid)
@@ -9,19 +9,16 @@ module Aspera
9
9
  class << self
10
10
  # Generate an ID from a list of object IDs
11
11
  # The generated ID is safe as file name
12
- # @param object_id [Array<String>, String] the object IDs
12
+ # @param ids [Array] the object IDs (can be nested, will be flattened, and nils removed)
13
13
  # @return [String] the generated ID
14
- def from_list(object_id)
14
+ def from_list(*ids)
15
15
  safe_char = Environment.instance.safe_filename_character
16
- if object_id.is_a?(Array)
17
- # compact: remove nils
18
- object_id = object_id.flatten.compact.map do |i|
19
- i.is_a?(String) && i.start_with?('https://') ? URI.parse(i).host : i.to_s
20
- end.join(safe_char)
21
- end
22
- Aspera.assert_type(object_id, String)
16
+ # compact: remove nils
17
+ id = ids.flatten.compact.map do |i|
18
+ i.is_a?(String) && i.start_with?('https://') ? URI.parse(i).host : i.to_s
19
+ end.join(safe_char)
23
20
  # keep dot for extension only (nicer)
24
- return Environment.instance.sanitized_filename(object_id.gsub('.', safe_char)).downcase
21
+ return Environment.instance.sanitized_filename(id.gsub('.', safe_char)).downcase
25
22
  end
26
23
  end
27
24
  end
@@ -49,7 +49,7 @@ module Aspera
49
49
  options[:path] = uri.path unless ['', '/'].include?(uri.path)
50
50
  options[:port] = uri.port unless uri.port.eql?(443) && !url.include?(':443/')
51
51
  end
52
- command_args = [command]
52
+ command_args = [SECURITY_UTILITY, command]
53
53
  options&.each do |k, v|
54
54
  Aspera.assert(supported.key?(k)){"unknown option: #{k}"}
55
55
  next if v.nil?
@@ -57,7 +57,7 @@ module Aspera
57
57
  command_args.push(v.shellescape) unless v.empty?
58
58
  end
59
59
  command_args.push(last_opt) unless last_opt.nil?
60
- return Environment.secure_capture(exec: SECURITY_UTILITY, args: command_args)
60
+ return Environment.secure_execute(*command_args, mode: :capture)
61
61
  end
62
62
 
63
63
  def key_chains(output)
data/lib/aspera/log.rb CHANGED
@@ -81,8 +81,9 @@ module Aspera
81
81
  # @param object [Hash, nil] Data to dump
82
82
  # @param level [Symbol] Debug level
83
83
  # @param block [Proc, nil] Give computed object
84
- def dump(name, object = nil, level: :debug)
84
+ def dump(name, object = nil, level: :debug, &block)
85
85
  return unless instance.logger.send(:"#{level}?")
86
+ Aspera.assert(object.nil? || block.nil?){'Use either object, or block, not both'}
86
87
  object = yield if block_given?
87
88
  instance.logger.send(level, obj_dump(name, object))
88
89
  end
@@ -14,50 +14,38 @@ module Aspera
14
14
  # Bearer Token Usage: https://tools.ietf.org/html/rfc6750
15
15
  class Base
16
16
  Aspera.require_method!(:create_token)
17
- # @param ** Parameters for REST
18
- # @param client_id [String, nil]
19
- # @param client_secret [String, nil]
20
- # @param scope [String, nil]
21
- # @param use_query [bool] Provide parameters in query instead of body
22
- # @param path_token [String] API end point to create a token from base URL
23
- # @param token_field [String] Field in result that contains the token
24
- # @param cache_ids [Array, nil] List of unique identifiers for cache id generation
17
+ # @param params [Hash] Parameters for token creation (client_id, client_secret, scope, etc...)
18
+ # @param use_query [Boolean] Provide parameters in query instead of body
19
+ # @param path_token [String] API end point to create a token from base URL
20
+ # @param token_field [String] Field in result that contains the token
21
+ # @param cache_ids [Array] List of unique identifiers for cache id generation
22
+ # @param **rest_params [Hash] Parameters for REST
25
23
  def initialize(
26
- client_id: nil,
27
- client_secret: nil,
28
- scope: nil,
24
+ params: {},
29
25
  use_query: false,
30
26
  path_token: 'token',
31
27
  token_field: Factory::TOKEN_FIELD,
32
- cache_ids: nil,
28
+ cache_ids: [],
33
29
  **rest_params
34
30
  )
31
+ Aspera.assert_type(params, Hash)
32
+ Aspera.assert_type(cache_ids, Array)
35
33
  # This is the OAuth API
36
34
  @api = Rest.new(**rest_params)
37
- @scope = nil
38
- @token_cache_id = nil
35
+ @params = params.dup.freeze
39
36
  @path_token = path_token
40
37
  @token_field = token_field
41
- @client_id = client_id
42
- @client_secret = client_secret
43
38
  @use_query = use_query
44
- @base_cache_ids = cache_ids.nil? ? [] : cache_ids.clone
45
- Aspera.assert_type(@base_cache_ids, Array)
46
- # TODO: this shall be done in class, using cache_ids
47
- @base_cache_ids.push(@api.auth_params[:username]) if @api.auth_params.key?(:username)
48
- @base_cache_ids.compact!
49
- @base_cache_ids.freeze
50
- self.scope = scope
39
+ # TODO: :username and :scope shall be done in class, using cache_ids
40
+ @token_cache_id = Factory.cache_id(@api.base_url, self.class, cache_ids, rest_params[:username], @params[:scope])
51
41
  end
52
42
 
53
- # Scope can be modified after creation, then update identifier for cache
54
- def scope=(scope)
55
- @scope = scope
56
- # generate token unique identifier for persistency (memory/disk cache)
57
- @token_cache_id = Factory.cache_id(@api.base_url, self.class, @base_cache_ids, @scope)
58
- end
59
-
60
- attr_reader :scope, :api, :path_token, :client_id
43
+ # The OAuth API Object
44
+ attr_reader :api
45
+ # Sub path to generate token
46
+ attr_reader :path_token
47
+ # Parameters to generate token
48
+ attr_reader :params
61
49
 
62
50
  # Helper method to create token as per RFC
63
51
  # @return [HTTPResponse]
@@ -68,17 +56,16 @@ module Aspera
68
56
  return @api.create(@path_token, creation_params, content_type: Rest::MIME_WWW, ret: :resp)
69
57
  end
70
58
 
59
+ # Create base parameters for token creation calls
71
60
  # @param add_secret [Boolean] Add secret in default call parameters
72
61
  # @return [Hash] Optional general parameters
73
- def optional_scope_client_id(add_secret: false)
74
- call_params = {}
75
- call_params[:scope] = @scope unless @scope.nil?
76
- call_params[:client_id] = @client_id unless @client_id.nil?
77
- call_params[:client_secret] = @client_secret unless !add_secret || @client_id.nil? || @client_secret.nil?
62
+ def base_params(add_secret: false)
63
+ call_params = @params.dup
64
+ call_params.delete(:client_secret) unless add_secret
78
65
  return call_params
79
66
  end
80
67
 
81
- # @return value suitable for Authorization header
68
+ # @return [String] value suitable for Authorization header
82
69
  def authorization(**kwargs)
83
70
  return OAuth::Factory.bearer_authorization(token(**kwargs))
84
71
  end
@@ -114,7 +101,7 @@ module Aspera
114
101
  if !refresh_token.nil?
115
102
  Log.log.info{"refresh token=[#{refresh_token}]".bg_green}
116
103
  begin
117
- http = create_token_call(optional_scope_client_id(add_secret: true).merge(grant_type: 'refresh_token', refresh_token: refresh_token))
104
+ http = create_token_call(base_params(add_secret: true).merge(grant_type: 'refresh_token', refresh_token: refresh_token))
118
105
  # Save only if success
119
106
  json_data = http.body
120
107
  token_data = JSON.parse(json_data)
@@ -36,14 +36,13 @@ module Aspera
36
36
  return authorization[SPACE_BEARER_AUTH_SCHEME.length..-1]
37
37
  end
38
38
 
39
+ # Generate a unique cache id for a token creator
40
+ # @param url [String] Base URL of the OAuth server
41
+ # @param creator_class [Class] Class of the token creator
42
+ # @param params [Array] List of parameters (can be nested) to uniquely identify the token
39
43
  # @return a unique cache identifier
40
44
  def cache_id(url, creator_class, *params)
41
- return IdGenerator.from_list([
42
- PERSIST_CATEGORY_TOKEN,
43
- url,
44
- Factory.class_to_id(creator_class)
45
- ] +
46
- params)
45
+ return IdGenerator.from_list(PERSIST_CATEGORY_TOKEN, url, Factory.class_to_id(creator_class), params)
47
46
  end
48
47
 
49
48
  # @return snake version of class name
@@ -23,7 +23,7 @@ module Aspera
23
23
  end
24
24
 
25
25
  def create_token
26
- return create_token_call(optional_scope_client_id.merge(@create_params))
26
+ return create_token_call(base_params.merge(@create_params))
27
27
  end
28
28
  end
29
29
  Factory.instance.register_token_creator(Generic)
@@ -61,7 +61,7 @@ module Aspera
61
61
  Log.log.debug{"private=[#{@private_key_obj}]"}
62
62
  assertion = JWT.encode(jwt_payload, @private_key_obj, 'RS256', @headers)
63
63
  Log.log.debug{"assertion=[#{assertion}]"}
64
- return create_token_call(optional_scope_client_id.merge(grant_type: GRANT_TYPE, assertion: assertion))
64
+ return create_token_call(base_params.merge(grant_type: GRANT_TYPE, assertion: assertion))
65
65
  end
66
66
  end
67
67
  Factory.instance.register_token_creator(Jwt)
@@ -6,8 +6,9 @@ module Aspera
6
6
  module OAuth
7
7
  # This class is used to create a token using a JSON body and a URL
8
8
  class UrlJson < Base
9
- # @param url URL to send the JSON body
10
- # @param json JSON body to send
9
+ # @param url [Hash] Query parameters to send
10
+ # @param json [Hash] Body parameters to send as JSON
11
+ # @param generic_params [Hash] Generic parameters for OAuth::Base
11
12
  def initialize(
12
13
  url:,
13
14
  json:,
@@ -22,7 +23,7 @@ module Aspera
22
23
  api.call(
23
24
  operation: 'POST',
24
25
  subpath: path_token,
25
- query: @query.merge(scope: scope), # scope is here because it may change over time (node)
26
+ query: @query.merge(scope: params[:scope]), # scope is here because it may change over time (node)
26
27
  content_type: Rest::MIME_JSON,
27
28
  body: @body,
28
29
  headers: {'Accept' => Rest::MIME_JSON},
@@ -32,7 +32,7 @@ module Aspera
32
32
  random_state = SecureRandom.uuid
33
33
  login_page_url = Rest.build_uri(
34
34
  "#{api.base_url}/#{@path_authorize}",
35
- optional_scope_client_id.merge(response_type: 'code', redirect_uri: @redirect_uri, state: random_state)
35
+ base_params.merge(response_type: 'code', redirect_uri: @redirect_uri, state: random_state)
36
36
  )
37
37
  # here, we need a human to authorize on a web page
38
38
  Log.log.info{"login_page_url=#{login_page_url}".bg_red.gray}
@@ -44,7 +44,7 @@ module Aspera
44
44
  received_params = web_server.received_request
45
45
  Aspera.assert(random_state.eql?(received_params['state'])){'wrong received state'}
46
46
  # exchange code for token
47
- return create_token_call(optional_scope_client_id(add_secret: true).merge(
47
+ return create_token_call(base_params(add_secret: true).merge(
48
48
  grant_type: 'authorization_code',
49
49
  code: received_params['code'],
50
50
  redirect_uri: @redirect_uri
@@ -53,7 +53,7 @@ module Aspera
53
53
 
54
54
  private_constant :SUPPORTED_MIME_TYPES
55
55
 
56
- # @attr use_mimemagic [bool] true to use mimemagic to determine real mime type based on file content
56
+ # @attr use_mimemagic [Boolean] `true` to use mimemagic to determine real mime type based on file content
57
57
  attr_accessor :use_mimemagic
58
58
 
59
59
  def initialize