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
@@ -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
|