etna 0.1.44 → 0.1.45
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/bin/etna +12 -1
- data/etna.completion +36788 -1
- data/lib/commands.rb +118 -0
- data/lib/etna/application.rb +13 -34
- data/lib/etna/auth.rb +15 -35
- data/lib/etna/clients/janus/models.rb +4 -0
- data/lib/etna/clients/magma/workflows/create_project_workflow.rb +1 -1
- data/lib/etna/controller.rb +20 -1
- data/lib/etna/cwl.rb +8 -0
- data/lib/etna/environment_variables.rb +50 -0
- data/lib/etna/injection.rb +57 -0
- data/lib/etna/janus_utils.rb +55 -0
- data/lib/etna/permissions.rb +5 -0
- data/lib/etna/redirect.rb +36 -0
- data/lib/etna/route.rb +47 -2
- data/lib/etna/spec/auth.rb +11 -0
- data/lib/etna/spec/vcr.rb +1 -0
- data/lib/etna/user.rb +1 -1
- data/lib/etna.rb +4 -0
- metadata +10 -6
data/lib/commands.rb
CHANGED
@@ -2,6 +2,7 @@ require 'date'
|
|
2
2
|
require 'logger'
|
3
3
|
require 'rollbar'
|
4
4
|
require 'tempfile'
|
5
|
+
require 'securerandom'
|
5
6
|
require_relative 'helpers'
|
6
7
|
require 'yaml'
|
7
8
|
|
@@ -88,6 +89,123 @@ class EtnaApp
|
|
88
89
|
end
|
89
90
|
end
|
90
91
|
|
92
|
+
class Metis
|
93
|
+
include Etna::CommandExecutor
|
94
|
+
string_flags << "--project-name"
|
95
|
+
string_flags << "--bucket-name"
|
96
|
+
string_flags << "--path"
|
97
|
+
ROLLING_COUNT = 14
|
98
|
+
|
99
|
+
class PutArchive < Etna::Command
|
100
|
+
include WithEtnaClients
|
101
|
+
|
102
|
+
def execute(file, project_name:, bucket_name:, path:)
|
103
|
+
@project_name = project_name
|
104
|
+
@bucket_name = bucket_name
|
105
|
+
basename = ::File.basename(path)
|
106
|
+
dir = ::File.dirname(path)
|
107
|
+
|
108
|
+
puts "Creating archive folder"
|
109
|
+
metis_client.create_folder(Etna::Clients::Metis::CreateFolderRequest.new(
|
110
|
+
project_name: project_name,
|
111
|
+
bucket_name: bucket_name,
|
112
|
+
folder_path: dir
|
113
|
+
))
|
114
|
+
|
115
|
+
puts "Listing archive folder"
|
116
|
+
files = metis_client.list_folder(Etna::Clients::Metis::ListFolderRequest.new(
|
117
|
+
project_name: project_name,
|
118
|
+
bucket_name: bucket_name,
|
119
|
+
folder_path: dir
|
120
|
+
)).files.all
|
121
|
+
|
122
|
+
if files.select { |f| f.file_name == basename }.length > 0
|
123
|
+
timestr = DateTime.now.strftime("%Y%m%d_%H%M")
|
124
|
+
puts "Backing up #{dir}/#{basename} to #{dir}/#{basename}.#{timestr}"
|
125
|
+
metis_client.rename_file(Etna::Clients::Metis::RenameFileRequest.new(
|
126
|
+
project_name: project_name,
|
127
|
+
bucket_name: bucket_name,
|
128
|
+
file_path: ::File.join(dir, basename),
|
129
|
+
new_bucket_name: bucket_name,
|
130
|
+
new_file_path: ::File.join(dir, "#{basename}.#{timestr}"),
|
131
|
+
))
|
132
|
+
end
|
133
|
+
|
134
|
+
create_upload_workflow.do_upload(
|
135
|
+
::File.open(file, "r"),
|
136
|
+
path
|
137
|
+
) do |progress|
|
138
|
+
case progress[0]
|
139
|
+
when :error
|
140
|
+
puts("Error while uploading: #{progress[1].to_s}")
|
141
|
+
else
|
142
|
+
end
|
143
|
+
end
|
144
|
+
puts "Completed upload"
|
145
|
+
|
146
|
+
backups = files.select { |f| f.file_name != basename }.sort_by(&:file_name).reverse
|
147
|
+
if backups.length > ROLLING_COUNT
|
148
|
+
backups.slice(ROLLING_COUNT..-1).reverse.each do |f|
|
149
|
+
puts "Removing rolling back up #{f.file_name}"
|
150
|
+
metis_client.delete_file(Etna::Clients::Metis::DeleteFileRequest.new(
|
151
|
+
project_name: project_name,
|
152
|
+
bucket_name: bucket_name,
|
153
|
+
file_path: ::File.join(dir, f.file_name)
|
154
|
+
))
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def create_upload_workflow
|
160
|
+
Etna::Clients::Metis::MetisUploadWorkflow.new(
|
161
|
+
metis_client: @metis_client,
|
162
|
+
metis_uid: SecureRandom.hex,
|
163
|
+
project_name: @project_name,
|
164
|
+
bucket_name: @bucket_name,
|
165
|
+
max_attempts: 3
|
166
|
+
)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
class PullArchive < Etna::Command
|
171
|
+
include WithEtnaClients
|
172
|
+
|
173
|
+
def execute(project_name:, bucket_name:, path:)
|
174
|
+
@project_name = project_name
|
175
|
+
@bucket_name = bucket_name
|
176
|
+
basename = ::File.basename(path)
|
177
|
+
dir = ::File.dirname(path)
|
178
|
+
|
179
|
+
files = metis_client.list_folder(Etna::Clients::Metis::ListFolderRequest.new(
|
180
|
+
project_name: project_name,
|
181
|
+
bucket_name: bucket_name,
|
182
|
+
folder_path: dir
|
183
|
+
)).files.all
|
184
|
+
|
185
|
+
files.sort_by(&:file_name).reverse
|
186
|
+
|
187
|
+
archived = files.select { |f| f.file_name == basename }.first
|
188
|
+
if archived.nil?
|
189
|
+
archived = files.first
|
190
|
+
end
|
191
|
+
|
192
|
+
tmp = Tempfile.new('download', Dir.pwd)
|
193
|
+
begin
|
194
|
+
puts "Downloading to #{basename} from #{archived.file_name}"
|
195
|
+
metis_client.download_file(archived) do |chunk|
|
196
|
+
tmp.write(chunk)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Atomic operation -- ensure we only place the file in position when it successfully loads
|
200
|
+
::File.rename(tmp.path, ::File.join(::Dir.pwd, basename))
|
201
|
+
rescue
|
202
|
+
tmp.close!
|
203
|
+
raise
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
91
209
|
class Administrate
|
92
210
|
include Etna::CommandExecutor
|
93
211
|
|
data/lib/etna/application.rb
CHANGED
@@ -50,41 +50,8 @@ 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
|
-
|
68
53
|
def configure(opts)
|
69
|
-
@config = opts
|
70
|
-
|
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)
|
76
|
-
path.shift # drop the first, just app name
|
77
|
-
|
78
|
-
target = @config
|
79
|
-
while path.length > 1
|
80
|
-
n = path.shift
|
81
|
-
target = (target[n.downcase.to_sym] ||= {})
|
82
|
-
end
|
83
|
-
|
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)
|
87
|
-
end
|
54
|
+
@config = Etna::EnvironmentVariables.load_from_env('ETNA', root: opts) { |path, value| load_config_from_env_path(path, value) }
|
88
55
|
|
89
56
|
if (rollbar_config = config(:rollbar)) && rollbar_config[:access_token]
|
90
57
|
Rollbar.configure do |config|
|
@@ -93,6 +60,14 @@ module Etna::Application
|
|
93
60
|
end
|
94
61
|
end
|
95
62
|
|
63
|
+
def load_config_from_env_path(path, value)
|
64
|
+
return nil if path.empty?
|
65
|
+
return nil unless path.last.end_with?('_file')
|
66
|
+
path.last.sub!(/_file$/, '')
|
67
|
+
|
68
|
+
[path.map(&:to_sym), YAML.load(File.read(value))]
|
69
|
+
end
|
70
|
+
|
96
71
|
def setup_yabeda
|
97
72
|
application = self.id
|
98
73
|
Yabeda.configure do
|
@@ -186,6 +161,10 @@ module Etna::Application
|
|
186
161
|
(ENV["#{self.class.name.upcase}_ENV"] || :development).to_sym
|
187
162
|
end
|
188
163
|
|
164
|
+
def test?
|
165
|
+
environment == "test"
|
166
|
+
end
|
167
|
+
|
189
168
|
def id
|
190
169
|
ENV["APP_NAME"] || self.class.name.snake_case.split(/::/).last
|
191
170
|
end
|
data/lib/etna/auth.rb
CHANGED
@@ -18,12 +18,16 @@ module Etna
|
|
18
18
|
if [ approve_noauth(request), approve_hmac(request), approve_user(request) ].all?{|approved| !approved}
|
19
19
|
return fail_or_redirect(request)
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
@app.call(request.env)
|
23
23
|
end
|
24
24
|
|
25
25
|
private
|
26
26
|
|
27
|
+
def janus
|
28
|
+
@janus ||= Etna::JanusUtils.new
|
29
|
+
end
|
30
|
+
|
27
31
|
def application
|
28
32
|
@application ||= Etna::Application.instance
|
29
33
|
end
|
@@ -61,7 +65,7 @@ module Etna
|
|
61
65
|
application.config(:auth_redirect).chomp('/') + '/login'
|
62
66
|
)
|
63
67
|
uri.query = URI.encode_www_form(refer: request.url)
|
64
|
-
|
68
|
+
Etna::Redirect(request).to(uri.to_s)
|
65
69
|
end
|
66
70
|
|
67
71
|
def approve_noauth(request)
|
@@ -77,52 +81,26 @@ module Etna
|
|
77
81
|
return true if route && route.ignore_janus?
|
78
82
|
|
79
83
|
# process task tokens
|
80
|
-
payload[
|
81
|
-
end
|
82
|
-
|
83
|
-
def resource_projects(token)
|
84
|
-
return [] unless has_janus_config?
|
85
|
-
|
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
|
93
|
-
|
94
|
-
def janus_client(token)
|
95
|
-
Etna::Clients::Janus.new(
|
96
|
-
token: token,
|
97
|
-
host: application.config(:janus)[:host]
|
98
|
-
)
|
84
|
+
payload[:task] ? janus.valid_task_token?(token) : true
|
99
85
|
end
|
100
86
|
|
101
|
-
def
|
102
|
-
|
103
|
-
|
104
|
-
response = janus_client(token).validate_task_token
|
105
|
-
|
106
|
-
return false unless response.code == '200'
|
107
|
-
|
108
|
-
return true
|
87
|
+
def symbolize_payload_keys(payload)
|
88
|
+
payload.map{|k,v| [k.to_sym, v]}.to_h
|
109
89
|
end
|
110
90
|
|
111
|
-
def
|
112
|
-
|
91
|
+
def permissions(payload)
|
92
|
+
Etna::Permissions.from_encoded_permissions(payload[:perm])
|
113
93
|
end
|
114
94
|
|
115
95
|
def update_payload(payload, token, request)
|
116
96
|
route = server.find_route(request)
|
117
97
|
|
118
|
-
payload = payload.map{|k,v| [k.to_sym, v]}.to_h
|
119
|
-
|
120
98
|
return payload unless route
|
121
99
|
|
122
100
|
begin
|
123
|
-
permissions =
|
101
|
+
permissions = permissions(payload)
|
124
102
|
|
125
|
-
resource_projects(token).each do |resource_project|
|
103
|
+
janus.resource_projects(token).each do |resource_project|
|
126
104
|
permissions.add_permission(
|
127
105
|
Etna::Permission.new('v', resource_project.project_name)
|
128
106
|
)
|
@@ -141,6 +119,8 @@ module Etna
|
|
141
119
|
begin
|
142
120
|
payload, header = application.sign.jwt_decode(token)
|
143
121
|
|
122
|
+
payload = symbolize_payload_keys(payload)
|
123
|
+
|
144
124
|
return false unless janus_approved?(payload, token, request)
|
145
125
|
return request.env['etna.user'] = Etna::User.new(
|
146
126
|
update_payload(payload, token, request),
|
@@ -59,7 +59,7 @@ module Etna
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def create_magma_project_record!
|
62
|
-
magma_client.
|
62
|
+
magma_client.update_json(Etna::Clients::Magma::UpdateRequest.new(
|
63
63
|
project_name: project_name,
|
64
64
|
revisions: {
|
65
65
|
'project' => { project_name => { name: project_name } },
|
data/lib/etna/controller.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'erb'
|
2
|
+
require 'net/smtp'
|
2
3
|
|
3
4
|
module Etna
|
4
5
|
class Controller
|
@@ -44,7 +45,7 @@ module Etna
|
|
44
45
|
rescue Exception => e
|
45
46
|
error = e
|
46
47
|
ensure
|
47
|
-
log_request
|
48
|
+
log_request if !@request.env['etna.dont_log'] || error
|
48
49
|
return handle_error(error) if error
|
49
50
|
end
|
50
51
|
|
@@ -76,6 +77,24 @@ module Etna
|
|
76
77
|
end
|
77
78
|
end
|
78
79
|
|
80
|
+
def send_email(to_name, to_email, subject, content)
|
81
|
+
message = <<MESSAGE_END
|
82
|
+
From: Data Library <noreply@janus>
|
83
|
+
To: #{to_name} <#{to_email}>
|
84
|
+
Subject: #{subject}
|
85
|
+
|
86
|
+
#{content}
|
87
|
+
MESSAGE_END
|
88
|
+
|
89
|
+
unless @server.send(:application).test?
|
90
|
+
Net::SMTP.start('smtp.ucsf.edu') do |smtp|
|
91
|
+
smtp.send_message message, 'noreply@janus', to_email
|
92
|
+
end
|
93
|
+
end
|
94
|
+
rescue => e
|
95
|
+
@logger.log_error(e)
|
96
|
+
end
|
97
|
+
|
79
98
|
def require_params(*params)
|
80
99
|
missing_params = params.reject{|p| @params.key?(p) }
|
81
100
|
raise Etna::BadRequest, "Missing param #{missing_params.join(', ')}" unless missing_params.empty?
|
data/lib/etna/cwl.rb
CHANGED
@@ -0,0 +1,50 @@
|
|
1
|
+
module Etna
|
2
|
+
module EnvironmentVariables
|
3
|
+
# a <- b
|
4
|
+
def self.deep_merge(a, b)
|
5
|
+
if a.is_a?(Hash)
|
6
|
+
if b.is_a?(Hash)
|
7
|
+
b.keys.each do |b_key|
|
8
|
+
a[b_key] = deep_merge(a[b_key], b[b_key])
|
9
|
+
end
|
10
|
+
|
11
|
+
return a
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
a.nil? ? b : a
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.load_from_env(prefix, root: {}, env: ENV, downcase: true, sep: '__', &path_to_value_mapper)
|
19
|
+
env.keys.each do |key|
|
20
|
+
next unless key.start_with?(prefix + sep)
|
21
|
+
|
22
|
+
path = key.split(sep, -1)
|
23
|
+
path.shift
|
24
|
+
if downcase
|
25
|
+
path.each(&:downcase!)
|
26
|
+
end
|
27
|
+
|
28
|
+
result = path_to_value_mapper.call(path, env[key])
|
29
|
+
next unless result
|
30
|
+
|
31
|
+
path, value = result
|
32
|
+
|
33
|
+
if path.empty?
|
34
|
+
root = EnvironmentVariables.deep_merge(root, value)
|
35
|
+
next
|
36
|
+
end
|
37
|
+
|
38
|
+
target = root
|
39
|
+
while path.length > 1
|
40
|
+
n = path.shift
|
41
|
+
target = (target[n] ||= {})
|
42
|
+
end
|
43
|
+
|
44
|
+
target[path.last] = EnvironmentVariables.deep_merge(target[path.last], value)
|
45
|
+
end
|
46
|
+
|
47
|
+
root
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Etna
|
2
|
+
class Injection
|
3
|
+
# Extend into class
|
4
|
+
module FromHash
|
5
|
+
def from_hash(hash, hash_has_string_keys, rest: nil, key_rest: nil)
|
6
|
+
::Etna::Injection.inject_new(self, hash ,hash_has_string_keys, rest: rest, key_rest: key_rest) do |missing_p|
|
7
|
+
raise "required argument '#{missing_p}' of #{self.name} is missing!"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.inject_new(cls, hash, hash_has_string_keys, rest: nil, key_rest: nil, &missing_req_param_cb)
|
13
|
+
args, kwds = prep_args(
|
14
|
+
cls.instance_method(:initialize), hash, hash_has_string_keys,
|
15
|
+
rest: rest, key_rest: key_rest, &missing_req_param_cb
|
16
|
+
)
|
17
|
+
|
18
|
+
cls.new(*args, **kwds)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.prep_args(method, hash, hash_has_string_keys, rest: nil, key_rest: nil, &missing_req_param_cb)
|
22
|
+
new_k_params = {}
|
23
|
+
new_p_params = []
|
24
|
+
|
25
|
+
method.parameters.each do |type, p_key|
|
26
|
+
h_key = hash_has_string_keys ? p_key.to_s : p_key
|
27
|
+
if type == :rest && rest
|
28
|
+
new_p_params.append(*rest)
|
29
|
+
elsif type == :keyrest && key_rest
|
30
|
+
new_k_params.update(key_rest)
|
31
|
+
elsif type == :req || type == :opt
|
32
|
+
if hash.include?(h_key)
|
33
|
+
new_p_params << hash[h_key]
|
34
|
+
elsif type == :req
|
35
|
+
if block_given?
|
36
|
+
new_p_params << missing_req_param_cb.call(h_key)
|
37
|
+
else
|
38
|
+
new_p_params << nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
elsif type == :keyreq || type == :key
|
42
|
+
if hash.include?(h_key)
|
43
|
+
new_k_params[p_key] = hash[h_key]
|
44
|
+
elsif type == :keyreq
|
45
|
+
if block_given?
|
46
|
+
new_k_params[p_key] = missing_req_param_cb.call(h_key)
|
47
|
+
else
|
48
|
+
new_k_params[p_key] = nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
[new_p_params, new_k_params]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Utility class to work with Janus for authz and authn
|
2
|
+
module Etna
|
3
|
+
class JanusUtils
|
4
|
+
def initialize
|
5
|
+
end
|
6
|
+
|
7
|
+
def projects(token)
|
8
|
+
return [] unless has_janus_config?
|
9
|
+
|
10
|
+
janus_client(token).get_projects.projects
|
11
|
+
rescue
|
12
|
+
# If encounter any issue with Janus, we'll return no projects
|
13
|
+
[]
|
14
|
+
end
|
15
|
+
|
16
|
+
def resource_projects(token)
|
17
|
+
projects(token).select do |project|
|
18
|
+
!!project.resource && !project.requires_agreement
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def community_projects(token)
|
23
|
+
projects(token).select do |project|
|
24
|
+
!!project.resource && !!project.requires_agreement
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def janus_client(token)
|
29
|
+
Etna::Clients::Janus.new(
|
30
|
+
token: token,
|
31
|
+
host: application.config(:janus)[:host],
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def valid_task_token?(token)
|
36
|
+
return false unless has_janus_config?
|
37
|
+
|
38
|
+
response = janus_client(token).validate_task_token
|
39
|
+
|
40
|
+
return false unless response.code == "200"
|
41
|
+
|
42
|
+
return true
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def application
|
48
|
+
@application ||= Etna::Application.instance
|
49
|
+
end
|
50
|
+
|
51
|
+
def has_janus_config?
|
52
|
+
application.config(:janus) && application.config(:janus)[:host]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/etna/permissions.rb
CHANGED
@@ -50,6 +50,10 @@ module Etna
|
|
50
50
|
@permissions << permission
|
51
51
|
end
|
52
52
|
|
53
|
+
def any?(&block)
|
54
|
+
@permissions.any?(&block)
|
55
|
+
end
|
56
|
+
|
53
57
|
private
|
54
58
|
|
55
59
|
def current_project_names
|
@@ -64,6 +68,7 @@ module Etna
|
|
64
68
|
"A" => :admin,
|
65
69
|
"E" => :editor,
|
66
70
|
"V" => :viewer,
|
71
|
+
"G" => :guest
|
67
72
|
}
|
68
73
|
|
69
74
|
def initialize(role_key, project_name)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Etna
|
2
|
+
def self.Redirect(req)
|
3
|
+
Redirect.new(req)
|
4
|
+
end
|
5
|
+
|
6
|
+
class Redirect
|
7
|
+
def initialize(request)
|
8
|
+
@request = request
|
9
|
+
end
|
10
|
+
|
11
|
+
def to(path, &block)
|
12
|
+
return Rack::Response.new(
|
13
|
+
[ { errors: [ 'Cannot redirect out of domain' ] }.to_json ], 422,
|
14
|
+
{ 'Content-Type' => 'application/json' }
|
15
|
+
).finish unless matches_domain?(path)
|
16
|
+
|
17
|
+
response = Rack::Response.new
|
18
|
+
|
19
|
+
response.redirect(path.gsub("http://", "https://"), 302)
|
20
|
+
|
21
|
+
yield response if block_given?
|
22
|
+
|
23
|
+
response.finish
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def matches_domain?(path)
|
29
|
+
top_domain(@request.hostname) == top_domain(URI(path).host)
|
30
|
+
end
|
31
|
+
|
32
|
+
def top_domain(host_name)
|
33
|
+
host_name.split('.')[-2..-1].join('.')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/etna/route.rb
CHANGED
@@ -15,6 +15,7 @@ module Etna
|
|
15
15
|
@block = block
|
16
16
|
@match_ext = options[:match_ext]
|
17
17
|
@log_redact_keys = options[:log_redact_keys]
|
18
|
+
@dont_log = options[:dont_log]
|
18
19
|
end
|
19
20
|
|
20
21
|
def to_hash
|
@@ -41,7 +42,7 @@ module Etna
|
|
41
42
|
if params
|
42
43
|
PARAM_TYPES.reduce(route) do |path,pat|
|
43
44
|
path.gsub(pat) do
|
44
|
-
|
45
|
+
params[$1.to_sym].split('/').map { |c| URI.encode_www_form_component(c) }.join('/')
|
45
46
|
end
|
46
47
|
end
|
47
48
|
else
|
@@ -123,10 +124,19 @@ module Etna
|
|
123
124
|
update_params(request)
|
124
125
|
|
125
126
|
unless authorized?(request)
|
127
|
+
if cc_available?(request)
|
128
|
+
if request.content_type == 'application/json'
|
129
|
+
return [ 403, { 'Content-Type' => 'application/json' }, [ { error: 'You are forbidden from performing this action, but you can visit the project home page and request access.' }.to_json ] ]
|
130
|
+
else
|
131
|
+
return cc_redirect(request)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
126
135
|
return [ 403, { 'Content-Type' => 'application/json' }, [ { error: 'You are forbidden from performing this action.' }.to_json ] ]
|
127
136
|
end
|
128
137
|
|
129
138
|
request.env['etna.redact_keys'] = @log_redact_keys
|
139
|
+
request.env['etna.dont_log'] = @dont_log
|
130
140
|
|
131
141
|
if @action
|
132
142
|
controller, action = @action.split('#')
|
@@ -160,6 +170,23 @@ module Etna
|
|
160
170
|
|
161
171
|
private
|
162
172
|
|
173
|
+
def janus
|
174
|
+
@janus ||= Etna::JanusUtils.new
|
175
|
+
end
|
176
|
+
|
177
|
+
# If the application asks for a code of conduct redirect
|
178
|
+
def cc_redirect(request, msg = 'You are unauthorized')
|
179
|
+
return [ 401, { 'Content-Type' => 'text/html' }, [msg] ] unless application.config(:auth_redirect)
|
180
|
+
|
181
|
+
params = request.env['rack.request.params']
|
182
|
+
|
183
|
+
uri = URI(
|
184
|
+
application.config(:auth_redirect).chomp('/') + "/#{params[:project_name]}/cc"
|
185
|
+
)
|
186
|
+
uri.query = URI.encode_www_form(refer: request.url)
|
187
|
+
Etna::Redirect(request).to(uri.to_s)
|
188
|
+
end
|
189
|
+
|
163
190
|
def application
|
164
191
|
@application ||= Etna::Application.instance
|
165
192
|
end
|
@@ -189,6 +216,24 @@ module Etna
|
|
189
216
|
end
|
190
217
|
end
|
191
218
|
|
219
|
+
def cc_available?(request)
|
220
|
+
user = request.env['etna.user']
|
221
|
+
|
222
|
+
return false unless user
|
223
|
+
|
224
|
+
params = request.env['rack.request.params']
|
225
|
+
|
226
|
+
return false unless params[:project_name]
|
227
|
+
|
228
|
+
# Only check for a CC if the user does not currently have permissions
|
229
|
+
# for the project
|
230
|
+
return false if user.permissions.keys.include?(params[:project_name])
|
231
|
+
|
232
|
+
!janus.community_projects(user.token).select do |project|
|
233
|
+
project.project_name == params[:project_name]
|
234
|
+
end.first.nil?
|
235
|
+
end
|
236
|
+
|
192
237
|
def hmac_authorized?(request)
|
193
238
|
# either there is no hmac requirement, or we have a valid hmac
|
194
239
|
!@auth[:hmac] || request.env['etna.hmac']&.valid?
|
@@ -211,7 +256,7 @@ module Etna
|
|
211
256
|
Hash[
|
212
257
|
match.names.map(&:to_sym).zip(
|
213
258
|
match.captures.map do |capture|
|
214
|
-
URI.
|
259
|
+
capture.split('/').map {|c| URI.decode_www_form_component(c) }.join('/')
|
215
260
|
end
|
216
261
|
)
|
217
262
|
]
|