aspera-cli 4.2.0 → 4.2.1
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
- data/README.md +42 -25
- data/docs/README.erb.md +37 -21
- data/docs/test_env.conf +7 -1
- data/examples/aoc.rb +14 -3
- data/examples/faspex4.rb +78 -0
- data/lib/aspera/cli/main.rb +1 -1
- data/lib/aspera/cli/plugins/faspex.rb +39 -31
- data/lib/aspera/cli/plugins/faspex5.rb +19 -17
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/fasp/manager.rb +3 -0
- data/lib/aspera/oauth.rb +18 -38
- data/lib/aspera/web_auth.rb +105 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89eb5bdf4ecda69281c147ad7e2da3027e98a55316f51774c3504c680c4411e4
|
4
|
+
data.tar.gz: 5d8f34bf429f575495890b656df0598f94a4ea67aa8d373d756ea7449c729562
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5a4c5f67e5f9685a1e11e7c1dc8fce6474157471ca5d5d7ad3583b4318a268a04f2fc3b3dcd84c0866f5ccb41a73596e28921d607311347c9537ec1cacec194
|
7
|
+
data.tar.gz: 40901b7baa26597164855e3f66b347666e37737315c075027a8dad81054fde424dd78c3e3432cd40093d7bb06af060981f6643b91a766e08ffe9c86b9d673f93
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
[comment1]: # (Do not edit this README.md, edit docs/README.erb.md, for details, read docs/README.md)
|
2
2
|
# `ascli` : Command Line Interface for IBM Aspera products
|
3
3
|
|
4
|
-
Version : 4.2.
|
4
|
+
Version : 4.2.1
|
5
5
|
|
6
6
|
_Laurent/2016-2021_
|
7
7
|
|
@@ -81,7 +81,7 @@ Once the gem is installed, `ascli` shall be accessible:
|
|
81
81
|
|
82
82
|
```
|
83
83
|
$ ascli --version
|
84
|
-
4.2.
|
84
|
+
4.2.1
|
85
85
|
```
|
86
86
|
|
87
87
|
## First use
|
@@ -1734,7 +1734,7 @@ ascli sync start --parameters=@json:'{"sessions":[{"name":"test","reset":true,"r
|
|
1734
1734
|
```
|
1735
1735
|
$ ascli -h
|
1736
1736
|
NAME
|
1737
|
-
ascli -- a command line tool for Aspera Applications (v4.2.
|
1737
|
+
ascli -- a command line tool for Aspera Applications (v4.2.1)
|
1738
1738
|
|
1739
1739
|
SYNOPSIS
|
1740
1740
|
ascli COMMANDS [OPTIONS] [ARGS]
|
@@ -1879,7 +1879,7 @@ OPTIONS:
|
|
1879
1879
|
|
1880
1880
|
|
1881
1881
|
COMMAND: faspex5
|
1882
|
-
SUBCOMMANDS: node package auth_client
|
1882
|
+
SUBCOMMANDS: node package auth_client jobs
|
1883
1883
|
OPTIONS:
|
1884
1884
|
--url=VALUE URL of application, e.g. https://org.asperafiles.com
|
1885
1885
|
--username=VALUE username to log in
|
@@ -1912,6 +1912,7 @@ OPTIONS:
|
|
1912
1912
|
--delivery-info=VALUE package delivery information (extended value)
|
1913
1913
|
--source-name=VALUE create package from remote source (by name)
|
1914
1914
|
--storage=VALUE Faspex local storage definition
|
1915
|
+
--recipient=VALUE use if recipient is a dropbox (with *)
|
1915
1916
|
--box=ENUM package box: inbox, sent, archive
|
1916
1917
|
|
1917
1918
|
|
@@ -2903,16 +2904,15 @@ to download files.
|
|
2903
2904
|
$ ascli node access_key create --value=@json:'{"id":"eudemo-sedemo","secret":"mystrongsecret","storage":{"type":"local","path":"/data/asperafiles"}}'
|
2904
2905
|
```
|
2905
2906
|
|
2906
|
-
# Plugin: IBM Aspera
|
2907
|
+
# Plugin: IBM Aspera Faspex5
|
2907
2908
|
|
2908
|
-
|
2909
|
-
|
2910
|
-
* the command "v4" requires the use of APIv4, refer to the Faspex Admin manual on how to activate.
|
2911
|
-
* for full details on Faspex API, refer to: [Reference on Developer Site](https://www.ibm.com/products/aspera/developer)
|
2909
|
+
3 authentication methods are supported:
|
2912
2910
|
|
2913
|
-
|
2911
|
+
* boot
|
2912
|
+
* web
|
2913
|
+
* jwt
|
2914
2914
|
|
2915
|
-
|
2915
|
+
For boot method:
|
2916
2916
|
|
2917
2917
|
* open a browser
|
2918
2918
|
* start developer mode
|
@@ -2925,30 +2925,42 @@ Use it as password and use `--auth=boot`.
|
|
2925
2925
|
$ ascli conf id f5boot update --url=https://localhost/aspera/faspex --auth=boot --password=ABC.DEF.GHI...
|
2926
2926
|
```
|
2927
2927
|
|
2928
|
-
|
2928
|
+
For web method, create an API client in Faspex, and use: --auth=web
|
2929
2929
|
|
2930
|
-
|
2931
|
-
|
2932
|
-
$ ascli faspex5 -Pf5boot auth_client create --value=@json:'{"name":"hello","client_type":"public","redirect_uris":["https://localhost:12345"],"allow_jwt_grant":true,"public_key":"'$jsonk'"}'
|
2933
|
-
```
|
2930
|
+
For JWT, create an API client in Faspex with jwt supporot, and use: --auth=jwt
|
2931
|
+
as of beta£3 this does not allow regular users.
|
2934
2932
|
|
2935
|
-
|
2933
|
+
Ready to use Faspex5 with CLI.
|
2934
|
+
|
2935
|
+
Once the graphical registration form exist, ther bootstrap method can be removed.
|
2936
|
+
|
2937
|
+
# Plugin: IBM Aspera Faspex (4.x)
|
2938
|
+
|
2939
|
+
Notes:
|
2940
|
+
|
2941
|
+
* the command "v4" requires the use of APIv4, refer to the Faspex Admin manual on how to activate.
|
2942
|
+
* for full details on Faspex API, refer to: [Reference on Developer Site](https://www.ibm.com/products/aspera/developer)
|
2943
|
+
|
2944
|
+
## Receiving a Package
|
2945
|
+
|
2946
|
+
The command is `package recv`, possible methosd are:
|
2947
|
+
|
2948
|
+
* provide a package id with option `id`
|
2949
|
+
* provide a public link with option `link`
|
2950
|
+
* provide a `faspe:` URI with option `link`
|
2936
2951
|
|
2937
2952
|
```
|
2938
|
-
$
|
2939
|
-
$ ascli
|
2953
|
+
$ ascli faspex package recv --id=12345
|
2954
|
+
$ ascli faspex package recv --link=faspe://...
|
2940
2955
|
```
|
2941
2956
|
|
2942
|
-
|
2957
|
+
If the package is in a specific dropbox, add option `recipient` for both the `list` and `recv` commands.
|
2943
2958
|
|
2944
2959
|
```
|
2945
|
-
$ ascli
|
2946
|
-
|
2947
|
-
```
|
2960
|
+
$ ascli faspex package list --recipient='*thedropboxname'
|
2961
|
+
```
|
2948
2962
|
|
2949
|
-
Ready to use Faspex5 with CLI.
|
2950
2963
|
|
2951
|
-
Once the graphical registration form exist, ther bootstrap method can be removed.
|
2952
2964
|
|
2953
2965
|
## Sending a Package
|
2954
2966
|
|
@@ -3656,6 +3668,11 @@ So, it evolved into `ascli`:
|
|
3656
3668
|
|
3657
3669
|
# Changes (Release notes)
|
3658
3670
|
|
3671
|
+
* 4.2.1
|
3672
|
+
|
3673
|
+
* new: command `faspex package recv` supports link of type: `faspe:`
|
3674
|
+
* new: command `faspex package recv` supports option `recipient` to specify dropbox with leading `*`
|
3675
|
+
|
3659
3676
|
* 4.2.0
|
3660
3677
|
|
3661
3678
|
* new: command `aoc remind` to receive organization membership by email
|
data/docs/README.erb.md
CHANGED
@@ -2335,16 +2335,15 @@ to download files.
|
|
2335
2335
|
$ <%=cmd%> node access_key create --value=@json:'{"id":"eudemo-sedemo","secret":"mystrongsecret","storage":{"type":"local","path":"/data/asperafiles"}}'
|
2336
2336
|
```
|
2337
2337
|
|
2338
|
-
# Plugin: IBM Aspera
|
2338
|
+
# Plugin: IBM Aspera Faspex5
|
2339
2339
|
|
2340
|
-
|
2341
|
-
|
2342
|
-
* the command "v4" requires the use of APIv4, refer to the Faspex Admin manual on how to activate.
|
2343
|
-
* for full details on Faspex API, refer to: [Reference on Developer Site](https://www.ibm.com/products/aspera/developer)
|
2340
|
+
3 authentication methods are supported:
|
2344
2341
|
|
2345
|
-
|
2342
|
+
* boot
|
2343
|
+
* web
|
2344
|
+
* jwt
|
2346
2345
|
|
2347
|
-
|
2346
|
+
For boot method:
|
2348
2347
|
|
2349
2348
|
* open a browser
|
2350
2349
|
* start developer mode
|
@@ -2357,30 +2356,42 @@ Use it as password and use `--auth=boot`.
|
|
2357
2356
|
$ <%=cmd%> conf id f5boot update --url=https://localhost/aspera/faspex --auth=boot --password=ABC.DEF.GHI...
|
2358
2357
|
```
|
2359
2358
|
|
2360
|
-
|
2359
|
+
For web method, create an API client in Faspex, and use: --auth=web
|
2361
2360
|
|
2362
|
-
|
2363
|
-
|
2364
|
-
$ <%=cmd%> faspex5 -Pf5boot auth_client create --value=@json:'{"name":"hello","client_type":"public","redirect_uris":["https://localhost:12345"],"allow_jwt_grant":true,"public_key":"'$jsonk'"}'
|
2365
|
-
```
|
2361
|
+
For JWT, create an API client in Faspex with jwt supporot, and use: --auth=jwt
|
2362
|
+
as of beta£3 this does not allow regular users.
|
2366
2363
|
|
2367
|
-
|
2364
|
+
Ready to use Faspex5 with CLI.
|
2365
|
+
|
2366
|
+
Once the graphical registration form exist, ther bootstrap method can be removed.
|
2367
|
+
|
2368
|
+
# Plugin: IBM Aspera Faspex (4.x)
|
2369
|
+
|
2370
|
+
Notes:
|
2371
|
+
|
2372
|
+
* the command "v4" requires the use of APIv4, refer to the Faspex Admin manual on how to activate.
|
2373
|
+
* for full details on Faspex API, refer to: [Reference on Developer Site](https://www.ibm.com/products/aspera/developer)
|
2374
|
+
|
2375
|
+
## Receiving a Package
|
2376
|
+
|
2377
|
+
The command is `package recv`, possible methosd are:
|
2378
|
+
|
2379
|
+
* provide a package id with option `id`
|
2380
|
+
* provide a public link with option `link`
|
2381
|
+
* provide a `faspe:` URI with option `link`
|
2368
2382
|
|
2369
2383
|
```
|
2370
|
-
$
|
2371
|
-
$ <%=cmd%>
|
2384
|
+
$ <%=cmd%> faspex package recv --id=12345
|
2385
|
+
$ <%=cmd%> faspex package recv --link=faspe://...
|
2372
2386
|
```
|
2373
2387
|
|
2374
|
-
|
2388
|
+
If the package is in a specific dropbox, add option `recipient` for both the `list` and `recv` commands.
|
2375
2389
|
|
2376
2390
|
```
|
2377
|
-
$ <%=cmd%>
|
2378
|
-
|
2379
|
-
```
|
2391
|
+
$ <%=cmd%> faspex package list --recipient='*thedropboxname'
|
2392
|
+
```
|
2380
2393
|
|
2381
|
-
Ready to use Faspex5 with CLI.
|
2382
2394
|
|
2383
|
-
Once the graphical registration form exist, ther bootstrap method can be removed.
|
2384
2395
|
|
2385
2396
|
## Sending a Package
|
2386
2397
|
|
@@ -3070,6 +3081,11 @@ So, it evolved into <%=tool%>:
|
|
3070
3081
|
|
3071
3082
|
* <%= gemspec.version.to_s %>
|
3072
3083
|
|
3084
|
+
* new: command `faspex package recv` supports link of type: `faspe:`
|
3085
|
+
* new: command `faspex package recv` supports option `recipient` to specify dropbox with leading `*`
|
3086
|
+
|
3087
|
+
* 4.2.0
|
3088
|
+
|
3073
3089
|
* new: command `aoc remind` to receive organization membership by email
|
3074
3090
|
* new: in `preview` option `value` to filter out on file name
|
3075
3091
|
* new: `initdemo` to initialize for demo server
|
data/docs/test_env.conf
CHANGED
@@ -5,7 +5,7 @@ default:
|
|
5
5
|
config: cli_default
|
6
6
|
aoc: tst_aoc1
|
7
7
|
faspex: tst_faspex
|
8
|
-
faspex5:
|
8
|
+
faspex5: tst_faspex5_web
|
9
9
|
shares: tst_shares_1
|
10
10
|
shares2: tst_shares2
|
11
11
|
node: tst_node
|
@@ -52,6 +52,12 @@ tst_faspex5_boot:
|
|
52
52
|
auth: your value here
|
53
53
|
username: your value here
|
54
54
|
password: your value here
|
55
|
+
tst_faspex5_web:
|
56
|
+
url: your value here
|
57
|
+
auth: your value here
|
58
|
+
redirect_uri: your value here
|
59
|
+
client_id: your value here
|
60
|
+
client_secret: your value here
|
55
61
|
tst_faspex5:
|
56
62
|
url: your value here
|
57
63
|
auth: your value here
|
data/examples/aoc.rb
CHANGED
@@ -4,11 +4,22 @@ require 'aspera/log'
|
|
4
4
|
|
5
5
|
Aspera::Log.instance.level=:debug
|
6
6
|
|
7
|
+
if ! ARGV.length.eql?(3)
|
8
|
+
Aspera::Log.log.error("wrong number of args: #{ARGV.length}")
|
9
|
+
Aspera::Log.log.error("Usage: #{$0} <aoc URL> <aoc username> <aoc private key content>")
|
10
|
+
Aspera::Log.log.error("Example: #{$0} https://myorg.ibmaspera.com john@example.com $(cat /home/john/my_key.pem)")
|
11
|
+
Process.exit(1)
|
12
|
+
end
|
13
|
+
|
14
|
+
aoc_url=ARGV[0]
|
15
|
+
aoc_user=ARGV[1]
|
16
|
+
aoc_key_value=ARGV[2]
|
17
|
+
|
7
18
|
aocapi=Aspera::AoC.new(
|
8
|
-
url:
|
19
|
+
url: aoc_url,
|
9
20
|
auth: :jwt,
|
10
|
-
private_key:
|
11
|
-
username:
|
21
|
+
private_key: aoc_key_value,
|
22
|
+
username: aoc_user,
|
12
23
|
scope: 'user:all',
|
13
24
|
subpath: 'api/v1')
|
14
25
|
|
data/examples/faspex4.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'aspera/rest'
|
3
|
+
require 'aspera/log'
|
4
|
+
require 'aspera/fasp/local'
|
5
|
+
require 'aspera/cli/listener/line_dump'
|
6
|
+
require 'aspera/cli/extended_value'
|
7
|
+
|
8
|
+
# set high log level for the example
|
9
|
+
Aspera::Log.instance.level=:debug
|
10
|
+
|
11
|
+
# set folder where SDK is installed
|
12
|
+
# (if ascp is not there, the lib will try to find in usual locations)
|
13
|
+
Aspera::Fasp::Installation.instance.folder='.'
|
14
|
+
|
15
|
+
if ! ARGV.length.eql?(3)
|
16
|
+
Aspera::Log.log.error("wrong number of args: #{ARGV.length}")
|
17
|
+
Aspera::Log.log.error("Usage: #{$0} <faspex URL> <faspex username> <faspex password>")
|
18
|
+
Aspera::Log.log.error("Example: #{$0} https://faspex.com/aspera/faspex john p@sSw0rd")
|
19
|
+
Process.exit(1)
|
20
|
+
end
|
21
|
+
|
22
|
+
faspex_url=ARGV[0]
|
23
|
+
faspex_user=ARGV[1]
|
24
|
+
faspex_pass=ARGV[2]
|
25
|
+
|
26
|
+
# 1: demo API v3
|
27
|
+
#---------------
|
28
|
+
|
29
|
+
# create REST API object
|
30
|
+
api_v3=Aspera::Rest.new({
|
31
|
+
:base_url => faspex_url,
|
32
|
+
:auth => {
|
33
|
+
:type => :basic,
|
34
|
+
:username => faspex_user,
|
35
|
+
:password => faspex_pass
|
36
|
+
}})
|
37
|
+
|
38
|
+
api_v3.read('me')
|
39
|
+
|
40
|
+
# 2: send a package
|
41
|
+
#---------------
|
42
|
+
|
43
|
+
# create a sample file to send
|
44
|
+
file_to_send='./myfile.bin'
|
45
|
+
File.open(file_to_send, "w") {|f| f.write("sample data") }
|
46
|
+
# package creation parameters
|
47
|
+
package_create_params={'delivery'=>{'title'=>'test package','recipients'=>['aspera.user1@gmail.com'],'sources'=>[{'paths'=>[file_to_send]}]}}
|
48
|
+
pkg_created=api_v3.create('send',package_create_params)[:data]
|
49
|
+
# get transfer specification
|
50
|
+
transfer_spec=pkg_created['xfer_sessions'].first
|
51
|
+
# set paths of files to send
|
52
|
+
transfer_spec['paths']=['source'=>file_to_send]
|
53
|
+
# get the local agent (i.e. ascp)
|
54
|
+
client=Aspera::Fasp::Local.new
|
55
|
+
# disable ascp output on stdout to not mix with JSON events
|
56
|
+
client.quiet=true
|
57
|
+
# start transfer (asynchronous)
|
58
|
+
job_id=client.start_transfer(transfer_spec)
|
59
|
+
result=client.wait_for_transfers_completion
|
60
|
+
# notify of any transfer error
|
61
|
+
result.select{|i|!i.eql?(:success)}.each do |e|
|
62
|
+
Aspera::Log.log.error("A transfer error occured: #{e.message}")
|
63
|
+
end
|
64
|
+
|
65
|
+
# 1: demo API v4
|
66
|
+
#---------------
|
67
|
+
api_v4=Aspera::Rest.new({
|
68
|
+
:base_url => faspex_url+'/api',
|
69
|
+
:auth => {
|
70
|
+
:type => :oauth2,
|
71
|
+
:base_url => faspex_url+'/auth/oauth2',
|
72
|
+
:grant => :header_userpass,
|
73
|
+
:user_name => faspex_user,
|
74
|
+
:user_pass => faspex_pass,
|
75
|
+
:scope => 'admin'
|
76
|
+
}})
|
77
|
+
|
78
|
+
Aspera::Log.dump('users',api_v4.read('users')[:data])
|
data/lib/aspera/cli/main.rb
CHANGED
@@ -256,7 +256,7 @@ module Aspera
|
|
256
256
|
@plugin_env[:formater].display_results({:type=>:single_object,:data=>@opt_mgr.declared_options(false)})
|
257
257
|
Process.exit(0)
|
258
258
|
end
|
259
|
-
# locking for
|
259
|
+
# locking for single execution (only after "per plugin" option, in case lock port is there)
|
260
260
|
lock_port=@opt_mgr.get_option(:lock_port,:optional)
|
261
261
|
if !lock_port.nil?
|
262
262
|
begin
|
@@ -26,6 +26,7 @@ module Aspera
|
|
26
26
|
self.options.add_opt_simple(:delivery_info,'package delivery information (extended value)')
|
27
27
|
self.options.add_opt_simple(:source_name,'create package from remote source (by name)')
|
28
28
|
self.options.add_opt_simple(:storage,'Faspex local storage definition')
|
29
|
+
self.options.add_opt_simple(:recipient,'use if recipient is a dropbox (with *)')
|
29
30
|
self.options.add_opt_list(:box,[:inbox,:sent,:archive],'package box')
|
30
31
|
self.options.set_option(:box,:inbox)
|
31
32
|
self.options.parse_options!
|
@@ -107,7 +108,7 @@ module Aspera
|
|
107
108
|
PACKAGE_MATCH_FIELD='package_id'
|
108
109
|
|
109
110
|
def mailbox_all_entries
|
110
|
-
|
111
|
+
recipient_name=self.options.get_option(:recipient,:optional) || self.options.get_option(:username,:mandatory)
|
111
112
|
mailbox=self.options.get_option(:box,:mandatory)
|
112
113
|
all_inbox_xml=api_v3.call({:operation=>'GET',:subpath=>"#{mailbox}.atom",:headers=>{'Accept'=>'application/xml'}})[:http].body
|
113
114
|
all_inbox_data=XmlSimple.xml_in(all_inbox_xml, {'ForceArray' => true})
|
@@ -116,7 +117,7 @@ module Aspera
|
|
116
117
|
result.each do |e|
|
117
118
|
case mailbox
|
118
119
|
when :inbox,:archive
|
119
|
-
recipient=e['to'].select{|i|i['name'].first.eql?(
|
120
|
+
recipient=e['to'].select{|i|i['name'].first.eql?(recipient_name)}.first
|
120
121
|
e[PACKAGE_MATCH_FIELD]=recipient.nil? ? 'n/a' : recipient['recipient_delivery_id'].first
|
121
122
|
when :sent
|
122
123
|
e[PACKAGE_MATCH_FIELD]=e['delivery_id'].first
|
@@ -208,9 +209,18 @@ module Aspera
|
|
208
209
|
#Log.dump('transfer_spec',transfer_spec)
|
209
210
|
return Main.result_transfer(self.transfer.start(transfer_spec,{:src=>:node_gen3}))
|
210
211
|
when :recv
|
211
|
-
|
212
|
-
|
213
|
-
|
212
|
+
link_url=self.options.get_option(:link,:optional)
|
213
|
+
# list of faspex ID/URI to download
|
214
|
+
pkg_id_uri=nil
|
215
|
+
skip_ids_data=[]
|
216
|
+
skip_ids_persistency=nil
|
217
|
+
case link_url
|
218
|
+
when nil
|
219
|
+
# usual case: no link
|
220
|
+
when /^faspe:/
|
221
|
+
pkg_id_uri=[{:id=>'package',:uri=>link_url}]
|
222
|
+
else
|
223
|
+
link_data=self.class.get_link_data(link_url)
|
214
224
|
if !link_data[:subpath].match(%r{external_deliveries/})
|
215
225
|
raise CliBadArgument,"pub link is #{link_data[:subpath]}, expecting external_deliveries/"
|
216
226
|
end
|
@@ -218,7 +228,7 @@ module Aspera
|
|
218
228
|
api_public_link=Rest.new({:base_url=>link_data[:base_url]})
|
219
229
|
pkgdatares=api_public_link.call({:operation=>'GET',:subpath=>link_data[:subpath],:url_params=>{:passcode=>link_data[:query]['passcode']},:headers=>{'Accept'=>'application/xml'}})
|
220
230
|
if !pkgdatares[:http].body.start_with?('<?xml ')
|
221
|
-
OpenApplication.instance.uri(
|
231
|
+
OpenApplication.instance.uri(link_url)
|
222
232
|
raise CliError, 'no such package'
|
223
233
|
end
|
224
234
|
package_entry=XmlSimple.xml_in(pkgdatares[:http].body, {'ForceArray' => false})
|
@@ -227,32 +237,30 @@ module Aspera
|
|
227
237
|
transfer_spec['direction']='receive'
|
228
238
|
return Main.result_transfer(self.transfer.start(transfer_spec,{:src=>:node_gen3}))
|
229
239
|
end # public link
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
240
|
+
if pkg_id_uri.nil?
|
241
|
+
# get command line parameters
|
242
|
+
delivid=self.options.get_option(:id,:mandatory)
|
243
|
+
if self.options.get_option(:once_only,:mandatory)
|
244
|
+
skip_ids_persistency=PersistencyActionOnce.new(
|
245
|
+
manager: @agents[:persistency],
|
246
|
+
data: skip_ids_data,
|
247
|
+
ids: ['faspex_recv',self.options.get_option(:url,:mandatory),self.options.get_option(:username,:mandatory),self.options.get_option(:box,:mandatory).to_s])
|
248
|
+
end
|
249
|
+
if delivid.eql?(VAL_ALL)
|
250
|
+
pkg_id_uri=mailbox_all_entries.map{|i|{:id=>i[PACKAGE_MATCH_FIELD],:uri=>self.class.get_fasp_uri_from_entry(i)}}
|
251
|
+
# TODO : remove ids from skip not present in inbox
|
252
|
+
# skip_ids_data.select!{|id|pkg_id_uri.select{|p|p[:id].eql?(id)}}
|
253
|
+
pkg_id_uri.select!{|i|!skip_ids_data.include?(i[:id])}
|
254
|
+
else
|
255
|
+
# TODO: delivery id is the right one if package was receive by group
|
256
|
+
endpoint=case self.options.get_option(:box,:mandatory)
|
257
|
+
when :inbox,:archive;'received'
|
258
|
+
when :sent; 'sent'
|
259
|
+
end
|
260
|
+
entry_xml=api_v3.call({:operation=>'GET',:subpath=>"#{endpoint}/#{delivid}",:headers=>{'Accept'=>'application/xml'}})[:http].body
|
261
|
+
package_entry=XmlSimple.xml_in(entry_xml, {'ForceArray' => true})
|
262
|
+
pkg_id_uri=[{:id=>delivid,:uri=>self.class.get_fasp_uri_from_entry(package_entry)}]
|
252
263
|
end
|
253
|
-
entry_xml=api_v3.call({:operation=>'GET',:subpath=>"#{endpoint}/#{delivid}",:headers=>{'Accept'=>'application/xml'}})[:http].body
|
254
|
-
package_entry=XmlSimple.xml_in(entry_xml, {'ForceArray' => true})
|
255
|
-
pkg_id_uri=[{:id=>delivid,:uri=>self.class.get_fasp_uri_from_entry(package_entry)}]
|
256
264
|
end
|
257
265
|
Log.dump(:pkg_id_uri,pkg_id_uri)
|
258
266
|
return Main.result_status('no package') if pkg_id_uri.empty?
|
@@ -17,14 +17,20 @@ module Aspera
|
|
17
17
|
options.set_option(:auth,:jwt)
|
18
18
|
options.parse_options!
|
19
19
|
end
|
20
|
-
ACTIONS=[ :node, :package, :auth_client ]
|
21
20
|
|
22
21
|
def set_api
|
23
22
|
faxpex5_api_base_url=options.get_option(:url,:mandatory)
|
24
23
|
faxpex5_api_v5_url="#{faxpex5_api_base_url}/api/v5"
|
25
24
|
faxpex5_api_auth_url="#{faxpex5_api_base_url}/auth"
|
26
25
|
case options.get_option(:auth,:mandatory)
|
26
|
+
when :boot
|
27
|
+
# the password here is the token copied directly from browser in developer mode
|
28
|
+
@api_v5=Rest.new({
|
29
|
+
:base_url => faxpex5_api_v5_url,
|
30
|
+
:headers => {'Authorization'=>options.get_option(:password,:mandatory)},
|
31
|
+
})
|
27
32
|
when :web
|
33
|
+
# opens a browser and ask user to auth using web
|
28
34
|
@api_v5=Rest.new({
|
29
35
|
:base_url => faxpex5_api_v5_url,
|
30
36
|
:auth => {
|
@@ -34,18 +40,9 @@ module Aspera
|
|
34
40
|
:state => SecureRandom.uuid,
|
35
41
|
:client_id => options.get_option(:client_id,:mandatory),
|
36
42
|
:redirect_uri => options.get_option(:redirect_uri,:mandatory),
|
37
|
-
#:token_field =>'auth_token',
|
38
|
-
#:path_token => 'token',
|
39
|
-
#:path_authorize => 'authorize',
|
40
|
-
#:userpass_body => {name: faxpex5_username,password: faxpex5_password}
|
41
43
|
}})
|
42
|
-
when :boot
|
43
|
-
@api_v5=Rest.new({
|
44
|
-
:base_url => faxpex5_api_v5_url,
|
45
|
-
:headers => {'Authorization'=>options.get_option(:password,:mandatory)},
|
46
|
-
})
|
47
44
|
when :jwt
|
48
|
-
#
|
45
|
+
# currently Faspex 5 beta 3 only supports non-user based apis (e.g. jobs)
|
49
46
|
app_client_id=options.get_option(:client_id,:mandatory)
|
50
47
|
@api_v5=Rest.new({
|
51
48
|
:base_url => faxpex5_api_v5_url,
|
@@ -55,16 +52,17 @@ module Aspera
|
|
55
52
|
:grant => :jwt,
|
56
53
|
:client_id => app_client_id,
|
57
54
|
:client_secret => options.get_option(:client_secret,:mandatory),
|
58
|
-
#:redirect_uri => options.get_option(:redirect_uri,:mandatory),
|
59
55
|
:jwt_subject => "client:#{app_client_id}", # TODO Mmmm
|
60
56
|
:jwt_audience => app_client_id, # TODO Mmmm
|
61
57
|
:jwt_private_key_obj => OpenSSL::PKey::RSA.new(options.get_option(:private_key,:mandatory)),
|
62
|
-
:jwt_is_f5 => true,
|
58
|
+
:jwt_is_f5 => true, # TODO: remove when clarified
|
63
59
|
:jwt_headers => {typ: 'JWT'}
|
64
60
|
}})
|
65
61
|
end
|
66
62
|
end
|
67
63
|
|
64
|
+
ACTIONS=[ :node, :package, :auth_client, :jobs ]
|
65
|
+
|
68
66
|
#
|
69
67
|
def execute_action
|
70
68
|
set_api
|
@@ -75,6 +73,9 @@ module Aspera
|
|
75
73
|
return self.entity_action(api_auth,'oauth_clients',nil,:id,nil,true)
|
76
74
|
when :node
|
77
75
|
return self.entity_action(@api_v5,'nodes',nil,:id,nil,true)
|
76
|
+
when :jobs
|
77
|
+
# to test JWT
|
78
|
+
return self.entity_action(@api_v5,'jobs',nil,:id,nil,true)
|
78
79
|
when :package
|
79
80
|
command=options.get_next_command([:list,:show,:send,:receive])
|
80
81
|
case command
|
@@ -86,7 +87,7 @@ module Aspera
|
|
86
87
|
return {:type => :single_object, :data=>@api_v5.read("packages/#{id}")[:data]}
|
87
88
|
when :send
|
88
89
|
parameters=options.get_option(:value,:mandatory)
|
89
|
-
raise CliBadArgument,'
|
90
|
+
raise CliBadArgument,'value must be hash, refer to API' unless parameters.is_a?(Hash)
|
90
91
|
package=@api_v5.create('packages',parameters)[:data]
|
91
92
|
transfer_spec=@api_v5.create("packages/#{package['id']}/transfer_spec/upload",{transfer_type: 'Connect'})[:data]
|
92
93
|
transfer_spec.delete('authentication')
|
@@ -98,16 +99,17 @@ module Aspera
|
|
98
99
|
skip_ids_data=[]
|
99
100
|
skip_ids_persistency=nil
|
100
101
|
if options.get_option(:once_only,:mandatory)
|
102
|
+
# read ids from persistency
|
101
103
|
skip_ids_persistency=PersistencyActionOnce.new(
|
102
104
|
manager: @agents[:persistency],
|
103
105
|
data: skip_ids_data,
|
104
106
|
ids: ['faspex_recv',options.get_option(:url,:mandatory),options.get_option(:username,:mandatory),pkg_type])
|
105
107
|
end
|
106
108
|
if pack_id.eql?(VAL_ALL)
|
107
|
-
#
|
109
|
+
# TODO: if packages have same name, they will overwrite
|
108
110
|
parameters=options.get_option(:value,:optional)
|
109
|
-
parameters||={
|
110
|
-
raise CliBadArgument,'value filter must be
|
111
|
+
parameters||={'type'=>'received','subtype'=>'mypackages','limit'=>1000}
|
112
|
+
raise CliBadArgument,'value filter must be Hash (API GET)' unless parameters.is_a?(Hash)
|
111
113
|
package_ids=@api_v5.read('packages',parameters)[:data]['packages'].map{|p|p['id']}
|
112
114
|
package_ids.select!{|i|!skip_ids_data.include?(i)}
|
113
115
|
end
|
data/lib/aspera/cli/version.rb
CHANGED
data/lib/aspera/fasp/manager.rb
CHANGED
@@ -72,6 +72,9 @@ module Aspera
|
|
72
72
|
# start_transfer(transfer_spec,options) : start and wait for completion
|
73
73
|
# wait_for_transfers_completion : wait for termination of all transfers, @return list of : :success or error message
|
74
74
|
# optional: shutdown
|
75
|
+
|
76
|
+
# This checks the validity of the value returned by wait_for_transfers_completion
|
77
|
+
# it must be a list of :success or exception
|
75
78
|
def self.validate_status_list(statuses)
|
76
79
|
raise "internal error: bad statuses type: #{statuses.class}" unless statuses.is_a?(Array)
|
77
80
|
raise "internal error: bad statuses content: #{statuses}" unless statuses.select{|i|!i.eql?(:success) and !i.is_a?(StandardError)}.empty?
|
data/lib/aspera/oauth.rb
CHANGED
@@ -1,25 +1,26 @@
|
|
1
1
|
require 'aspera/open_application'
|
2
|
+
require 'aspera/web_auth'
|
2
3
|
require 'base64'
|
3
4
|
require 'date'
|
4
5
|
require 'socket'
|
5
6
|
require 'securerandom'
|
6
7
|
|
7
8
|
module Aspera
|
8
|
-
#
|
9
|
+
# Implement OAuth 2 for the REST client and generate a bearer token
|
9
10
|
# call get_authorization() to get a token.
|
10
11
|
# bearer tokens are kept in memory and also in a file cache for later re-use
|
11
12
|
# if a token is expired (api returns 4xx), call again get_authorization({:refresh=>true})
|
12
13
|
class Oauth
|
13
14
|
private
|
14
15
|
# remove 5 minutes to account for time offset (TODO: configurable?)
|
15
|
-
|
16
|
+
JWT_NOTBEFORE_OFFSET_SEC=300
|
16
17
|
# one hour validity (TODO: configurable?)
|
17
|
-
|
18
|
+
JWT_EXPIRY_OFFSET_SEC=3600
|
19
|
+
# tokens older than 30 minutes will be discarded from cache
|
20
|
+
TOKEN_CACHE_EXPIRY_SEC=1800
|
21
|
+
# a prefix for persistency of tokens (garbage collect)
|
18
22
|
PERSIST_CATEGORY_TOKEN='token'
|
19
|
-
|
20
|
-
TOKEN_EXPIRY_SECONDS=1800
|
21
|
-
THANK_YOU_HTML = "<html><head><title>Ok</title></head><body><h1>Thank you !</h1><p>You can close this window.</p></body></html>"
|
22
|
-
private_constant :JWT_NOTBEFORE_OFFSET,:JWT_EXPIRY_OFFSET,:PERSIST_CATEGORY_TOKEN,:TOKEN_EXPIRY_SECONDS,:THANK_YOU_HTML
|
23
|
+
private_constant :JWT_NOTBEFORE_OFFSET_SEC,:JWT_EXPIRY_OFFSET_SEC,:PERSIST_CATEGORY_TOKEN,:TOKEN_CACHE_EXPIRY_SEC
|
23
24
|
class << self
|
24
25
|
# OAuth methods supported
|
25
26
|
def auth_types
|
@@ -89,12 +90,18 @@ module Aspera
|
|
89
90
|
# we could check that host is localhost or local address
|
90
91
|
end
|
91
92
|
# cleanup expired tokens
|
92
|
-
self.class.persist_mgr.garbage_collect(PERSIST_CATEGORY_TOKEN,
|
93
|
+
self.class.persist_mgr.garbage_collect(PERSIST_CATEGORY_TOKEN,TOKEN_CACHE_EXPIRY_SEC)
|
93
94
|
end
|
94
95
|
|
95
96
|
# open the login page, wait for code and check_code, then return code
|
96
97
|
def goto_page_and_get_code(login_page_url,check_code)
|
97
|
-
|
98
|
+
Log.log.info("login_page_url=#{login_page_url}".bg_red.gray)
|
99
|
+
# start a web server to receive request code
|
100
|
+
webserver=WebAuth.new(@params[:redirect_uri])
|
101
|
+
# start browser on login page
|
102
|
+
OpenApplication.instance.uri(login_page_url)
|
103
|
+
# wait for code in request
|
104
|
+
request_params=webserver.get_request
|
98
105
|
Log.log.error("state does not match") if !check_code.eql?(request_params['state'])
|
99
106
|
code=request_params['code']
|
100
107
|
return code
|
@@ -222,8 +229,8 @@ module Aspera
|
|
222
229
|
:iss => @params[:client_id], # issuer
|
223
230
|
:sub => @params[:jwt_subject], # subject
|
224
231
|
:aud => @params[:jwt_audience], # audience
|
225
|
-
:nbf => seconds_since_epoch-
|
226
|
-
:exp => seconds_since_epoch+
|
232
|
+
:nbf => seconds_since_epoch-JWT_NOTBEFORE_OFFSET_SEC, # not before
|
233
|
+
:exp => seconds_since_epoch+JWT_EXPIRY_OFFSET_SEC # expiration
|
227
234
|
}
|
228
235
|
# Hum.. compliant ? TODO: remove when Faspex5 API is clarified
|
229
236
|
if @params[:jwt_is_f5]
|
@@ -311,32 +318,5 @@ module Aspera
|
|
311
318
|
return 'Bearer '+token_data[@params[:token_field]]
|
312
319
|
end
|
313
320
|
|
314
|
-
# open the login page, wait for code and return parameters
|
315
|
-
def self.goto_page_and_get_request(redirect_uri,login_page_url,html_page=THANK_YOU_HTML)
|
316
|
-
Log.log.info "login_page_url=#{login_page_url}".bg_red().gray()
|
317
|
-
# browser start is not blocking, we hope here that starting is slower than opening port
|
318
|
-
OpenApplication.instance.uri(login_page_url)
|
319
|
-
port=URI.parse(redirect_uri).port
|
320
|
-
Log.log.info "listening on port #{port}"
|
321
|
-
request_params=nil
|
322
|
-
TCPServer.open('127.0.0.1', port) { |webserver|
|
323
|
-
Log.log.info "server=#{webserver}"
|
324
|
-
websession = webserver.accept
|
325
|
-
sleep 1 # TODO: sometimes: returns nil ? use webrick ?
|
326
|
-
line = websession.gets.chomp
|
327
|
-
Log.log.info "line=#{line}"
|
328
|
-
if ! line.start_with?('GET /?') then
|
329
|
-
raise "unexpected request"
|
330
|
-
end
|
331
|
-
request = line.partition('?').last.partition(' ').first
|
332
|
-
data=URI.decode_www_form(request)
|
333
|
-
request_params=data.to_h
|
334
|
-
Log.log.debug "request_params=#{request_params}"
|
335
|
-
websession.print "HTTP/1.1 200/OK\r\nContent-type:text/html\r\n\r\n#{html_page}"
|
336
|
-
websession.close
|
337
|
-
}
|
338
|
-
return request_params
|
339
|
-
end
|
340
|
-
|
341
321
|
end # OAuth
|
342
322
|
end # Aspera
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'webrick'
|
2
|
+
require 'webrick/https'
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
module Aspera
|
6
|
+
class WebAuth
|
7
|
+
# server for auth page
|
8
|
+
class FxGwServlet < WEBrick::HTTPServlet::AbstractServlet
|
9
|
+
def initialize(server,info) # additional args get here
|
10
|
+
@shared=info
|
11
|
+
end
|
12
|
+
|
13
|
+
def do_GET (request, response)
|
14
|
+
if ! request.path.eql?(@shared[:expected_path])
|
15
|
+
response.status=400
|
16
|
+
return
|
17
|
+
end
|
18
|
+
@shared[:mutex].synchronize do
|
19
|
+
@shared[:query]=request.query
|
20
|
+
@shared[:cond].signal
|
21
|
+
end
|
22
|
+
response.status=200
|
23
|
+
response.content_type = 'text/html'
|
24
|
+
response.body='<html><head><title>Ok</title></head><body><h1>Thank you !</h1><p>You can close this window.</p></body></html>'
|
25
|
+
end
|
26
|
+
end # FxGwServlet
|
27
|
+
|
28
|
+
# generates and adds self signed cert to provided webrick options
|
29
|
+
def fill_self_signed_cert(options)
|
30
|
+
key = OpenSSL::PKey::RSA.new(4096)
|
31
|
+
cert = OpenSSL::X509::Certificate.new
|
32
|
+
cert.subject = cert.issuer = OpenSSL::X509::Name.parse('/C=FR/O=Test/OU=Test/CN=Test')
|
33
|
+
cert.not_before = Time.now
|
34
|
+
cert.not_after = Time.now + 365 * 24 * 60 * 60
|
35
|
+
cert.public_key = key.public_key
|
36
|
+
cert.serial = 0x0
|
37
|
+
cert.version = 2
|
38
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
39
|
+
ef.issuer_certificate = cert
|
40
|
+
ef.subject_certificate = cert
|
41
|
+
cert.extensions = [
|
42
|
+
ef.create_extension('basicConstraints','CA:TRUE', true),
|
43
|
+
ef.create_extension('subjectKeyIdentifier', 'hash'),
|
44
|
+
# ef.create_extension('keyUsage', 'cRLSign,keyCertSign', true),
|
45
|
+
]
|
46
|
+
cert.add_extension(ef.create_extension('authorityKeyIdentifier','keyid:always,issuer:always'))
|
47
|
+
cert.sign(key, OpenSSL::Digest::SHA256.new)
|
48
|
+
options[:SSLPrivateKey] = key
|
49
|
+
options[:SSLCertificate] = cert
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(endpoint_url)
|
53
|
+
uri=URI.parse(endpoint_url)
|
54
|
+
webrick_options = {
|
55
|
+
:app => WebAuth,
|
56
|
+
:Port => uri.port,
|
57
|
+
:Logger => Log.log
|
58
|
+
}
|
59
|
+
uri_path=uri.path.empty? ? '/' : uri.path
|
60
|
+
case uri.scheme
|
61
|
+
when 'http'
|
62
|
+
Log.log.debug('HTTP mode')
|
63
|
+
when 'https'
|
64
|
+
webrick_options[:SSLEnable]=true
|
65
|
+
webrick_options[:SSLVerifyClient]=OpenSSL::SSL::VERIFY_NONE
|
66
|
+
case 0
|
67
|
+
when 0
|
68
|
+
# generate self signed cert
|
69
|
+
fill_self_signed_cert(webrick_options)
|
70
|
+
when 1
|
71
|
+
# short
|
72
|
+
webrick_options[:SSLCertName] = [ [ 'CN',WEBrick::Utils::getservername ] ]
|
73
|
+
Log.log.error(">>>#{webrick_options[:SSLCertName]}")
|
74
|
+
when 2
|
75
|
+
# good cert
|
76
|
+
webrick_options[:SSLPrivateKey] =OpenSSL::PKey::RSA.new(File.read('/Users/laurent/workspace/Tools/certificate/myserver.key'))
|
77
|
+
webrick_options[:SSLCertificate] = OpenSSL::X509::Certificate.new(File.read('/Users/laurent/workspace/Tools/certificate/myserver.crt'))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
# parameters for servlet
|
81
|
+
@shared_info={
|
82
|
+
expected_path: uri_path,
|
83
|
+
mutex: Mutex.new,
|
84
|
+
cond: ConditionVariable.new
|
85
|
+
}
|
86
|
+
@server = WEBrick::HTTPServer.new(webrick_options)
|
87
|
+
@server.mount(uri_path, FxGwServlet, @shared_info) # additional args provided to constructor
|
88
|
+
Thread.new { @server.start }
|
89
|
+
end
|
90
|
+
|
91
|
+
# wait for request on web server
|
92
|
+
# @return Hash the query
|
93
|
+
def get_request
|
94
|
+
Log.log.debug('get_request')
|
95
|
+
# called only once
|
96
|
+
raise "error, called twice ?" if @server.nil?
|
97
|
+
@shared_info[:mutex].synchronize do
|
98
|
+
@shared_info[:cond].wait(@shared_info[:mutex])
|
99
|
+
end
|
100
|
+
@server.shutdown
|
101
|
+
@server=nil
|
102
|
+
return @shared_info[:query]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aspera-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.2.
|
4
|
+
version: 4.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Laurent Martin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: xml-simple
|
@@ -213,6 +213,7 @@ files:
|
|
213
213
|
- docs/test_env.conf
|
214
214
|
- docs/transfer_spec.html
|
215
215
|
- examples/aoc.rb
|
216
|
+
- examples/faspex4.rb
|
216
217
|
- examples/proxy.pac
|
217
218
|
- examples/transfer.rb
|
218
219
|
- lib/aspera/aoc.rb
|
@@ -298,6 +299,7 @@ files:
|
|
298
299
|
- lib/aspera/sync.rb
|
299
300
|
- lib/aspera/temp_file_manager.rb
|
300
301
|
- lib/aspera/uri_reader.rb
|
302
|
+
- lib/aspera/web_auth.rb
|
301
303
|
homepage: https://github.com/IBM/aspera-cli
|
302
304
|
licenses:
|
303
305
|
- Apache-2.0
|