proxes 0.9.8 → 0.9.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ditty/components/proxes.rb +89 -0
- data/lib/proxes/controllers/permissions.rb +41 -0
- data/lib/proxes/controllers/search.rb +55 -0
- data/lib/proxes/controllers/status.rb +115 -0
- data/lib/proxes/forwarder.rb +49 -0
- data/lib/proxes/helpers/indices.rb +33 -0
- data/lib/proxes/loggers/elasticsearch.rb +10 -0
- data/lib/proxes/middleware/error_handling.rb +64 -0
- data/lib/proxes/middleware/metrics.rb +25 -0
- data/lib/proxes/middleware/security.rb +59 -0
- data/lib/proxes/models/permission.rb +55 -0
- data/lib/proxes/policies/permission_policy.rb +37 -0
- data/lib/proxes/policies/request/bulk_policy.rb +24 -0
- data/lib/proxes/policies/request/cat_policy.rb +12 -0
- data/lib/proxes/policies/request/create_policy.rb +15 -0
- data/lib/proxes/policies/request/index_policy.rb +19 -0
- data/lib/proxes/policies/request/root_policy.rb +13 -0
- data/lib/proxes/policies/request/search_policy.rb +14 -0
- data/lib/proxes/policies/request/snapshot_policy.rb +15 -0
- data/lib/proxes/policies/request/stats_policy.rb +12 -0
- data/lib/proxes/policies/request_policy.rb +62 -0
- data/lib/proxes/policies/search_policy.rb +29 -0
- data/lib/proxes/policies/status_policy.rb +21 -0
- data/lib/proxes/request.rb +84 -0
- data/lib/proxes/request/bulk.rb +40 -0
- data/lib/proxes/request/cat.rb +32 -0
- data/lib/proxes/request/create.rb +33 -0
- data/lib/proxes/request/index.rb +33 -0
- data/lib/proxes/request/root.rb +11 -0
- data/lib/proxes/request/search.rb +37 -0
- data/lib/proxes/request/snapshot.rb +17 -0
- data/lib/proxes/request/stats.rb +35 -0
- data/lib/proxes/services/es.rb +34 -0
- data/lib/proxes/services/listener.rb +29 -0
- data/lib/proxes/services/search.rb +45 -0
- data/lib/proxes/version.rb +5 -0
- data/migrate/20170209_permissions.rb +13 -0
- data/migrate/20170416_user_specific_permissions.rb +9 -0
- data/public/browserconfig.xml +9 -0
- data/public/manifest.json +25 -0
- data/views/index.haml +1 -0
- data/views/layout.haml +60 -0
- metadata +44 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f06c68171f233ccce5d7cb06ab76f6503254ae8159c8906a53170af8e6e4b78
|
4
|
+
data.tar.gz: c4022844adcbf0d5740fe3b78f841a2be8c0db6b6d182a9d383f79ceb6d28299
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b7d4ad313c188b7dd2e9db490870828bf6b6c2590c15349ccc58d167bc29c6f1e455e3706a6202cb78d072a1b105308c0c9004cb239d06cefb4fe0ae951e7cf
|
7
|
+
data.tar.gz: 3ed39b26565420d629984d4e932bce2b239369554b84d0daf2744cca469a65ec634ee17f514790b11d8a5a4d7833d23b15e55519051fdec046371d9b0e49524d
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty'
|
4
|
+
|
5
|
+
module Ditty
|
6
|
+
class ProxES
|
7
|
+
def self.load
|
8
|
+
controllers = File.expand_path('../../proxes/controllers', __dir__)
|
9
|
+
Dir.glob("#{controllers}/*.rb").each { |f| require f }
|
10
|
+
require 'proxes/models/permission'
|
11
|
+
require 'proxes/services/listener'
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.migrations
|
15
|
+
File.expand_path('../../../migrate', __dir__)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.view_folder
|
19
|
+
File.expand_path('../../../views', __dir__)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.public_folder
|
23
|
+
File.expand_path('../../../public', __dir__)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.routes
|
27
|
+
load
|
28
|
+
{
|
29
|
+
'/search' => ::ProxES::Search,
|
30
|
+
'/status' => ::ProxES::Status,
|
31
|
+
'/permissions' => ::ProxES::Permissions
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.navigation
|
36
|
+
load
|
37
|
+
[
|
38
|
+
{ order: 0, link: '/status/check', text: 'Status Check', target: ::ProxES::Status, icon: 'dashboard' },
|
39
|
+
{ order: 1, link: '/search', text: 'Search', target: ::ProxES::Status, icon: 'search' },
|
40
|
+
{ order: 15, link: '/permissions', text: 'Permissions', target: ::ProxES::Permission, icon: 'check-square' }
|
41
|
+
]
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.seeder
|
45
|
+
proc do
|
46
|
+
require 'ditty/models/user'
|
47
|
+
require 'ditty/models/role'
|
48
|
+
require 'proxes/models/permission'
|
49
|
+
|
50
|
+
sa = ::Ditty::Role.find_or_create(name: 'super_admin')
|
51
|
+
%w[GET POST PUT DELETE HEAD OPTIONS INDEX].each do |verb|
|
52
|
+
::ProxES::Permission.find_or_create(role: sa, verb: verb, pattern: '.*')
|
53
|
+
end
|
54
|
+
|
55
|
+
# Admin Role
|
56
|
+
::Ditty::Role.find_or_create(name: 'admin')
|
57
|
+
|
58
|
+
# User Role
|
59
|
+
user_role = ::Ditty::Role.find_or_create(name: 'user')
|
60
|
+
::ProxES::Permission.find_or_create(role: user_role, verb: 'GET', pattern: '/_cluster/stats')
|
61
|
+
::ProxES::Permission.find_or_create(role: user_role, verb: 'GET', pattern: '/_nodes')
|
62
|
+
::ProxES::Permission.find_or_create(role: user_role, verb: 'GET', pattern: '/_nodes/stats')
|
63
|
+
::ProxES::Permission.find_or_create(role: user_role, verb: 'GET', pattern: '/_stats')
|
64
|
+
::ProxES::Permission.find_or_create(role: user_role, verb: 'INDEX', pattern: 'user-{user.id}')
|
65
|
+
|
66
|
+
# Kibana Specific
|
67
|
+
anon = ::Ditty::User.find_or_create(email: 'anonymous@proxes.io')
|
68
|
+
anon.remove_role user_role
|
69
|
+
anon_role = ::Ditty::Role.find_or_create(name: 'anonymous')
|
70
|
+
anon.add_role anon_role unless anon.role?('anonymous')
|
71
|
+
::ProxES::Permission.find_or_create(role: anon_role, verb: 'GET', pattern: '/.kibana/config/*')
|
72
|
+
::ProxES::Permission.find_or_create(role: anon_role, verb: 'INDEX', pattern: '.kibana')
|
73
|
+
|
74
|
+
kibana = ::Ditty::Role.find_or_create(name: 'kibana')
|
75
|
+
::ProxES::Permission.find_or_create(role: kibana, verb: 'INDEX', pattern: '.kibana')
|
76
|
+
::ProxES::Permission.find_or_create(role: kibana, verb: 'HEAD', pattern: '/')
|
77
|
+
::ProxES::Permission.find_or_create(role: kibana, verb: 'GET', pattern: '/_nodes*')
|
78
|
+
::ProxES::Permission.find_or_create(role: kibana, verb: 'GET', pattern: '/_cluster/health*')
|
79
|
+
::ProxES::Permission.find_or_create(role: kibana, verb: 'GET', pattern: '/_cluster/settings*')
|
80
|
+
::ProxES::Permission.find_or_create(role: kibana, verb: 'POST', pattern: '/_mget')
|
81
|
+
::ProxES::Permission.find_or_create(role: kibana, verb: 'POST', pattern: '/_search')
|
82
|
+
::ProxES::Permission.find_or_create(role: kibana, verb: 'POST', pattern: '/_msearch')
|
83
|
+
::ProxES::Permission.find_or_create(role: kibana, verb: 'POST', pattern: '/_refresh')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
Ditty::Components.register_component(:proxes, Ditty::ProxES)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/controllers/component'
|
4
|
+
require 'proxes/models/permission'
|
5
|
+
require 'ditty/policies/user_policy'
|
6
|
+
require 'ditty/policies/role_policy'
|
7
|
+
require 'proxes/policies/permission_policy'
|
8
|
+
|
9
|
+
module ProxES
|
10
|
+
class Permissions < Ditty::Component
|
11
|
+
set model_class: Permission
|
12
|
+
|
13
|
+
FILTERS = [
|
14
|
+
{ name: :user, field: 'user.email' },
|
15
|
+
{ name: :role, field: 'role.name' },
|
16
|
+
{ name: :verb }
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
SEARCHABLE = %i[pattern].freeze
|
20
|
+
|
21
|
+
helpers do
|
22
|
+
def user_options
|
23
|
+
policy_scope(::Ditty::User).as_hash(:email, :email)
|
24
|
+
end
|
25
|
+
|
26
|
+
def role_options
|
27
|
+
policy_scope(::Ditty::Role).as_hash(:name, :name)
|
28
|
+
end
|
29
|
+
|
30
|
+
def verb_options
|
31
|
+
ProxES::Permission.verbs
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def find_template(views, name, engine, &block)
|
36
|
+
super(views, name, engine, &block) # Root
|
37
|
+
super(::Ditty::ProxES.view_folder, name, engine, &block) # This Component
|
38
|
+
super(::Ditty::App.view_folder, name, engine, &block) # Ditty
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/object/blank'
|
4
|
+
require 'ditty/controllers/application'
|
5
|
+
require 'proxes/services/search'
|
6
|
+
require 'proxes/policies/search_policy'
|
7
|
+
|
8
|
+
module ProxES
|
9
|
+
class Search < Ditty::Application
|
10
|
+
set base_path: "#{settings.map_path}/search"
|
11
|
+
|
12
|
+
get '/' do
|
13
|
+
authorize self, :search
|
14
|
+
|
15
|
+
param :page, Integer, min: 0, default: 1
|
16
|
+
param :count, Integer, min: 0, default: 25
|
17
|
+
from = ((params[:page] - 1) * params[:count])
|
18
|
+
params[:q] = '*' if params[:q].blank?
|
19
|
+
result = ProxES::Services::Search.search(params[:q], index: params[:indices], from: from, size: params[:count])
|
20
|
+
haml :"#{view_location}/index",
|
21
|
+
locals: {
|
22
|
+
title: 'Search',
|
23
|
+
indices: ProxES::Services::Search.indices,
|
24
|
+
fields: ProxES::Services::Search.fields(index: params[:indices], names_only: true),
|
25
|
+
result: result
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
get '/fields/?:indices?/?' do
|
30
|
+
authorize self, :fields
|
31
|
+
|
32
|
+
param :names_only, Boolean, default: false
|
33
|
+
json ProxES::Services::Search.fields index: params[:indices], names_only: params[:names_only]
|
34
|
+
end
|
35
|
+
|
36
|
+
get '/indices/?' do
|
37
|
+
authorize self, :indices
|
38
|
+
|
39
|
+
json ProxES::Services::Search.indices
|
40
|
+
end
|
41
|
+
|
42
|
+
get '/values/:field/?:indices?/?' do |field|
|
43
|
+
authorize self, :values
|
44
|
+
|
45
|
+
param :size, Integer, min: 0, default: 25
|
46
|
+
json ProxES::Services::Search.values(field, size: params[:size], index: params[:indices])
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_template(views, name, engine, &block)
|
50
|
+
super(views, name, engine, &block) # Root
|
51
|
+
super(::Ditty::ProxES.view_folder, name, engine, &block) # This Component
|
52
|
+
super(::Ditty::App.view_folder, name, engine, &block) # Ditty
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/controllers/application'
|
4
|
+
require 'proxes/policies/status_policy'
|
5
|
+
require 'proxes/services/es'
|
6
|
+
|
7
|
+
module ProxES
|
8
|
+
class Status < Ditty::Application
|
9
|
+
helpers ProxES::Services::ES
|
10
|
+
|
11
|
+
def find_template(views, name, engine, &block)
|
12
|
+
super(views, name, engine, &block) # Root
|
13
|
+
super(::Ditty::ProxES.view_folder, name, engine, &block) # This Component
|
14
|
+
super(::Ditty::App.view_folder, name, engine, &block) # Ditty
|
15
|
+
end
|
16
|
+
|
17
|
+
# This provides a URL that can be polled by a monitoring system. It will return
|
18
|
+
# 200 OK if all the checks pass, or 500 if any of the checks fail.
|
19
|
+
get '/check' do
|
20
|
+
checks = []
|
21
|
+
begin
|
22
|
+
health = client.cluster.health level: 'cluster'
|
23
|
+
checks << { text: 'Cluster Reachable', passed: true, value: health['cluster_name'] }
|
24
|
+
checks << { text: 'Cluster Health', passed: health['status'] == 'green', value: health['status'] }
|
25
|
+
|
26
|
+
node_stats = client.nodes.stats
|
27
|
+
|
28
|
+
master_nodes = []
|
29
|
+
data_nodes = []
|
30
|
+
ingestion_nodes = []
|
31
|
+
node_stats['nodes'].each_value do |node|
|
32
|
+
if node['roles']
|
33
|
+
master_nodes << node['name'] if node['roles'].include? 'master'
|
34
|
+
data_nodes << node['name'] if node['roles'].include? 'data'
|
35
|
+
ingestion_nodes << node['name'] if node['roles'].include? 'ingest'
|
36
|
+
elsif node['attributes']
|
37
|
+
master_nodes << node['name'] unless node['attributes']['master'] == 'false'
|
38
|
+
data_nodes << node['name'] unless node['attributes']['data'] == 'false'
|
39
|
+
ingestion_nodes << node['name'] unless node['attributes']['ingest'] == 'false'
|
40
|
+
elsif node['settings']
|
41
|
+
master_nodes << node['name'] unless node['settings']['node']['master'] == 'false'
|
42
|
+
data_nodes << node['name'] unless node['settings']['node']['data'] == 'false'
|
43
|
+
ingestion_nodes << node['name'] unless node['settings']['node']['ingest'] == 'false'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
checks << {
|
47
|
+
text: 'Master Nodes',
|
48
|
+
passed: master_nodes.count > 0,
|
49
|
+
value: master_nodes.count > 0 ? master_nodes.sort : 'None'
|
50
|
+
}
|
51
|
+
checks << {
|
52
|
+
text: 'Data Nodes',
|
53
|
+
passed: data_nodes.count > 0,
|
54
|
+
value: data_nodes.count > 0 ? data_nodes.sort : 'None'
|
55
|
+
}
|
56
|
+
checks << {
|
57
|
+
text: 'Ingestion Nodes',
|
58
|
+
passed: true,
|
59
|
+
value: ingestion_nodes.count > 0 ? ingestion_nodes.sort : 'None'
|
60
|
+
}
|
61
|
+
|
62
|
+
jvm_values = []
|
63
|
+
jvm_passed = true
|
64
|
+
node_stats['nodes'].each_value do |node|
|
65
|
+
jvm_values << "#{node['name']}: #{node['jvm']['mem']['heap_used_percent']}%"
|
66
|
+
jvm_passed = false if node['jvm']['mem']['heap_used_percent'] > 85
|
67
|
+
end
|
68
|
+
checks << { text: 'Node JVM Heap', passed: jvm_passed, value: jvm_values.sort }
|
69
|
+
|
70
|
+
fs_values = []
|
71
|
+
fs_passed = true
|
72
|
+
node_stats['nodes'].each_value do |node|
|
73
|
+
next if node['attributes'] && node['attributes']['data'] == 'false'
|
74
|
+
next if node['roles'] && node['roles'].include?('data') == false
|
75
|
+
stats = node['fs']['total']
|
76
|
+
left = stats['available_in_bytes'] / stats['total_in_bytes'].to_f * 100
|
77
|
+
fs_values << "#{node['name']}: #{format('%.02f', left)}% Free"
|
78
|
+
fs_passed = false if left < 10
|
79
|
+
end
|
80
|
+
checks << { text: 'Node File Systems', passed: fs_passed, value: fs_values.sort }
|
81
|
+
|
82
|
+
cpu_values = []
|
83
|
+
cpu_passed = true
|
84
|
+
node_stats['nodes'].each_value do |node|
|
85
|
+
value = (node['os']['cpu_percent'] || node['os']['cpu']['percent'])
|
86
|
+
cpu_values << "#{node['name']}: #{value}"
|
87
|
+
cpu_passed = false if value.to_i > 70
|
88
|
+
end
|
89
|
+
checks << { text: 'Node CPU Usage', passed: cpu_passed, value: cpu_values.sort }
|
90
|
+
|
91
|
+
memory_values = []
|
92
|
+
memory_sum = 0
|
93
|
+
node_stats['nodes'].each_value do |node|
|
94
|
+
memory_sum += node['os']['mem']['used_percent']
|
95
|
+
memory_values << "#{node['name']}: #{node['os']['mem']['used_percent']}"
|
96
|
+
end
|
97
|
+
memory_passed = (memory_sum / memory_values.size).to_i < 100
|
98
|
+
checks << { text: 'Node Memory Usage', passed: memory_passed, value: memory_values.sort }
|
99
|
+
rescue Faraday::Error => e
|
100
|
+
checks << { text: 'Cluster Reachable', passed: false, value: e.message }
|
101
|
+
end
|
102
|
+
|
103
|
+
status checks.find { |c| c[:passed] == false } ? 500 : 200
|
104
|
+
|
105
|
+
respond_to do |format|
|
106
|
+
format.html do
|
107
|
+
haml :'status/check', locals: { title: 'Status Check', checks: checks }
|
108
|
+
end
|
109
|
+
format.json do
|
110
|
+
json checks
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'proxes/services/es'
|
2
|
+
require 'net/http/persistent'
|
3
|
+
require 'singleton'
|
4
|
+
require 'rack'
|
5
|
+
|
6
|
+
module ProxES
|
7
|
+
# A lot of code in this comes from Rack::Proxy
|
8
|
+
class Forwarder
|
9
|
+
include Singleton
|
10
|
+
include ProxES::Services::ES
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
source = Rack::Request.new(env)
|
14
|
+
response = conn.send(source.request_method.downcase) do |req|
|
15
|
+
source_body = body_from(source)
|
16
|
+
req.body = source_body if source_body
|
17
|
+
req.url source.fullpath == '' ? URI.parse(env['REQUEST_URI']).request_uri : source.fullpath
|
18
|
+
end
|
19
|
+
mangle response
|
20
|
+
end
|
21
|
+
|
22
|
+
def mangle(response)
|
23
|
+
headers = (response.respond_to?(:headers) && response.headers) || self.class.normalize_headers(response.to_hash)
|
24
|
+
body = response.body || ['']
|
25
|
+
body = [body] unless body.respond_to?(:each)
|
26
|
+
|
27
|
+
# Not sure where this is coming from, but it causes timeouts on the client
|
28
|
+
headers.delete('transfer-encoding')
|
29
|
+
# Ensure that the content length rack middleware kicks in
|
30
|
+
headers.delete('content-length')
|
31
|
+
|
32
|
+
[response.status, headers, body]
|
33
|
+
end
|
34
|
+
|
35
|
+
def body_from(request)
|
36
|
+
return nil if request.body.nil? || (Kernel.const_defined?('::Puma::NullIO') && request.body.is_a?(Puma::NullIO))
|
37
|
+
request.body.read.tap { |_r| request.body.rewind }
|
38
|
+
end
|
39
|
+
|
40
|
+
class << self
|
41
|
+
def normalize_headers(headers)
|
42
|
+
mapped = headers.map do |k, v|
|
43
|
+
[k, v.is_a?(Array) ? v.join("\n") : v]
|
44
|
+
end
|
45
|
+
Rack::Utils::HeaderHash.new Hash[mapped]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/object/blank'
|
5
|
+
|
6
|
+
module ProxES
|
7
|
+
module Helpers
|
8
|
+
module Indices
|
9
|
+
def filter(asked, against)
|
10
|
+
return against.map { |a| a.gsub(/\.\*/, '*') } if asked == ['*'] || asked.blank?
|
11
|
+
|
12
|
+
answer = []
|
13
|
+
against.each do |pattern|
|
14
|
+
answer.concat(asked.select { |idx| idx =~ /#{pattern}/ })
|
15
|
+
end
|
16
|
+
answer
|
17
|
+
end
|
18
|
+
|
19
|
+
def patterns
|
20
|
+
return [] if user.nil?
|
21
|
+
patterns_for('INDEX').map do |permission|
|
22
|
+
return nil if permission.pattern.blank?
|
23
|
+
permission.pattern.gsub(/\{user.(.*)\}/) { |_match| user.send(Regexp.last_match[1].to_sym) }
|
24
|
+
end.compact
|
25
|
+
end
|
26
|
+
|
27
|
+
def patterns_for(action)
|
28
|
+
return [] if user.nil?
|
29
|
+
Permission.for_user(user, action)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'wisper'
|
4
|
+
require 'proxes/request'
|
5
|
+
require 'ditty/services/logger'
|
6
|
+
|
7
|
+
module ProxES
|
8
|
+
module Middleware
|
9
|
+
class ErrorHandling
|
10
|
+
attr_reader :logger
|
11
|
+
|
12
|
+
include Wisper::Publisher
|
13
|
+
|
14
|
+
def initialize(app, logger = nil)
|
15
|
+
@app = app
|
16
|
+
@logger = logger || ::Ditty::Services::Logger.instance
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
request = ProxES::Request.from_env(env)
|
21
|
+
response = @app.call env
|
22
|
+
broadcast(:es_request_failed, request, response) unless (200..299).cover?(response[0])
|
23
|
+
response
|
24
|
+
rescue Errno::EHOSTUNREACH
|
25
|
+
error 'Could not reach Elasticsearch at ' + ENV['ELASTICSEARCH_URL']
|
26
|
+
rescue Errno::ECONNREFUSED, Faraday::ConnectionFailed, SocketError
|
27
|
+
error 'Elasticsearch not listening at ' + ENV['ELASTICSEARCH_URL']
|
28
|
+
rescue Pundit::NotAuthorizedError, Ditty::Helpers::NotAuthenticated => e
|
29
|
+
broadcast(:es_request_denied, request, e)
|
30
|
+
log_not_authorized request
|
31
|
+
raise e if env['APP_ENV'] == 'development'
|
32
|
+
return [401, {}, []] if request.head?
|
33
|
+
request.html? && request.user.nil? ? login_and_redirect(request) : error('Not Authorized', 401)
|
34
|
+
rescue StandardError => e
|
35
|
+
broadcast(:es_request_denied, request, e)
|
36
|
+
log_not_authorized request
|
37
|
+
raise e if env['APP_ENV'] == 'development'
|
38
|
+
return [403, {}. []] if request.head?
|
39
|
+
error 'Forbidden', 403
|
40
|
+
end
|
41
|
+
|
42
|
+
def log_not_authorized(request)
|
43
|
+
user = request.user ? request.user.email : 'unauthenticated request'
|
44
|
+
logger.error "Access denied for #{user} by security layer: #{request.detail}"
|
45
|
+
end
|
46
|
+
|
47
|
+
# Response Helpers
|
48
|
+
def error(message, code = 500)
|
49
|
+
headers = { 'Content-Type' => 'application/json' }
|
50
|
+
headers['WWW-Authenticate'] = 'Basic realm="security"' if code == 401
|
51
|
+
[code, headers, ['{"error":"' + message + '"}']]
|
52
|
+
end
|
53
|
+
|
54
|
+
def login_and_redirect(request)
|
55
|
+
request.session['omniauth.origin'] = request.url unless request.url == '/_proxes/auth/login'
|
56
|
+
redirect '/_proxes/auth/login'
|
57
|
+
end
|
58
|
+
|
59
|
+
def redirect(destination, code = 302)
|
60
|
+
[code, { 'Location' => destination }, []]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|