proxes 0.9.8 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ditty/components/proxes.rb +89 -0
  3. data/lib/proxes/controllers/permissions.rb +41 -0
  4. data/lib/proxes/controllers/search.rb +55 -0
  5. data/lib/proxes/controllers/status.rb +115 -0
  6. data/lib/proxes/forwarder.rb +49 -0
  7. data/lib/proxes/helpers/indices.rb +33 -0
  8. data/lib/proxes/loggers/elasticsearch.rb +10 -0
  9. data/lib/proxes/middleware/error_handling.rb +64 -0
  10. data/lib/proxes/middleware/metrics.rb +25 -0
  11. data/lib/proxes/middleware/security.rb +59 -0
  12. data/lib/proxes/models/permission.rb +55 -0
  13. data/lib/proxes/policies/permission_policy.rb +37 -0
  14. data/lib/proxes/policies/request/bulk_policy.rb +24 -0
  15. data/lib/proxes/policies/request/cat_policy.rb +12 -0
  16. data/lib/proxes/policies/request/create_policy.rb +15 -0
  17. data/lib/proxes/policies/request/index_policy.rb +19 -0
  18. data/lib/proxes/policies/request/root_policy.rb +13 -0
  19. data/lib/proxes/policies/request/search_policy.rb +14 -0
  20. data/lib/proxes/policies/request/snapshot_policy.rb +15 -0
  21. data/lib/proxes/policies/request/stats_policy.rb +12 -0
  22. data/lib/proxes/policies/request_policy.rb +62 -0
  23. data/lib/proxes/policies/search_policy.rb +29 -0
  24. data/lib/proxes/policies/status_policy.rb +21 -0
  25. data/lib/proxes/request.rb +84 -0
  26. data/lib/proxes/request/bulk.rb +40 -0
  27. data/lib/proxes/request/cat.rb +32 -0
  28. data/lib/proxes/request/create.rb +33 -0
  29. data/lib/proxes/request/index.rb +33 -0
  30. data/lib/proxes/request/root.rb +11 -0
  31. data/lib/proxes/request/search.rb +37 -0
  32. data/lib/proxes/request/snapshot.rb +17 -0
  33. data/lib/proxes/request/stats.rb +35 -0
  34. data/lib/proxes/services/es.rb +34 -0
  35. data/lib/proxes/services/listener.rb +29 -0
  36. data/lib/proxes/services/search.rb +45 -0
  37. data/lib/proxes/version.rb +5 -0
  38. data/migrate/20170209_permissions.rb +13 -0
  39. data/migrate/20170416_user_specific_permissions.rb +9 -0
  40. data/public/browserconfig.xml +9 -0
  41. data/public/manifest.json +25 -0
  42. data/views/index.haml +1 -0
  43. data/views/layout.haml +60 -0
  44. 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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'proxes/policies/request_policy'
4
+
5
+ module ProxES
6
+ class Request
7
+ class CatPolicy < RequestPolicy
8
+ class Scope < RequestPolicy::Scope
9
+ end
10
+ end
11
+ end
12
+ 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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProxES
4
+ class Request
5
+ class RootPolicy < RequestPolicy
6
+ class Scope < RequestPolicy::Scope
7
+ def resolve
8
+ request
9
+ end
10
+ end
11
+ end
12
+ end
13
+ 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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'proxes/policies/request_policy'
4
+
5
+ module ProxES
6
+ class Request
7
+ class SnapshotPolicy < RequestPolicy
8
+ class Scope < RequestPolicy::Scope
9
+ def resolve
10
+ request
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'proxes/policies/request_policy'
4
+
5
+ module ProxES
6
+ class Request
7
+ class StatsPolicy < RequestPolicy
8
+ class Scope < RequestPolicy::Scope
9
+ end
10
+ end
11
+ end
12
+ 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