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
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'wisper'
|
4
|
+
|
5
|
+
module ProxES
|
6
|
+
module Middleware
|
7
|
+
class Metrics
|
8
|
+
include Wisper::Publisher
|
9
|
+
|
10
|
+
def initialize(app)
|
11
|
+
@app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
request = Request.from_env(env)
|
16
|
+
broadcast(:call_started, request)
|
17
|
+
|
18
|
+
result = @app.call request.env
|
19
|
+
|
20
|
+
broadcast(:call_completed, request) if result[0].to_i >= 200 && result[0].to_i < 300
|
21
|
+
result
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'proxes/request'
|
4
|
+
require 'proxes/policies/request_policy'
|
5
|
+
require 'ditty/services/logger'
|
6
|
+
require 'ditty/helpers/pundit'
|
7
|
+
require 'ditty/helpers/authentication'
|
8
|
+
|
9
|
+
module ProxES
|
10
|
+
module Middleware
|
11
|
+
class Security
|
12
|
+
attr_reader :env, :logger
|
13
|
+
|
14
|
+
include Ditty::Helpers::Authentication
|
15
|
+
include Ditty::Helpers::Pundit
|
16
|
+
|
17
|
+
def initialize(app, logger = nil)
|
18
|
+
@app = app
|
19
|
+
@logger = logger || ::Ditty::Services::Logger.instance
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
@env = env
|
24
|
+
request = ProxES::Request.from_env(env)
|
25
|
+
log(request, 'BEFORE')
|
26
|
+
|
27
|
+
check_basic request
|
28
|
+
authorize request
|
29
|
+
|
30
|
+
request.index = policy_scope(request) if request.indices?
|
31
|
+
log(request, 'AFTER')
|
32
|
+
|
33
|
+
@app.call env
|
34
|
+
end
|
35
|
+
|
36
|
+
def check_basic(request)
|
37
|
+
auth = Rack::Auth::Basic::Request.new(request.env)
|
38
|
+
return false unless auth.provided? && auth.basic?
|
39
|
+
|
40
|
+
identity = ::Ditty::Identity.find(username: auth.credentials[0])
|
41
|
+
identity ||= ::Ditty::Identity.find(username: CGI.unescape(auth.credentials[0]))
|
42
|
+
return false unless identity && identity.authenticate(auth.credentials[1])
|
43
|
+
request.env['rack.session'] ||= {}
|
44
|
+
request.env['rack.session']['user_id'] = identity.user_id
|
45
|
+
end
|
46
|
+
|
47
|
+
def authorize(request)
|
48
|
+
Pundit.authorize(request.user, request, request.request_method.downcase + '?')
|
49
|
+
end
|
50
|
+
|
51
|
+
def log(request, stage)
|
52
|
+
logger.debug '============' + stage.ljust(56) + '============'
|
53
|
+
logger.debug '= ' + "Request: #{request.detail}".ljust(76) + ' ='
|
54
|
+
logger.debug '= ' + "Endpoint: #{request.endpoint}".ljust(76) + ' ='
|
55
|
+
logger.debug '================================================================================'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/models/base'
|
4
|
+
require 'ditty/models/user'
|
5
|
+
require 'ditty/models/role'
|
6
|
+
|
7
|
+
module ProxES
|
8
|
+
class Permission < ::Sequel::Model
|
9
|
+
include ::Ditty::Base
|
10
|
+
|
11
|
+
many_to_one :role, class: ::Ditty::Role
|
12
|
+
many_to_one :user, class: ::Ditty::User
|
13
|
+
|
14
|
+
dataset_module do
|
15
|
+
def for_user(a_user, action)
|
16
|
+
where(verb: action).where { Sequel.|({ role: a_user.roles }, { user_id: a_user.id }) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate
|
21
|
+
validates_presence %i[verb pattern]
|
22
|
+
validates_presence :role_id unless user_id
|
23
|
+
validates_presence :user_id unless role_id
|
24
|
+
validates_includes self.class.verbs, :verb
|
25
|
+
end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
def verbs
|
29
|
+
%w[GET POST PUT DELETE HEAD OPTIONS TRACE INDEX]
|
30
|
+
end
|
31
|
+
|
32
|
+
def from_audit_log(audit_log)
|
33
|
+
return {} if audit_log.details.nil?
|
34
|
+
match = audit_log.details.match(/^(\w)+ (\S+)/)
|
35
|
+
return {} if match.nil?
|
36
|
+
{
|
37
|
+
verb: match[1],
|
38
|
+
path: match[2]
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module Ditty
|
46
|
+
class User < ::Sequel::Model
|
47
|
+
one_to_many :permissions, class: ::ProxES::Permission
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module Ditty
|
52
|
+
class Role < ::Sequel::Model
|
53
|
+
one_to_many :permissions, class: ::ProxES::Permission
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/policies/application_policy'
|
4
|
+
|
5
|
+
module ProxES
|
6
|
+
class PermissionPolicy < Ditty::ApplicationPolicy
|
7
|
+
def create?
|
8
|
+
user && user.super_admin?
|
9
|
+
end
|
10
|
+
|
11
|
+
def list?
|
12
|
+
create?
|
13
|
+
end
|
14
|
+
|
15
|
+
def read?
|
16
|
+
create?
|
17
|
+
end
|
18
|
+
|
19
|
+
def update?
|
20
|
+
read?
|
21
|
+
end
|
22
|
+
|
23
|
+
def delete?
|
24
|
+
create?
|
25
|
+
end
|
26
|
+
|
27
|
+
def permitted_attributes
|
28
|
+
%i[verb pattern role_id user_id]
|
29
|
+
end
|
30
|
+
|
31
|
+
class Scope < Ditty::ApplicationPolicy::Scope
|
32
|
+
def resolve
|
33
|
+
user && user.super_admin? ? scope : scope.where(id: -1)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/object/blank'
|
5
|
+
require 'proxes/policies/request_policy'
|
6
|
+
|
7
|
+
module ProxES
|
8
|
+
class Request
|
9
|
+
class BulkPolicy < RequestPolicy
|
10
|
+
def post?
|
11
|
+
return false if user.nil? ||
|
12
|
+
(request.index && !index_allowed?) ||
|
13
|
+
(request.bulk_indices == '' || patterns.blank?)
|
14
|
+
|
15
|
+
patterns.find do |pattern|
|
16
|
+
request.bulk_indices.find { |idx| idx !~ /#{pattern}/ }
|
17
|
+
end.nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
class Scope < RequestPolicy::Scope
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'proxes/policies/request_policy'
|
4
|
+
|
5
|
+
module ProxES
|
6
|
+
class Request
|
7
|
+
class CreatePolicy < RequestPolicy
|
8
|
+
class Scope < RequestPolicy::Scope
|
9
|
+
def resolve
|
10
|
+
super.count > 0 ? request.index : []
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/db'
|
4
|
+
require 'proxes/models/permission'
|
5
|
+
require 'proxes/policies/request_policy'
|
6
|
+
|
7
|
+
module ProxES
|
8
|
+
class Request
|
9
|
+
class IndexPolicy < RequestPolicy
|
10
|
+
class Scope < RequestPolicy::Scope
|
11
|
+
def resolve
|
12
|
+
result = super
|
13
|
+
return [] unless result.count > 0
|
14
|
+
%w[POST PUT].include?(request.request_method) ? request.index : result
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/object/blank'
|
5
|
+
require 'proxes/policies/request_policy'
|
6
|
+
|
7
|
+
module ProxES
|
8
|
+
class Request
|
9
|
+
class SearchPolicy < RequestPolicy
|
10
|
+
class Scope < RequestPolicy::Scope
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/object/blank'
|
5
|
+
require 'ditty/services/logger'
|
6
|
+
require 'proxes/models/permission'
|
7
|
+
require 'proxes/helpers/indices'
|
8
|
+
|
9
|
+
module ProxES
|
10
|
+
class RequestPolicy
|
11
|
+
include Helpers::Indices
|
12
|
+
|
13
|
+
attr_reader :user, :record
|
14
|
+
alias request record
|
15
|
+
|
16
|
+
def initialize(user, record)
|
17
|
+
@user = user
|
18
|
+
@record = record
|
19
|
+
end
|
20
|
+
|
21
|
+
def method_missing(method_sym, *arguments, &block)
|
22
|
+
return super if method_sym.to_s[-1] != '?'
|
23
|
+
|
24
|
+
return false if request.indices? && !index_allowed?
|
25
|
+
action_allowed? method_sym[0..-2].upcase
|
26
|
+
end
|
27
|
+
|
28
|
+
def respond_to_missing?(name, _include_private = false)
|
29
|
+
name[-1] == '?'
|
30
|
+
end
|
31
|
+
|
32
|
+
def index_allowed?
|
33
|
+
patterns = patterns_for('INDEX').map do |permission|
|
34
|
+
return nil if permission.pattern.blank?
|
35
|
+
permission.pattern.gsub(/\{user.(.*)\}/) { |_match| user.send(Regexp.last_match[1].to_sym) }
|
36
|
+
end.compact
|
37
|
+
filter(request.index, patterns).count > 0
|
38
|
+
end
|
39
|
+
|
40
|
+
def action_allowed?(action)
|
41
|
+
# Give me all the user's permissions that match the verb
|
42
|
+
!!patterns_for(action).find { |permission| (request.path =~ /#{permission.pattern}/) }
|
43
|
+
end
|
44
|
+
|
45
|
+
class Scope
|
46
|
+
include Helpers::Indices
|
47
|
+
|
48
|
+
attr_reader :user, :scope
|
49
|
+
alias request scope
|
50
|
+
|
51
|
+
def initialize(user, scope)
|
52
|
+
@user = user
|
53
|
+
@scope = scope
|
54
|
+
end
|
55
|
+
|
56
|
+
def resolve
|
57
|
+
return [] if user.nil?
|
58
|
+
filter request.index, patterns
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/policies/application_policy'
|
4
|
+
|
5
|
+
module ProxES
|
6
|
+
class SearchPolicy < Ditty::ApplicationPolicy
|
7
|
+
def search?
|
8
|
+
user && user.super_admin?
|
9
|
+
end
|
10
|
+
|
11
|
+
def fields?
|
12
|
+
search?
|
13
|
+
end
|
14
|
+
|
15
|
+
def indices?
|
16
|
+
search?
|
17
|
+
end
|
18
|
+
|
19
|
+
def values?
|
20
|
+
search?
|
21
|
+
end
|
22
|
+
|
23
|
+
class Scope < Ditty::ApplicationPolicy::Scope
|
24
|
+
def resolve
|
25
|
+
user && user.super_admin? ? scope : scope.where(id: -1)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/policies/application_policy'
|
4
|
+
|
5
|
+
module ProxES
|
6
|
+
class StatusPolicy < Ditty::ApplicationPolicy
|
7
|
+
def check?
|
8
|
+
user
|
9
|
+
end
|
10
|
+
|
11
|
+
def list?
|
12
|
+
check?
|
13
|
+
end
|
14
|
+
|
15
|
+
class Scope < Ditty::ApplicationPolicy::Scope
|
16
|
+
def resolve
|
17
|
+
[]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
module ProxES
|
6
|
+
class Request < Rack::Request
|
7
|
+
ID_ENDPOINTS = %w[_create _explain _mlt _percolate _source _termvector _update].freeze
|
8
|
+
WRITE_METHODS = %w[POST PUT DELETE].freeze
|
9
|
+
|
10
|
+
def initialize(env)
|
11
|
+
@started = Time.now.to_f
|
12
|
+
super
|
13
|
+
parse
|
14
|
+
end
|
15
|
+
|
16
|
+
def endpoint
|
17
|
+
path_parts[0]
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse
|
21
|
+
path_parts
|
22
|
+
end
|
23
|
+
|
24
|
+
def indices?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def html?
|
29
|
+
get_header('HTTP_ACCEPT') && get_header('HTTP_ACCEPT').include?('text/html')
|
30
|
+
end
|
31
|
+
|
32
|
+
def duration
|
33
|
+
Time.now.to_f - @started
|
34
|
+
end
|
35
|
+
|
36
|
+
def user_id
|
37
|
+
return env['omniauth.auth'].uid if env['omniauth.auth']
|
38
|
+
env['rack.session']['user_id'] if env['rack.session']
|
39
|
+
end
|
40
|
+
|
41
|
+
def user
|
42
|
+
return nil if user_id.nil?
|
43
|
+
@user ||= Ditty::User[user_id]
|
44
|
+
end
|
45
|
+
|
46
|
+
def detail
|
47
|
+
"#{request_method.upcase} #{fullpath} (#{self.class.name})"
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def path_parts
|
53
|
+
@path_parts ||= path.split('?')[0][1..-1].split('/')
|
54
|
+
end
|
55
|
+
|
56
|
+
def check_part(val)
|
57
|
+
return val if val.nil?
|
58
|
+
return [] if [endpoint, '_all'].include?(val) && !WRITE_METHODS.include?(request_method)
|
59
|
+
val.split(',')
|
60
|
+
end
|
61
|
+
|
62
|
+
class << self
|
63
|
+
def from_env(env)
|
64
|
+
endpoint = path_endpoint(env['REQUEST_PATH'])
|
65
|
+
endpoint_class = endpoint.nil? ? 'index' : endpoint[1..-1]
|
66
|
+
begin
|
67
|
+
require 'proxes/request/' + endpoint_class.downcase
|
68
|
+
Request.const_get(endpoint_class.titlecase).new(env)
|
69
|
+
rescue LoadError
|
70
|
+
new(env)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def path_endpoint(path)
|
75
|
+
return '_root' if ['', nil, '/'].include? path
|
76
|
+
path_parts = path[1..-1].split('/')
|
77
|
+
return path_parts[-1] if ID_ENDPOINTS.include? path_parts[-1]
|
78
|
+
return path_parts[-2] if path_parts[-1] == 'count' && path_parts[-2] == '_percolate'
|
79
|
+
return path_parts[-2] if path_parts[-1] == 'scroll' && path_parts[-2] == '_search'
|
80
|
+
path_parts.find { |part| part[0] == '_' }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|