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 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