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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1cc7508185a5c551f30a30e54a0cfa2d0ac7931a1bbb565bc7b15e98cca31479
4
- data.tar.gz: ca9aae212a8411ddcfa4d1db4aa842fb273bf56fe247490b31037dcb0d628c94
3
+ metadata.gz: 89eb5bdf4ecda69281c147ad7e2da3027e98a55316f51774c3504c680c4411e4
4
+ data.tar.gz: 5d8f34bf429f575495890b656df0598f94a4ea67aa8d373d756ea7449c729562
5
5
  SHA512:
6
- metadata.gz: 929bcc3dee9158eb60c810796ed8dc88cfd52d187abb47cb6733adb09971e67849ce1b0bf5cff7daf3dc1f7af327490b574ab8351c94435c6ac83f0674423cd8
7
- data.tar.gz: 5c3cba44333dfca675a3cc3b0b77bb6d16654f322b580c936c911551dc85bcc55b16b01649faac2a5ae1f0805b3a2a83315c648ea6c1fb7987c569dc431887b4
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.0
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.0
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.0)
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 Faspex
2907
+ # Plugin: IBM Aspera Faspex5
2907
2908
 
2908
- Notes:
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
- ## Faspex 5 Beta1
2911
+ * boot
2912
+ * web
2913
+ * jwt
2914
2914
 
2915
- As the web UI does not yet allow adding API client yet, the way to use CLI is:
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
- An JWT client can then be created with a private key:
2928
+ For web method, create an API client in Faspex, and use: --auth=web
2929
2929
 
2930
- ```
2931
- $ jsonk=$(openssl rsa -in ~/.aspera/ascli/aspera_on_cloud_key -pubout 2> /dev/null | sed -e :a -e N -e '$!ba' -e 's/\n/\\n/g')
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
- or deleted by name:
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
- $ id=$(ascli faspex5 auth_client list --select=@json:'{"name":"hello"}' --fields=client_id --format=csv)
2939
- $ ascli faspex5 auth_client delete --id=$id
2953
+ $ ascli faspex package recv --id=12345
2954
+ $ ascli faspex package recv --link=faspe://...
2940
2955
  ```
2941
2956
 
2942
- Once the API client is created with a client_id and secret (result of create command), create a configuration:
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 conf id f5 update --url=https://localhost/aspera/faspex --auth=jwt --client-id=abcd --client-secret=def --username=pierre@example.com --private-key=@val:@file:~/.aspera/ascli/aspera_on_cloud_key
2946
- $ ascli conf id default set faspex5 f5
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 Faspex
2338
+ # Plugin: IBM Aspera Faspex5
2339
2339
 
2340
- Notes:
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
- ## Faspex 5 Beta1
2342
+ * boot
2343
+ * web
2344
+ * jwt
2346
2345
 
2347
- As the web UI does not yet allow adding API client yet, the way to use CLI is:
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
- An JWT client can then be created with a private key:
2359
+ For web method, create an API client in Faspex, and use: --auth=web
2361
2360
 
2362
- ```
2363
- $ jsonk=$(openssl rsa -in ~/.aspera/ascli/aspera_on_cloud_key -pubout 2> /dev/null | sed -e :a -e N -e '$!ba' -e 's/\n/\\n/g')
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
- or deleted by name:
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
- $ id=$(<%=cmd%> faspex5 auth_client list --select=@json:'{"name":"hello"}' --fields=client_id --format=csv)
2371
- $ <%=cmd%> faspex5 auth_client delete --id=$id
2384
+ $ <%=cmd%> faspex package recv --id=12345
2385
+ $ <%=cmd%> faspex package recv --link=faspe://...
2372
2386
  ```
2373
2387
 
2374
- Once the API client is created with a client_id and secret (result of create command), create a configuration:
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%> conf id f5 update --url=https://localhost/aspera/faspex --auth=jwt --client-id=abcd --client-secret=def --username=pierre@example.com --private-key=@val:@file:~/.aspera/ascli/aspera_on_cloud_key
2378
- $ <%=cmd%> conf id default set faspex5 f5
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: tst_faspex5_boot
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: 'https://myorg.ibmaspera.com',
19
+ url: aoc_url,
9
20
  auth: :jwt,
10
- private_key: File.read('path/to_your_private_key.pem'),
11
- username: 'my.email@example.com',
21
+ private_key: aoc_key_value,
22
+ username: aoc_user,
12
23
  scope: 'user:all',
13
24
  subpath: 'api/v1')
14
25
 
@@ -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])
@@ -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 singkle execution (only after "per plugin" option, in case lock port is there)
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
- my_user_name=self.options.get_option(:username,:mandatory)
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?(my_user_name)}.first
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
- public_link_url=self.options.get_option(:link,:optional)
212
- if !public_link_url.nil?
213
- link_data=self.class.get_link_data(public_link_url)
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(public_link_url)
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
- # get command line parameters
231
- delivid=self.options.get_option(:id,:mandatory)
232
- # list of faspex ID/URI to download
233
- pkg_id_uri=nil
234
- skip_ids_data=[]
235
- skip_ids_persistency=nil
236
- if self.options.get_option(:once_only,:mandatory)
237
- skip_ids_persistency=PersistencyActionOnce.new(
238
- manager: @agents[:persistency],
239
- data: skip_ids_data,
240
- ids: ['faspex_recv',self.options.get_option(:url,:mandatory),self.options.get_option(:username,:mandatory),self.options.get_option(:box,:mandatory).to_s])
241
- end
242
- if delivid.eql?(VAL_ALL)
243
- pkg_id_uri=mailbox_all_entries.map{|i|{:id=>i[PACKAGE_MATCH_FIELD],:uri=>self.class.get_fasp_uri_from_entry(i)}}
244
- # TODO : remove ids from skip not present in inbox
245
- # skip_ids_data.select!{|id|pkg_id_uri.select{|p|p[:id].eql?(id)}}
246
- pkg_id_uri.select!{|i|!skip_ids_data.include?(i[:id])}
247
- else
248
- # TODO: delivery id is the right one if package was receive by group
249
- endpoint=case self.options.get_option(:box,:mandatory)
250
- when :inbox,:archive;'received'
251
- when :sent; 'sent'
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
- #raise "JWT to be implemented"
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,'package value must be hash, refer to API' unless parameters.is_a?(Hash)
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
- # todo: if packages have same name, they will overwrite
109
+ # TODO: if packages have same name, they will overwrite
108
110
  parameters=options.get_option(:value,:optional)
109
- parameters||={"type"=>"received","subtype"=>"mypackages","limit"=>1000}
110
- raise CliBadArgument,'value filter must be hash (API GET)' unless parameters.is_a?(Hash)
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
@@ -1,5 +1,5 @@
1
1
  module Aspera
2
2
  module Cli
3
- VERSION = "4.2.0"
3
+ VERSION = "4.2.1"
4
4
  end
5
5
  end
@@ -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
- # implement OAuth 2 for the REST client and generate a bearer token
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
- JWT_NOTBEFORE_OFFSET=300
16
+ JWT_NOTBEFORE_OFFSET_SEC=300
16
17
  # one hour validity (TODO: configurable?)
17
- JWT_EXPIRY_OFFSET=3600
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
- # tokens older than 30 minutes will be discarded
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,TOKEN_EXPIRY_SECONDS)
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
- request_params=self.class.goto_page_and_get_request(@params[:redirect_uri],login_page_url)
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-JWT_NOTBEFORE_OFFSET, # not before
226
- :exp => seconds_since_epoch+JWT_EXPIRY_OFFSET # expiration
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.0
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-08-24 00:00:00.000000000 Z
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