etna 0.1.32 → 0.1.33

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: cb19650be05ef97433ec00556981388436eb54ee48844055218f3ec290b10f3f
4
- data.tar.gz: fbbee21325ddd316b4a742bc0cd143cba33c4bce635ad767720ed2d330da5446
3
+ metadata.gz: 49dacbad5431a0004c77536e600b8f46ee544effb472a459cd9205cb6d3d0aed
4
+ data.tar.gz: 669d95414188d35adcff630545dde4d49caa43c064a7db3cfc19bf81fba6eff8
5
5
  SHA512:
6
- metadata.gz: ee5548756441715e1944ced74613ae21f328910c3dcd982d638918f0df432d0ca8ee24b4ac62d7f86b17b3325483635e7e2b2ae750f93b82ef9a5629d925de01
7
- data.tar.gz: ce866396a93210565bfc6bd9d5ff1acd69833c3bb9ff55a845e4fbdaad3769b65b0402ee3dc4c0ea937aacb7eac1432b9460ff5612c4344317bd4fcffaec4771
6
+ metadata.gz: d1089f55be44686e14264f9b0df51b926a1ad758d40944010ca4fee40e69ced8d6be1cdaf452dc9ea6e05ab0fb099b597b6c4746f2bee400b090be4ec7a9ec56
7
+ data.tar.gz: d28be042a1210b12472a39502fa3ae8164ff4cd0ee3a2e0b552fb2f520ff5863d15cf88e7d66b9776aa6c7ecd54e1795e3527a16bdb5f7e6f860b944a87a20eb
data/etna.completion CHANGED
@@ -32,7 +32,7 @@ arg_flag_completion_names="$arg_flag_completion_names "
32
32
  multi_flags="$multi_flags "
33
33
  while [[ "$#" != "0" ]]; do
34
34
  if [[ "$#" == "1" ]]; then
35
- all_completion_names="help models project"
35
+ all_completion_names="help models project token"
36
36
  all_completion_names="$all_completion_names $all_flag_completion_names"
37
37
  if [[ -z "$(echo $all_completion_names | xargs)" ]]; then
38
38
  return
@@ -722,6 +722,120 @@ else
722
722
  return
723
723
  fi
724
724
  done
725
+ elif [[ "$1" == "token" ]]; then
726
+ shift
727
+ all_flag_completion_names="$all_flag_completion_names "
728
+ arg_flag_completion_names="$arg_flag_completion_names "
729
+ multi_flags="$multi_flags "
730
+ while [[ "$#" != "0" ]]; do
731
+ if [[ "$#" == "1" ]]; then
732
+ all_completion_names="generate help"
733
+ all_completion_names="$all_completion_names $all_flag_completion_names"
734
+ if [[ -z "$(echo $all_completion_names | xargs)" ]]; then
735
+ return
736
+ fi
737
+ COMPREPLY=($(compgen -W "$all_completion_names" -- "$1"))
738
+ return
739
+ elif [[ "$1" == "generate" ]]; then
740
+ shift
741
+ all_flag_completion_names="$all_flag_completion_names --task --project-name "
742
+ arg_flag_completion_names="$arg_flag_completion_names --project-name "
743
+ multi_flags="$multi_flags "
744
+ declare _completions_for_project_name="__project_name__"
745
+ while [[ "$#" != "0" ]]; do
746
+ if [[ "$#" == "1" ]]; then
747
+ all_completion_names=""
748
+ all_completion_names="$all_completion_names $all_flag_completion_names"
749
+ if [[ -z "$(echo $all_completion_names | xargs)" ]]; then
750
+ return
751
+ fi
752
+ COMPREPLY=($(compgen -W "$all_completion_names" -- "$1"))
753
+ return
754
+ elif [[ -z "$(echo $all_flag_completion_names | xargs)" ]]; then
755
+ return
756
+ elif [[ "$all_flag_completion_names" =~ $1\ ]]; then
757
+ if ! [[ "$multi_flags" =~ $1\ ]]; then
758
+ all_flag_completion_names="${all_flag_completion_names//$1\ /}"
759
+ fi
760
+ a=$1
761
+ shift
762
+ if [[ "$arg_flag_completion_names" =~ $a\ ]]; then
763
+ if [[ "$#" == "1" ]]; then
764
+ a="${a//--/}"
765
+ a="${a//-/_}"
766
+ i="_completions_for_$a"
767
+ all_completion_names="${!i}"
768
+ COMPREPLY=($(compgen -W "$all_completion_names" -- "$1"))
769
+ return
770
+ fi
771
+ shift
772
+ fi
773
+ else
774
+ return
775
+ fi
776
+ done
777
+ return
778
+ elif [[ "$1" == "help" ]]; then
779
+ shift
780
+ all_flag_completion_names="$all_flag_completion_names "
781
+ arg_flag_completion_names="$arg_flag_completion_names "
782
+ multi_flags="$multi_flags "
783
+ while [[ "$#" != "0" ]]; do
784
+ if [[ "$#" == "1" ]]; then
785
+ all_completion_names=""
786
+ all_completion_names="$all_completion_names $all_flag_completion_names"
787
+ if [[ -z "$(echo $all_completion_names | xargs)" ]]; then
788
+ return
789
+ fi
790
+ COMPREPLY=($(compgen -W "$all_completion_names" -- "$1"))
791
+ return
792
+ elif [[ -z "$(echo $all_flag_completion_names | xargs)" ]]; then
793
+ return
794
+ elif [[ "$all_flag_completion_names" =~ $1\ ]]; then
795
+ if ! [[ "$multi_flags" =~ $1\ ]]; then
796
+ all_flag_completion_names="${all_flag_completion_names//$1\ /}"
797
+ fi
798
+ a=$1
799
+ shift
800
+ if [[ "$arg_flag_completion_names" =~ $a\ ]]; then
801
+ if [[ "$#" == "1" ]]; then
802
+ a="${a//--/}"
803
+ a="${a//-/_}"
804
+ i="_completions_for_$a"
805
+ all_completion_names="${!i}"
806
+ COMPREPLY=($(compgen -W "$all_completion_names" -- "$1"))
807
+ return
808
+ fi
809
+ shift
810
+ fi
811
+ else
812
+ return
813
+ fi
814
+ done
815
+ return
816
+ elif [[ -z "$(echo $all_flag_completion_names | xargs)" ]]; then
817
+ return
818
+ elif [[ "$all_flag_completion_names" =~ $1\ ]]; then
819
+ if ! [[ "$multi_flags" =~ $1\ ]]; then
820
+ all_flag_completion_names="${all_flag_completion_names//$1\ /}"
821
+ fi
822
+ a=$1
823
+ shift
824
+ if [[ "$arg_flag_completion_names" =~ $a\ ]]; then
825
+ if [[ "$#" == "1" ]]; then
826
+ a="${a//--/}"
827
+ a="${a//-/_}"
828
+ i="_completions_for_$a"
829
+ all_completion_names="${!i}"
830
+ COMPREPLY=($(compgen -W "$all_completion_names" -- "$1"))
831
+ return
832
+ fi
833
+ shift
834
+ fi
835
+ else
836
+ return
837
+ fi
838
+ done
725
839
  elif [[ -z "$(echo $all_flag_completion_names | xargs)" ]]; then
726
840
  return
727
841
  elif [[ "$all_flag_completion_names" =~ $1\ ]]; then
data/lib/commands.rb CHANGED
@@ -91,6 +91,36 @@ class EtnaApp
91
91
  class Administrate
92
92
  include Etna::CommandExecutor
93
93
 
94
+ class Token
95
+ include Etna::CommandExecutor
96
+
97
+ class Generate < Etna::Command
98
+ include WithLogger
99
+
100
+ boolean_flags << "--task"
101
+ string_flags << "--project-name"
102
+ string_flags << "--email"
103
+
104
+ def execute(email:, task: false, project_name: nil)
105
+ # the token is not required, but can be used if available
106
+ # to generate a task token, so we pass it in here
107
+ janus_client = Etna::Clients::Janus.new(
108
+ token: ENV['TOKEN'],
109
+ ignore_ssl: EtnaApp.instance.config(:ignore_ssl),
110
+ **EtnaApp.instance.config(:janus, EtnaApp.instance.environment))
111
+
112
+ generate_token_workflow = Etna::Clients::Janus::GenerateTokenWorkflow.new(
113
+ janus_client: janus_client,
114
+ token_type: task ? 'task' : 'login',
115
+ email: email,
116
+ project_name: project_name,
117
+ private_key_file: EtnaApp.instance.config(:private_key, EtnaApp.instance.environment)
118
+ )
119
+ generate_token_workflow.generate!
120
+ end
121
+ end
122
+ end
123
+
94
124
  class Project
95
125
  include Etna::CommandExecutor
96
126
 
data/lib/etna/auth.rb CHANGED
@@ -70,6 +70,29 @@ module Etna
70
70
  return route && route.noauth?
71
71
  end
72
72
 
73
+ def janus_approved?(payload, token, request)
74
+ route = server.find_route(request)
75
+
76
+ # some routes don't need janus approval
77
+ return true if route && route.ignore_janus?
78
+
79
+ # only process task tokens right now
80
+ return true unless payload['task']
81
+
82
+ return false unless application.config(:janus) && application.config(:janus)[:host]
83
+
84
+ janus_client = Etna::Clients::Janus.new(
85
+ token: token,
86
+ host: application.config(:janus)[:host]
87
+ )
88
+
89
+ response = janus_client.validate_task_token()
90
+
91
+ return false unless response.code == '200'
92
+
93
+ return true
94
+ end
95
+
73
96
  def approve_user(request)
74
97
  token = request.cookies[application.config(:token_name)] || auth(request, :etna)
75
98
 
@@ -77,6 +100,8 @@ module Etna
77
100
 
78
101
  begin
79
102
  payload, header = application.sign.jwt_decode(token)
103
+
104
+ return false unless janus_approved?(payload, token, request)
80
105
  return request.env['etna.user'] = Etna::User.new(payload.map{|k,v| [k.to_sym, v]}.to_h, token)
81
106
  rescue
82
107
  # bail out if anything goes wrong
data/lib/etna/client.rb CHANGED
@@ -17,6 +17,13 @@ module Etna
17
17
 
18
18
  attr_reader :routes
19
19
 
20
+ def with_headers(headers, &block)
21
+ @request_headers = headers.compact
22
+ result = instance_eval(&block)
23
+ @request_headers = nil
24
+ return result
25
+ end
26
+
20
27
  def signed_route_path(route, params)
21
28
  path = route_path(route,params)
22
29
 
@@ -111,7 +118,7 @@ module Etna
111
118
 
112
119
  def body_request(type, endpoint, params = {}, &block)
113
120
  uri = request_uri(endpoint)
114
- req = type.new(uri.request_uri, request_params)
121
+ req = type.new(uri.request_uri, request_headers)
115
122
  req.body = params.to_json
116
123
  request(uri, req, &block)
117
124
  end
@@ -124,7 +131,7 @@ module Etna
124
131
  else
125
132
  uri.query = URI.encode_www_form(params)
126
133
  end
127
- req = type.new(uri.request_uri, request_params)
134
+ req = type.new(uri.request_uri, request_headers)
128
135
  request(uri, req, &block)
129
136
  end
130
137
 
@@ -132,12 +139,14 @@ module Etna
132
139
  URI("#{@host}#{endpoint}")
133
140
  end
134
141
 
135
- def request_params
142
+ def request_headers
136
143
  {
137
144
  'Content-Type' => 'application/json',
138
145
  'Accept' => 'application/json, text/*',
139
146
  'Authorization' => "Etna #{@token}"
140
- }
147
+ }.update(
148
+ @request_headers || {}
149
+ )
141
150
  end
142
151
 
143
152
  def status_check!(response)
@@ -8,10 +8,9 @@ module Etna
8
8
  attr_reader :host, :token, :ignore_ssl
9
9
  def initialize(host:, token:, ignore_ssl: false)
10
10
  raise "#{self.class.name} client configuration is missing host." unless host
11
- raise "#{self.class.name} client configuration is missing token." unless token
12
11
 
13
12
  @token = token
14
- raise "Your token is expired." if token_expired?
13
+ raise "Your token is expired." if token && token_expired?
15
14
 
16
15
  @etna_client = ::Etna::Client.new(
17
16
  host,
@@ -36,4 +35,4 @@ module Etna
36
35
  end
37
36
  end
38
37
  end
39
- end
38
+ end
@@ -1,2 +1,3 @@
1
1
  require_relative './janus/client'
2
2
  require_relative './janus/models'
3
+ require_relative './janus/workflows'
@@ -57,6 +57,25 @@ module Etna
57
57
 
58
58
  TokenResponse.new(token)
59
59
  end
60
+
61
+ def validate_task_token(validate_task_token_request = ValidateTaskTokenRequest.new)
62
+ token = nil
63
+ @etna_client.post('/api/tokens/task/validate', validate_task_token_request)
64
+ end
65
+
66
+ def get_nonce
67
+ @etna_client.get('/api/tokens/nonce').body
68
+ end
69
+
70
+ def generate_token(token_type, signed_nonce: nil, project_name: nil)
71
+ response = @etna_client.with_headers(
72
+ 'Authorization' => signed_nonce ? "Signed-Nonce #{signed_nonce}" : nil
73
+ ) do
74
+ post('/api/tokens/generate', token_type: token_type, project_name: project_name)
75
+ end
76
+
77
+ response.body
78
+ end
60
79
  end
61
80
  end
62
81
  end
@@ -51,6 +51,12 @@ module Etna
51
51
  end
52
52
  end
53
53
 
54
+ class ValidateTaskTokenRequest
55
+ def map
56
+ []
57
+ end
58
+ end
59
+
54
60
  class HtmlResponse
55
61
  attr_reader :raw
56
62
 
@@ -76,4 +82,4 @@ module Etna
76
82
  end
77
83
  end
78
84
  end
79
- end
85
+ end
@@ -0,0 +1 @@
1
+ require_relative './workflows/generate_token_workflow'
@@ -0,0 +1,77 @@
1
+ # Base workflow for setting up a project by a super user.
2
+ # 1) Creates the project in janus
3
+ # 2) Adds administrator(s) to Janus
4
+ # 3) Refreshes the user's token with the new privileges.
5
+ # 4) Creates the project in .
6
+
7
+ require 'base64'
8
+ require 'json'
9
+ require 'ostruct'
10
+ require_relative '../models'
11
+
12
+ module Etna
13
+ module Clients
14
+ class Janus
15
+ class GenerateTokenWorkflow < Struct.new(:janus_client, :email, :project_name, :token_type, :private_key_file, keyword_init: true)
16
+ def generate!
17
+ nonce = janus_client.get_nonce
18
+
19
+ unless email
20
+ puts "Email address for #{janus_client.host} account?"
21
+ email = STDIN.gets.chomp
22
+ end
23
+
24
+ if use_nonce?
25
+ until private_key_file
26
+ puts "Location of private key file?"
27
+ private_key_file = ::File.expand_path(STDIN.gets.chomp)
28
+ unless File.exists?(private_key_file)
29
+ puts "No such file."
30
+ private_key_file = nil
31
+ end
32
+ end
33
+ end
34
+
35
+ if needs_project_name?
36
+ puts "Project name?"
37
+ project_name = STDIN.gets.chomp
38
+ end
39
+
40
+ token = janus_client.generate_token(token_type, signed_nonce: use_nonce? ? signed_nonce(nonce) : nil, project_name: project_name)
41
+
42
+ puts token
43
+ end
44
+
45
+ private
46
+
47
+ def signed_nonce(nonce)
48
+ private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file))
49
+
50
+ txt_to_sign = "#{nonce}.#{Base64.strict_encode64(email)}"
51
+
52
+ sig = Base64.strict_encode64(
53
+ private_key.sign(OpenSSL::Digest::SHA256.new,txt_to_sign)
54
+ )
55
+
56
+ "#{txt_to_sign}.#{sig}"
57
+ end
58
+
59
+ def use_nonce?
60
+ !task_token? || !janus_client.token
61
+ end
62
+
63
+ def needs_project_name?
64
+ task_token? && !project_name
65
+ end
66
+
67
+ def task_token?
68
+ token_type == 'task'
69
+ end
70
+
71
+ def user
72
+ @user ||= JSON.parse(Base64.urlsafe_decode64(magma_client.token.split('.')[1]))
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -95,7 +95,9 @@ module Etna
95
95
  file_path = ::File.dirname(file_path)
96
96
  {attribute_name => "https://metis.ucsf.edu/#{project_name}/browse/#{bucket_name}/#{file_path}"}
97
97
  else
98
- {attribute_name => {path: "metis://#{project_name}/#{bucket_name}/#{file_path}"}}
98
+ {attribute_name => {
99
+ path: "metis://#{project_name}/#{bucket_name}/#{file_path}",
100
+ original_filename: File.basename(file_path)}}
99
101
  end
100
102
  end
101
103
 
data/lib/etna/command.rb CHANGED
@@ -203,6 +203,7 @@ module Etna
203
203
  @subcommands ||= self.class.constants.sort.reduce({}) do |acc, n|
204
204
  acc.tap do
205
205
  c = self.class.const_get(n)
206
+ next unless c.respond_to?(:instance_methods)
206
207
  next unless c.instance_methods.include?(:find_command)
207
208
  v = c.new(self)
208
209
  acc[v.command_name] = v
@@ -7,6 +7,59 @@ class DirectedGraph
7
7
  attr_reader :children
8
8
  attr_reader :parents
9
9
 
10
+ def full_parentage(n)
11
+ [].tap do |result|
12
+ q = @parents[n].keys.dup
13
+ seen = Set.new
14
+
15
+ until q.empty?
16
+ n = q.shift
17
+ next if seen.include?(n)
18
+ seen.add(n)
19
+
20
+ result << n
21
+ q.push(*@parents[n].keys)
22
+ end
23
+
24
+ result.uniq!
25
+ end
26
+ end
27
+
28
+ def as_normalized_hash(root, include_root = true)
29
+ q = [root]
30
+ {}.tap do |result|
31
+ if include_root
32
+ result[root] = []
33
+ end
34
+
35
+ seen = Set.new
36
+
37
+ until q.empty?
38
+ n = q.shift
39
+ next if seen.include?(n)
40
+ seen.add(n)
41
+
42
+ parentage = full_parentage(n)
43
+
44
+ @children[n].keys.each do |child_node|
45
+ q << child_node
46
+
47
+ if result.include?(n)
48
+ result[n] << child_node
49
+ end
50
+
51
+ parentage.each do |grandparent|
52
+ result[grandparent] << child_node if result.include?(grandparent)
53
+ end
54
+
55
+ result[child_node] = []
56
+ end
57
+ end
58
+
59
+ result.values.each(&:uniq!)
60
+ end
61
+ end
62
+
10
63
  def add_connection(parent, child)
11
64
  children = @children[parent] ||= {}
12
65
  child_children = @children[child] ||= {}
@@ -18,12 +71,13 @@ class DirectedGraph
18
71
  parents[parent] = parent_parents
19
72
  end
20
73
 
21
- def serialized_path_from(root)
74
+ def serialized_path_from(root, include_root = true)
22
75
  seen = Set.new
23
76
  [].tap do |result|
24
- result << root
77
+ result << root if include_root
25
78
  seen.add(root)
26
- path_q = paths_from(root)
79
+ path_q = paths_from(root, include_root)
80
+ traversables = path_q.flatten
27
81
 
28
82
  until path_q.empty?
29
83
  next_path = path_q.shift
@@ -34,7 +88,7 @@ class DirectedGraph
34
88
  next if next_n.nil?
35
89
  next if seen.include?(next_n)
36
90
 
37
- if @parents[next_n].keys.any? { |p| !seen.include?(p) }
91
+ if @parents[next_n].keys.any? { |p| !seen.include?(p) && traversables.include?(p) }
38
92
  next_path.unshift(next_n)
39
93
  path_q.push(next_path)
40
94
  break
@@ -47,9 +101,9 @@ class DirectedGraph
47
101
  end
48
102
  end
49
103
 
50
- def paths_from(root)
104
+ def paths_from(root, include_root = true)
51
105
  [].tap do |result|
52
- parents_of_map = descendants(root)
106
+ parents_of_map = descendants(root, include_root)
53
107
  seen = Set.new
54
108
 
55
109
  parents_of_map.to_a.sort_by { |k, parents| [-parents.length, k.inspect] }.each do |k, parents|
@@ -68,7 +122,7 @@ class DirectedGraph
68
122
  end
69
123
  end
70
124
 
71
- def descendants(parent)
125
+ def descendants(parent, include_root = true)
72
126
  seen = Set.new
73
127
 
74
128
  seen.add(parent)
@@ -90,7 +144,7 @@ class DirectedGraph
90
144
  while child = queue.pop
91
145
  next if seen.include? child
92
146
  seen.add(child)
93
- path = (paths[child] ||= [parent])
147
+ path = (paths[child] ||= (include_root ? [parent] : []))
94
148
 
95
149
  @children[child].keys.each do |child_child|
96
150
  queue.push child_child
data/lib/etna/route.rb CHANGED
@@ -91,6 +91,10 @@ module Etna
91
91
  @auth && @auth[:noauth]
92
92
  end
93
93
 
94
+ def ignore_janus?
95
+ @auth && @auth[:ignore_janus]
96
+ end
97
+
94
98
  private
95
99
 
96
100
  def application
data/lib/helpers.rb CHANGED
@@ -57,9 +57,9 @@ module WithEtnaClients
57
57
  **EtnaApp.instance.config(:metis, environment) || {})
58
58
  end
59
59
 
60
- def janus_client
60
+ def janus_client(opts={})
61
61
  @janus_client ||= Etna::Clients::Janus.new(
62
- token: token,
62
+ token: opts.has_key?(:token) ? opts[:token] : token,
63
63
  ignore_ssl: EtnaApp.instance.config(:ignore_ssl),
64
64
  **EtnaApp.instance.config(:janus, environment) || {})
65
65
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: etna
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.32
4
+ version: 0.1.33
5
5
  platform: ruby
6
6
  authors:
7
7
  - Saurabh Asthana
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-05 00:00:00.000000000 Z
11
+ date: 2021-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -94,20 +94,6 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: concurrent-ruby-ext
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :runtime
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
97
  description: See summary
112
98
  email: Saurabh.Asthana@ucsf.edu
113
99
  executables:
@@ -131,6 +117,8 @@ files:
131
117
  - lib/etna/clients/janus.rb
132
118
  - lib/etna/clients/janus/client.rb
133
119
  - lib/etna/clients/janus/models.rb
120
+ - lib/etna/clients/janus/workflows.rb
121
+ - lib/etna/clients/janus/workflows/generate_token_workflow.rb
134
122
  - lib/etna/clients/magma.rb
135
123
  - lib/etna/clients/magma/client.rb
136
124
  - lib/etna/clients/magma/formatting.rb