proxes 0.9.8 → 0.9.9
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/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
|