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 +4 -4
- data/lib/etna/application.rb +23 -5
- data/lib/etna/auth.rb +50 -7
- data/lib/etna/clients/janus/client.rb +17 -18
- data/lib/etna/clients/janus/models.rb +48 -0
- data/lib/etna/clients/magma/client.rb +41 -0
- data/lib/etna/clients/magma/models.rb +7 -7
- data/lib/etna/clients/magma/workflows/json_validators.rb +0 -4
- data/lib/etna/clients/magma/workflows/model_synchronization_workflow.rb +2 -2
- data/lib/etna/clients/metis/client.rb +7 -2
- data/lib/etna/clients/metis/models.rb +6 -3
- data/lib/etna/clients/metis/workflows/metis_download_workflow.rb +1 -1
- data/lib/etna/clients/metis/workflows/walk_metis_diff_workflow.rb +95 -0
- data/lib/etna/clients/metis/workflows/walk_metis_workflow.rb +31 -0
- data/lib/etna/clients/metis/workflows.rb +2 -0
- data/lib/etna/controller.rb +28 -0
- data/lib/etna/permissions.rb +99 -0
- data/lib/etna/route.rb +4 -0
- data/lib/etna/spec/vcr.rb +27 -3
- data/lib/etna/test_auth.rb +22 -1
- data/lib/etna/user.rb +3 -25
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bea4cbc1d94fca11f52c02b75ef487d11e7b0a6f2aaa7d64c03a09803891d627
|
4
|
+
data.tar.gz: cb764eb044b4b711ddcc4ea46efa598763a34fedf2dc1984a9a2e88234094e2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 30100186f9af0e44654cc0a72d23eff04872f9479e119d308ac749bac4c7125358ea6e62f0a5cafd751512068715ac7831cbb5770ec554394c35c8c3781c30ce
|
7
|
+
data.tar.gz: dcee2b46965993e00d0dcae0a516b501a2afbbe968f1c9df35e45fcff4cd05535af7de52ddfec7e345943f3a1f4536aca196dd8150632d1a628559af482c6d7a
|
data/lib/etna/application.rb
CHANGED
@@ -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 "
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
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
|
-
#
|
80
|
-
|
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
|
-
|
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
|
-
|
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(
|
106
|
-
|
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
|
-
|
11
|
+
json = nil
|
12
12
|
@etna_client.get(
|
13
|
-
"/
|
14
|
-
|
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
|
-
|
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
|
-
"/
|
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
|
-
"/
|
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.
|
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
|
-
|
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:
|
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
|
-
|
28
|
-
|
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
|
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 <
|
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 <
|
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"
|
data/lib/etna/controller.rb
CHANGED
@@ -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
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
|
-
|
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, :
|
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
|
data/lib/etna/test_auth.rb
CHANGED
@@ -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(
|
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
|
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.
|
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:
|
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
|