ditty 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.pryrc +6 -0
- data/.rspec +2 -0
- data/.rubocop.yml +15 -0
- data/.travis.yml +15 -0
- data/Gemfile.ci +19 -0
- data/License.txt +7 -0
- data/Rakefile +10 -0
- data/Readme.md +67 -0
- data/config.ru +33 -0
- data/ditty.gemspec +46 -0
- data/lib/ditty/components/app.rb +78 -0
- data/lib/ditty/controllers/application.rb +79 -0
- data/lib/ditty/controllers/audit_logs.rb +44 -0
- data/lib/ditty/controllers/component.rb +161 -0
- data/lib/ditty/controllers/main.rb +86 -0
- data/lib/ditty/controllers/roles.rb +16 -0
- data/lib/ditty/controllers/users.rb +183 -0
- data/lib/ditty/db.rb +12 -0
- data/lib/ditty/helpers/authentication.rb +58 -0
- data/lib/ditty/helpers/component.rb +63 -0
- data/lib/ditty/helpers/pundit.rb +34 -0
- data/lib/ditty/helpers/views.rb +50 -0
- data/lib/ditty/helpers/wisper.rb +14 -0
- data/lib/ditty/listener.rb +23 -0
- data/lib/ditty/models/audit_log.rb +14 -0
- data/lib/ditty/models/base.rb +7 -0
- data/lib/ditty/models/identity.rb +70 -0
- data/lib/ditty/models/role.rb +16 -0
- data/lib/ditty/models/user.rb +63 -0
- data/lib/ditty/policies/application_policy.rb +21 -0
- data/lib/ditty/policies/audit_log_policy.rb +41 -0
- data/lib/ditty/policies/identity_policy.rb +25 -0
- data/lib/ditty/policies/role_policy.rb +41 -0
- data/lib/ditty/policies/user_policy.rb +47 -0
- data/lib/ditty/rake_tasks.rb +85 -0
- data/lib/ditty/seed.rb +1 -0
- data/lib/ditty/services/logger.rb +48 -0
- data/lib/ditty/version.rb +5 -0
- data/lib/ditty.rb +142 -0
- data/migrate/20170207_base_tables.rb +40 -0
- data/migrate/20170208_audit_log.rb +12 -0
- data/migrate/20170416_audit_log_details.rb +9 -0
- data/public/browserconfig.xml +9 -0
- data/public/images/apple-icon.png +0 -0
- data/public/images/favicon-16x16.png +0 -0
- data/public/images/favicon-32x32.png +0 -0
- data/public/images/launcher-icon-1x.png +0 -0
- data/public/images/launcher-icon-2x.png +0 -0
- data/public/images/launcher-icon-4x.png +0 -0
- data/public/images/mstile-150x150.png +0 -0
- data/public/images/safari-pinned-tab.svg +43 -0
- data/public/manifest.json +25 -0
- data/views/404.haml +7 -0
- data/views/audit_logs/index.haml +30 -0
- data/views/error.haml +4 -0
- data/views/identity/login.haml +19 -0
- data/views/identity/register.haml +14 -0
- data/views/index.haml +1 -0
- data/views/layout.haml +55 -0
- data/views/partials/delete_form.haml +4 -0
- data/views/partials/footer.haml +5 -0
- data/views/partials/form_control.haml +20 -0
- data/views/partials/navbar.haml +24 -0
- data/views/partials/notifications.haml +24 -0
- data/views/partials/pager.haml +14 -0
- data/views/partials/sidebar.haml +35 -0
- data/views/roles/display.haml +18 -0
- data/views/roles/edit.haml +11 -0
- data/views/roles/form.haml +1 -0
- data/views/roles/index.haml +22 -0
- data/views/roles/new.haml +10 -0
- data/views/users/display.haml +50 -0
- data/views/users/edit.haml +11 -0
- data/views/users/identity.haml +3 -0
- data/views/users/index.haml +23 -0
- data/views/users/new.haml +11 -0
- data/views/users/profile.haml +39 -0
- data/views/users/user.haml +3 -0
- metadata +431 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/controllers/application'
|
4
|
+
|
5
|
+
module Ditty
|
6
|
+
class Main < Application
|
7
|
+
def find_template(views, name, engine, &block)
|
8
|
+
super(views, name, engine, &block) # Root
|
9
|
+
super(::Ditty::App.view_folder, name, engine, &block) # Basic Plugin
|
10
|
+
end
|
11
|
+
|
12
|
+
# Home Page
|
13
|
+
get '/' do
|
14
|
+
authenticate!
|
15
|
+
haml :index, locals: { title: 'Home' }
|
16
|
+
end
|
17
|
+
|
18
|
+
# OmniAuth Identity Stuff
|
19
|
+
# Log in Page
|
20
|
+
get '/auth/identity' do
|
21
|
+
haml :'identity/login', locals: { title: 'Log In' }
|
22
|
+
end
|
23
|
+
|
24
|
+
get '/auth/failure' do
|
25
|
+
broadcast(:identity_failed_login)
|
26
|
+
flash[:warning] = 'Invalid credentials. Please try again.'
|
27
|
+
redirect "#{settings.map_path}/auth/identity"
|
28
|
+
end
|
29
|
+
|
30
|
+
post '/auth/identity/callback' do
|
31
|
+
if env['omniauth.auth']
|
32
|
+
# Successful Login
|
33
|
+
user = User.find(email: env['omniauth.auth']['info']['email'])
|
34
|
+
self.current_user = user
|
35
|
+
log_action(:identity_login, user: user)
|
36
|
+
flash[:success] = 'Logged In'
|
37
|
+
redirect "#{settings.map_path}/"
|
38
|
+
else
|
39
|
+
# Failed Login
|
40
|
+
broadcast(:identity_failed_login)
|
41
|
+
flash[:warning] = 'Invalid credentials. Please try again.'
|
42
|
+
redirect "#{settings.map_path}/auth/identity"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Register Page
|
47
|
+
get '/auth/identity/register' do
|
48
|
+
identity = Identity.new
|
49
|
+
haml :'identity/register', locals: { title: 'Register', identity: identity }
|
50
|
+
end
|
51
|
+
|
52
|
+
# Register Action
|
53
|
+
post '/auth/identity/new' do
|
54
|
+
identity = Identity.new(params['identity'])
|
55
|
+
if identity.valid? && identity.save
|
56
|
+
user = User.find_or_create(email: identity.username)
|
57
|
+
user.add_identity identity
|
58
|
+
|
59
|
+
# Create the SA user if none is present
|
60
|
+
sa = Role.find_or_create(name: 'super_admin')
|
61
|
+
user.add_role sa if User.where(roles: sa).count == 0
|
62
|
+
|
63
|
+
log_action(:identity_register, user: user)
|
64
|
+
flash[:info] = 'Successfully Registered. Please log in'
|
65
|
+
redirect "#{settings.map_path}/auth/identity"
|
66
|
+
else
|
67
|
+
flash.now[:warning] = 'Could not complete the registration. Please try again.'
|
68
|
+
haml :'identity/register', locals: { identity: identity }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Logout Action
|
73
|
+
delete '/auth/identity' do
|
74
|
+
log_action(:identity_logout)
|
75
|
+
logout
|
76
|
+
flash[:info] = 'Logged Out'
|
77
|
+
|
78
|
+
redirect "#{settings.map_path}/"
|
79
|
+
end
|
80
|
+
|
81
|
+
# Unauthenticated
|
82
|
+
get '/unauthenticated' do
|
83
|
+
redirect "#{settings.map_path}/auth/identity"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/controllers/component'
|
4
|
+
require 'ditty/models/role'
|
5
|
+
require 'ditty/policies/role_policy'
|
6
|
+
|
7
|
+
module Ditty
|
8
|
+
class Roles < Ditty::Component
|
9
|
+
set model_class: Role
|
10
|
+
|
11
|
+
def find_template(views, name, engine, &block)
|
12
|
+
super(views, name, engine, &block) # Root
|
13
|
+
super(::Ditty::App.view_folder, name, engine, &block) # Ditty
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/controllers/component'
|
4
|
+
require 'ditty/models/user'
|
5
|
+
require 'ditty/policies/user_policy'
|
6
|
+
require 'ditty/models/identity'
|
7
|
+
require 'ditty/policies/identity_policy'
|
8
|
+
|
9
|
+
module Ditty
|
10
|
+
class Users < Ditty::Component
|
11
|
+
set model_class: User
|
12
|
+
set track_actions: true
|
13
|
+
|
14
|
+
def find_template(views, name, engine, &block)
|
15
|
+
super(views, name, engine, &block) # Root
|
16
|
+
super(::Ditty::App.view_folder, name, engine, &block) # Ditty
|
17
|
+
end
|
18
|
+
|
19
|
+
# New
|
20
|
+
get '/new' do
|
21
|
+
authorize settings.model_class, :create
|
22
|
+
|
23
|
+
locals = {
|
24
|
+
title: heading(:new),
|
25
|
+
entity: User.new,
|
26
|
+
identity: Identity.new
|
27
|
+
}
|
28
|
+
haml :"#{view_location}/new", locals: locals
|
29
|
+
end
|
30
|
+
|
31
|
+
# Create
|
32
|
+
post '/' do
|
33
|
+
authorize settings.model_class, :create
|
34
|
+
|
35
|
+
locals = { title: heading(:new) }
|
36
|
+
|
37
|
+
user_params = permitted_attributes(User, :create)
|
38
|
+
identity_params = permitted_attributes(Identity, :create)
|
39
|
+
user_params['email'] = identity_params['username']
|
40
|
+
roles = user_params.delete('role_id')
|
41
|
+
|
42
|
+
user = locals[:user] = User.new(user_params)
|
43
|
+
identity = locals[:identity] = Identity.new(identity_params)
|
44
|
+
|
45
|
+
if identity.valid? && user.valid?
|
46
|
+
DB.transaction(isolation: :serializable) do
|
47
|
+
identity.save
|
48
|
+
user.save
|
49
|
+
user.add_identity identity
|
50
|
+
if roles
|
51
|
+
roles.each do |role_id|
|
52
|
+
user.add_role(role_id) unless user.roles.map(&:id).include? role_id.to_i
|
53
|
+
end
|
54
|
+
end
|
55
|
+
user.check_roles
|
56
|
+
end
|
57
|
+
|
58
|
+
log_action("#{dehumanized}_create".to_sym) if settings.track_actions
|
59
|
+
respond_to do |format|
|
60
|
+
format.html do
|
61
|
+
flash[:success] = 'User created'
|
62
|
+
redirect "/users/#{user.id}"
|
63
|
+
end
|
64
|
+
format.json do
|
65
|
+
headers 'Content-Type' => 'application/json'
|
66
|
+
redirect "/users/#{user.id}", 201
|
67
|
+
end
|
68
|
+
end
|
69
|
+
else
|
70
|
+
respond_to do |format|
|
71
|
+
format.html do
|
72
|
+
flash.now[:danger] = 'Could not create the user'
|
73
|
+
locals[:entity] = user
|
74
|
+
locals[:identity] = identity
|
75
|
+
haml :"#{view_location}/new", locals: locals
|
76
|
+
end
|
77
|
+
format.json do
|
78
|
+
headers \
|
79
|
+
'Content-Type' => 'application/json',
|
80
|
+
'Content-Location' => "#{view_location}/new"
|
81
|
+
body ''
|
82
|
+
status 402
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Update
|
89
|
+
put '/:id' do |id|
|
90
|
+
entity = dataset[id.to_i]
|
91
|
+
halt 404 unless entity
|
92
|
+
authorize entity, :update
|
93
|
+
|
94
|
+
values = permitted_attributes(settings.model_class, :update)
|
95
|
+
roles = values.delete('role_id')
|
96
|
+
entity.set values
|
97
|
+
if entity.valid? && entity.save
|
98
|
+
entity.remove_all_roles
|
99
|
+
roles.each { |role_id| entity.add_role(role_id) } if roles
|
100
|
+
entity.check_roles
|
101
|
+
log_action("#{dehumanized}_update".to_sym) if settings.track_actions
|
102
|
+
respond_to do |format|
|
103
|
+
format.html do
|
104
|
+
flash[:success] = "#{heading} Updated"
|
105
|
+
redirect "/users/#{entity.id}"
|
106
|
+
end
|
107
|
+
format.json do
|
108
|
+
content_type 'application/json'
|
109
|
+
headers 'Location' => "/users/#{entity.id}"
|
110
|
+
body entity.to_hash.to_json
|
111
|
+
status 200
|
112
|
+
end
|
113
|
+
end
|
114
|
+
else
|
115
|
+
haml :"#{view_location}/edit", locals: { entity: entity, title: heading(:edit) }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
put '/:id/identity' do |id|
|
120
|
+
entity = dataset[id.to_i]
|
121
|
+
halt 404 unless entity
|
122
|
+
authorize entity, :update
|
123
|
+
|
124
|
+
identity = entity.identity.first
|
125
|
+
identity_params = params['identity']
|
126
|
+
|
127
|
+
unless identity_params['password'] == identity_params['password_confirmation']
|
128
|
+
flash[:warning] = 'Password didn\'t match'
|
129
|
+
return redirect back
|
130
|
+
end
|
131
|
+
|
132
|
+
unless current_user.super_admin? || identity.authenticate(identity_params['old_password'])
|
133
|
+
log_action("#{dehumanized}_update_password_failed".to_sym) if settings.track_actions
|
134
|
+
flash[:danger] = 'Old Password didn\'t match'
|
135
|
+
return redirect back
|
136
|
+
end
|
137
|
+
|
138
|
+
values = permitted_attributes(Identity, :create)
|
139
|
+
identity.set values
|
140
|
+
if identity.valid? && identity.save
|
141
|
+
log_action("#{dehumanized}_update_password".to_sym) if settings.track_actions
|
142
|
+
flash[:success] = 'Password Updated'
|
143
|
+
redirect "#{base_path}/#{entity.id}"
|
144
|
+
elsif current_user.super_admin?
|
145
|
+
haml :"#{view_location}/display", locals: { entity: entity, identity: identity, title: heading }
|
146
|
+
else
|
147
|
+
haml :"#{view_location}/profile", locals: { entity: entity, identity: identity, title: heading }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Delete
|
152
|
+
delete '/:id', provides: %i[html json] do |id|
|
153
|
+
entity = dataset[id.to_i]
|
154
|
+
halt 404 unless entity
|
155
|
+
authorize entity, :delete
|
156
|
+
|
157
|
+
entity.remove_all_identity
|
158
|
+
entity.remove_all_roles
|
159
|
+
entity.destroy
|
160
|
+
|
161
|
+
log_action("#{dehumanized}_delete".to_sym) if settings.track_actions
|
162
|
+
respond_to do |format|
|
163
|
+
format.html do
|
164
|
+
flash[:success] = "#{heading} Deleted"
|
165
|
+
redirect '/users'
|
166
|
+
end
|
167
|
+
format.json do
|
168
|
+
content_type 'application/json'
|
169
|
+
headers 'Location' => '/users'
|
170
|
+
status 204
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Profile
|
176
|
+
get '/profile' do
|
177
|
+
entity = current_user
|
178
|
+
authorize entity, :read
|
179
|
+
|
180
|
+
haml :"#{view_location}/profile", locals: { entity: entity, identity: entity.identity.first, title: 'My Account' }
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
data/lib/ditty/db.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sequel'
|
4
|
+
require 'ditty/services/logger'
|
5
|
+
|
6
|
+
# Delete DATABASE_URL from the environment, so it isn't accidently
|
7
|
+
# passed to subprocesses. DATABASE_URL may contain passwords.
|
8
|
+
DB = Sequel.connect(ENV['RACK_ENV'] == 'production' ? ENV.delete('DATABASE_URL') : ENV['DATABASE_URL'])
|
9
|
+
|
10
|
+
log_level = (ENV['SEQUEL_LOGGING_LEVEL'] || :debug).to_sym
|
11
|
+
DB.sql_log_level = log_level
|
12
|
+
DB.loggers << Ditty::Services::Logger.instance
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ditty
|
4
|
+
module Helpers
|
5
|
+
module Authentication
|
6
|
+
def current_user
|
7
|
+
if env['rack.session'].nil? || env['rack.session']['user_id'].nil?
|
8
|
+
self.current_user = anonymous_user
|
9
|
+
end
|
10
|
+
@users ||= Hash.new { |h, k| h[k] = User[k] }
|
11
|
+
@users[env['rack.session']['user_id']]
|
12
|
+
end
|
13
|
+
|
14
|
+
def current_user=(user)
|
15
|
+
env['rack.session'] = {} if env['rack.session'].nil?
|
16
|
+
env['rack.session']['user_id'] = user.id if user
|
17
|
+
end
|
18
|
+
|
19
|
+
def authenticate
|
20
|
+
authenticated?
|
21
|
+
end
|
22
|
+
|
23
|
+
def authenticated?
|
24
|
+
current_user && !current_user.role?('anonymous')
|
25
|
+
end
|
26
|
+
|
27
|
+
def authenticate!
|
28
|
+
raise NotAuthenticated unless authenticated?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def logout
|
33
|
+
env['rack.session'].delete('user_id')
|
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
|
43
|
+
self.current_user = identity.user if identity.authenticate(auth.credentials[1])
|
44
|
+
end
|
45
|
+
|
46
|
+
def anonymous_user
|
47
|
+
return @anonymous_user if defined? @anonymous_user
|
48
|
+
@anonymous_user ||= begin
|
49
|
+
role = ::Ditty::Role.where(name: 'anonymous').first
|
50
|
+
::Ditty::User.where(roles: role).first unless role.nil?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class NotAuthenticated < StandardError
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/inflector'
|
5
|
+
|
6
|
+
module Ditty
|
7
|
+
module Helpers
|
8
|
+
module Component
|
9
|
+
include ActiveSupport::Inflector
|
10
|
+
|
11
|
+
def dataset
|
12
|
+
filtered(policy_scope(settings.model_class))
|
13
|
+
end
|
14
|
+
|
15
|
+
def list
|
16
|
+
params['count'] ||= 10
|
17
|
+
params['page'] ||= 1
|
18
|
+
|
19
|
+
dataset.select.paginate(params['page'].to_i, params['count'].to_i)
|
20
|
+
end
|
21
|
+
|
22
|
+
def heading(action = nil)
|
23
|
+
@headings ||= begin
|
24
|
+
heading = titleize(demodulize(settings.model_class))
|
25
|
+
h = Hash.new(heading)
|
26
|
+
h[:new] = "New #{heading}"
|
27
|
+
h[:list] = pluralize heading
|
28
|
+
h[:edit] = "Edit #{heading}"
|
29
|
+
h
|
30
|
+
end
|
31
|
+
@headings[action]
|
32
|
+
end
|
33
|
+
|
34
|
+
def dehumanized
|
35
|
+
settings.dehumanized || underscore(heading)
|
36
|
+
end
|
37
|
+
|
38
|
+
def base_path
|
39
|
+
settings.base_path || "#{settings.map_path}/#{dasherize(view_location)}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def filters
|
43
|
+
self.class.const_defined?('FILTERS') ? self.class::FILTERS : []
|
44
|
+
end
|
45
|
+
|
46
|
+
def filtered(dataset)
|
47
|
+
filters.each do |filter|
|
48
|
+
next unless params[filter[:name].to_s]
|
49
|
+
filter[:field] ||= filter[:name]
|
50
|
+
dataset = apply_filter(dataset, filter)
|
51
|
+
end
|
52
|
+
dataset
|
53
|
+
end
|
54
|
+
|
55
|
+
def apply_filter(dataset, filter)
|
56
|
+
value = params[filter[:name].to_s]
|
57
|
+
return dataset if value == '' || value.nil?
|
58
|
+
value = value.send(filter[:modifier]) if filter[:modifier]
|
59
|
+
dataset.where(filter[:field].to_sym => value)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pundit'
|
4
|
+
|
5
|
+
module Ditty
|
6
|
+
module Helpers
|
7
|
+
module Pundit
|
8
|
+
include ::Pundit
|
9
|
+
|
10
|
+
def authorize(record, query)
|
11
|
+
query = :"#{query}?" unless query[-1] == '?'
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def permitted_attributes(record, action)
|
16
|
+
param_key = PolicyFinder.new(record).param_key
|
17
|
+
policy = policy(record)
|
18
|
+
method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
|
19
|
+
"permitted_attributes_for_#{action}"
|
20
|
+
else
|
21
|
+
'permitted_attributes'
|
22
|
+
end
|
23
|
+
|
24
|
+
request.params.fetch(param_key, {}).select do |key, _value|
|
25
|
+
policy.public_send(method_name).include? key.to_sym
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def pundit_user
|
30
|
+
current_user
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ditty
|
4
|
+
module Helpers
|
5
|
+
module Views
|
6
|
+
def form_control(name, model, opts = {})
|
7
|
+
label = opts.delete(:label) || name.to_s.titlecase
|
8
|
+
klass = opts.delete(:class) || 'form-control' unless opts[:type] == 'file'
|
9
|
+
group = opts.delete(:group) || model.class.to_s.demodulize.underscore
|
10
|
+
field = opts.delete(:field) || name
|
11
|
+
default = opts.delete(:default) || nil
|
12
|
+
|
13
|
+
attributes = { type: 'text', id: name, name: "#{group}[#{name}]", class: klass }.merge(opts)
|
14
|
+
haml :'partials/form_control', locals: {
|
15
|
+
model: model,
|
16
|
+
label: label,
|
17
|
+
attributes: attributes,
|
18
|
+
name: name,
|
19
|
+
group: group,
|
20
|
+
field: field,
|
21
|
+
default: default
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def flash_messages(key = :flash)
|
26
|
+
return '' if flash(key).empty?
|
27
|
+
id = (key == :flash ? 'flash' : "flash_#{key}")
|
28
|
+
messages = flash(key).collect do |message|
|
29
|
+
" <div class='alert alert-#{message[0]} alert-dismissable' role='alert'>#{message[1]}</div>\n"
|
30
|
+
end
|
31
|
+
"<div id='#{id}'>\n" + messages.join + '</div>'
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete_form(entity, label = 'Delete')
|
35
|
+
locals = { delete_label: label, entity: entity }
|
36
|
+
haml :'partials/delete_form', locals: locals
|
37
|
+
end
|
38
|
+
|
39
|
+
def pagination(list, base_path)
|
40
|
+
locals = {
|
41
|
+
next_link: list.last_page? ? '#' : "#{base_path}?page=#{list.next_page}&count=#{list.page_size}",
|
42
|
+
prev_link: list.first_page? ? '#' : "#{base_path}?page=#{list.prev_page}&count=#{list.page_size}",
|
43
|
+
base_path: base_path,
|
44
|
+
list: list
|
45
|
+
}
|
46
|
+
haml :'partials/pager', locals: locals
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'wisper'
|
2
|
+
|
3
|
+
module Ditty
|
4
|
+
class Listener
|
5
|
+
def initialize
|
6
|
+
@mutex = Mutex.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(method, *args)
|
10
|
+
vals = { action: method }
|
11
|
+
return unless args[0].is_a? Hash
|
12
|
+
vals[:user] = args[0][:user] if args[0] && args[0].key?(:user)
|
13
|
+
vals[:details] = args[0][:details] if args[0] && args[0].key?(:details)
|
14
|
+
@mutex.synchronize { AuditLog.create vals }
|
15
|
+
end
|
16
|
+
|
17
|
+
def respond_to_missing?(_method, _include_private = false)
|
18
|
+
true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Wisper.subscribe(Ditty::Listener.new)
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bcrypt'
|
4
|
+
require 'ditty/models/base'
|
5
|
+
require 'omniauth-identity'
|
6
|
+
require 'active_support'
|
7
|
+
require 'active_support/core_ext/object/blank'
|
8
|
+
|
9
|
+
module Ditty
|
10
|
+
class Identity < Sequel::Model
|
11
|
+
include ::Ditty::Base
|
12
|
+
many_to_one :user
|
13
|
+
|
14
|
+
attr_accessor :password, :password_confirmation
|
15
|
+
|
16
|
+
# OmniAuth Related
|
17
|
+
include OmniAuth::Identity::Model
|
18
|
+
|
19
|
+
def self.locate(conditions)
|
20
|
+
where(conditions).first
|
21
|
+
end
|
22
|
+
|
23
|
+
def authenticate(unencrypted)
|
24
|
+
self if ::BCrypt::Password.new(crypted_password) == unencrypted
|
25
|
+
end
|
26
|
+
|
27
|
+
def persisted?
|
28
|
+
!new? && @destroyed != true
|
29
|
+
end
|
30
|
+
|
31
|
+
# Return whatever we want to pass to the omniauth hash here
|
32
|
+
def info
|
33
|
+
{
|
34
|
+
email: username
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Validation
|
39
|
+
def validate
|
40
|
+
validates_presence :username
|
41
|
+
unless username.blank?
|
42
|
+
validates_unique :username
|
43
|
+
validates_format(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :username)
|
44
|
+
end
|
45
|
+
|
46
|
+
if password_required
|
47
|
+
validates_presence :password
|
48
|
+
validates_presence :password_confirmation
|
49
|
+
validates_min_length 8, :password
|
50
|
+
end
|
51
|
+
|
52
|
+
errors.add(:password_confirmation, 'must match password') if !password.blank? && password != password_confirmation
|
53
|
+
end
|
54
|
+
|
55
|
+
# Callbacks
|
56
|
+
def before_save
|
57
|
+
encrypt_password unless password == '' || password.nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def encrypt_password
|
63
|
+
self.crypted_password = ::BCrypt::Password.create(password)
|
64
|
+
end
|
65
|
+
|
66
|
+
def password_required
|
67
|
+
crypted_password.blank? || !password.blank?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ditty/models/base'
|
4
|
+
|
5
|
+
module Ditty
|
6
|
+
class Role < Sequel::Model
|
7
|
+
include ::Ditty::Base
|
8
|
+
|
9
|
+
many_to_many :users
|
10
|
+
|
11
|
+
def validate
|
12
|
+
validates_presence [:name]
|
13
|
+
validates_unique [:name]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|