aspera-cli 4.27.2 → 4.27.3
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +50 -0
- data/bin/ascli +2 -1
- data/docs/README.md +804 -746
- data/lib/aspera/agent/connect.rb +6 -4
- data/lib/aspera/agent/desktop.rb +2 -2
- data/lib/aspera/agent/direct.rb +3 -1
- data/lib/aspera/agent/node.rb +3 -3
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +14 -12
- data/lib/aspera/api/ats.rb +1 -1
- data/lib/aspera/api/cos_node.rb +2 -2
- data/lib/aspera/api/faspex.rb +9 -7
- data/lib/aspera/api/httpgw.rb +37 -33
- data/lib/aspera/api/node.rb +38 -33
- data/lib/aspera/ascmd.rb +3 -1
- data/lib/aspera/ascp/installation.rb +62 -27
- data/lib/aspera/ascp/management.rb +1 -0
- data/lib/aspera/assert.rb +4 -0
- data/lib/aspera/cli/ascp_actions.rb +20 -41
- data/lib/aspera/cli/async_transfer_store.rb +2 -2
- data/lib/aspera/cli/bootstrapper.rb +11 -15
- data/lib/aspera/cli/command_line.rb +252 -0
- data/lib/aspera/cli/command_registry.rb +149 -33
- data/lib/aspera/cli/command_spec.rb +103 -14
- data/lib/aspera/cli/completion/ascli.bash +12 -0
- data/lib/aspera/cli/completion/ascli.fish +16 -0
- data/lib/aspera/cli/completion/ascli.zsh +19 -0
- data/lib/aspera/cli/context.rb +3 -0
- data/lib/aspera/cli/deprecation.rb +37 -0
- data/lib/aspera/cli/extended_value.rb +2 -0
- data/lib/aspera/cli/formatter.rb +87 -75
- data/lib/aspera/cli/gem_checker.rb +1 -1
- data/lib/aspera/cli/hints.rb +7 -6
- data/lib/aspera/cli/http.rb +21 -21
- data/lib/aspera/cli/info.rb +3 -0
- data/lib/aspera/cli/mcp_tool.rb +47 -83
- data/lib/aspera/cli/option_declarator.rb +33 -42
- data/lib/aspera/cli/option_registry.rb +69 -0
- data/lib/aspera/cli/option_types.rb +103 -0
- data/lib/aspera/cli/option_value.rb +281 -0
- data/lib/aspera/cli/options.schema.yaml +38 -5
- data/lib/aspera/cli/parser.rb +307 -848
- data/lib/aspera/cli/plugins/alee.rb +7 -4
- data/lib/aspera/cli/plugins/aoc.rb +430 -380
- data/lib/aspera/cli/plugins/ats.rb +58 -73
- data/lib/aspera/cli/plugins/base.rb +190 -240
- data/lib/aspera/cli/plugins/basic_auth.rb +2 -10
- data/lib/aspera/cli/plugins/config.rb +244 -178
- data/lib/aspera/cli/plugins/console.rb +102 -38
- data/lib/aspera/cli/plugins/cos.rb +6 -23
- data/lib/aspera/cli/plugins/factory.rb +3 -0
- data/lib/aspera/cli/plugins/faspex5.rb +176 -173
- data/lib/aspera/cli/plugins/faspio.rb +5 -10
- data/lib/aspera/cli/plugins/httpgw.rb +8 -11
- data/lib/aspera/cli/plugins/mcp.rb +20 -55
- data/lib/aspera/cli/plugins/node.rb +277 -311
- data/lib/aspera/cli/plugins/orchestrator.rb +90 -77
- data/lib/aspera/cli/plugins/preview.rb +79 -90
- data/lib/aspera/cli/plugins/server.rb +76 -50
- data/lib/aspera/cli/plugins/shares.rb +68 -116
- data/lib/aspera/cli/preset_actions.rb +17 -10
- data/lib/aspera/cli/preset_manager.rb +12 -2
- data/lib/aspera/cli/prompt.rb +35 -0
- data/lib/aspera/cli/result.rb +13 -18
- data/lib/aspera/cli/runner.rb +31 -54
- data/lib/aspera/cli/special_values.rb +5 -0
- data/lib/aspera/cli/sync_actions.rb +41 -37
- data/lib/aspera/cli/terminal_formatter.rb +9 -3
- data/lib/aspera/cli/transfer_actions.rb +0 -6
- data/lib/aspera/cli/transfer_agent.rb +29 -35
- data/lib/aspera/cli/vault_manager.rb +0 -17
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +4 -2
- data/lib/aspera/coverage.rb +1 -0
- data/lib/aspera/environment.rb +6 -0
- data/lib/aspera/faspex_gw.rb +2 -1
- data/lib/aspera/faspex_postproc.rb +1 -0
- data/lib/aspera/graphql.rb +5 -5
- data/lib/aspera/json_rpc/client.rb +5 -5
- data/lib/aspera/keychain/encrypted_hash.rb +1 -1
- data/lib/aspera/keychain/one_password_api.rb +1 -1
- data/lib/aspera/link_header.rb +2 -2
- data/lib/aspera/log.rb +22 -25
- data/lib/aspera/markdown.rb +2 -0
- data/lib/aspera/mime.rb +25 -0
- data/lib/aspera/node_simulator.rb +1 -0
- data/lib/aspera/oauth/base.rb +35 -25
- data/lib/aspera/oauth/factory.rb +1 -0
- data/lib/aspera/oauth/generic.rb +1 -1
- data/lib/aspera/oauth/jwt.rb +1 -1
- data/lib/aspera/oauth/web.rb +9 -8
- data/lib/aspera/preview/file_types.rb +4 -4
- data/lib/aspera/preview/generator.rb +7 -0
- data/lib/aspera/preview/options.rb +4 -4
- data/lib/aspera/preview/terminal.rb +4 -3
- data/lib/aspera/preview/utils.rb +9 -6
- data/lib/aspera/products/connect.rb +1 -1
- data/lib/aspera/rainbow.rb +7 -0
- data/lib/aspera/rest/aspera_errors.rb +60 -0
- data/lib/aspera/rest/call_error.rb +27 -0
- data/lib/aspera/rest/client.rb +514 -0
- data/lib/aspera/rest/error_analyzer.rb +113 -0
- data/lib/aspera/rest/list.rb +143 -0
- data/lib/aspera/rest/parameters.rb +55 -0
- data/lib/aspera/rest/util.rb +176 -0
- data/lib/aspera/rest.rb +7 -621
- data/lib/aspera/schema/IBM Aspera Console-enhanced.yaml +1125 -0
- data/lib/aspera/schema/IBM Aspera on Cloud Automation API-1.0.5-enhanced.yaml +2395 -0
- data/lib/aspera/schema/documentation.rb +13 -3
- data/lib/aspera/schema/registry.rb +18 -1
- data/lib/aspera/schema/validator.rb +92 -0
- data/lib/aspera/secret_hider.rb +36 -25
- data/lib/aspera/string_ext.rb +15 -0
- data/lib/aspera/temp_file_manager.rb +6 -5
- data/lib/aspera/transfer/parameters.rb +2 -0
- data/lib/aspera/transfer/spec.rb +1 -0
- data/lib/aspera/uri_reader.rb +11 -11
- data/lib/aspera/web_auth/index.html +147 -0
- data/lib/aspera/web_auth/server.rb +81 -0
- data.tar.gz.sig +0 -0
- metadata +39 -7
- metadata.gz.sig +0 -0
- data/lib/aspera/colors.rb +0 -79
- data/lib/aspera/rest_call_error.rb +0 -25
- data/lib/aspera/rest_error_analyzer.rb +0 -111
- data/lib/aspera/rest_errors_aspera.rb +0 -58
- data/lib/aspera/rest_list.rb +0 -136
- data/lib/aspera/web_auth.rb +0 -211
data/lib/aspera/cli/wizard.rb
CHANGED
|
@@ -4,6 +4,8 @@ require 'aspera/oauth/jwt'
|
|
|
4
4
|
require 'aspera/assert'
|
|
5
5
|
require 'aspera/cli/option_declarator'
|
|
6
6
|
require 'aspera/cli/plugins/factory'
|
|
7
|
+
require 'aspera/rainbow'
|
|
8
|
+
using Rainbow
|
|
7
9
|
|
|
8
10
|
module Aspera
|
|
9
11
|
module Cli
|
|
@@ -60,7 +62,7 @@ module Aspera
|
|
|
60
62
|
detection_info = nil
|
|
61
63
|
begin
|
|
62
64
|
Log.log.debug { "detecting #{plugin_name_sym} at #{app_url}" }
|
|
63
|
-
|
|
65
|
+
Rest::Parameters.instance.spinner_cb.call(plugin_name_sym.to_s)
|
|
64
66
|
detection_info = plugin_klass.detect(app_url)
|
|
65
67
|
rescue OpenSSL::SSL::SSLError => e
|
|
66
68
|
Log.log.warn(e.message)
|
|
@@ -76,7 +78,7 @@ module Aspera
|
|
|
76
78
|
# If there is a redirect, then the detector can override the url.
|
|
77
79
|
found_apps.push({product: plugin_name_sym, name: app_name, url: app_url, version: 'unknown'}.merge(detection_info))
|
|
78
80
|
end
|
|
79
|
-
|
|
81
|
+
Rest::Parameters.instance.spinner_cb.call(action: :success)
|
|
80
82
|
Aspera.assert(!found_apps.empty?) { "No known application found at #{app_url}" }
|
|
81
83
|
Aspera.assert(found_apps.all? { |a| a.keys.all?(Symbol) }, 'all app info keys must be symbols')
|
|
82
84
|
return found_apps
|
data/lib/aspera/coverage.rb
CHANGED
data/lib/aspera/environment.rb
CHANGED
|
@@ -8,6 +8,8 @@ require 'singleton'
|
|
|
8
8
|
require 'open3'
|
|
9
9
|
require 'English'
|
|
10
10
|
require 'shellwords'
|
|
11
|
+
require 'aspera/rainbow'
|
|
12
|
+
using Rainbow
|
|
11
13
|
|
|
12
14
|
# cspell:words MEBI mswin bccwin
|
|
13
15
|
|
|
@@ -201,12 +203,16 @@ module Aspera
|
|
|
201
203
|
I18N_VARS.each { |var| ENV[var] = 'C' }
|
|
202
204
|
end
|
|
203
205
|
|
|
206
|
+
# Override detection of Unicode support: `true` or `false`, or `nil` for auto-detection
|
|
207
|
+
attr_writer :unicode
|
|
208
|
+
|
|
204
209
|
# @return [Boolean] true if we can display Unicode characters
|
|
205
210
|
# Uses Encoding.locale_charmap for OS-independent detection.
|
|
206
211
|
# Falls back to locale env vars for systems where charmap is not available.
|
|
207
212
|
# https://www.gnu.org/software/libc/manual/html_node/Locale-Categories.html
|
|
208
213
|
# https://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
|
|
209
214
|
def terminal_supports_unicode?
|
|
215
|
+
return @unicode unless @unicode.nil?
|
|
210
216
|
return false unless terminal?
|
|
211
217
|
locale_charmap_utf8? || I18N_VARS.any? { |var| ENV[var]&.include?('UTF-8') }
|
|
212
218
|
end
|
data/lib/aspera/faspex_gw.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'aspera/log'
|
|
4
4
|
require 'aspera/assert'
|
|
5
|
+
require 'aspera/mime'
|
|
5
6
|
require 'webrick'
|
|
6
7
|
require 'json'
|
|
7
8
|
|
|
@@ -10,7 +11,7 @@ module Aspera
|
|
|
10
11
|
class Faspex4GWServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
11
12
|
AOC_API = 'Aspera::Api::AoC'
|
|
12
13
|
FX_API = 'Aspera::Api::Faspex'
|
|
13
|
-
# @param app_api [Rest] API object
|
|
14
|
+
# @param app_api [Rest::Client] API object
|
|
14
15
|
# @param app_context [String] workspace id (aoc only)
|
|
15
16
|
def initialize(server, app_api, app_context)
|
|
16
17
|
Aspera.assert_values(app_api.class.name, [AOC_API, FX_API])
|
data/lib/aspera/graphql.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'aspera/
|
|
3
|
+
require 'aspera/rest/call_error'
|
|
4
4
|
|
|
5
5
|
module Aspera
|
|
6
|
-
# Helper to execute GraphQL queries via a Rest object.
|
|
6
|
+
# Helper to execute GraphQL queries via a Rest::Client object.
|
|
7
7
|
# Follows the GraphQL-over-HTTP convention: POST {query, variables} to endpoint,
|
|
8
8
|
# parse top-level `data` key, raise on top-level `errors` array.
|
|
9
9
|
module GraphQL
|
|
@@ -12,16 +12,16 @@ module Aspera
|
|
|
12
12
|
|
|
13
13
|
class << self
|
|
14
14
|
# Execute a GraphQL query loaded from a .graphql file in lib/aspera/api/queries/
|
|
15
|
-
# @param rest [Rest] API object pointing at the GraphQL endpoint
|
|
15
|
+
# @param rest [Rest::Client] API object pointing at the GraphQL endpoint
|
|
16
16
|
# @param name [String] Base filename without extension (e.g. 'my_query')
|
|
17
17
|
# @param variables [Hash] GraphQL variables
|
|
18
18
|
# @return [Hash] The `data` object from the response
|
|
19
|
-
# @raise [
|
|
19
|
+
# @raise [Rest::CallError] if the response contains GraphQL errors
|
|
20
20
|
def execute(rest, name, variables = {})
|
|
21
21
|
query = load_query(name)
|
|
22
22
|
response = rest.create(nil, {query: query, variables: variables})
|
|
23
23
|
errors = response['errors']
|
|
24
|
-
raise
|
|
24
|
+
raise Rest::CallError, errors.map { |e| e['message'] }.join("\n") if errors.is_a?(Array) && !errors.empty?
|
|
25
25
|
response['data']
|
|
26
26
|
end
|
|
27
27
|
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
# cspell:ignore blankslate jsonrpc
|
|
4
4
|
|
|
5
|
-
require 'aspera/
|
|
5
|
+
require 'aspera/rest/error_analyzer'
|
|
6
6
|
require 'aspera/assert'
|
|
7
7
|
require 'aspera/json_rpc/version'
|
|
8
8
|
require 'blankslate'
|
|
9
9
|
|
|
10
|
-
Aspera::
|
|
10
|
+
Aspera::Rest::ErrorAnalyzer.instance.add_simple_handler(name: 'JSON RPC', path: %w[error message], always: true)
|
|
11
11
|
|
|
12
12
|
module Aspera
|
|
13
13
|
module JsonRpc
|
|
14
|
-
# JSON-RPC 2.0 client over an Aspera::Rest HTTP endpoint.
|
|
14
|
+
# JSON-RPC 2.0 client over an Aspera::Rest::Client HTTP endpoint.
|
|
15
15
|
# Methods are dispatched dynamically via method_missing.
|
|
16
16
|
# Example:
|
|
17
|
-
# client = JsonRpc::Client.new(Rest.new(base_url: 'http://127.0.0.1:33024'))
|
|
17
|
+
# client = JsonRpc::Client.new(Rest::Client.new(base_url: 'http://127.0.0.1:33024'))
|
|
18
18
|
# client.get_info
|
|
19
19
|
# client.start_transfer(app_id: '...', transfer_spec: {...})
|
|
20
20
|
class Client < BlankSlate
|
|
@@ -22,7 +22,7 @@ module Aspera
|
|
|
22
22
|
reveal :inspect
|
|
23
23
|
reveal :to_s
|
|
24
24
|
|
|
25
|
-
# @param api [Rest] Aspera REST object pointing at the JSON-RPC endpoint
|
|
25
|
+
# @param api [Rest::Client] Aspera REST object pointing at the JSON-RPC endpoint
|
|
26
26
|
# @param namespace [String, nil] optional method prefix, e.g. "myns."
|
|
27
27
|
def initialize(api, namespace = nil)
|
|
28
28
|
super()
|
|
@@ -138,7 +138,7 @@ module Aspera
|
|
|
138
138
|
end
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
-
# Derive an AES key from
|
|
141
|
+
# Derive an AES key from `new_password`.
|
|
142
142
|
# @legacy_key is true for vaults created by v4.26.0 or earlier (no kdf field):
|
|
143
143
|
# key = password truncated/zero-padded to key_bytes (same as symmetric-encryption did).
|
|
144
144
|
# Otherwise PBKDF2 is used. When @kdf_params is nil (new vault or password change)
|
data/lib/aspera/link_header.rb
CHANGED
|
@@ -31,7 +31,7 @@ module Aspera
|
|
|
31
31
|
@links = links
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
# Return the href of the first link whose
|
|
34
|
+
# Return the href of the first link whose `rel` attribute matches `rel`.
|
|
35
35
|
# Comparison is case-insensitive per RFC 7230 s.3.2 and RFC 8288 s.3.
|
|
36
36
|
# Returns nil if no link with that relation exists.
|
|
37
37
|
# @param rel [String]
|
|
@@ -41,7 +41,7 @@ module Aspera
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
class << self
|
|
44
|
-
# Parse a raw Link header value into a
|
|
44
|
+
# Parse a raw Link header value into a `LinkHeader` instance.
|
|
45
45
|
# Uses StringScanner so that commas inside <URI> are not treated as separators.
|
|
46
46
|
# @param raw [String, nil]
|
|
47
47
|
# @return [LinkHeader]
|
data/lib/aspera/log.rb
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'aspera/assert'
|
|
4
|
-
require 'aspera/colors'
|
|
5
4
|
require 'aspera/secret_hider'
|
|
6
5
|
require 'logger'
|
|
7
6
|
require 'pp'
|
|
8
7
|
require 'json'
|
|
9
8
|
require 'singleton'
|
|
10
9
|
require 'stringio'
|
|
10
|
+
require 'aspera/rainbow'
|
|
11
|
+
using Rainbow
|
|
11
12
|
|
|
12
13
|
# Ignore warnings
|
|
13
14
|
old_verbose = $VERBOSE
|
|
@@ -118,13 +119,13 @@ module Aspera
|
|
|
118
119
|
|
|
119
120
|
# Returns the last 2 containers (module/class) and method caller
|
|
120
121
|
def caller_method
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
locations = caller_locations
|
|
123
|
+
# Outermost frame of logging code: Ruby logger, or this file (level methods, dump)
|
|
124
|
+
i = locations.rindex { |loc| loc.path.end_with?('/logger.rb') || loc.path.eql?(__FILE__) }
|
|
125
|
+
frame = locations[i + 1] if i
|
|
124
126
|
return '???' unless frame
|
|
125
|
-
#
|
|
126
|
-
full = frame
|
|
127
|
-
return '???' unless full
|
|
127
|
+
# "Class::Module#method" (Ruby >= 3.4) or "method" (Ruby < 3.4)
|
|
128
|
+
full = frame.label
|
|
128
129
|
# Split into class/module and method parts
|
|
129
130
|
parts = full.split(/(::|#)/)
|
|
130
131
|
# Reconstruct keeping only last two class/module names + separator + method
|
|
@@ -138,6 +139,7 @@ module Aspera
|
|
|
138
139
|
end
|
|
139
140
|
class_parts = classes.split('::')
|
|
140
141
|
selected_classes = class_parts.last(2).join('::')
|
|
142
|
+
return method if selected_classes.empty?
|
|
141
143
|
"#{selected_classes}.#{method}"
|
|
142
144
|
end
|
|
143
145
|
end
|
|
@@ -230,34 +232,29 @@ module Aspera
|
|
|
230
232
|
self.logger_type = @logger_type
|
|
231
233
|
end
|
|
232
234
|
|
|
233
|
-
#
|
|
234
|
-
|
|
235
|
-
TRACE2:
|
|
236
|
-
TRACE1:
|
|
237
|
-
DEBUG:
|
|
238
|
-
INFO:
|
|
239
|
-
WARN:
|
|
240
|
-
ERROR:
|
|
241
|
-
FATAL:
|
|
242
|
-
UNKNOWN:
|
|
235
|
+
# Short (4-letters) levels with color (evaluated on each call, as colors can be enabled or disabled by option)
|
|
236
|
+
LVL_COLOR = {
|
|
237
|
+
TRACE2: -> { short_levl(:TRACE2).faint },
|
|
238
|
+
TRACE1: -> { short_levl(:TRACE1).blue },
|
|
239
|
+
DEBUG: -> { short_levl(:DEBUG).cyan },
|
|
240
|
+
INFO: -> { short_levl(:INFO).green },
|
|
241
|
+
WARN: -> { short_levl(:WARN).bg(:yellow).black },
|
|
242
|
+
ERROR: -> { short_levl(:ERROR).bg(:red).blink },
|
|
243
|
+
FATAL: -> { short_levl(:FATAL).magenta },
|
|
244
|
+
UNKNOWN: -> { short_levl(:UNKNOWN).blink }
|
|
243
245
|
}.freeze
|
|
244
246
|
|
|
245
|
-
|
|
246
|
-
LVL_COLOR = LVL_DECO.to_h do |k, v|
|
|
247
|
-
[k, short_levl(k).apply(*v)]
|
|
248
|
-
end.freeze
|
|
249
|
-
|
|
250
|
-
DEFAULT_FORMATTER = ->(s, _d, _p, m) { "#{LVL_COLOR[s]} #{m}\n" }
|
|
247
|
+
DEFAULT_FORMATTER = ->(s, _d, _p, m) { "#{LVL_COLOR[s]&.call} #{m}\n" }
|
|
251
248
|
|
|
252
249
|
# pre-defined formatters
|
|
253
250
|
FORMATTER_LAMBDAS = {
|
|
254
251
|
standard: Logger::Formatter.new,
|
|
255
252
|
default: DEFAULT_FORMATTER,
|
|
256
|
-
caller: ->(s, _d, _p, m) { "#{LVL_COLOR[s]} #{Log.caller_method}\n#{m}\n" }
|
|
253
|
+
caller: ->(s, _d, _p, m) { "#{LVL_COLOR[s]&.call} #{Log.caller_method}\n#{m}\n" }
|
|
257
254
|
}.freeze
|
|
258
255
|
|
|
259
256
|
FORMATTERS = FORMATTER_LAMBDAS.keys
|
|
260
257
|
|
|
261
|
-
private_constant :
|
|
258
|
+
private_constant :DEFAULT_FORMATTER, :FORMATTER_LAMBDAS
|
|
262
259
|
end
|
|
263
260
|
end
|
data/lib/aspera/markdown.rb
CHANGED
|
@@ -82,6 +82,8 @@ module Aspera
|
|
|
82
82
|
col_widths = table.transpose.map do |col|
|
|
83
83
|
[col.flat_map { |c| c.to_s.delete('`').split(HTML_BREAK).map(&:size) }.max, COL_WIDTH].min
|
|
84
84
|
end
|
|
85
|
+
# do not modify caller's table
|
|
86
|
+
table = table.dup
|
|
85
87
|
headings = table.shift
|
|
86
88
|
table.unshift(col_widths.map { |col_width| '-' * col_width })
|
|
87
89
|
table.unshift(headings)
|
data/lib/aspera/mime.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aspera
|
|
4
|
+
# MIME types used in `Content-Type` and `Accept`
|
|
5
|
+
module Mime
|
|
6
|
+
# JSON body
|
|
7
|
+
JSON = 'application/json'
|
|
8
|
+
# URL encoded form body
|
|
9
|
+
WWW = 'application/x-www-form-urlencoded'
|
|
10
|
+
# Plain text body
|
|
11
|
+
TEXT = 'text/plain'
|
|
12
|
+
# JSON:API body (https://jsonapi.org)
|
|
13
|
+
JSON_API = 'application/vnd.api+json'
|
|
14
|
+
# Check if a media type is JSON: `application/json` or structured syntax suffix `+json` (RFC 6839)
|
|
15
|
+
# @param mime [String] Media type, without parameters (see `Rest.parse_header`)
|
|
16
|
+
# @return [Boolean] `true` if JSON
|
|
17
|
+
def json?(mime) = mime == JSON || mime.end_with?(JSON_SUFFIX) || mime == LEGACY_JSON
|
|
18
|
+
module_function :json?
|
|
19
|
+
# Structured syntax suffix for JSON
|
|
20
|
+
JSON_SUFFIX = '+json'
|
|
21
|
+
# Non standard type used by some servers for JSON
|
|
22
|
+
LEGACY_JSON = 'application/x-javascript'
|
|
23
|
+
private_constant :JSON_SUFFIX, :LEGACY_JSON
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/aspera/oauth/base.rb
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
require 'aspera/oauth/factory'
|
|
4
4
|
require 'aspera/log'
|
|
5
5
|
require 'aspera/assert'
|
|
6
|
+
require 'aspera/mime'
|
|
6
7
|
require 'date'
|
|
8
|
+
require 'aspera/rainbow'
|
|
9
|
+
using Rainbow
|
|
7
10
|
|
|
8
11
|
module Aspera
|
|
9
12
|
module OAuth
|
|
@@ -31,7 +34,7 @@ module Aspera
|
|
|
31
34
|
Aspera.assert_type(params, Hash)
|
|
32
35
|
Aspera.assert_type(cache_ids, Array)
|
|
33
36
|
# This is the OAuth API
|
|
34
|
-
@api = Rest.new(**rest_params)
|
|
37
|
+
@api = Rest::Client.new(**rest_params)
|
|
35
38
|
@params = params.dup.freeze
|
|
36
39
|
@path_token = path_token
|
|
37
40
|
@token_field = token_field
|
|
@@ -47,36 +50,39 @@ module Aspera
|
|
|
47
50
|
# Sub path to generate token
|
|
48
51
|
attr_reader :path_token
|
|
49
52
|
|
|
53
|
+
# Parameters for token creation call
|
|
54
|
+
# @param include_secret [Boolean] Add secret in default call parameters
|
|
55
|
+
# @param other_params [Hash] Other parameters to merge
|
|
56
|
+
# @return [Hash] Token creation parameters
|
|
57
|
+
def base_parameters(include_secret: false, **other_params)
|
|
58
|
+
call_params = @params.dup
|
|
59
|
+
call_params.delete(:client_secret) unless include_secret
|
|
60
|
+
call_params.merge(other_params)
|
|
61
|
+
end
|
|
62
|
+
|
|
50
63
|
# Helper method to create token as per RFC
|
|
51
|
-
# @
|
|
52
|
-
# @
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
# @param other_params [Hash] Additional parameter to base parameters
|
|
65
|
+
# @return [Net::HTTPResponse] Raw HTTP response with token
|
|
66
|
+
# @raise [Rest::CallError] If not 2XX code
|
|
67
|
+
def create_token_base(include_secret: false, **other_params)
|
|
68
|
+
Log.log.debug { 'Generating a new token'.bg(:green) }
|
|
69
|
+
creation_params = base_parameters(include_secret: include_secret, **other_params)
|
|
55
70
|
return @api.create(@path_token, nil, query: creation_params, ret: :resp) if @use_query
|
|
56
71
|
return @api.create(@path_token, creation_params, content_type: Mime::WWW, ret: :resp)
|
|
57
72
|
end
|
|
58
73
|
|
|
59
|
-
#
|
|
60
|
-
# @param add_secret [Boolean] Add secret in default call parameters
|
|
61
|
-
# @return [Hash] Optional general parameters
|
|
62
|
-
def base_params(add_secret: false)
|
|
63
|
-
call_params = @params.dup
|
|
64
|
-
call_params.delete(:client_secret) unless add_secret
|
|
65
|
-
return call_params
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# @return [String] value suitable for Authorization header
|
|
74
|
+
# @return [String] Value suitable for Authorization header: adds `Bearer `
|
|
69
75
|
def authorization(**kwargs)
|
|
70
76
|
return OAuth::Factory.bearer_authorization(token(**kwargs))
|
|
71
77
|
end
|
|
72
78
|
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
# if a token is expired (api returns 4xx), call again token(refresh: true)
|
|
79
|
+
# Get a OAuth v2 token (generated, cached, refreshed)
|
|
80
|
+
# If a token is expired (api returns 4xx), call again token(refresh: true)
|
|
76
81
|
# @param cache [Boolean] set to false to disable cache
|
|
77
82
|
# @param refresh [Boolean] set to true to force refresh or re-generation (if previous failed)
|
|
83
|
+
# @return [String] The bearer token
|
|
78
84
|
def token(cache: true, refresh: false)
|
|
79
|
-
#
|
|
85
|
+
# Get token info from cache (or nil), decoded with date and expiration status
|
|
80
86
|
token_info = Factory.instance.get_token_info(@token_cache_id) if cache
|
|
81
87
|
token_data = nil
|
|
82
88
|
unless token_info.nil?
|
|
@@ -90,17 +96,21 @@ module Aspera
|
|
|
90
96
|
Log.log.trace1 { "refresh: #{refresh} expired: #{token_info[:expired]}" }
|
|
91
97
|
refresh_token = nil
|
|
92
98
|
if token_data.key?('refresh_token') && !token_data['refresh_token'].eql?('not_supported')
|
|
93
|
-
#
|
|
99
|
+
# Save possible refresh token, before deleting the cache
|
|
94
100
|
refresh_token = token_data['refresh_token']
|
|
95
101
|
end
|
|
96
|
-
#
|
|
102
|
+
# Delete cache
|
|
97
103
|
Factory.instance.persist_mgr.delete(@token_cache_id)
|
|
98
104
|
token_data = nil
|
|
99
|
-
#
|
|
105
|
+
# Lets try the existing refresh token
|
|
100
106
|
# NOTE: AoC admin token has no refresh, and lives by default 1800secs
|
|
101
107
|
if !refresh_token.nil?
|
|
102
108
|
begin
|
|
103
|
-
http =
|
|
109
|
+
http = create_token_base(
|
|
110
|
+
include_secret: true,
|
|
111
|
+
grant_type: 'refresh_token',
|
|
112
|
+
refresh_token: refresh_token
|
|
113
|
+
)
|
|
104
114
|
# Save only if success
|
|
105
115
|
json_data = http.body
|
|
106
116
|
token_data = JSON.parse(json_data)
|
|
@@ -116,13 +126,13 @@ module Aspera
|
|
|
116
126
|
# no cache, nor refresh: generate a token
|
|
117
127
|
if token_data.nil?
|
|
118
128
|
# Call the method-specific token creation
|
|
119
|
-
# which returns the result of
|
|
129
|
+
# which returns the result of create_token_base
|
|
120
130
|
json_data = create_token.body
|
|
121
131
|
token_data = JSON.parse(json_data)
|
|
122
132
|
Factory.instance.persist_mgr.put(@token_cache_id, json_data)
|
|
123
133
|
end
|
|
124
134
|
Aspera.assert(token_data.key?(@token_field)) { "API error: No such field in answer: #{@token_field}" } unless token_data.nil?
|
|
125
|
-
#
|
|
135
|
+
# Ok we shall have a token here
|
|
126
136
|
return token_data[@token_field]
|
|
127
137
|
end
|
|
128
138
|
end
|
data/lib/aspera/oauth/factory.rb
CHANGED
data/lib/aspera/oauth/generic.rb
CHANGED
data/lib/aspera/oauth/jwt.rb
CHANGED
|
@@ -59,7 +59,7 @@ module Aspera
|
|
|
59
59
|
}.merge(@additional_payload)
|
|
60
60
|
Log.dump(:jwt_payload, jwt_payload)
|
|
61
61
|
assertion = JWT.encode(jwt_payload, @private_key_obj, 'RS256', @headers)
|
|
62
|
-
|
|
62
|
+
create_token_base(grant_type: GRANT_TYPE, assertion: assertion)
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
Factory.instance.register_token_creator(Jwt)
|
data/lib/aspera/oauth/web.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'aspera/oauth/base'
|
|
4
4
|
require 'aspera/environment'
|
|
5
|
-
require 'aspera/web_auth'
|
|
5
|
+
require 'aspera/web_auth/server'
|
|
6
6
|
require 'aspera/assert'
|
|
7
7
|
module Aspera
|
|
8
8
|
module OAuth
|
|
@@ -32,23 +32,24 @@ module Aspera
|
|
|
32
32
|
random_state = SecureRandom.uuid
|
|
33
33
|
login_page_url = Rest.build_uri(
|
|
34
34
|
"#{api.base_url}/#{@path_authorize}",
|
|
35
|
-
|
|
35
|
+
base_parameters(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.debug { "login_page_url=#{login_page_url}" }
|
|
39
39
|
# start a web server to receive request code
|
|
40
|
-
web_server = WebAuth.new(@redirect_uri, self.class.additional_info)
|
|
40
|
+
web_server = WebAuth::Server.new(@redirect_uri, self.class.additional_info)
|
|
41
41
|
# start browser on login page
|
|
42
42
|
Environment.instance.open_uri(login_page_url)
|
|
43
43
|
# wait for code in request
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
create_token_base(
|
|
48
|
+
include_secret: true,
|
|
49
|
+
grant_type: 'authorization_code',
|
|
50
|
+
code: received_params['code'],
|
|
51
|
+
redirect_uri: @redirect_uri
|
|
52
|
+
)
|
|
52
53
|
end
|
|
53
54
|
end
|
|
54
55
|
Factory.instance.register_token_creator(Web)
|
|
@@ -57,11 +57,11 @@ module Aspera
|
|
|
57
57
|
|
|
58
58
|
private_constant :SUPPORTED_MIME_TYPES
|
|
59
59
|
|
|
60
|
-
# @attr
|
|
61
|
-
attr_accessor :
|
|
60
|
+
# @attr detect_mime [Boolean] `true` to determine real mime type based on file content
|
|
61
|
+
attr_accessor :detect_mime
|
|
62
62
|
|
|
63
63
|
def initialize
|
|
64
|
-
@
|
|
64
|
+
@detect_mime = false
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
# @param mimetype [String] mime type
|
|
@@ -81,7 +81,7 @@ module Aspera
|
|
|
81
81
|
# @return [Symbol] file type, one of enum CONVERSION_TYPES
|
|
82
82
|
# @raise [RuntimeError] if no conversion type found
|
|
83
83
|
def conversion_type(filepath, mimetype)
|
|
84
|
-
Log.log.debug { "conversion_type(#{filepath},mime=#{mimetype},magic=#{@
|
|
84
|
+
Log.log.debug { "conversion_type(#{filepath},mime=#{mimetype},magic=#{@detect_mime})" }
|
|
85
85
|
# Default type or empty means no type
|
|
86
86
|
mimetype = TYPE_NOT_FOUND if mimetype.nil? || (mimetype.is_a?(String) && mimetype.empty?)
|
|
87
87
|
mimetype = Marcel::MimeType.for(Pathname.new(filepath), name: File.basename(filepath), declared_type: mimetype)
|
|
@@ -251,6 +251,7 @@ module Aspera
|
|
|
251
251
|
tmp_pdf_file = File.join(this_tmpdir, "#{File.basename(@source, File.extname(@source))}.pdf")
|
|
252
252
|
case @options.office_conversion
|
|
253
253
|
when :unoconv
|
|
254
|
+
# `unoconv` is a python wrapper over `soffice`
|
|
254
255
|
Utils.silent_execute(
|
|
255
256
|
:unoconv,
|
|
256
257
|
'-f', 'pdf',
|
|
@@ -261,6 +262,12 @@ module Aspera
|
|
|
261
262
|
Utils.silent_execute(
|
|
262
263
|
:soffice,
|
|
263
264
|
'--headless',
|
|
265
|
+
'--invisible',
|
|
266
|
+
'--nocrashreport',
|
|
267
|
+
'--nodefault',
|
|
268
|
+
'--nofirststartwizard',
|
|
269
|
+
'--nologo',
|
|
270
|
+
'--norestore',
|
|
264
271
|
'--convert-to', 'pdf',
|
|
265
272
|
'--outdir', File.dirname(tmp_pdf_file),
|
|
266
273
|
@source
|
|
@@ -10,19 +10,19 @@ module Aspera
|
|
|
10
10
|
# types of generation for video files
|
|
11
11
|
VIDEO_CONVERSION_METHODS = %i[reencode blend clips].freeze
|
|
12
12
|
VIDEO_THUMBNAIL_METHODS = %i[fixed animated].freeze
|
|
13
|
-
# methods for office document conversion
|
|
13
|
+
# @type [Array<Symbol>] methods for office document conversion
|
|
14
14
|
OFFICE_CONVERSION_METHODS = %i[soffice unoconv].freeze
|
|
15
|
-
#
|
|
16
|
-
#
|
|
15
|
+
# Options used in generator
|
|
16
|
+
# For scaling see: https://trac.ffmpeg.org/wiki/Scaling
|
|
17
17
|
# iw/ih : input width or height
|
|
18
18
|
# -x : keep aspect ratio, having value a multiple of x
|
|
19
19
|
DESCRIPTIONS = [
|
|
20
|
+
{name: :office_conversion, default: :soffice, description: 'office: method for office document conversion', values: OFFICE_CONVERSION_METHODS},
|
|
20
21
|
{name: :max_size, default: 1 << 24, description: 'maximum size (in bytes) of preview file'},
|
|
21
22
|
{name: :thumb_vid_scale, default: '-1:min(ih,100)', description: 'png: video: size (ffmpeg scale argument)'},
|
|
22
23
|
{name: :thumb_vid_fraction, default: 0.1, description: 'png: video: time percent position of snapshot'},
|
|
23
24
|
{name: :thumb_img_size, default: 800, description: 'png: non-video: height (and width)'},
|
|
24
25
|
{name: :thumb_text_font, default: 'Courier', description: 'png: plaintext: font for text rendering: `magick identify -list font`'},
|
|
25
|
-
{name: :office_conversion, default: :soffice, description: 'office: method for office document conversion', values: OFFICE_CONVERSION_METHODS},
|
|
26
26
|
{name: :video_conversion, default: :reencode, description: 'mp4: method for preview generation', values: VIDEO_CONVERSION_METHODS},
|
|
27
27
|
{name: :video_png_conv, default: :fixed, description: 'mp4: method for thumbnail generation', values: VIDEO_THUMBNAIL_METHODS},
|
|
28
28
|
{name: :video_scale, default: 'min(iw,360):-2', description: 'mp4: all: video scale (ffmpeg scale argument)'},
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
# cspell:words Magick MAGICKCORE ITERM mintty winsize termcap
|
|
4
4
|
|
|
5
|
-
require 'rainbow'
|
|
6
5
|
require 'base64'
|
|
7
6
|
require 'io/console'
|
|
8
7
|
require 'aspera/log'
|
|
9
8
|
require 'aspera/environment'
|
|
9
|
+
require 'aspera/rainbow'
|
|
10
|
+
using Rainbow
|
|
10
11
|
|
|
11
12
|
module Aspera
|
|
12
13
|
module Preview
|
|
@@ -155,9 +156,9 @@ module Aspera
|
|
|
155
156
|
row_data.each_with_index do |pixel_rgb, col|
|
|
156
157
|
text_pixels.push("\n") if col.eql?(0) && !row.eql?(0)
|
|
157
158
|
if double
|
|
158
|
-
text_pixels.push(
|
|
159
|
+
text_pixels.push('▄'.bg(pixel_rgb).fg(pixel_colors[row + 1][col]))
|
|
159
160
|
else
|
|
160
|
-
text_pixels.push(
|
|
161
|
+
text_pixels.push(' '.bg(pixel_rgb))
|
|
161
162
|
end
|
|
162
163
|
end
|
|
163
164
|
end
|