proxes 0.1.0

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 (101) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +24 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +12 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +18 -0
  8. data/Gemfile +4 -0
  9. data/Gemfile.ci +15 -0
  10. data/Gemfile.dev +10 -0
  11. data/Gemfile.dev.lock +155 -0
  12. data/LICENSE.txt +8 -0
  13. data/README.md +83 -0
  14. data/Rakefile +9 -0
  15. data/Vagrantfile +46 -0
  16. data/bin/console +15 -0
  17. data/bin/setup +8 -0
  18. data/config.ru +64 -0
  19. data/config/logger.yml +3 -0
  20. data/gulpfile.js +80 -0
  21. data/lib/proxes.rb +3 -0
  22. data/lib/proxes/app.rb +48 -0
  23. data/lib/proxes/controllers/application.rb +53 -0
  24. data/lib/proxes/controllers/audit_logs.rb +34 -0
  25. data/lib/proxes/controllers/auth_identity.rb +21 -0
  26. data/lib/proxes/controllers/component.rb +108 -0
  27. data/lib/proxes/controllers/permissions.rb +10 -0
  28. data/lib/proxes/controllers/roles.rb +10 -0
  29. data/lib/proxes/controllers/users.rb +119 -0
  30. data/lib/proxes/db.rb +17 -0
  31. data/lib/proxes/helpers/authentication.rb +45 -0
  32. data/lib/proxes/helpers/component.rb +40 -0
  33. data/lib/proxes/helpers/indices.rb +16 -0
  34. data/lib/proxes/helpers/pundit.rb +39 -0
  35. data/lib/proxes/helpers/views.rb +41 -0
  36. data/lib/proxes/loggers/elasticsearch.rb +9 -0
  37. data/lib/proxes/models/audit_log.rb +12 -0
  38. data/lib/proxes/models/identity.rb +67 -0
  39. data/lib/proxes/models/permission.rb +17 -0
  40. data/lib/proxes/models/role.rb +14 -0
  41. data/lib/proxes/models/user.rb +57 -0
  42. data/lib/proxes/policies/application_policy.rb +20 -0
  43. data/lib/proxes/policies/audit_log_policy.rb +40 -0
  44. data/lib/proxes/policies/identity_policy.rb +24 -0
  45. data/lib/proxes/policies/permission_policy.rb +40 -0
  46. data/lib/proxes/policies/request/root_policy.rb +12 -0
  47. data/lib/proxes/policies/request/search_policy.rb +15 -0
  48. data/lib/proxes/policies/request/snapshot_policy.rb +12 -0
  49. data/lib/proxes/policies/request/stats_policy.rb +15 -0
  50. data/lib/proxes/policies/request_policy.rb +69 -0
  51. data/lib/proxes/policies/role_policy.rb +40 -0
  52. data/lib/proxes/policies/token_policy.rb +46 -0
  53. data/lib/proxes/policies/user_policy.rb +46 -0
  54. data/lib/proxes/rake_tasks.rb +59 -0
  55. data/lib/proxes/request.rb +51 -0
  56. data/lib/proxes/request/root.rb +10 -0
  57. data/lib/proxes/request/search.rb +37 -0
  58. data/lib/proxes/request/snapshot.rb +16 -0
  59. data/lib/proxes/request/stats.rb +30 -0
  60. data/lib/proxes/security.rb +59 -0
  61. data/lib/proxes/seed.rb +10 -0
  62. data/lib/proxes/services/logger.rb +50 -0
  63. data/lib/proxes/version.rb +4 -0
  64. data/migrate/001_tables.rb +47 -0
  65. data/migrate/002_audit_log.rb +11 -0
  66. data/package.json +34 -0
  67. data/proxes.gemspec +44 -0
  68. data/public/js/bundle.js +28988 -0
  69. data/src/scripts/app.js +10 -0
  70. data/views/404.haml +1 -0
  71. data/views/audit_logs/index.haml +18 -0
  72. data/views/error.haml +4 -0
  73. data/views/getting_started.haml +16 -0
  74. data/views/identity/login.haml +19 -0
  75. data/views/identity/register.haml +17 -0
  76. data/views/index.haml +3 -0
  77. data/views/layout.haml +48 -0
  78. data/views/partials/delete_form.haml +4 -0
  79. data/views/partials/form_control.haml +21 -0
  80. data/views/partials/navbar.haml +25 -0
  81. data/views/partials/notifications.haml +24 -0
  82. data/views/partials/pager.haml +19 -0
  83. data/views/partials/sidebar.haml +32 -0
  84. data/views/permissions/display.haml +24 -0
  85. data/views/permissions/edit.haml +11 -0
  86. data/views/permissions/form.haml +3 -0
  87. data/views/permissions/index.haml +14 -0
  88. data/views/permissions/new.haml +10 -0
  89. data/views/roles/display.haml +33 -0
  90. data/views/roles/edit.haml +11 -0
  91. data/views/roles/form.haml +1 -0
  92. data/views/roles/index.haml +17 -0
  93. data/views/roles/new.haml +10 -0
  94. data/views/users/display.haml +32 -0
  95. data/views/users/edit.haml +11 -0
  96. data/views/users/identity.haml +3 -0
  97. data/views/users/index.haml +20 -0
  98. data/views/users/new.haml +11 -0
  99. data/views/users/profile.haml +37 -0
  100. data/views/users/user.haml +3 -0
  101. metadata +424 -0
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ require 'proxes/controllers/component'
3
+ require 'proxes/models/role'
4
+ require 'proxes/policies/role_policy'
5
+
6
+ module ProxES
7
+ class Roles < Component
8
+ set model_class: ProxES::Role
9
+ end
10
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+ require 'proxes/controllers/component'
3
+ require 'proxes/models/user'
4
+ require 'proxes/policies/user_policy'
5
+ require 'proxes/models/identity'
6
+ require 'proxes/policies/identity_policy'
7
+
8
+ module ProxES
9
+ class Users < Component
10
+ set model_class: ProxES::User
11
+
12
+ # New
13
+ get '/new' do
14
+ authorize settings.model_class, :create
15
+
16
+ locals = {
17
+ title: heading(:new),
18
+ entity: ProxES::User.new,
19
+ identity: ProxES::Identity.new
20
+ }
21
+ haml :"#{view_location}/new", locals: locals, layout_opts: { locals: locals }
22
+ end
23
+
24
+ # Create
25
+ post '/' do
26
+ authorize settings.model_class, :create
27
+
28
+ locals = { title: heading(:new) }
29
+
30
+ user_params = permitted_attributes(User, :create)
31
+ identity_params = permitted_attributes(Identity, :create)
32
+ user_params['email'] = identity_params['username']
33
+ roles = user_params.delete('role_id')
34
+
35
+ user = locals[:user] = User.new(user_params)
36
+ identity = locals[:identity] = Identity.new(identity_params)
37
+
38
+ if identity.valid? && user.valid?
39
+ DB.transaction(isolation: :serializable) do
40
+ identity.save
41
+ user.save
42
+ user.add_identity identity
43
+ roles.each do |role_id|
44
+ user.add_role(role_id) unless user.roles.map(&:id).include? role_id.to_i
45
+ end if roles
46
+ user.check_roles
47
+ end
48
+
49
+ flash[:success] = 'User created'
50
+ redirect "/_proxes/users/#{user.id}"
51
+ else
52
+ flash.now[:danger] = 'Could not create the user'
53
+ locals[:entity] = user
54
+ locals[:identity] = identity
55
+ haml :"#{view_location}/new", locals: locals
56
+ end
57
+ end
58
+
59
+ # Update
60
+ put '/:id' do |id|
61
+ entity = dataset[id.to_i]
62
+ halt 404 unless entity
63
+ authorize entity, :update
64
+
65
+ values = permitted_attributes(settings.model_class, :update)
66
+ roles = values.delete('role_id')
67
+ entity.set values
68
+ if entity.valid? && entity.save
69
+ entity.remove_all_roles
70
+ roles.each { |role_id| entity.add_role(role_id) } if roles
71
+ entity.check_roles
72
+ flash[:success] = "#{heading} Updated"
73
+ redirect "/_proxes/users/#{entity.id}"
74
+ else
75
+ haml :"#{view_location}/edit", locals: { entity: entity, title: heading(:edit) }
76
+ end
77
+ end
78
+
79
+ put '/:id/identity' do |id|
80
+ entity = dataset[id.to_i]
81
+ halt 404 unless entity
82
+ authorize entity, :update
83
+
84
+ identity = entity.identity.first
85
+
86
+ values = permitted_attributes(Identity, :create)
87
+ identity.set values
88
+ if identity.valid? && identity.save
89
+ flash[:success] = "Password Updated"
90
+ redirect '/_proxes/users/profile'
91
+ else
92
+ haml :"#{view_location}/profile", locals: { entity: entity, identity: identity, title: heading }
93
+ end
94
+ end
95
+
96
+ # Delete
97
+ delete '/:id' do |id|
98
+ entity = dataset[id.to_i]
99
+ halt 404 unless entity
100
+ authorize entity, :delete
101
+
102
+ entity.remove_all_identity
103
+ entity.remove_all_roles
104
+ entity.destroy
105
+
106
+ flash[:success] = "#{heading} Deleted"
107
+ redirect '/_proxes/users'
108
+ end
109
+
110
+ # Profile
111
+ get '/profile' do
112
+ entity = current_user
113
+ halt 404 unless entity
114
+ authorize entity, :read
115
+
116
+ haml :"#{view_location}/profile", locals: { entity: entity, identity: entity.identity.first, title: 'My Account' }
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ require 'sequel'
3
+ require 'proxes/services/logger'
4
+
5
+ # Delete DATABASE_URL from the environment, so it isn't accidently
6
+ # passed to subprocesses. DATABASE_URL may contain passwords.
7
+ DB = Sequel.connect(ENV['RACK_ENV'] == 'production' ? ENV.delete('DATABASE_URL') : ENV['DATABASE_URL'])
8
+
9
+ DB.loggers << ProxES::Services::Logger.instance
10
+
11
+ DB.extension(:pagination)
12
+
13
+ Sequel::Model.plugin :auto_validations
14
+ Sequel::Model.plugin :update_or_create
15
+ # Sequel::Model.plugin :prepared_statements
16
+ # Sequel::Model.plugin :prepared_statements_associations
17
+ Sequel::Model.plugin :timestamps, update_on_create: true
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ module ProxES
3
+ module Helpers
4
+ module Authentication
5
+ def current_user
6
+ return nil unless env['rack.session'] && env['rack.session']['user_id']
7
+ @user ||= User[env['rack.session']['user_id']]
8
+ end
9
+
10
+ def current_user=(user)
11
+ env['rack.session']['user_id'] = user.id
12
+ end
13
+
14
+ def authenticate
15
+ authenticated?
16
+ end
17
+
18
+ def authenticated?
19
+ !env['rack.session']['user_id'].nil?
20
+ end
21
+
22
+ def authenticate!
23
+ raise NotAuthenticated unless env['rack.session']['user_id']
24
+ true
25
+ end
26
+
27
+ def logout
28
+ env['rack.session'].delete('user_id')
29
+ end
30
+
31
+ def check_basic
32
+ auth = Rack::Auth::Basic::Request.new(env)
33
+ return unless auth.provided?
34
+ return unless auth.basic?
35
+
36
+ identity = ProxES::Identity.find(username: auth.credentials[0])
37
+ raise NotAuthenticated unless identity
38
+ self.current_user = identity.user if identity.authenticate(auth.credentials[1])
39
+ end
40
+ end
41
+
42
+ class NotAuthenticated < StandardError
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ require 'active_support'
3
+ require 'active_support/inflector'
4
+
5
+ module ProxES
6
+ module Helpers
7
+ module Component
8
+ def dataset
9
+ policy_scope(settings.model_class)
10
+ end
11
+
12
+ def list
13
+ params['count'] ||= 10
14
+ params['page'] ||= 1
15
+
16
+ dataset.select.paginate(params['page'].to_i, params['count'].to_i)
17
+ end
18
+
19
+ def heading(action = nil)
20
+ @headings ||= begin
21
+ heading = ActiveSupport::Inflector.demodulize settings.model_class
22
+ h = Hash.new(heading)
23
+ h[:new] = "New #{heading}"
24
+ h[:list] = ActiveSupport::Inflector.pluralize heading
25
+ h[:edit] = "Edit #{heading}"
26
+ h
27
+ end
28
+ @headings[action]
29
+ end
30
+
31
+ def base_path
32
+ settings.base_path || "/_proxes/#{heading(:list).downcase}"
33
+ end
34
+
35
+ def view_location
36
+ settings.view_location || heading(:list).underscore.to_s
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module ProxES
3
+ module Helpers
4
+ module Indices
5
+ def filter(asked, against)
6
+ return against.map { |a| a.gsub(%r{\.\*}, '*') } if asked == ['*'] || asked == []
7
+
8
+ answer = []
9
+ against.each do |pattern|
10
+ answer.concat asked.select { |idx| idx =~ %r{#{pattern}} }
11
+ end
12
+ answer
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+ require 'pundit'
3
+ require 'proxes/request'
4
+
5
+ module ProxES
6
+ module Helpers
7
+ module Pundit
8
+ include ::Pundit
9
+
10
+ def authorize(record, query = nil)
11
+ if record.is_a?(::ProxES::Request)
12
+ query = record.request_method.downcase
13
+ elsif query.nil?
14
+ raise ArgumentError, 'Pundit cannot determine the query'
15
+ end
16
+ query = :"#{query}?" unless query[-1] == '?'
17
+ super
18
+ end
19
+
20
+ def permitted_attributes(record, action)
21
+ param_key = PolicyFinder.new(record).param_key
22
+ policy = policy(record)
23
+ method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
24
+ "permitted_attributes_for_#{action}"
25
+ else
26
+ 'permitted_attributes'
27
+ end
28
+
29
+ request.params.fetch(param_key, {}).select do |key, _value|
30
+ policy.public_send(method_name).include? key.to_sym
31
+ end
32
+ end
33
+
34
+ def pundit_user
35
+ current_user
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ module ProxES
3
+ module Helpers
4
+ module Views
5
+ def form_control(name, model, opts = {})
6
+ label = opts.delete(:label) || name.to_s.titlecase
7
+ klass = opts.delete(:class) || 'form-control' unless opts[:type] == 'file'
8
+ group = opts.delete(:group) || model.class.to_s.demodulize.underscore
9
+ field = opts.delete(:field) || name
10
+
11
+ attributes = { type: 'text', id: name, name: "#{group}[#{name}]", class: klass }.merge(opts)
12
+ locals = { model: model, label: label, attributes: attributes, name: name, group: group, field: field }
13
+ haml :'partials/form_control', locals: locals
14
+ end
15
+
16
+ def flash_messages(key = :flash)
17
+ return '' if flash(key).empty?
18
+ id = (key == :flash ? 'flash' : "flash_#{key}")
19
+ messages = flash(key).collect do |message|
20
+ " <div class='alert alert-#{message[0]} alert-dismissable' role='alert'>#{message[1]}</div>\n"
21
+ end
22
+ "<div id='#{id}'>\n" + messages.join + '</div>'
23
+ end
24
+
25
+ def delete_form(entity, label = 'Delete')
26
+ locals = { delete_label: label, entity: entity }
27
+ haml :'partials/delete_form', locals: locals
28
+ end
29
+
30
+ def pagination(list, base_path)
31
+ locals = {
32
+ next_link: list.last_page? ? '#' : "#{base_path}?page=#{list.next_page}&count=#{list.page_size}",
33
+ prev_link: list.first_page? ? '#' : "#{base_path}?page=#{list.prev_page}&count=#{list.page_size}",
34
+ base_path: base_path,
35
+ list: list
36
+ }
37
+ haml :'partials/pager', locals: locals
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ require 'logger'
3
+
4
+ module ProxES
5
+ module Loggers
6
+ class Elasticsearch < ::Logger
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ require 'sequel'
3
+
4
+ module ProxES
5
+ class AuditLog < Sequel::Model
6
+ many_to_one :user
7
+
8
+ def validate
9
+ validates_presence [:user_id, :action]
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+ require 'sequel'
3
+ require 'omniauth-identity'
4
+ require 'active_support'
5
+ require 'active_support/core_ext/object/blank'
6
+
7
+ module ProxES
8
+ class Identity < Sequel::Model
9
+ many_to_one :user
10
+
11
+ attr_accessor :password, :password_confirmation
12
+
13
+ # OmniAuth Related
14
+ include OmniAuth::Identity::Model
15
+
16
+ def self.locate(conditions)
17
+ where(conditions).first
18
+ end
19
+
20
+ def authenticate(unencrypted)
21
+ self if ::BCrypt::Password.new(crypted_password) == unencrypted
22
+ end
23
+
24
+ def persisted?
25
+ !new? && @destroyed != true
26
+ end
27
+
28
+ # Return whatever we want to pass to the omniauth hash here
29
+ def info
30
+ {
31
+ email: username
32
+ }
33
+ end
34
+
35
+ # Validation
36
+ def validate
37
+ validates_presence :username
38
+ unless username.blank?
39
+ validates_unique :username
40
+ validates_format(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :username)
41
+ end
42
+
43
+ if password_required
44
+ validates_presence :password
45
+ validates_presence :password_confirmation
46
+ validates_min_length 8, :password
47
+ end
48
+
49
+ errors.add(:password_confirmation, 'must match password') if !password.blank? && password != password_confirmation
50
+ end
51
+
52
+ # Callbacks
53
+ def before_save
54
+ encrypt_password unless password == '' || password.nil?
55
+ end
56
+
57
+ private
58
+
59
+ def encrypt_password
60
+ self.crypted_password = ::BCrypt::Password.create(password)
61
+ end
62
+
63
+ def password_required
64
+ crypted_password.blank? || !password.blank?
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ require 'sequel'
3
+
4
+ module ProxES
5
+ class Permission < Sequel::Model
6
+ many_to_one :role
7
+
8
+ def validate
9
+ validates_presence [:role_id, :verb, :pattern]
10
+ validates_includes self.class.verbs, :verb
11
+ end
12
+
13
+ def self.verbs
14
+ ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE', 'INDEX']
15
+ end
16
+ end
17
+ end