etna 0.1.32 → 0.1.33

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