etna 0.1.43 → 0.1.44

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