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