aspera-cli 4.2.0 → 4.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|