aspera-cli 4.18.1 → 4.20.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +33 -0
- data/CONTRIBUTING.md +17 -12
- data/README.md +396 -185
- data/bin/asession +26 -19
- data/examples/build_exec +74 -0
- data/examples/{rubyc → build_exec_rubyc} +18 -2
- data/examples/get_proto_file.rb +7 -0
- data/lib/aspera/agent/alpha.rb +8 -8
- data/lib/aspera/agent/base.rb +4 -18
- data/lib/aspera/agent/connect.rb +14 -13
- data/lib/aspera/agent/direct.rb +123 -120
- data/lib/aspera/agent/httpgw.rb +2 -3
- data/lib/aspera/agent/node.rb +10 -10
- data/lib/aspera/agent/trsdk.rb +17 -20
- data/lib/aspera/api/alee.rb +15 -0
- data/lib/aspera/api/aoc.rb +128 -99
- data/lib/aspera/api/ats.rb +1 -1
- data/lib/aspera/api/cos_node.rb +1 -1
- data/lib/aspera/api/httpgw.rb +104 -64
- data/lib/aspera/api/node.rb +33 -12
- data/lib/aspera/ascmd.rb +56 -48
- data/lib/aspera/ascp/installation.rb +142 -70
- data/lib/aspera/ascp/management.rb +7 -3
- data/lib/aspera/ascp/products.rb +13 -7
- data/lib/aspera/assert.rb +10 -5
- data/lib/aspera/cli/formatter.rb +42 -26
- data/lib/aspera/cli/hints.rb +2 -1
- data/lib/aspera/cli/info.rb +12 -10
- data/lib/aspera/cli/main.rb +16 -13
- data/lib/aspera/cli/manager.rb +15 -10
- data/lib/aspera/cli/plugin.rb +17 -31
- data/lib/aspera/cli/plugin_factory.rb +10 -1
- data/lib/aspera/cli/plugins/alee.rb +3 -3
- data/lib/aspera/cli/plugins/aoc.rb +222 -194
- data/lib/aspera/cli/plugins/ats.rb +16 -14
- data/lib/aspera/cli/plugins/config.rb +66 -53
- data/lib/aspera/cli/plugins/console.rb +3 -3
- data/lib/aspera/cli/plugins/faspex.rb +11 -21
- data/lib/aspera/cli/plugins/faspex5.rb +44 -42
- data/lib/aspera/cli/plugins/faspio.rb +2 -2
- data/lib/aspera/cli/plugins/httpgw.rb +1 -1
- data/lib/aspera/cli/plugins/node.rb +155 -96
- data/lib/aspera/cli/plugins/orchestrator.rb +14 -13
- data/lib/aspera/cli/plugins/preview.rb +8 -9
- data/lib/aspera/cli/plugins/server.rb +6 -10
- data/lib/aspera/cli/plugins/shares.rb +13 -9
- data/lib/aspera/cli/sync_actions.rb +72 -31
- data/lib/aspera/cli/transfer_agent.rb +13 -14
- data/lib/aspera/cli/transfer_progress.rb +36 -18
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +3 -4
- data/lib/aspera/coverage.rb +13 -1
- data/lib/aspera/environment.rb +59 -10
- data/lib/aspera/faspex_gw.rb +3 -3
- data/lib/aspera/json_rpc.rb +1 -1
- data/lib/aspera/keychain/encrypted_hash.rb +2 -0
- data/lib/aspera/keychain/macos_security.rb +7 -12
- data/lib/aspera/log.rb +4 -4
- data/lib/aspera/node_simulator.rb +1 -1
- data/lib/aspera/oauth/base.rb +39 -45
- data/lib/aspera/oauth/factory.rb +11 -4
- data/lib/aspera/oauth/generic.rb +4 -8
- data/lib/aspera/oauth/jwt.rb +4 -4
- data/lib/aspera/oauth/url_json.rb +3 -2
- data/lib/aspera/oauth/web.rb +10 -6
- data/lib/aspera/persistency_action_once.rb +16 -8
- data/lib/aspera/preview/utils.rb +5 -16
- data/lib/aspera/rest.rb +100 -76
- data/lib/aspera/secret_hider.rb +3 -2
- data/lib/aspera/ssh.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +7 -5
- data/lib/aspera/transfer/parameters.rb +41 -35
- data/lib/aspera/transfer/spec.rb +16 -18
- data/lib/aspera/transfer/sync.rb +51 -50
- data/lib/aspera/transfer/uri.rb +1 -1
- data/lib/aspera/uri_reader.rb +1 -1
- data/lib/aspera/web_auth.rb +166 -18
- data/lib/aspera/web_server_simple.rb +27 -15
- data/lib/transfer_pb.rb +84 -0
- data/lib/transfer_services_pb.rb +82 -0
- data.tar.gz.sig +0 -0
- metadata +25 -6
- metadata.gz.sig +0 -0
data/lib/aspera/transfer/sync.rb
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
|
5
5
|
require 'aspera/command_line_builder'
|
6
6
|
require 'aspera/ascp/installation'
|
7
|
+
require 'aspera/agent/direct'
|
7
8
|
require 'aspera/log'
|
8
9
|
require 'aspera/assert'
|
9
10
|
require 'json'
|
@@ -13,12 +14,12 @@ require 'English'
|
|
13
14
|
|
14
15
|
module Aspera
|
15
16
|
module Transfer
|
16
|
-
# builds command line arg for async
|
17
|
+
# builds command line arg for async and execute it
|
17
18
|
module Sync
|
18
19
|
# sync direction, default is push
|
19
20
|
DIRECTIONS = %i[push pull bidi].freeze
|
20
|
-
#
|
21
|
-
|
21
|
+
# JSON for async instance command line options
|
22
|
+
CMDLINE_PARAMS_INSTANCE =
|
22
23
|
{
|
23
24
|
'alt_logdir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
24
25
|
'watchd' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
@@ -28,7 +29,7 @@ module Aspera
|
|
28
29
|
}.freeze
|
29
30
|
|
30
31
|
# map sync session parameters to transfer spec: sync -> ts, true if same
|
31
|
-
|
32
|
+
CMDLINE_PARAMS_SESSION =
|
32
33
|
{
|
33
34
|
'name' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
34
35
|
'local_dir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
@@ -65,13 +66,13 @@ module Aspera
|
|
65
66
|
'license' => { cli: { type: :envvar, variable: 'ASPERA_SCP_LICENSE'}}
|
66
67
|
}.freeze
|
67
68
|
|
68
|
-
CommandLineBuilder.normalize_description(
|
69
|
-
CommandLineBuilder.normalize_description(
|
69
|
+
CommandLineBuilder.normalize_description(CMDLINE_PARAMS_INSTANCE)
|
70
|
+
CommandLineBuilder.normalize_description(CMDLINE_PARAMS_SESSION)
|
70
71
|
|
71
|
-
|
72
|
+
CMDLINE_PARAMS_KEYS = %w[instance sessions].freeze
|
72
73
|
|
73
74
|
# Translation of transfer spec parameters to async v2 API (asyncs)
|
74
|
-
|
75
|
+
TSPEC_TO_ASYNC_CONF = {
|
75
76
|
'remote_host' => 'remote.host',
|
76
77
|
'remote_user' => 'remote.user',
|
77
78
|
'remote_password' => 'remote.pass',
|
@@ -83,10 +84,9 @@ module Aspera
|
|
83
84
|
'tags' => 'tags'
|
84
85
|
}.freeze
|
85
86
|
|
86
|
-
ASYNC_EXECUTABLE = 'async'
|
87
87
|
ASYNC_ADMIN_EXECUTABLE = 'asyncadmin'
|
88
88
|
|
89
|
-
private_constant :
|
89
|
+
private_constant :CMDLINE_PARAMS_INSTANCE, :CMDLINE_PARAMS_SESSION, :CMDLINE_PARAMS_KEYS, :TSPEC_TO_ASYNC_CONF, :ASYNC_ADMIN_EXECUTABLE
|
90
90
|
|
91
91
|
class << self
|
92
92
|
# Set remote_dir in sync parameters based on transfer spec
|
@@ -126,7 +126,7 @@ module Aspera
|
|
126
126
|
remote.delete('ws_port')
|
127
127
|
# add SSH bypass keys when authentication is token and no auth is provided
|
128
128
|
if remote.key?('token') && !remote.key?('pass')
|
129
|
-
certificates_to_use.concat(Ascp::Installation.instance.aspera_token_ssh_key_paths)
|
129
|
+
certificates_to_use.concat(Ascp::Installation.instance.aspera_token_ssh_key_paths(:rsa))
|
130
130
|
end
|
131
131
|
end
|
132
132
|
return certificates_to_use
|
@@ -134,23 +134,27 @@ module Aspera
|
|
134
134
|
|
135
135
|
# @param sync_params [Hash] sync parameters, old or new format
|
136
136
|
# @param block [nil, Proc] block to generate transfer spec, takes: direction (one of DIRECTIONS), local_dir, remote_dir
|
137
|
-
def start(
|
137
|
+
def start(
|
138
|
+
sync_params,
|
139
|
+
&block
|
140
|
+
)
|
141
|
+
Log.log.debug{Log.dump(:sync_params_initial, sync_params)}
|
138
142
|
Aspera.assert_type(sync_params, Hash)
|
139
143
|
env_args = {
|
140
144
|
args: [],
|
141
145
|
env: {}
|
142
146
|
}
|
143
147
|
if sync_params.key?('local')
|
148
|
+
# async native JSON format (conf option)
|
149
|
+
Aspera.assert_type(sync_params['local'], Hash){'local'}
|
144
150
|
remote = sync_params['remote']
|
145
|
-
|
146
|
-
Aspera.assert_type(remote,
|
151
|
+
Aspera.assert_type(remote, Hash){'remote'}
|
152
|
+
Aspera.assert_type(remote['path'], String){'remote path'}
|
147
153
|
# get transfer spec if possible, and feed back to new structure
|
148
154
|
if block
|
149
155
|
transfer_spec = yield((sync_params['direction'] || 'push').to_sym, sync_params['local']['path'], remote['path'])
|
150
|
-
# async native JSON format
|
151
|
-
Aspera.assert_type(sync_params['local'], Hash)
|
152
156
|
# translate transfer spec to async parameters
|
153
|
-
|
157
|
+
TSPEC_TO_ASYNC_CONF.each do |ts_param, sy_path|
|
154
158
|
next unless transfer_spec.key?(ts_param)
|
155
159
|
sy_dig = sy_path.split('.')
|
156
160
|
param = sy_dig.pop
|
@@ -160,36 +164,41 @@ module Aspera
|
|
160
164
|
end
|
161
165
|
update_remote_dir(remote, 'path', transfer_spec)
|
162
166
|
end
|
163
|
-
remote['connect_mode'] ||=
|
167
|
+
remote['connect_mode'] ||= transfer_spec['wss_enabled'] ? 'ws' : 'ssh'
|
164
168
|
add_certificates = remote_certificates(remote)
|
165
169
|
if !add_certificates.empty?
|
166
170
|
remote['private_key_paths'] ||= []
|
167
171
|
remote['private_key_paths'].concat(add_certificates)
|
168
172
|
end
|
169
|
-
|
173
|
+
# '--exclusive-mgmt-port=12345', '--arg-err-path=-',
|
170
174
|
env_args[:args] = ["--conf64=#{Base64.strict_encode64(JSON.generate(sync_params))}"]
|
175
|
+
Log.log.debug{Log.dump(:sync_params_enriched, sync_params)}
|
176
|
+
agent = Agent::Direct.new
|
177
|
+
agent.start_and_monitor_process(session: {}, name: :async, **env_args)
|
171
178
|
elsif sync_params.key?('sessions')
|
172
|
-
# ascli JSON format (
|
179
|
+
# ascli JSON format (cmdline)
|
180
|
+
raise StandardError, "Only 'sessions', and optionally 'instance' keys are allowed" unless
|
181
|
+
sync_params.keys.push('instance').uniq.sort.eql?(CMDLINE_PARAMS_KEYS)
|
182
|
+
Aspera.assert_type(sync_params['sessions'], Array)
|
183
|
+
Aspera.assert_type(sync_params['sessions'].first, Hash)
|
173
184
|
if block
|
174
185
|
sync_params['sessions'].each do |session|
|
186
|
+
Aspera.assert_type(session['local_dir'], String){'local_dir'}
|
187
|
+
Aspera.assert_type(session['remote_dir'], String){'remote_dir'}
|
175
188
|
transfer_spec = yield((session['direction'] || 'push').to_sym, session['local_dir'], session['remote_dir'])
|
176
|
-
|
189
|
+
CMDLINE_PARAMS_SESSION.each do |async_param, behavior|
|
177
190
|
if behavior.key?(:ts)
|
178
191
|
tspec_param = behavior[:ts].is_a?(TrueClass) ? async_param : behavior[:ts].to_s
|
179
192
|
session[async_param] ||= transfer_spec[tspec_param] if transfer_spec.key?(tspec_param)
|
180
193
|
end
|
181
194
|
end
|
182
|
-
session['private_key_paths'] = Ascp::Installation.instance.aspera_token_ssh_key_paths if transfer_spec.key?('token')
|
195
|
+
session['private_key_paths'] = Ascp::Installation.instance.aspera_token_ssh_key_paths(:rsa) if transfer_spec.key?('token')
|
183
196
|
update_remote_dir(session, 'remote_dir', transfer_spec)
|
184
197
|
end
|
185
198
|
end
|
186
|
-
raise StandardError, "Only 'sessions', and optionally 'instance' keys are allowed" unless
|
187
|
-
sync_params.keys.push('instance').uniq.sort.eql?(PARAMS_VX_KEYS)
|
188
|
-
Aspera.assert_type(sync_params['sessions'], Array)
|
189
|
-
Aspera.assert_type(sync_params['sessions'].first, Hash)
|
190
199
|
if sync_params.key?('instance')
|
191
200
|
Aspera.assert_type(sync_params['instance'], Hash)
|
192
|
-
instance_builder = CommandLineBuilder.new(sync_params['instance'],
|
201
|
+
instance_builder = CommandLineBuilder.new(sync_params['instance'], CMDLINE_PARAMS_INSTANCE)
|
193
202
|
instance_builder.process_params
|
194
203
|
instance_builder.add_env_args(env_args)
|
195
204
|
end
|
@@ -197,23 +206,19 @@ module Aspera
|
|
197
206
|
sync_params['sessions'].each do |session_params|
|
198
207
|
Aspera.assert_type(session_params, Hash)
|
199
208
|
Aspera.assert(session_params.key?('name')){'session must contain at least name'}
|
200
|
-
session_builder = CommandLineBuilder.new(session_params,
|
209
|
+
session_builder = CommandLineBuilder.new(session_params, CMDLINE_PARAMS_SESSION)
|
201
210
|
session_builder.process_params
|
202
211
|
session_builder.add_env_args(env_args)
|
203
212
|
end
|
213
|
+
async_exec = Ascp::Installation.instance.path(:async)
|
214
|
+
Process.wait(Environment.secure_spawn(env: env_args[:env], exec: async_exec, args: env_args[:args]))
|
215
|
+
if $CHILD_STATUS.exitstatus != 0
|
216
|
+
raise "Sync failed with exit: #{$CHILD_STATUS.exitstatus}"
|
217
|
+
end
|
204
218
|
else
|
205
219
|
raise 'At least one of `local` or `sessions` must be present in async parameters'
|
206
220
|
end
|
207
|
-
|
208
|
-
Log.log.debug{"execute: #{env_args[:env].map{|k, v| "#{k}=\"#{v}\""}.join(' ')} \"#{ASYNC_EXECUTABLE}\" \"#{env_args[:args].join('" "')}\""}
|
209
|
-
res = system(env_args[:env], [ASYNC_EXECUTABLE, ASYNC_EXECUTABLE], *env_args[:args])
|
210
|
-
Log.log.debug{"result=#{res}"}
|
211
|
-
case res
|
212
|
-
when true then return nil
|
213
|
-
when false then raise "failed: #{$CHILD_STATUS}"
|
214
|
-
when nil then raise "not started: #{$CHILD_STATUS}"
|
215
|
-
else Aspera.error_unexpected_value(res)
|
216
|
-
end
|
221
|
+
return nil
|
217
222
|
end
|
218
223
|
|
219
224
|
def parse_status(stdout)
|
@@ -234,15 +239,15 @@ module Aspera
|
|
234
239
|
end
|
235
240
|
|
236
241
|
def admin_status(sync_params, session_name)
|
237
|
-
|
242
|
+
arguments = ['--quiet']
|
238
243
|
if sync_params.key?('local')
|
239
244
|
Aspera.assert(!sync_params['name'].nil?){'Missing session name'}
|
240
245
|
Aspera.assert(session_name.nil? || session_name.eql?(sync_params['name'])){'Session not found'}
|
241
|
-
|
246
|
+
arguments.push("--name=#{sync_params['name']}")
|
242
247
|
if sync_params.key?('local_db_dir')
|
243
|
-
|
248
|
+
arguments.push("--local-db-dir=#{sync_params['local_db_dir']}")
|
244
249
|
elsif sync_params.dig('local', 'path')
|
245
|
-
|
250
|
+
arguments.push("--local-dir=#{sync_params.dig('local', 'path')}")
|
246
251
|
else
|
247
252
|
raise 'Missing either local_db_dir or local.path'
|
248
253
|
end
|
@@ -250,22 +255,18 @@ module Aspera
|
|
250
255
|
session = session_name.nil? ? sync_params['sessions'].first : sync_params['sessions'].find{|s|s['name'].eql?(session_name)}
|
251
256
|
raise "Session #{session_name} not found in #{sync_params['sessions'].map{|s|s['name']}.join(',')}" if session.nil?
|
252
257
|
raise 'Missing session name' if session['name'].nil?
|
253
|
-
|
258
|
+
arguments.push("--name=#{session['name']}")
|
254
259
|
if session.key?('local_db_dir')
|
255
|
-
|
260
|
+
arguments.push("--local-db-dir=#{session['local_db_dir']}")
|
256
261
|
elsif session.key?('local_dir')
|
257
|
-
|
262
|
+
arguments.push("--local-dir=#{session['local_dir']}")
|
258
263
|
else
|
259
264
|
raise 'Missing either local_db_dir or local_dir'
|
260
265
|
end
|
261
266
|
else
|
262
267
|
raise 'At least one of `local` or `sessions` must be present in async parameters'
|
263
268
|
end
|
264
|
-
|
265
|
-
stdout, stderr, status = Open3.capture3(*command_line)
|
266
|
-
Log.log.debug{"status=#{status}, stderr=#{stderr}"}
|
267
|
-
Log.log.trace1{"stdout=#{stdout}"}
|
268
|
-
raise "Sync failed: #{status.exitstatus} : #{stderr}" unless status.success?
|
269
|
+
stdout = Environment.secure_capture(exec: ASYNC_ADMIN_EXECUTABLE, args: arguments)
|
269
270
|
return parse_status(stdout)
|
270
271
|
end
|
271
272
|
end
|
data/lib/aspera/transfer/uri.rb
CHANGED
@@ -26,7 +26,7 @@ module Aspera
|
|
26
26
|
# faspex 4 does not encode trailing base64 padding, fix that to be able to decode properly
|
27
27
|
fixed_query = @fasp_uri.query.gsub(/(=+)$/){|trail_equals|'%3D' * trail_equals.length}
|
28
28
|
|
29
|
-
Rest.
|
29
|
+
Rest.query_to_h(fixed_query).each do |name, value|
|
30
30
|
case name
|
31
31
|
when 'cookie' then result_ts['cookie'] = value
|
32
32
|
when 'token' then result_ts['token'] = value
|
data/lib/aspera/uri_reader.rb
CHANGED
@@ -12,7 +12,7 @@ module Aspera
|
|
12
12
|
uri = URI.parse(uri_to_read)
|
13
13
|
case uri.scheme
|
14
14
|
when 'http', 'https'
|
15
|
-
return Rest.new(base_url: uri_to_read, redirect_max: 5).call(operation: 'GET',
|
15
|
+
return Rest.new(base_url: uri_to_read, redirect_max: 5).call(operation: 'GET', headers: {'Accept' => '*/*'})[:data]
|
16
16
|
when 'file', NilClass
|
17
17
|
local_file_path = uri.path
|
18
18
|
raise 'URL shall have a path, check syntax' if local_file_path.nil?
|
data/lib/aspera/web_auth.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'aspera/web_server_simple'
|
4
|
-
require '
|
4
|
+
require 'aspera/assert'
|
5
5
|
|
6
6
|
module Aspera
|
7
7
|
# servlet called on callback: it records the callback request
|
@@ -16,41 +16,189 @@ module Aspera
|
|
16
16
|
|
17
17
|
def service(request, response)
|
18
18
|
Log.log.debug{"received request from browser #{request.request_method} #{request.path}"}
|
19
|
-
|
20
|
-
|
21
|
-
# acquire lock and signal change
|
22
|
-
@web_auth.mutex.synchronize do
|
23
|
-
@web_auth.query = request.query
|
24
|
-
@web_auth.cond.signal
|
25
|
-
end
|
19
|
+
Aspera.assert_values(request.request_method, ['GET'], exception_class: WEBrick::HTTPStatus::MethodNotAllowed){'HTTP verb'}
|
20
|
+
additionnal_info = @web_auth.signal_request(request)
|
26
21
|
response.status = 200
|
27
22
|
response.content_type = 'text/html'
|
28
|
-
response.body =
|
23
|
+
response.body = <<~HTML
|
24
|
+
<!DOCTYPE html>
|
25
|
+
<html lang="en">
|
26
|
+
<head>
|
27
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
28
|
+
<link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDMyIDMyIiBmaWxsPSJub25lIiBzdHJva2U9IiMyMjIiIHN0cm9rZS13aWR0aD0iMyI+CiAgPGxpbmUgeDE9IjMiIHkxPSIzIiB4Mj0iMjkiIHkyPSIyOSIgIHN0cm9rZT0icmVkIi8+CiAgPGxpbmUgeDE9IjI5IiB5MT0iMyIgeDI9IjMiIHkyPSIyOSIgc3Ryb2tlPSJyZWQiIC8+Cjwvc3ZnPg==" type="image/svg+xml">
|
29
|
+
<title>Close Now</title>
|
30
|
+
<style>
|
31
|
+
body {
|
32
|
+
font-family: Arial, sans-serif;
|
33
|
+
text-align: center;
|
34
|
+
padding: 2rem;
|
35
|
+
margin: 0;
|
36
|
+
background: linear-gradient(135deg, #f0f4f8, #d9e2ec);
|
37
|
+
color: #333;
|
38
|
+
overflow: hidden; /* Ensure no scrollbars for the background animation */
|
39
|
+
position: relative;
|
40
|
+
}
|
41
|
+
h1 {
|
42
|
+
font-size: 2.5rem;
|
43
|
+
color: #0078d4;
|
44
|
+
}
|
45
|
+
p {
|
46
|
+
font-size: 1.2rem;
|
47
|
+
margin-top: 1rem;
|
48
|
+
}
|
49
|
+
|
50
|
+
/* Styling for animated IBM logos */
|
51
|
+
.logo {
|
52
|
+
position: absolute;
|
53
|
+
bottom: -100px;
|
54
|
+
width: 40px;
|
55
|
+
height: 40px;
|
56
|
+
background: none;
|
57
|
+
display: flex;
|
58
|
+
justify-content: center;
|
59
|
+
align-items: center;
|
60
|
+
animation: rise 10s infinite ease-in-out;
|
61
|
+
}
|
62
|
+
|
63
|
+
.logo svg {
|
64
|
+
width: 100%;
|
65
|
+
height: 100%;
|
66
|
+
}
|
67
|
+
|
68
|
+
.logo:nth-child(odd) {
|
69
|
+
animation-duration: 8s;
|
70
|
+
}
|
71
|
+
|
72
|
+
.logo:nth-child(even) {
|
73
|
+
animation-duration: 12s;
|
74
|
+
}
|
75
|
+
|
76
|
+
@keyframes rise {
|
77
|
+
0% {
|
78
|
+
transform: translateY(0) scale(1);
|
79
|
+
opacity: 1;
|
80
|
+
}
|
81
|
+
50% {
|
82
|
+
opacity: 0.7;
|
83
|
+
}
|
84
|
+
100% {
|
85
|
+
transform: translateY(-120vh) scale(0.7);
|
86
|
+
opacity: 0;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
</style>
|
90
|
+
</head>
|
91
|
+
<body>
|
92
|
+
<h1>Thank You!</h1>
|
93
|
+
<p>You can close this window.</p>
|
94
|
+
<p>#{additionnal_info}</p>
|
95
|
+
|
96
|
+
<!-- JavaScript to generate IBM logos -->
|
97
|
+
<script>
|
98
|
+
// Function to create logos dynamically
|
99
|
+
function createLogos() {
|
100
|
+
const body = document.body;
|
101
|
+
const svgContent = `
|
102
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000px" height="401.149px" viewBox="0 0 1000 401.149" xml:space="preserve">
|
103
|
+
<g>
|
104
|
+
<rect fill="#1F70C1" x="0" y="373.217" width="194.433" height="27.833"/>
|
105
|
+
<rect fill="#1F70C1" x="0" y="319.83" width="194.433" height="27.931"/>
|
106
|
+
<rect fill="#1F70C1" x="55.468" y="266.541" width="83.399" height="27.932"/>
|
107
|
+
<rect fill="#1F70C1" x="55.468" y="213.253" width="83.399" height="27.932"/>
|
108
|
+
<rect fill="#1F70C1" x="55.468" y="159.964" width="83.399" height="27.932"/>
|
109
|
+
<rect fill="#1F70C1" x="55.468" y="106.577" width="83.399" height="27.932"/>
|
110
|
+
<rect fill="#1F70C1" x="0" y="53.288" width="194.433" height="27.932"/>
|
111
|
+
<rect fill="#1F70C1" x="0" y="0" width="194.433" height="27.932"/>
|
112
|
+
<path fill="#1F70C1" d="M222.167,347.761h299.029c5.051-8.617,8.815-18.027,11.094-27.932H222.167V347.761z"/>
|
113
|
+
<path fill="#1F70C1" d="M497.92,213.253H277.734v27.932h243.463C514.857,230.487,507.032,221.078,497.92,213.253z"/>
|
114
|
+
<path fill="#1F70C1" d="M277.734,159.964v27.932H497.92c9.311-7.825,17.135-17.235,23.277-27.932H277.734z"/>
|
115
|
+
<path fill="#1F70C1" d="M521.197,53.288H222.167V81.22H532.29C529.715,71.315,525.951,61.906,521.197,53.288z"/>
|
116
|
+
<path fill="#1F70C1" d="M429.279,0H222.167v27.932h278.526C482.072,10.697,456.815,0,429.279,0z"/>
|
117
|
+
<rect fill="#1F70C1" x="277.734" y="106.577" width="83.3" height="27.932"/>
|
118
|
+
<path fill="#1F70C1" d="M444.433,134.509h87.163c2.476-8.914,3.764-18.324,3.764-27.932h-90.927z"/>
|
119
|
+
<rect fill="#1F70C1" x="277.734" y="266.541" width="83.3" height="27.932"/>
|
120
|
+
<path fill="#1F70C1" d="M444.433,266.541v27.932h90.927c0-9.608-1.288-19.017-3.764-27.932H444.433z"/>
|
121
|
+
<path fill="#1F70C1" d="M222.167,400.852h207.112c27.734,0,52.793-10.697,71.513-27.932H222.167V400.852z"/>
|
122
|
+
<rect fill="#1F70C1" x="555.567" y="373.217" width="138.866" height="27.833"/>
|
123
|
+
<rect fill="#1F70C1" x="555.567" y="319.83" width="138.866" height="27.931"/>
|
124
|
+
<rect fill="#1F70C1" x="611.034" y="266.541" width="83.399" height="27.932"/>
|
125
|
+
<rect fill="#1F70C1" x="611.034" y="213.253" width="83.399" height="27.932"/>
|
126
|
+
<polygon fill="#1F70C1" points="733.063,53.288 555.567,53.288 555.567,81.22 742.67,81.22"/>
|
127
|
+
<polygon fill="#1F70C1" points="714.639,0 555.567,0 555.567,27.932 724.247,27.932"/>
|
128
|
+
<rect fill="#1F70C1" x="861.034" y="373.217" width="138.866" height="27.833"/>
|
129
|
+
<rect fill="#1F70C1" x="861.034" y="319.83" width="138.866" height="27.931"/>
|
130
|
+
<rect fill="#1F70C1" x="861.034" y="266.541" width="83.399" height="27.932"/>
|
131
|
+
<rect fill="#1F70C1" x="861.034" y="213.253" width="83.399" height="27.932"/>
|
132
|
+
<polygon fill="#1F70C1" points="861.034,187.896 944.433,187.896 944.433,159.964 861.034,159.964 694.433,159.964 611.034,159.964 611.034,187.896 694.433,187.896 852.219,187.896"/>
|
133
|
+
<polygon fill="#1F70C1" points="944.433,106.577 803.982,106.577 794.374,134.509 944.433,134.509"/>
|
134
|
+
<polygon fill="#1F70C1" points="840.927,0 831.319,27.932 1000,27.932 1000,0"/>
|
135
|
+
<polygon fill="#1F70C1" points="777.734,400.852 787.341,373.217 768.126,373.217"/>
|
136
|
+
<polygon fill="#1F70C1" points="759.311,347.761 796.157,347.761 806.062,319.83 749.505,319.83"/>
|
137
|
+
<polygon fill="#1F70C1" points="740.59,294.473 814.877,294.473 824.683,266.541 730.784,266.541"/>
|
138
|
+
<polygon fill="#1F70C1" points="721.969,241.185 833.597,241.185 843.106,213.253 712.361,213.253"/>
|
139
|
+
<polygon fill="#1F70C1" points="611.034,134.509 761.093,134.509 751.486,106.577 611.034,106.577"/>
|
140
|
+
<polygon fill="#1F70C1" points="812.896,81.22 1000,81.22 1000,53.288 822.405,53.288"/>
|
141
|
+
</g>
|
142
|
+
</svg>
|
143
|
+
`;
|
144
|
+
for (let i = 0; i < 20; i++) {
|
145
|
+
const logo = document.createElement('div');
|
146
|
+
logo.className = 'logo';
|
147
|
+
const size = Math.random() * 30 + 20; // Random size between 20px and 50px
|
148
|
+
logo.style.width = `${size}px`;
|
149
|
+
logo.style.height = `${size}px`;
|
150
|
+
logo.style.left = `${Math.random() * 100}vw`;
|
151
|
+
logo.style.animationDelay = `${Math.random() * 5}s`;
|
152
|
+
logo.innerHTML = svgContent;
|
153
|
+
body.appendChild(logo);
|
154
|
+
}
|
155
|
+
}
|
156
|
+
|
157
|
+
// Call the function to create logos on load
|
158
|
+
createLogos();
|
159
|
+
</script>
|
160
|
+
</body>
|
161
|
+
</html>
|
162
|
+
HTML
|
163
|
+
|
29
164
|
return nil
|
30
165
|
end
|
31
166
|
end
|
32
167
|
|
33
|
-
# start a local web server
|
168
|
+
# start a local web server
|
169
|
+
# then start a browser that will callback the local server upon authentication
|
170
|
+
# store the final query
|
34
171
|
class WebAuth < WebServerSimple
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
# @param endpoint_url [String] e.g. 'https://127.0.0.1:12345'
|
39
|
-
def initialize(endpoint_url)
|
172
|
+
# @param endpoint_url [String] e.g. 'https://127.0.0.1:12345'
|
173
|
+
# @param additionnal_info [String] Information in web page
|
174
|
+
def initialize(endpoint_url, additionnal_info = nil)
|
40
175
|
uri = URI.parse(endpoint_url)
|
41
176
|
super(uri)
|
42
|
-
# parameters for servlet
|
43
177
|
@mutex = Mutex.new
|
44
178
|
@cond = ConditionVariable.new
|
45
179
|
@expected_path = uri.path.empty? ? '/' : uri.path
|
46
180
|
@query = nil
|
181
|
+
@additionnal_info = additionnal_info
|
47
182
|
# last argument (self) is provided to constructor of servlet
|
48
183
|
mount(@expected_path, WebAuthServlet, self)
|
184
|
+
# server runs in thread
|
49
185
|
Thread.new { start }
|
50
186
|
end
|
51
187
|
|
52
|
-
#
|
53
|
-
# @return
|
188
|
+
# Called by web server thread on received request
|
189
|
+
# @return [String] additional information for web page
|
190
|
+
def signal_request(request)
|
191
|
+
raise WEBrick::HTTPStatus::NotFound, "unexpected path: #{request.path}" unless request.path.eql?(@expected_path)
|
192
|
+
# acquire lock and signal change
|
193
|
+
@mutex.synchronize do
|
194
|
+
@query = request.query
|
195
|
+
@cond.signal
|
196
|
+
end
|
197
|
+
return @additionnal_info
|
198
|
+
end
|
199
|
+
|
200
|
+
# wait for request on web server (main thread)
|
201
|
+
# @return [Hash] the query
|
54
202
|
def received_request
|
55
203
|
# wait for signal from thread
|
56
204
|
@mutex.synchronize{@cond.wait(@mutex)}
|
@@ -4,19 +4,20 @@ require 'webrick'
|
|
4
4
|
require 'webrick/https'
|
5
5
|
require 'aspera/log'
|
6
6
|
require 'aspera/assert'
|
7
|
+
require 'aspera/hash_ext'
|
7
8
|
require 'openssl'
|
8
9
|
|
9
10
|
module Aspera
|
10
11
|
# Simple WEBrick server with HTTPS support
|
11
12
|
class WebServerSimple < WEBrick::HTTPServer
|
12
|
-
CERT_PARAMETERS = %i[key cert chain].freeze
|
13
|
+
CERT_PARAMETERS = %i[key cert chain pkcs12].freeze
|
13
14
|
GENERIC_ISSUER = '/C=FR/O=Test/OU=Test/CN=Test'
|
14
15
|
ONE_YEAR_SECONDS = 365 * 24 * 60 * 60
|
15
16
|
|
16
17
|
private_constant :CERT_PARAMETERS, :GENERIC_ISSUER, :ONE_YEAR_SECONDS
|
17
18
|
|
18
19
|
class << self
|
19
|
-
#
|
20
|
+
# Fill and self sign provided certificate
|
20
21
|
def fill_self_signed_cert(cert, key, digest = 'SHA256')
|
21
22
|
cert.subject = cert.issuer = OpenSSL::X509::Name.parse(GENERIC_ISSUER)
|
22
23
|
cert.not_before = cert.not_after = Time.now
|
@@ -58,24 +59,35 @@ module Aspera
|
|
58
59
|
Aspera.assert_type(certificate, Hash)
|
59
60
|
certificate = certificate.symbolize_keys
|
60
61
|
raise "unexpected key in certificate config: only: #{CERT_PARAMETERS.join(', ')}" if certificate.keys.any?{|key|!CERT_PARAMETERS.include?(key)}
|
61
|
-
|
62
|
-
|
62
|
+
if certificate.key?(:pkcs12)
|
63
|
+
Log.log.debug('Using PKCS12 certificate')
|
64
|
+
raise 'pkcs12 requires a key (password)' unless certificate.key?(:key)
|
65
|
+
pkcs12 = OpenSSL::PKCS12.new(File.read(certificate[:pkcs12]), certificate[:key])
|
66
|
+
webrick_options[:SSLCertificate] = pkcs12.certificate
|
67
|
+
webrick_options[:SSLPrivateKey] = pkcs12.key
|
68
|
+
webrick_options[:SSLExtraChainCert] = pkcs12.ca_certs
|
63
69
|
else
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
70
|
+
Log.log.debug('Using PEM certificate')
|
71
|
+
webrick_options[:SSLPrivateKey] = if certificate.key?(:key)
|
72
|
+
OpenSSL::PKey::RSA.new(File.read(certificate[:key]))
|
73
|
+
else
|
74
|
+
OpenSSL::PKey::RSA.new(4096)
|
75
|
+
end
|
76
|
+
if certificate.key?(:cert)
|
77
|
+
webrick_options[:SSLCertificate] = OpenSSL::X509::Certificate.new(File.read(certificate[:cert]))
|
78
|
+
else
|
79
|
+
webrick_options[:SSLCertificate] = OpenSSL::X509::Certificate.new
|
80
|
+
self.class.fill_self_signed_cert(webrick_options[:SSLCertificate], webrick_options[:SSLPrivateKey])
|
81
|
+
end
|
82
|
+
if certificate.key?(:chain)
|
83
|
+
webrick_options[:SSLExtraChainCert] = [OpenSSL::X509::Certificate.new(File.read(certificate[:chain]))]
|
84
|
+
end
|
74
85
|
end
|
75
86
|
end
|
76
87
|
end
|
77
88
|
# call constructor of parent class, but capture STDERR
|
78
|
-
# self signed certificate generates characters on STDERR
|
89
|
+
# self signed certificate generates characters on STDERR
|
90
|
+
# see create_self_signed_cert in webrick/ssl.rb
|
79
91
|
Log.capture_stderr { super(webrick_options) }
|
80
92
|
end
|
81
93
|
|