etna 0.1.43 → 0.1.44

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: 2e30f0d83fdf8f5781f723080ddd042d371f3131a415c85ec441b56178cef27c
4
- data.tar.gz: a543a3464ca34e0d6daedddbf702cf067f7dfdcbdc972e4e9a9a626118d5fbbd
3
+ metadata.gz: bea4cbc1d94fca11f52c02b75ef487d11e7b0a6f2aaa7d64c03a09803891d627
4
+ data.tar.gz: cb764eb044b4b711ddcc4ea46efa598763a34fedf2dc1984a9a2e88234094e2c
5
5
  SHA512:
6
- metadata.gz: d11b9f2882e7499decb4854c5c399bce24e31b14ff52c83006708bd4f83706e4a754d2c96838241b1885ae2ad4f2eb360a0f815b38f5009f1a75fb4fe7246275
7
- data.tar.gz: 3447fa61b285fdbe78e5e0646b36d489f3fd2677cce2415ebe001f8b691ddc281b9514a12bd9424ed9023f0b58609482edba77156066377ab1840e06bf359a5a
6
+ metadata.gz: 30100186f9af0e44654cc0a72d23eff04872f9479e119d308ac749bac4c7125358ea6e62f0a5cafd751512068715ac7831cbb5770ec554394c35c8c3781c30ce
7
+ data.tar.gz: dcee2b46965993e00d0dcae0a516b501a2afbbe968f1c9df35e45fcff4cd05535af7de52ddfec7e345943f3a1f4536aca196dd8150632d1a628559af482c6d7a
@@ -50,13 +50,29 @@ module Etna::Application
50
50
  Etna::Application.register(self)
51
51
  end
52
52
 
53
+ # a <- b
54
+ def deep_merge(a, b)
55
+ if a.is_a?(Hash)
56
+ if b.is_a?(Hash)
57
+ b.keys.each do |b_key|
58
+ a[b_key] = deep_merge(a[b_key], b[b_key])
59
+ end
60
+
61
+ return a
62
+ end
63
+ end
64
+
65
+ a.nil? ? b : a
66
+ end
67
+
53
68
  def configure(opts)
54
69
  @config = opts
55
70
 
56
- # Apply environmental variables of the form "APP__x__y__z"
57
- prefix = "#{self.class.name.upcase}__"
58
- ENV.keys.select { |k| k.start_with?(prefix) }.each do |key|
59
- path = key.split("__", -1)
71
+ # Apply environmental variables of the form "ETNA__x__y__z_FILE"
72
+ # by reading the given file.
73
+ prefix = "ETNA__"
74
+ ENV.keys.select { |k| k.start_with?(prefix) && k.end_with?("_FILE") }.each do |key|
75
+ path = key.sub(/_FILE/, '').split("__", -1)
60
76
  path.shift # drop the first, just app name
61
77
 
62
78
  target = @config
@@ -65,7 +81,9 @@ module Etna::Application
65
81
  target = (target[n.downcase.to_sym] ||= {})
66
82
  end
67
83
 
68
- target[path.last.downcase.to_sym] ||= ENV[key]
84
+ v = YAML.load(File.read(ENV[key]))
85
+ target[path.last.downcase.to_sym] =
86
+ deep_merge(target[path.last.downcase.to_sym], v)
69
87
  end
70
88
 
71
89
  if (rollbar_config = config(:rollbar)) && rollbar_config[:access_token]
data/lib/etna/auth.rb CHANGED
@@ -76,23 +76,63 @@ module Etna
76
76
  # some routes don't need janus approval
77
77
  return true if route && route.ignore_janus?
78
78
 
79
- # only process task tokens right now
80
- return true unless payload['task']
79
+ # process task tokens
80
+ payload['task'] ? valid_task_token?(token) : true
81
+ end
82
+
83
+ def resource_projects(token)
84
+ return [] unless has_janus_config?
81
85
 
82
- return false unless application.config(:janus) && application.config(:janus)[:host]
86
+ janus_client(token).get_projects.projects.select do |project|
87
+ project.resource
88
+ end
89
+ rescue
90
+ # If encounter any issue with Janus, we'll return no resource projects
91
+ []
92
+ end
83
93
 
84
- janus_client = Etna::Clients::Janus.new(
94
+ def janus_client(token)
95
+ Etna::Clients::Janus.new(
85
96
  token: token,
86
97
  host: application.config(:janus)[:host]
87
98
  )
99
+ end
100
+
101
+ def valid_task_token?(token)
102
+ return false unless has_janus_config?
88
103
 
89
- response = janus_client.validate_task_token
104
+ response = janus_client(token).validate_task_token
90
105
 
91
106
  return false unless response.code == '200'
92
107
 
93
108
  return true
94
109
  end
95
110
 
111
+ def has_janus_config?
112
+ application.config(:janus) && application.config(:janus)[:host]
113
+ end
114
+
115
+ def update_payload(payload, token, request)
116
+ route = server.find_route(request)
117
+
118
+ payload = payload.map{|k,v| [k.to_sym, v]}.to_h
119
+
120
+ return payload unless route
121
+
122
+ begin
123
+ permissions = Etna::Permissions.from_encoded_permissions(payload[:perm])
124
+
125
+ resource_projects(token).each do |resource_project|
126
+ permissions.add_permission(
127
+ Etna::Permission.new('v', resource_project.project_name)
128
+ )
129
+ end
130
+ payload[:perm] = permissions.to_string
131
+ end if (!route.ignore_janus? && route.has_user_constraint?(:can_view?))
132
+
133
+ payload
134
+ end
135
+
96
136
  def approve_user(request)
97
137
  token = request.cookies[application.config(:token_name)] || auth(request, :etna)
98
138
 
@@ -102,8 +142,11 @@ module Etna
102
142
  payload, header = application.sign.jwt_decode(token)
103
143
 
104
144
  return false unless janus_approved?(payload, token, request)
105
- return request.env['etna.user'] = Etna::User.new(payload.map{|k,v| [k.to_sym, v]}.to_h, token)
106
- rescue
145
+ return request.env['etna.user'] = Etna::User.new(
146
+ update_payload(payload, token, request),
147
+ token)
148
+ rescue => e
149
+ application.logger.log_error(e)
107
150
  # bail out if anything goes wrong
108
151
  return false
109
152
  end
@@ -8,25 +8,33 @@ module Etna
8
8
  module Clients
9
9
  class Janus < Etna::Clients::BaseClient
10
10
  def get_project(get_project_request = GetProjectRequest.new)
11
- html = nil
11
+ json = nil
12
12
  @etna_client.get(
13
- "/project/#{get_project_request.project_name}",
14
- get_project_request) do |res|
15
- html = res.body
13
+ "/api/admin/#{get_project_request.project_name}/info") do |res|
14
+ json = JSON.parse(res.body, symbolize_names: true)
16
15
  end
17
16
 
18
- HtmlResponse.new(html)
17
+ GetProjectResponse.new(json)
18
+ end
19
+
20
+ def get_projects()
21
+ json = nil
22
+ @etna_client.get('/api/user/projects') do |res|
23
+ json = JSON.parse(res.body, symbolize_names: true)
24
+ end
25
+
26
+ GetProjectsResponse.new(json)
19
27
  end
20
28
 
21
29
  def add_project(add_project_request = AddProjectRequest.new)
22
- @etna_client.post('/add_project', add_project_request) do |res|
30
+ @etna_client.post('/api/admin/add_project', add_project_request) do |res|
23
31
  # Redirect, no response data
24
32
  end
25
33
  end
26
34
 
27
35
  def add_user(add_user_request = AddUserRequest.new)
28
36
  @etna_client.post(
29
- "/add_user/#{add_user_request.project_name}",
37
+ "/api/admin/#{add_user_request.project_name}/add_user",
30
38
  add_user_request) do |res|
31
39
  # Redirect, no response data
32
40
  end
@@ -34,7 +42,7 @@ module Etna
34
42
 
35
43
  def update_permission(update_permission_request = UpdatePermissionRequest.new)
36
44
  @etna_client.post(
37
- "/update_permission/#{update_permission_request.project_name}",
45
+ "/api/admin/#{update_permission_request.project_name}/update_permission",
38
46
  update_permission_request) do |res|
39
47
  # Redirect, no response data
40
48
  end
@@ -42,16 +50,7 @@ module Etna
42
50
 
43
51
  def refresh_token(refresh_token_request = RefreshTokenRequest.new)
44
52
  token = nil
45
- @etna_client.get('/refresh_token', refresh_token_request) do |res|
46
- token = res.body
47
- end
48
-
49
- TokenResponse.new(token)
50
- end
51
-
52
- def viewer_token(viewer_token_request = ViewerTokenRequest.new)
53
- token = nil
54
- @etna_client.get('/viewer_token', viewer_token_request) do |res|
53
+ @etna_client.post('/api/tokens/generate') do |res|
55
54
  token = res.body
56
55
  end
57
56
 
@@ -69,6 +69,54 @@ module Etna
69
69
  end
70
70
  end
71
71
 
72
+ class GetProjectResponse
73
+ attr_reader :raw
74
+
75
+ def initialize(raw = '')
76
+ @raw = raw
77
+ end
78
+
79
+ def project
80
+ Project.new(@raw[:project])
81
+ end
82
+ end
83
+
84
+ class GetProjectsResponse
85
+ attr_reader :raw
86
+
87
+ def initialize(raw = '')
88
+ @raw = raw
89
+ end
90
+
91
+ def projects
92
+ @raw[:projects].map do |project|
93
+ Project.new(project)
94
+ end
95
+ end
96
+ end
97
+
98
+ class Project
99
+ def initialize(raw = '')
100
+ @raw = raw
101
+ end
102
+
103
+ def project_name
104
+ @raw[:project_name]
105
+ end
106
+
107
+ def project_name_full
108
+ @raw[:project_name_full]
109
+ end
110
+
111
+ def permissions
112
+ @raw[:permissions] || []
113
+ end
114
+
115
+ def resource
116
+ !!@raw[:resource]
117
+ end
118
+ end
119
+
72
120
  class TokenResponse
73
121
  attr_reader :raw
74
122
 
@@ -6,6 +6,47 @@ require_relative './models'
6
6
 
7
7
  module Etna
8
8
  module Clients
9
+ class LocalMagmaClient # May only be used from within magma app.
10
+ def logger
11
+ ::Magma.instance.logger
12
+ end
13
+
14
+ def user
15
+ permissions = Etna::Permissions.new([])
16
+ permissions.add_permission(Etna::Permission.new('A', 'administration'))
17
+ @user ||= Etna::User.new({name: 'Admin', email: 'etnaagent@ucsf.edu', perm: permissions.to_string})
18
+ end
19
+
20
+ def fake_request(request)
21
+ Rack::Request.new({
22
+ 'rack.request.params' => request.as_json,
23
+ 'etna.logger' => self.logger,
24
+ 'etna.user' => user,
25
+ 'etna.request_id' => 'local',
26
+ })
27
+ end
28
+
29
+ def parse_json_response(response)
30
+ status, headers, body = response
31
+ body = body.join('')
32
+ if status < 200 || status >= 300
33
+ raise Etna::Error.new(body, status)
34
+ end
35
+
36
+ JSON.parse(body)
37
+ end
38
+
39
+ def retrieve(retrieval_request = RetrievalRequest.new)
40
+ controller = ::RetrieveController.new(fake_request(retrieval_request), nil)
41
+ Magma::RetrievalResponse.new(parse_json_response(controller.action))
42
+ end
43
+
44
+ def update_model(update_model_request = UpdateModelRequest.new)
45
+ controller = ::UpdateModelController.new(fake_request(update_model_request), nil)
46
+ Magma::UpdateModelResponse.new(parse_json_response(controller.action))
47
+ end
48
+ end
49
+
9
50
  class Magma < Etna::Clients::BaseClient
10
51
  # This endpoint returns models and records by name:
11
52
  # e.g. params:
@@ -22,12 +22,12 @@ module Etna
22
22
  include JsonSerializableStruct
23
23
  end
24
24
 
25
- class UpdateRequest < Struct.new(:revisions, :project_name, keyword_init: true)
25
+ class UpdateRequest < Struct.new(:revisions, :project_name, :dry_run, keyword_init: true)
26
26
  include JsonSerializableStruct
27
27
  include MultipartSerializableNestedHash
28
28
 
29
29
  def initialize(**params)
30
- super({revisions: {}}.update(params))
30
+ super({revisions: {}, dry_run: false}.update(params))
31
31
  end
32
32
 
33
33
  def update_revision(model_name, record_name, attrs)
@@ -110,7 +110,7 @@ module Etna
110
110
  include JsonSerializableStruct
111
111
  end
112
112
 
113
- class AddProjectAction < Struct.new(:action_name, keyword_init: true)
113
+ class AddProjectAction < Struct.new(:action_name, :no_metis_bucket, keyword_init: true)
114
114
  include JsonSerializableStruct
115
115
 
116
116
  def initialize(**args)
@@ -215,8 +215,8 @@ module Etna
215
215
  end
216
216
 
217
217
  def model(model_key)
218
- return nil unless raw.include?(model_key)
219
- Model.new(raw[model_key])
218
+ return nil unless raw.include?(model_key.to_s)
219
+ Model.new(raw[model_key.to_s])
220
220
  end
221
221
 
222
222
  def all
@@ -434,8 +434,8 @@ module Etna
434
434
  end
435
435
 
436
436
  def attribute(attribute_key)
437
- return nil unless raw.include?(attribute_key)
438
- Attribute.new(raw[attribute_key])
437
+ return nil unless raw.include?(attribute_key.to_s)
438
+ Attribute.new(raw[attribute_key.to_s])
439
439
  end
440
440
 
441
441
  def build_attribute(key)
@@ -172,10 +172,6 @@ module Etna
172
172
  link_attributes.each do |attribute|
173
173
  check_key("attribute #{attribute.attribute_name}", attribute.raw, 'link_model_name')
174
174
 
175
- if attribute.attribute_name != attribute.link_model_name
176
- @errors << "Linked model, \"#{attribute.link_model_name}\", does not match attribute #{attribute.attribute_name}, link attribute names must match the model name."
177
- end
178
-
179
175
  unless @models.model_keys.include?(attribute.link_model_name)
180
176
  @errors << "Linked model, \"#{attribute.link_model_name}\", on attribute #{attribute.attribute_name} does not exist!"
181
177
  end
@@ -29,7 +29,7 @@ module Etna
29
29
  end
30
30
 
31
31
  def execute_planned!
32
- @planned_actions.each do |action|
32
+ planned_actions.each do |action|
33
33
  execute_update(action)
34
34
  end
35
35
  end
@@ -161,7 +161,7 @@ module Etna
161
161
  return if target_attributes.attribute_keys.include?(target_link_model_name)
162
162
 
163
163
  add_link = AddLinkAction.new
164
- add_link.links << AddLinkDefinition.new(model_name: target_model_name, attribute_name: target_link_model_name, type: source_attribute.attribute_type)
164
+ add_link.links << AddLinkDefinition.new(model_name: target_model_name, attribute_name: attribute_name, type: source_attribute.attribute_type)
165
165
  add_link.links << AddLinkDefinition.new(model_name: target_link_model_name, attribute_name: reciprocal.attribute_name, type: reciprocal.attribute_type)
166
166
 
167
167
  queue_update(add_link)
@@ -24,8 +24,13 @@ module Etna
24
24
  end
25
25
 
26
26
  def list_folder(list_folder_request = ListFolderRequest.new)
27
- FoldersAndFilesResponse.new(
28
- @etna_client.folder_list(list_folder_request.to_h))
27
+ if list_folder_request.folder_path != ''
28
+ FoldersAndFilesResponse.new(
29
+ @etna_client.folder_list(list_folder_request.to_h))
30
+ else
31
+ FoldersAndFilesResponse.new(
32
+ @etna_client.bucket_list(list_folder_request.to_h))
33
+ end
29
34
  end
30
35
 
31
36
  def list_folder_by_id(list_folder_by_id_request = ListFolderByIdRequest.new)
@@ -50,7 +50,10 @@ module Etna
50
50
  end
51
51
  end
52
52
 
53
- class ListFolderRequest < Struct.new(:project_name, :bucket_name, :folder_path, keyword_init: true)
53
+ class FolderRequest < Struct.new(:project_name, :bucket_name, :folder_path, keyword_init: true)
54
+ end
55
+
56
+ class ListFolderRequest < FolderRequest
54
57
  include JsonSerializableStruct
55
58
 
56
59
  def initialize(**params)
@@ -110,7 +113,7 @@ module Etna
110
113
  end
111
114
  end
112
115
 
113
- class CreateFolderRequest < Struct.new(:project_name, :bucket_name, :folder_path, keyword_init: true)
116
+ class CreateFolderRequest < FolderRequest
114
117
  include JsonSerializableStruct
115
118
 
116
119
  def initialize(**params)
@@ -125,7 +128,7 @@ module Etna
125
128
  end
126
129
  end
127
130
 
128
- class DeleteFolderRequest < Struct.new(:project_name, :bucket_name, :folder_path, keyword_init: true)
131
+ class DeleteFolderRequest < FolderRequest
129
132
  include JsonSerializableStruct
130
133
 
131
134
  def initialize(**params)
@@ -18,7 +18,7 @@ module Etna
18
18
  completed = 0.0
19
19
  start = Time.now
20
20
 
21
- unless dest_file_or_io.is_a?(IO)
21
+ unless dest_file_or_io.is_a?(IO) || dest_file_or_io.is_a?(StringIO)
22
22
  ::File.open(dest_file_or_io, 'w') do |io|
23
23
  return do_download(dest_file_or_io, metis_file, &block)
24
24
  end
@@ -0,0 +1,95 @@
1
+ module Etna
2
+ module Clients
3
+ class Metis
4
+ class WalkMetisDiffWorkflow < Struct.new(:left_walker, :right_walker, keyword_init: true)
5
+ # Iterates entries of the form [kind, left | nil, right | nil]
6
+ # where kind is one of
7
+ # :left_unique | :right_unique | :left_is_folder | :right_is_folder
8
+ # :unknown | :equal | :right_older | :left_older
9
+ # and left / right is one of
10
+ # nil | Etna::Clients::Metis::File | Etna::Clients::Metis::Folder
11
+ def each(&block)
12
+ left_enum = self.left_walker.to_enum
13
+ right_enum = self.right_walker.to_enum
14
+
15
+ l, l_path = next_or_nil(left_enum)
16
+ r, r_path = next_or_nil(right_enum)
17
+
18
+ while l && r
19
+ if l_path == r_path
20
+ yield [compare_file_or_folders(l, r), l, r]
21
+
22
+ l, l_path = next_or_nil(left_enum)
23
+ r, r_path = next_or_nil(right_enum)
24
+ elsif l_path < r_path
25
+ yield [:left_unique, l, nil]
26
+ l, l_path = next_or_nil(left_enum)
27
+ else
28
+ yield [:right_unique, nil, r]
29
+ r, r_path = next_or_nil(right_enum)
30
+ end
31
+ end
32
+
33
+ while l
34
+ yield [:left_unique, l, nil]
35
+ l, l_path = next_or_nil(left_enum)
36
+ end
37
+
38
+ while r
39
+ yield [:right_unique, nil, r]
40
+ r, r_path = next_or_nil(right_enum)
41
+ end
42
+ end
43
+
44
+ def next_or_nil(enum)
45
+ enum.next
46
+ rescue StopIteration
47
+ [nil, nil]
48
+ end
49
+
50
+ def compare_file_or_folders(l, r)
51
+ if l.is_a?(Etna::Clients::Metis::Folder)
52
+ if r.is_a?(Etna::Clients::Metis::Folder)
53
+ return compare_file_or_folder_age(l, r)
54
+ end
55
+
56
+ return :left_is_folder
57
+ end
58
+
59
+ if r.is_a?(Etna::Clients::Metis::Folder)
60
+ return :right_is_folder
61
+ end
62
+
63
+
64
+ if l.file_hash.nil? || r.file_hash.nil?
65
+ return :unknown
66
+ end
67
+
68
+ if l.file_hash == r.file_hash
69
+ return :equal
70
+ end
71
+
72
+ return compare_file_or_folder_age(l, r)
73
+ end
74
+
75
+ def compare_file_or_folder_age(l, r)
76
+ if l.updated_at.nil?
77
+ return :unknown
78
+ end
79
+
80
+ if r.updated_at.nil?
81
+ return :unknown
82
+ end
83
+
84
+ if l.updated_at < r.updated_at
85
+ return :left_older
86
+ elsif l.updated_at > r.updated_at
87
+ return :right_older
88
+ else
89
+ return :equal
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,31 @@
1
+ module Etna
2
+ module Clients
3
+ class Metis
4
+ class WalkMetisWorkflow < Struct.new(:metis_client, :project_name,
5
+ :bucket_name, :logger, :root_dir, keyword_init: true)
6
+ def each(&block)
7
+ q = [self.root_dir]
8
+
9
+ while (n = q.pop)
10
+ req = Etna::Clients::Metis::ListFolderRequest.new(
11
+ project_name: project_name,
12
+ bucket_name: bucket_name,
13
+ folder_path: n
14
+ )
15
+ next unless metis_client.folder_exists?(req)
16
+ resp = metis_client.list_folder(req)
17
+
18
+ resp.files.all.sort_by { |f| f.file_path[self.root_dir.length..-1] }.each do |file|
19
+ yield [file, file.file_path[self.root_dir.length..-1]]
20
+ end
21
+
22
+ resp.folders.all.sort_by { |f| f.folder_path[self.root_dir.length..-1] }.each do |f|
23
+ yield [f, f.folder_path[self.root_dir.length..-1]]
24
+ q << f.folder_path
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -2,3 +2,5 @@ require_relative "./workflows/metis_download_workflow"
2
2
  require_relative "./workflows/metis_upload_workflow"
3
3
  require_relative "./workflows/sync_metis_data_workflow"
4
4
  require_relative "./workflows/ingest_metis_data_workflow"
5
+ require_relative "./workflows/walk_metis_workflow"
6
+ require_relative "./workflows/walk_metis_diff_workflow"
@@ -48,6 +48,34 @@ module Etna
48
48
  return handle_error(error) if error
49
49
  end
50
50
 
51
+ def try_stream(content_type, &block)
52
+ if @request.env['rack.hijack?']
53
+ @request.env['rack.hijack'].call
54
+ stream = @request.env['rack.hijack_io']
55
+
56
+ headers = [
57
+ "HTTP/1.1 200 OK",
58
+ "Content-Type: #{content_type}"
59
+ ]
60
+ stream.write(headers.map { |header| header + "\r\n" }.join)
61
+ stream.write("\r\n")
62
+ stream.flush
63
+
64
+ Thread.new do
65
+ block.call(stream)
66
+ ensure
67
+ stream.close
68
+ end
69
+
70
+ # IO is now streaming and will be processed by above thread.
71
+ @response.close
72
+ else
73
+ @response['Content-Type'] = content_type
74
+ block.call(@response)
75
+ @response.finish
76
+ end
77
+ end
78
+
51
79
  def require_params(*params)
52
80
  missing_params = params.reject{|p| @params.key?(p) }
53
81
  raise Etna::BadRequest, "Missing param #{missing_params.join(', ')}" unless missing_params.empty?
@@ -0,0 +1,99 @@
1
+ module Etna
2
+ class Permissions
3
+ def initialize(permissions)
4
+ @permissions = permissions
5
+ end
6
+
7
+ def self.from_encoded_permissions(encoded_permissions)
8
+ perms = encoded_permissions.split(/\;/).map do |roles|
9
+ role, projects = roles.split(/:/)
10
+
11
+ projects.split(/\,/).reduce([]) do |perms, project_name|
12
+ perms << Etna::Permission.new(role, project_name)
13
+ end
14
+ end.flatten
15
+
16
+ Etna::Permissions.new(perms)
17
+ end
18
+
19
+ def self.from_hash(permissions_hash)
20
+ perms = permissions_hash.map do |project_name, role_hash|
21
+ Etna::Permission.new(
22
+ Etna::Role.new(role_hash[:role], role_hash[:restricted]).key,
23
+ project_name
24
+ )
25
+ end
26
+
27
+ Etna::Permissions.new(perms)
28
+ end
29
+
30
+ def to_string
31
+ @permissions_string ||= @permissions.group_by(&:role_key)
32
+ .sort_by(&:first)
33
+ .map do |role_key, permissions|
34
+ [
35
+ role_key,
36
+ permissions.map(&:project_name).sort.join(","),
37
+ ].join(":")
38
+ end.join(";")
39
+ end
40
+
41
+ def to_hash
42
+ @permissions_hash ||= @permissions.map do |permission|
43
+ [permission.project_name, permission.to_hash]
44
+ end.to_h
45
+ end
46
+
47
+ def add_permission(permission)
48
+ return if current_project_names.include?(permission.project_name)
49
+
50
+ @permissions << permission
51
+ end
52
+
53
+ private
54
+
55
+ def current_project_names
56
+ @permissions.map(&:project_name)
57
+ end
58
+ end
59
+
60
+ class Permission
61
+ attr_reader :role, :project_name, :role_key
62
+
63
+ ROLE_NAMES = {
64
+ "A" => :admin,
65
+ "E" => :editor,
66
+ "V" => :viewer,
67
+ }
68
+
69
+ def initialize(role_key, project_name)
70
+ @role_key = role_key
71
+ @role = Etna::Role.new(ROLE_NAMES[role_key.upcase], role_key == role_key.upcase)
72
+ @project_name = project_name
73
+ end
74
+
75
+ def to_hash
76
+ role.to_hash
77
+ end
78
+ end
79
+
80
+ class Role
81
+ attr_reader :role, :restricted
82
+ def initialize(role, restricted)
83
+ @role = role
84
+ @restricted = restricted
85
+ end
86
+
87
+ def key
88
+ role_key = role.to_s[0]
89
+ restricted ? role_key.upcase : role_key
90
+ end
91
+
92
+ def to_hash
93
+ {
94
+ role: role,
95
+ restricted: restricted,
96
+ }
97
+ end
98
+ end
99
+ end
data/lib/etna/route.rb CHANGED
@@ -154,6 +154,10 @@ module Etna
154
154
  @auth && @auth[:ignore_janus]
155
155
  end
156
156
 
157
+ def has_user_constraint?(constraint)
158
+ @auth && @auth[:user] && @auth[:user][constraint]
159
+ end
160
+
157
161
  private
158
162
 
159
163
  def application
data/lib/etna/spec/vcr.rb CHANGED
@@ -1,9 +1,27 @@
1
+ require 'cgi'
1
2
  require 'webmock/rspec'
2
3
  require 'vcr'
3
4
  require 'openssl'
4
5
  require 'digest/sha2'
5
6
  require 'base64'
6
7
 
8
+ def clean_query(json_or_string)
9
+ if json_or_string.is_a?(Hash) && json_or_string.include?('upload_path')
10
+ json_or_string['upload_path'] = clean_query(json_or_string['upload_path'])
11
+ json_or_string
12
+ elsif json_or_string.is_a?(String)
13
+ uri = URI(json_or_string)
14
+
15
+ if uri.query&.include?('X-Etna-Signature')
16
+ uri.query = 'etna-signature'
17
+ end
18
+
19
+ uri.to_s
20
+ else
21
+ json_or_string
22
+ end
23
+ end
24
+
7
25
  def setup_base_vcr(spec_helper_dir, server: nil, application: nil)
8
26
  VCR.configure do |c|
9
27
  c.hook_into :webmock
@@ -30,6 +48,10 @@ def setup_base_vcr(spec_helper_dir, server: nil, application: nil)
30
48
  end
31
49
  end
32
50
 
51
+ c.register_request_matcher :try_uri do |request_1, request_2|
52
+ clean_query(request_1.uri) == clean_query(request_2.uri)
53
+ end
54
+
33
55
  c.register_request_matcher :try_body do |request_1, request_2|
34
56
  if request_1.headers['Content-Type'].first =~ /application\/json/
35
57
  if request_2.headers['Content-Type'].first =~ /application\/json/
@@ -40,7 +62,7 @@ def setup_base_vcr(spec_helper_dir, server: nil, application: nil)
40
62
  JSON.parse(request_2.body) rescue 'not-json'
41
63
  end
42
64
 
43
- request_1_json == request_2_json
65
+ clean_query(request_1_json) == clean_query(request_2_json)
44
66
  else
45
67
  false
46
68
  end
@@ -49,7 +71,9 @@ def setup_base_vcr(spec_helper_dir, server: nil, application: nil)
49
71
  end
50
72
  end
51
73
 
52
- # c.debug_logger = File.open('log/vcr_debug.log', 'w')
74
+ if File.exists?('log')
75
+ c.debug_logger = File.open('log/vcr_debug.log', 'w')
76
+ end
53
77
 
54
78
  c.default_cassette_options = {
55
79
  serialize_with: :compressed,
@@ -58,7 +82,7 @@ def setup_base_vcr(spec_helper_dir, server: nil, application: nil)
58
82
  else
59
83
  ENV['RERECORD'] ? :all : :once
60
84
  end,
61
- match_requests_on: [:method, :uri, :try_body, :verify_uri_route]
85
+ match_requests_on: [:method, :try_uri, :try_body, :verify_uri_route]
62
86
  }
63
87
 
64
88
  # Filter the authorization headers of any request by replacing any occurrence of that request's
@@ -31,6 +31,24 @@ module Etna
31
31
  end.to_h
32
32
  end
33
33
 
34
+ def update_payload(payload, token, request)
35
+ route = server.find_route(request)
36
+
37
+ payload = payload.map{|k,v| [k.to_sym, v]}.to_h
38
+
39
+ return payload unless route
40
+
41
+ begin
42
+ permissions = Etna::Permissions.from_encoded_permissions(payload[:perm])
43
+
44
+ # Skip making an actual call to Janus. This behavior is tested in auth_spec
45
+
46
+ payload[:perm] = permissions.to_string
47
+ end if (!route.ignore_janus? && route.has_user_constraint?(:can_view?))
48
+
49
+ payload
50
+ end
51
+
34
52
  def approve_user(request)
35
53
  token = auth(request,:etna)
36
54
 
@@ -42,7 +60,10 @@ module Etna
42
60
  # We do this to support Metis client tests, we pass in tokens with multiple "."-separated parts, so
43
61
  # have to account for that.
44
62
  payload = JSON.parse(Base64.decode64(token.split('.')[1]))
45
- request.env['etna.user'] = Etna::User.new(payload.map{|k,v| [k.to_sym, v]}.to_h, token)
63
+ request.env['etna.user'] = Etna::User.new(
64
+ update_payload(payload, token, request),
65
+ token
66
+ )
46
67
  end
47
68
 
48
69
  def approve_hmac(request)
data/lib/etna/user.rb CHANGED
@@ -1,11 +1,7 @@
1
+ require_relative './permissions'
2
+
1
3
  module Etna
2
4
  class User
3
- ROLE_NAMES = {
4
- 'A' => :admin,
5
- 'E' => :editor,
6
- 'V' => :viewer
7
- }
8
-
9
5
  def initialize params, token=nil
10
6
  @name, @email, @encoded_permissions, encoded_flags, @task = params.values_at(:name, :email, :perm, :flags, :task)
11
7
 
@@ -21,19 +17,7 @@ module Etna
21
17
  end
22
18
 
23
19
  def permissions
24
- @permissions ||= @encoded_permissions.split(/\;/).map do |roles|
25
- role, projects = roles.split(/:/)
26
-
27
- projects.split(/\,/).reduce([]) do |perms,project_name|
28
- perms.push([
29
- project_name,
30
- {
31
- role: ROLE_NAMES[role.upcase],
32
- restricted: role == role.upcase
33
- }
34
- ])
35
- end
36
- end.inject([],:+).to_h
20
+ @permissions ||= Etna::Permissions.from_encoded_permissions(@encoded_permissions).to_hash
37
21
  end
38
22
 
39
23
  def has_flag?(flag)
@@ -44,12 +28,6 @@ module Etna
44
28
  permissions.keys
45
29
  end
46
30
 
47
- ROLE_MATCH = {
48
- admin: /[Aa]/,
49
- editor: /[Ee]/,
50
- viewer: /[Vv]/,
51
- restricted: /[AEV]/,
52
- }
53
31
  def has_any_role?(project, *roles)
54
32
  perm = permissions[project.to_s]
55
33
 
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.43
4
+ version: 0.1.44
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-10-28 00:00:00.000000000 Z
11
+ date: 2022-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -177,6 +177,8 @@ files:
177
177
  - lib/etna/clients/metis/workflows/metis_download_workflow.rb
178
178
  - lib/etna/clients/metis/workflows/metis_upload_workflow.rb
179
179
  - lib/etna/clients/metis/workflows/sync_metis_data_workflow.rb
180
+ - lib/etna/clients/metis/workflows/walk_metis_diff_workflow.rb
181
+ - lib/etna/clients/metis/workflows/walk_metis_workflow.rb
180
182
  - lib/etna/clients/polyphemus.rb
181
183
  - lib/etna/clients/polyphemus/client.rb
182
184
  - lib/etna/clients/polyphemus/models.rb
@@ -201,6 +203,7 @@ files:
201
203
  - lib/etna/metrics.rb
202
204
  - lib/etna/multipart_serializable_nested_hash.rb
203
205
  - lib/etna/parse_body.rb
206
+ - lib/etna/permissions.rb
204
207
  - lib/etna/remote.rb
205
208
  - lib/etna/route.rb
206
209
  - lib/etna/server.rb