proxes 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|