etna 0.1.28 → 0.1.33

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/etna.completion +115 -1
  3. data/lib/commands.rb +30 -0
  4. data/lib/etna/application.rb +4 -0
  5. data/lib/etna/auth.rb +25 -0
  6. data/lib/etna/client.rb +43 -6
  7. data/lib/etna/clients/base_client.rb +2 -3
  8. data/lib/etna/clients/janus.rb +1 -0
  9. data/lib/etna/clients/janus/client.rb +19 -0
  10. data/lib/etna/clients/janus/models.rb +7 -1
  11. data/lib/etna/clients/janus/workflows.rb +1 -0
  12. data/lib/etna/clients/janus/workflows/generate_token_workflow.rb +77 -0
  13. data/lib/etna/clients/magma/models.rb +13 -1
  14. data/lib/etna/clients/magma/workflows/create_project_workflow.rb +1 -1
  15. data/lib/etna/clients/magma/workflows/crud_workflow.rb +19 -2
  16. data/lib/etna/clients/magma/workflows/file_linking_workflow.rb +3 -1
  17. data/lib/etna/clients/magma/workflows/materialize_magma_record_files_workflow.rb +43 -28
  18. data/lib/etna/clients/magma/workflows/model_synchronization_workflow.rb +1 -1
  19. data/lib/etna/clients/magma/workflows/update_attributes_from_csv_workflow.rb +19 -6
  20. data/lib/etna/clients/magma/workflows/walk_model_tree_workflow.rb +33 -6
  21. data/lib/etna/clients/metis/client.rb +6 -1
  22. data/lib/etna/clients/metis/models.rb +15 -0
  23. data/lib/etna/clients/metis/workflows/metis_download_workflow.rb +15 -11
  24. data/lib/etna/clients/metis/workflows/metis_upload_workflow.rb +83 -13
  25. data/lib/etna/clients/metis/workflows/sync_metis_data_workflow.rb +43 -79
  26. data/lib/etna/command.rb +1 -0
  27. data/lib/etna/cwl.rb +4 -0
  28. data/lib/etna/directed_graph.rb +88 -5
  29. data/lib/etna/filesystem.rb +143 -15
  30. data/lib/etna/hmac.rb +2 -2
  31. data/lib/etna/route.rb +4 -0
  32. data/lib/etna/spec/auth.rb +6 -6
  33. data/lib/etna/user.rb +15 -11
  34. data/lib/helpers.rb +2 -2
  35. metadata +18 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f72e476ca58afe56ac2d2334ae35dfe159409892f54bda2624b73d83da574309
4
- data.tar.gz: 89e4ccce963ca79bddcd668da5817c94d95ce609612d2d34a635707588dfe88a
3
+ metadata.gz: 49dacbad5431a0004c77536e600b8f46ee544effb472a459cd9205cb6d3d0aed
4
+ data.tar.gz: 669d95414188d35adcff630545dde4d49caa43c064a7db3cfc19bf81fba6eff8
5
5
  SHA512:
6
- metadata.gz: 76f01865ad042215703f305f1ceaf7eeee4febacb114490481e91c9cbbfbf83bedb004d725d18f90d4ffa34efb8ed91741c91e0244fb60f704fba7e3f2e65057
7
- data.tar.gz: e8c352582c61d655881d8290a3be01a03cd27c4b3167d60bab077256a5222cfba94a7401130cff14582230fdcef2068498abaf66fda4176b0c3fc486b3cf202e
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
 
@@ -91,6 +91,10 @@ module Etna::Application
91
91
  (ENV["#{self.class.name.upcase}_ENV"] || :development).to_sym
92
92
  end
93
93
 
94
+ def id
95
+ ENV["APP_NAME"] || self.class.name.snake_case.split(/::/).last
96
+ end
97
+
94
98
  def find_descendents(klass)
95
99
  ObjectSpace.each_object(Class).select do |k|
96
100
  k < klass
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,36 @@ 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
+
27
+ def signed_route_path(route, params)
28
+ path = route_path(route,params)
29
+
30
+ signatory = params.delete(:signatory)
31
+
32
+ return path unless signatory
33
+
34
+ hmac = Etna::Hmac.new(
35
+ signatory,
36
+ method: route[:method],
37
+ host: URI(@host).host,
38
+ path: path,
39
+ expiration: (DateTime.now + 10).iso8601,
40
+ id: signatory.id,
41
+ nonce: SecureRandom.hex,
42
+ headers: params.except(*route[:params].map(&:to_sym))
43
+ )
44
+
45
+ url_params = hmac.url_params(route[:method] == 'GET')
46
+
47
+ return url_params[:path] + '?' + url_params[:query]
48
+ end
49
+
20
50
  def route_path(route, params)
21
51
  Etna::Route.path(route[:route], params)
22
52
  end
@@ -60,14 +90,19 @@ module Etna
60
90
  @routes.each do |route|
61
91
  next unless route[:name]
62
92
  self.define_singleton_method(route[:name]) do |params = {}|
63
-
64
93
  missing_params = (route[:params] - params.keys.map(&:to_s))
94
+
65
95
  unless missing_params.empty?
66
96
  raise ArgumentError, "Missing required #{missing_params.size > 1 ?
67
97
  'params' : 'param'} #{missing_params.join(', ')}"
68
98
  end
69
99
 
70
- response = send(route[:method].downcase, route_path(route, params), params)
100
+ response = send(
101
+ route[:method].downcase,
102
+ signed_route_path(route, params),
103
+ params
104
+ )
105
+
71
106
  if block_given?
72
107
  yield response
73
108
  else
@@ -83,7 +118,7 @@ module Etna
83
118
 
84
119
  def body_request(type, endpoint, params = {}, &block)
85
120
  uri = request_uri(endpoint)
86
- req = type.new(uri.request_uri, request_params)
121
+ req = type.new(uri.request_uri, request_headers)
87
122
  req.body = params.to_json
88
123
  request(uri, req, &block)
89
124
  end
@@ -96,7 +131,7 @@ module Etna
96
131
  else
97
132
  uri.query = URI.encode_www_form(params)
98
133
  end
99
- req = type.new(uri.request_uri, request_params)
134
+ req = type.new(uri.request_uri, request_headers)
100
135
  request(uri, req, &block)
101
136
  end
102
137
 
@@ -104,12 +139,14 @@ module Etna
104
139
  URI("#{@host}#{endpoint}")
105
140
  end
106
141
 
107
- def request_params
142
+ def request_headers
108
143
  {
109
144
  'Content-Type' => 'application/json',
110
145
  'Accept' => 'application/json, text/*',
111
146
  'Authorization' => "Etna #{@token}"
112
- }
147
+ }.update(
148
+ @request_headers || {}
149
+ )
113
150
  end
114
151
 
115
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