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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +24 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.rubocop.yml +12 -0
- data/.ruby-version +1 -0
- data/.travis.yml +18 -0
- data/Gemfile +4 -0
- data/Gemfile.ci +15 -0
- data/Gemfile.dev +10 -0
- data/Gemfile.dev.lock +155 -0
- data/LICENSE.txt +8 -0
- data/README.md +83 -0
- data/Rakefile +9 -0
- data/Vagrantfile +46 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config.ru +64 -0
- data/config/logger.yml +3 -0
- data/gulpfile.js +80 -0
- data/lib/proxes.rb +3 -0
- data/lib/proxes/app.rb +48 -0
- data/lib/proxes/controllers/application.rb +53 -0
- data/lib/proxes/controllers/audit_logs.rb +34 -0
- data/lib/proxes/controllers/auth_identity.rb +21 -0
- data/lib/proxes/controllers/component.rb +108 -0
- data/lib/proxes/controllers/permissions.rb +10 -0
- data/lib/proxes/controllers/roles.rb +10 -0
- data/lib/proxes/controllers/users.rb +119 -0
- data/lib/proxes/db.rb +17 -0
- data/lib/proxes/helpers/authentication.rb +45 -0
- data/lib/proxes/helpers/component.rb +40 -0
- data/lib/proxes/helpers/indices.rb +16 -0
- data/lib/proxes/helpers/pundit.rb +39 -0
- data/lib/proxes/helpers/views.rb +41 -0
- data/lib/proxes/loggers/elasticsearch.rb +9 -0
- data/lib/proxes/models/audit_log.rb +12 -0
- data/lib/proxes/models/identity.rb +67 -0
- data/lib/proxes/models/permission.rb +17 -0
- data/lib/proxes/models/role.rb +14 -0
- data/lib/proxes/models/user.rb +57 -0
- data/lib/proxes/policies/application_policy.rb +20 -0
- data/lib/proxes/policies/audit_log_policy.rb +40 -0
- data/lib/proxes/policies/identity_policy.rb +24 -0
- data/lib/proxes/policies/permission_policy.rb +40 -0
- data/lib/proxes/policies/request/root_policy.rb +12 -0
- data/lib/proxes/policies/request/search_policy.rb +15 -0
- data/lib/proxes/policies/request/snapshot_policy.rb +12 -0
- data/lib/proxes/policies/request/stats_policy.rb +15 -0
- data/lib/proxes/policies/request_policy.rb +69 -0
- data/lib/proxes/policies/role_policy.rb +40 -0
- data/lib/proxes/policies/token_policy.rb +46 -0
- data/lib/proxes/policies/user_policy.rb +46 -0
- data/lib/proxes/rake_tasks.rb +59 -0
- data/lib/proxes/request.rb +51 -0
- data/lib/proxes/request/root.rb +10 -0
- data/lib/proxes/request/search.rb +37 -0
- data/lib/proxes/request/snapshot.rb +16 -0
- data/lib/proxes/request/stats.rb +30 -0
- data/lib/proxes/security.rb +59 -0
- data/lib/proxes/seed.rb +10 -0
- data/lib/proxes/services/logger.rb +50 -0
- data/lib/proxes/version.rb +4 -0
- data/migrate/001_tables.rb +47 -0
- data/migrate/002_audit_log.rb +11 -0
- data/package.json +34 -0
- data/proxes.gemspec +44 -0
- data/public/js/bundle.js +28988 -0
- data/src/scripts/app.js +10 -0
- data/views/404.haml +1 -0
- data/views/audit_logs/index.haml +18 -0
- data/views/error.haml +4 -0
- data/views/getting_started.haml +16 -0
- data/views/identity/login.haml +19 -0
- data/views/identity/register.haml +17 -0
- data/views/index.haml +3 -0
- data/views/layout.haml +48 -0
- data/views/partials/delete_form.haml +4 -0
- data/views/partials/form_control.haml +21 -0
- data/views/partials/navbar.haml +25 -0
- data/views/partials/notifications.haml +24 -0
- data/views/partials/pager.haml +19 -0
- data/views/partials/sidebar.haml +32 -0
- data/views/permissions/display.haml +24 -0
- data/views/permissions/edit.haml +11 -0
- data/views/permissions/form.haml +3 -0
- data/views/permissions/index.haml +14 -0
- data/views/permissions/new.haml +10 -0
- data/views/roles/display.haml +33 -0
- data/views/roles/edit.haml +11 -0
- data/views/roles/form.haml +1 -0
- data/views/roles/index.haml +17 -0
- data/views/roles/new.haml +10 -0
- data/views/users/display.haml +32 -0
- data/views/users/edit.haml +11 -0
- data/views/users/identity.haml +3 -0
- data/views/users/index.haml +20 -0
- data/views/users/new.haml +11 -0
- data/views/users/profile.haml +37 -0
- data/views/users/user.haml +3 -0
- metadata +424 -0
@@ -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
|
data/lib/proxes/db.rb
ADDED
@@ -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,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
|