fortifier 0.1.4
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/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +29 -0
- data/app/controllers/fortifier/application_controller.rb +17 -0
- data/app/controllers/fortifier/auth_users_controller.rb +107 -0
- data/app/helpers/fortifier/application_helper.rb +4 -0
- data/app/helpers/fortifier/auth_users_helper.rb +4 -0
- data/app/helpers/fortifier/date_helper.rb +46 -0
- data/app/helpers/fortifier/passwords_helper.rb +4 -0
- data/app/mailers/fortifier/notifier_mailer.rb +66 -0
- data/app/models/fortifier/auth_log.rb +18 -0
- data/app/models/fortifier/auth_rule.rb +11 -0
- data/app/models/fortifier/auth_steps/check_for_blocked_ip.rb +22 -0
- data/app/models/fortifier/auth_steps/check_for_blocked_user.rb +16 -0
- data/app/models/fortifier/auth_steps/check_for_us_external_ip.rb +14 -0
- data/app/models/fortifier/auth_steps/check_for_whitelisted_ip.rb +38 -0
- data/app/models/fortifier/auth_steps/initialize_auth_attempt.rb +19 -0
- data/app/models/fortifier/auth_steps/initialize_batch_sso_auth_attempt.rb +18 -0
- data/app/models/fortifier/auth_steps/initialize_on_demand_sso_auth_attempt.rb +18 -0
- data/app/models/fortifier/auth_steps/messaging.rb +16 -0
- data/app/models/fortifier/auth_user.rb +256 -0
- data/app/models/fortifier/auth_user_api.rb +356 -0
- data/app/models/fortifier/auth_users_auth_rule.rb +8 -0
- data/app/models/fortifier/authentication.rb +17 -0
- data/app/models/fortifier/authentication_steps.rb +46 -0
- data/app/models/fortifier/batch_updater.rb +148 -0
- data/app/models/fortifier/max_mind.rb +64 -0
- data/app/models/fortifier/max_mind_reference_ip.rb +5 -0
- data/app/models/fortifier/rufus/rufus_password_expiration.rb +23 -0
- data/app/models/fortifier/secret.rb +189 -0
- data/app/views/fortifier/notifier_mailer/account_ip_blocked.html.erb +30 -0
- data/app/views/fortifier/notifier_mailer/account_ip_blocked_providigm.html.erb +20 -0
- data/app/views/fortifier/notifier_mailer/exception_notification.html.erb +88 -0
- data/app/views/fortifier/notifier_mailer/foreign_access.html.erb +22 -0
- data/app/views/fortifier/notifier_mailer/password_expiration.html.erb +28 -0
- data/app/views/fortifier/notifier_mailer/password_reset_token.html.erb +28 -0
- data/app/views/fortifier/notifier_mailer/task_exception.html.erb +18 -0
- data/app/views/layouts/fortifier/application.html.erb +14 -0
- data/config/Initializers/bcrypt.rb +1 -0
- data/config/Initializers/ipaddr.rb +1 -0
- data/config/database.yml +18 -0
- data/config/routes.rb +27 -0
- data/db/migrate/20130916194012_create_fortifier_tables.rb +63 -0
- data/db/migrate/20140415210139_add_auth_user_search_keywords_field.rb +9 -0
- data/db/migration_scripts/20140403_temp_whitelist_migration.rb +5 -0
- data/lib/fortifier/engine.rb +40 -0
- data/lib/fortifier/version.rb +3 -0
- data/lib/fortifier.rb +4 -0
- data/lib/tasks/fortifier_tasks.rake +4 -0
- metadata +176 -0
@@ -0,0 +1,256 @@
|
|
1
|
+
require 'attr-csv' # Don't delete! Questions? See Dave.
|
2
|
+
|
3
|
+
module Fortifier
|
4
|
+
class AuthUser < ActiveRecord::Base
|
5
|
+
ABAQIS = 1
|
6
|
+
EMP_SAT = 2
|
7
|
+
|
8
|
+
has_many :secrets
|
9
|
+
has_many :auth_logs
|
10
|
+
has_many :auth_users_auth_rules
|
11
|
+
|
12
|
+
attr_csv :app_uuids, :account_uuids, :search_keywords
|
13
|
+
|
14
|
+
before_create :create_unique_uuid
|
15
|
+
before_create :set_consecutive_failed_login_count
|
16
|
+
|
17
|
+
validate :login_is_present
|
18
|
+
validate :login_is_correct_length
|
19
|
+
validate :login_is_unique
|
20
|
+
validate :email_is_correct_length
|
21
|
+
validate :email_is_unique
|
22
|
+
validate :email_is_correct_format
|
23
|
+
validate :name_is_correct_format
|
24
|
+
validate :name_is_correct_length
|
25
|
+
validate :note_is_correct_length
|
26
|
+
|
27
|
+
# NOTE: These custom validations are used so that symbols/codes
|
28
|
+
# can be returned to applications, rather than explicit text.
|
29
|
+
# Fortifier provides the symbols and the apps are responsible
|
30
|
+
# for handling error messaging.
|
31
|
+
def login_is_present
|
32
|
+
return if login.present?
|
33
|
+
errors[:base] << :login_blank
|
34
|
+
end
|
35
|
+
|
36
|
+
def login_is_correct_length
|
37
|
+
return if login.blank?
|
38
|
+
return if (login.length >= 3 && login.length <= 60) unless login.blank?
|
39
|
+
errors[:base] << :login_length_incorrect
|
40
|
+
end
|
41
|
+
|
42
|
+
def login_is_unique
|
43
|
+
return if login.blank?
|
44
|
+
return if unique?(:login, login)
|
45
|
+
|
46
|
+
errors[:base] << :login_not_unique
|
47
|
+
end
|
48
|
+
|
49
|
+
def email_is_correct_length
|
50
|
+
return if (email.blank?)
|
51
|
+
return if (email.length >= 6 && email.length <= 100)
|
52
|
+
errors[:base] << :email_length_incorrect
|
53
|
+
end
|
54
|
+
|
55
|
+
def email_is_unique
|
56
|
+
return if (email.blank?)
|
57
|
+
return if unique?(:email, email)
|
58
|
+
errors[:base] << :email_not_unique
|
59
|
+
end
|
60
|
+
|
61
|
+
def email_is_correct_format
|
62
|
+
return if (email.blank?)
|
63
|
+
return if (Fortifier::Authentication::EMAIL_REGEX.match email).present?
|
64
|
+
errors[:base] << :email_format_incorrect
|
65
|
+
end
|
66
|
+
|
67
|
+
def name_is_correct_format
|
68
|
+
return if (name.blank?)
|
69
|
+
return if (Fortifier::Authentication::NAME_REGEX.match name).present?
|
70
|
+
errors[:base] << :name_format_incorrect
|
71
|
+
end
|
72
|
+
|
73
|
+
def name_is_correct_length
|
74
|
+
return if (name.blank? || name.length <= 100)
|
75
|
+
errors[:base] << :name_length_incorrect
|
76
|
+
end
|
77
|
+
|
78
|
+
def note_is_correct_length
|
79
|
+
return if (note.blank? || note.length <= 255)
|
80
|
+
errors[:base] << :note_length_incorrect
|
81
|
+
end
|
82
|
+
|
83
|
+
def current_secret
|
84
|
+
self.secrets.order("created_at, id asc").last
|
85
|
+
end
|
86
|
+
|
87
|
+
def current_secret_non_token
|
88
|
+
self.secrets.where(enc_type: (Secret::BCRYPT || Secret::SHA)).order("created_at, id asc").last
|
89
|
+
end
|
90
|
+
|
91
|
+
def secrets_match?(secret_string)
|
92
|
+
current_secret_model = current_secret_non_token
|
93
|
+
return false if current_secret_model.blank?
|
94
|
+
|
95
|
+
auth_result = current_secret_model.matches?(secret_string)
|
96
|
+
current_secret_model.update_encryption_method(secret_string) if (auth_result && current_secret_model.enc_type == "SHA")
|
97
|
+
|
98
|
+
return auth_result
|
99
|
+
end
|
100
|
+
|
101
|
+
def authenticated?(secret_string)
|
102
|
+
# TODO: (DK) do not increase consecutive_failed_logins if user is attempting a pw change
|
103
|
+
# move consecutive_failed_logins updates to a different method so this method
|
104
|
+
# doesn't do multiple things (code smell)
|
105
|
+
# TODO: (DK) remove if self
|
106
|
+
if self
|
107
|
+
if self.secrets_match?(secret_string)
|
108
|
+
self.update_column(:consecutive_failed_logins, 0)
|
109
|
+
true
|
110
|
+
else
|
111
|
+
self.update_column(:consecutive_failed_logins, self.consecutive_failed_logins + 1)
|
112
|
+
false
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.authenticate_batch_sso(account_uuid, token)
|
118
|
+
return nil if token.blank?
|
119
|
+
AuthUser.
|
120
|
+
joins(:secrets).
|
121
|
+
where("find_in_set(? , account_uuids_csv)
|
122
|
+
AND (expired IS NULL OR expired = false)
|
123
|
+
AND enc_type = '#{Secret::SSO_TOKEN}'
|
124
|
+
AND secret_value = ?", account_uuid, token).
|
125
|
+
first
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.authenticate_on_demand_sso(account_uuid, token)
|
129
|
+
return nil if token.blank?
|
130
|
+
AuthUser.
|
131
|
+
joins(:secrets).
|
132
|
+
where("find_in_set(? , account_uuids_csv)
|
133
|
+
AND (expired IS NULL OR expired = false)
|
134
|
+
AND enc_type = '#{Secret::SSO_TOKEN}'
|
135
|
+
AND secret_value = ?", account_uuid, token).
|
136
|
+
first
|
137
|
+
end
|
138
|
+
|
139
|
+
def blocked?
|
140
|
+
self.consecutive_failed_logins > 5
|
141
|
+
end
|
142
|
+
|
143
|
+
def disable!
|
144
|
+
self.update_column(:app_uuids_csv, nil)
|
145
|
+
self.update_column(:account_uuids_csv, nil)
|
146
|
+
end
|
147
|
+
|
148
|
+
def disabled?
|
149
|
+
self.app_uuids.blank? && self.account_uuids.blank?
|
150
|
+
end
|
151
|
+
|
152
|
+
def successful_log_in?
|
153
|
+
# TODO: clarify this so there's no confusion (user can be logged out with a count of 0,
|
154
|
+
# so technically they wouldn't be succesfully logged in, even though this method would
|
155
|
+
# say otherwise).
|
156
|
+
self.consecutive_failed_logins == 0
|
157
|
+
end
|
158
|
+
|
159
|
+
def matching_whitelist_ip?(ip)
|
160
|
+
return false if ip.blank?
|
161
|
+
au_auth_rule_ids = AuthUsersAuthRule.where(auth_user_id: self.id).pluck(:auth_rule_id)
|
162
|
+
auth_rule_ips = au_auth_rule_ids.map {|auid| AuthRule.find(auid).rule_value}
|
163
|
+
auth_rule_ips.flatten.include?(ip)
|
164
|
+
end
|
165
|
+
|
166
|
+
def public_attribute_hash
|
167
|
+
auth_log = self.auth_logs.last
|
168
|
+
{
|
169
|
+
uuid: self.uuid,
|
170
|
+
email: self.email,
|
171
|
+
login: self.login,
|
172
|
+
name: self.name,
|
173
|
+
note: self.note,
|
174
|
+
disabled: self.disabled?,
|
175
|
+
last_auth_log: ({user_agent: auth_log.user_agent, status: auth_log.status, created_at: auth_log.created_at.to_time} if auth_log)
|
176
|
+
}
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.paged_and_sorted_search(params)
|
180
|
+
search = params[:search]
|
181
|
+
sort_col = params[:sortcol] || 'name'
|
182
|
+
sort_dir = params[:sortdir] || 'asc'
|
183
|
+
per_page = (params[:per_page].blank? || params[:per_page]<25) ? 25 : params[:per_page]
|
184
|
+
page = params[:page] || 1
|
185
|
+
app_uuid = params[:app_uuid]
|
186
|
+
account_uuid = params[:account_uuid]
|
187
|
+
search_keywords = params[:search_keywords]
|
188
|
+
|
189
|
+
if search.blank?
|
190
|
+
user_search_query = ''
|
191
|
+
else
|
192
|
+
user_search_query = [:login, :email, :name, :note].map{|f| "#{f} LIKE '%#{search}%'"}.join(" OR ")
|
193
|
+
end
|
194
|
+
|
195
|
+
# aggregate_query is conditional b/c account_uuid isn't always provided (such as the Users page for providigm users in abaqis)
|
196
|
+
app_uuids_query = "app_uuids_csv = '#{app_uuid}'"
|
197
|
+
account_uuids_query = account_uuid ? " AND account_uuids_csv = '#{account_uuid}'" : ""
|
198
|
+
search_keywords_query = search_keywords ? " AND search_keywords_csv = '#{search_keywords}'" : ''
|
199
|
+
aggregate_query = app_uuids_query + account_uuids_query + search_keywords_query
|
200
|
+
|
201
|
+
# b/c there's no 'enabled' field on auth user and abaqis allows this sorting (providigm/users in abaqis)
|
202
|
+
if sort_col=='enabled'
|
203
|
+
Fortifier::AuthUser.where(aggregate_query)
|
204
|
+
.where(user_search_query)
|
205
|
+
.order("app_uuids_csv #{sort_dir}, account_uuids_csv #{sort_dir}")
|
206
|
+
.paginate(:page=>page, :per_page=>per_page)
|
207
|
+
elsif sort_col=='last_login_at'
|
208
|
+
# In other words, pull all users associated with the app in question (if available),
|
209
|
+
# joined with their most recent AuthLog,
|
210
|
+
# sort by the AuthLog created_at date (according to user preference),
|
211
|
+
# then paginate with WillPaginate. Capisce?
|
212
|
+
Fortifier::AuthUser.find_by_sql("SELECT * FROM fortifier_auth_users fau
|
213
|
+
LEFT OUTER JOIN ( select auth_user_id, max(created_at)
|
214
|
+
AS last_login_at
|
215
|
+
FROM fortifier_auth_logs fal
|
216
|
+
GROUP BY auth_user_id)
|
217
|
+
AS last_seen
|
218
|
+
ON fau.id = last_seen.auth_user_id
|
219
|
+
#{'WHERE ' + aggregate_query}
|
220
|
+
#{'AND ' + user_search_query if user_search_query.present?}
|
221
|
+
ORDER BY last_login_at #{sort_dir}")
|
222
|
+
.paginate(:page=>page, :per_page=>per_page)
|
223
|
+
else
|
224
|
+
Fortifier::AuthUser.where(aggregate_query)
|
225
|
+
.where(user_search_query)
|
226
|
+
.order("#{sort_col} #{sort_dir}")
|
227
|
+
.paginate(:page=>page, :per_page=>per_page)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
private
|
232
|
+
|
233
|
+
def create_unique_uuid
|
234
|
+
self.uuid = loop do
|
235
|
+
uuid = SecureRandom.uuid
|
236
|
+
break uuid unless Fortifier::AuthUser.where(:uuid=>uuid).exists?
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def set_consecutive_failed_login_count
|
241
|
+
self.consecutive_failed_logins = 0
|
242
|
+
end
|
243
|
+
|
244
|
+
def unique?(type, value)
|
245
|
+
case type
|
246
|
+
when :login then active_relation = AuthUser.where("login = ?", value)
|
247
|
+
when :email then active_relation = AuthUser.where("email = ?", value)
|
248
|
+
end
|
249
|
+
|
250
|
+
matching_auth_users = active_relation.to_a # converted to an array so the AuthUser isn't deleted from the db
|
251
|
+
matching_auth_users.delete(self) # don't check for uniqueness against self
|
252
|
+
matching_auth_users.blank?
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
end
|
@@ -0,0 +1,356 @@
|
|
1
|
+
# TODO: (DK) DCI!
|
2
|
+
module Fortifier
|
3
|
+
|
4
|
+
class AuthUserApi
|
5
|
+
|
6
|
+
def authenticate(login, pw, user_agent="IE", request_ip="127.0.0.1", account={})
|
7
|
+
results = AuthenticationSteps.stepper(auth_type: "standard_auth",
|
8
|
+
login: login,
|
9
|
+
secret: pw,
|
10
|
+
user_agent: user_agent,
|
11
|
+
remote_addr: request_ip,
|
12
|
+
account: account)
|
13
|
+
auth_user = results[:auth_user]
|
14
|
+
uuid = auth_user ? auth_user.uuid : nil
|
15
|
+
failed_logins = auth_user ? auth_user.consecutive_failed_logins : nil
|
16
|
+
error_msg = results[:auth_msg]
|
17
|
+
|
18
|
+
if failed_logins==0 && !error_msg #successful authentication
|
19
|
+
{ uuid: uuid,
|
20
|
+
status: true }
|
21
|
+
else
|
22
|
+
{ uuid: uuid,
|
23
|
+
status: false,
|
24
|
+
errors: [error_msg],
|
25
|
+
consecutive_failed_logins: failed_logins }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def authenticate_uuid(uuid, pw, user_agent="IE", request_ip="127.0.0.1", account={})
|
30
|
+
login = AuthUser.where(uuid: uuid).pluck(:login).first
|
31
|
+
authenticate(login, pw, user_agent, request_ip, account)
|
32
|
+
end
|
33
|
+
|
34
|
+
def authenticate_batch_sso(account_uuid, token, user_agent="IE", request_ip="127.0.0.1")
|
35
|
+
results = Fortifier::AuthenticationSteps.stepper(auth_type: "batch_sso_auth",
|
36
|
+
account_uuid: account_uuid,
|
37
|
+
token: token,
|
38
|
+
user_agent: user_agent,
|
39
|
+
remote_addr: request_ip)
|
40
|
+
auth_user = results[:auth_user]
|
41
|
+
|
42
|
+
if auth_user
|
43
|
+
{ uuid: results[:auth_user].uuid,
|
44
|
+
status: true }
|
45
|
+
else
|
46
|
+
{ status: false,
|
47
|
+
errors: [results[:auth_msg]] }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def authenticate_on_demand_sso(login_token, user_agent="IE", request_ip="127.0.0.1")
|
52
|
+
results = Fortifier::AuthenticationSteps.stepper(auth_type: "on_demand_sso_auth",
|
53
|
+
token: login_token,
|
54
|
+
user_agent: user_agent,
|
55
|
+
remote_addr: request_ip)
|
56
|
+
auth_user = results[:auth_user]
|
57
|
+
|
58
|
+
if auth_user
|
59
|
+
{ uuid: results[:auth_user].uuid,
|
60
|
+
status: true }
|
61
|
+
else
|
62
|
+
{ status: false,
|
63
|
+
errors: [results[:auth_msg]] }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate(params)
|
68
|
+
uuid,login,email,note = params[:uuid],params[:login],params[:email],params[:note]
|
69
|
+
enc_type = params[:sso_user].present? ? Secret::SSO_TOKEN : nil
|
70
|
+
validation_type = params[:validation_type]
|
71
|
+
|
72
|
+
auth_user = (validation_type=='create') ? AuthUser.new() : AuthUser.where("uuid = ?", uuid).first
|
73
|
+
|
74
|
+
if params[:sso_user]
|
75
|
+
secret, secret_confirmation, enc_type = Secret.make_sso_token, secret, Secret::SSO_TOKEN
|
76
|
+
else
|
77
|
+
secret, secret_confirmation, enc_type = params[:password], params[:password_confirmation], nil
|
78
|
+
end
|
79
|
+
|
80
|
+
auth_user.assign_attributes(login:login, email:email, note:note)
|
81
|
+
new_secret = Secret.new(auth_user: auth_user,
|
82
|
+
secret: secret,
|
83
|
+
secret_confirmation: secret_confirmation,
|
84
|
+
enc_type: enc_type)
|
85
|
+
auth_user.valid?
|
86
|
+
new_secret.valid?
|
87
|
+
|
88
|
+
if validation_type=='update' && (secret.blank? && secret_confirmation.blank?)
|
89
|
+
secret_errors = []
|
90
|
+
else
|
91
|
+
secret_errors = new_secret.errors.full_messages
|
92
|
+
end
|
93
|
+
|
94
|
+
errors = auth_user.errors.full_messages
|
95
|
+
secret_errors = secret_errors
|
96
|
+
|
97
|
+
validation_status = (errors.blank? && secret_errors.blank?) ? true : false
|
98
|
+
|
99
|
+
{ status: validation_status, errors: errors, secret_errors: secret_errors }
|
100
|
+
end
|
101
|
+
|
102
|
+
def batch_update(user_info)
|
103
|
+
updater = BatchUpdater.new(user_info)
|
104
|
+
updater.perform_updates
|
105
|
+
updater.user_status
|
106
|
+
end
|
107
|
+
|
108
|
+
def create(params)
|
109
|
+
auth_user = AuthUser.new()
|
110
|
+
auth_user.login = params[:login]
|
111
|
+
auth_user.email = params[:email]
|
112
|
+
auth_user.name = params[:name]
|
113
|
+
auth_user.note = params[:note]
|
114
|
+
auth_user.app_uuids = params[:app_uuids]
|
115
|
+
auth_user.account_uuids = params[:account_uuids]
|
116
|
+
auth_user.search_keywords = params[:search_keywords]
|
117
|
+
|
118
|
+
sso_user = params[:sso_user]
|
119
|
+
skip_validation = params[:skip_validation]
|
120
|
+
|
121
|
+
secret = sso_user ? Secret.make_sso_token : params[:password]
|
122
|
+
secret_confirmation = sso_user ? secret : params[:password_confirmation]
|
123
|
+
enc_type = sso_user ? Secret::SSO_TOKEN : nil
|
124
|
+
|
125
|
+
new_secret = Secret.new(secret_value: (params[:crypted_password] if skip_validation),
|
126
|
+
salt: (params[:salt] if skip_validation),
|
127
|
+
secret: (secret if !skip_validation),
|
128
|
+
secret_confirmation: (secret_confirmation if !skip_validation),
|
129
|
+
enc_type: (skip_validation ? Secret::SHA : enc_type),
|
130
|
+
skip_validation: (true if skip_validation))
|
131
|
+
|
132
|
+
valid_auth_user = auth_user.valid?
|
133
|
+
valid_secret = new_secret.valid?
|
134
|
+
|
135
|
+
if valid_auth_user && valid_secret
|
136
|
+
auth_user.save
|
137
|
+
auth_user.secrets << new_secret
|
138
|
+
{ uuid: auth_user.uuid,
|
139
|
+
token: (auth_user.current_secret.secret_value if sso_user),
|
140
|
+
status: true }
|
141
|
+
else
|
142
|
+
{ status: false,
|
143
|
+
errors: auth_user.errors.full_messages,
|
144
|
+
secret_errors: new_secret.errors.full_messages }
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def update(params)
|
149
|
+
# TODO: (DK) change user pw by way of email reset rather than admin reset?
|
150
|
+
# could clean some of this code up
|
151
|
+
auth_user = Fortifier::AuthUser.where("uuid = ?", params[:uuid]).first
|
152
|
+
return {status: false, errors: [:auth_user_not_found]} if auth_user.blank?
|
153
|
+
|
154
|
+
auth_user.login = params[:login] if params[:login]
|
155
|
+
auth_user.email = params[:email] if params[:email]
|
156
|
+
auth_user.name = params[:name] if params[:name]
|
157
|
+
auth_user.note = params[:note] if params[:note]
|
158
|
+
sso_user = params[:sso_user]
|
159
|
+
|
160
|
+
if params[:password] || sso_user
|
161
|
+
secret = sso_user ? Secret.make_sso_token : params[:password]
|
162
|
+
secret_confirmation = sso_user ? secret : params[:password_confirmation]
|
163
|
+
enc_type = sso_user ? Secret::SSO_TOKEN : nil
|
164
|
+
end
|
165
|
+
|
166
|
+
if secret && secret_confirmation
|
167
|
+
new_secret = Secret.new(secret: secret,
|
168
|
+
secret_confirmation: secret_confirmation)
|
169
|
+
end
|
170
|
+
|
171
|
+
valid_auth_user = auth_user.valid?
|
172
|
+
valid_secret = new_secret ? new_secret.valid? : true
|
173
|
+
|
174
|
+
if valid_auth_user && valid_secret
|
175
|
+
auth_user.save
|
176
|
+
auth_user.secrets << new_secret if new_secret
|
177
|
+
{ uuid: auth_user.uuid,
|
178
|
+
token: (auth_user.current_secret.secret_value if sso_user),
|
179
|
+
status: true }
|
180
|
+
else
|
181
|
+
{ status: false,
|
182
|
+
errors: auth_user.errors.full_messages,
|
183
|
+
secret_errors: (new_secret.errors.full_messages if new_secret) }
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def change_password(params)
|
188
|
+
return {status: false, errors: [:uuid_not_provided]} if params[:uuid].blank?
|
189
|
+
|
190
|
+
pw = params[:password]
|
191
|
+
pwc = params[:password_confirmation]
|
192
|
+
auth_user = Fortifier::AuthUser.where("uuid = ?", params[:uuid]).first
|
193
|
+
correct_current_pw = auth_user.authenticated?(params[:current]).present? unless auth_user.blank?
|
194
|
+
|
195
|
+
return {status: false, errors: [:bad_current_password]} if !correct_current_pw
|
196
|
+
return {status: false, errors: [:auth_user_not_found]} if !auth_user
|
197
|
+
|
198
|
+
new_secret = Secret.new(auth_user: auth_user, secret: pw, secret_confirmation: pwc)
|
199
|
+
|
200
|
+
if new_secret.valid?
|
201
|
+
new_secret.save
|
202
|
+
{ status: true }
|
203
|
+
else
|
204
|
+
{ status: false,
|
205
|
+
errors: new_secret.errors.full_messages }
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def reset_password(params)
|
210
|
+
return {status: false, errors: [:uuid_not_provided]} if params[:uuid].blank?
|
211
|
+
|
212
|
+
pw = params[:password]
|
213
|
+
pwc = params[:password_confirmation]
|
214
|
+
auth_user = Fortifier::AuthUser.where("uuid = ?", params[:uuid]).first
|
215
|
+
|
216
|
+
return {status: false, errors: [:auth_user_not_found]} if !auth_user
|
217
|
+
|
218
|
+
new_secret = Secret.new(auth_user: auth_user, secret: pw, secret_confirmation: pwc)
|
219
|
+
|
220
|
+
if new_secret.valid?
|
221
|
+
new_secret.save
|
222
|
+
{ status: true }
|
223
|
+
else
|
224
|
+
{ status: false,
|
225
|
+
errors: new_secret.errors.full_messages }
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def create_password_reset_token(email)
|
230
|
+
return {status: false, errors: [:email_not_provided]} if email.blank?
|
231
|
+
|
232
|
+
auth_user = Fortifier::AuthUser.where("email = ?", email).first
|
233
|
+
return {status: false, errors: [:auth_user_not_found]} if !auth_user
|
234
|
+
|
235
|
+
pw = Secret.make_token
|
236
|
+
|
237
|
+
new_secret = Secret.new(auth_user: auth_user, secret: pw, secret_confirmation: pw, reset_token: true)
|
238
|
+
|
239
|
+
if new_secret.valid?
|
240
|
+
new_secret.save
|
241
|
+
{ status: true, token: pw }
|
242
|
+
else
|
243
|
+
{ status: false,
|
244
|
+
errors: new_secret.errors.full_messages }
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def link(params)
|
249
|
+
return {status: false, errors: [:uuid_not_provided]} if params[:uuid].blank?
|
250
|
+
|
251
|
+
app_uuids = params[:app_uuids] || []
|
252
|
+
account_uuids = params[:account_uuids] || []
|
253
|
+
search_keywords = params[:search_keywords] || []
|
254
|
+
return {status: false, errors: [:app_and_account_uuids_not_provided]} if (app_uuids.blank? && account_uuids.blank?)
|
255
|
+
|
256
|
+
auth_user = AuthUser.where(uuid: params[:uuid]).first
|
257
|
+
return {status: false, errors: [:auth_user_not_found]} if auth_user.blank?
|
258
|
+
|
259
|
+
auth_user.app_uuids.concat(app_uuids)
|
260
|
+
auth_user.account_uuids.concat(account_uuids)
|
261
|
+
auth_user.search_keywords.concat(search_keywords)
|
262
|
+
result = auth_user.save
|
263
|
+
|
264
|
+
result ? {status: true} : {status: false, errors: (result.errors.full_messages if result)}
|
265
|
+
end
|
266
|
+
|
267
|
+
def unlink(params)
|
268
|
+
return {status: false, errors: [:uuid_not_provided]} if params[:uuid].blank?
|
269
|
+
|
270
|
+
app_uuids = params[:app_uuids] || []
|
271
|
+
account_uuids = params[:account_uuids] || []
|
272
|
+
search_keywords = params[:search_keywords] || []
|
273
|
+
return {status: false, errors: [:app_and_account_uuids_not_provided]} if (app_uuids.blank? && account_uuids.blank?)
|
274
|
+
|
275
|
+
auth_user = AuthUser.where(uuid: params[:uuid]).first
|
276
|
+
return {status: false, errors: [:auth_user_not_found]} if auth_user.blank?
|
277
|
+
|
278
|
+
auth_user.app_uuids - app_uuids
|
279
|
+
auth_user.account_uuids - account_uuids
|
280
|
+
auth_user.search_keywords - search_keywords
|
281
|
+
result = auth_user.save
|
282
|
+
|
283
|
+
result ? {status: true} : {status: false, errors: (result.errors.full_messages if result)}
|
284
|
+
end
|
285
|
+
|
286
|
+
def find_auth_user(field, param)
|
287
|
+
field = field.to_s || ''
|
288
|
+
|
289
|
+
case field
|
290
|
+
when 'uuid' then auth_user = AuthUser.where("uuid = ?", param).first
|
291
|
+
when 'login', 'email' then auth_user = AuthUser.where("login = ? OR email = ?", param, param).first
|
292
|
+
when 'token' then auth_user = AuthUser.joins(:secrets).where("secret_value = ? AND (expired IS NULL OR expired = false)", param).first
|
293
|
+
end
|
294
|
+
|
295
|
+
auth_user.blank? ? {} : auth_user.public_attribute_hash
|
296
|
+
end
|
297
|
+
|
298
|
+
def find_auth_user_emails(array_of_uuids)
|
299
|
+
return {errors: [:no_uuids_provided]} if array_of_uuids.blank?
|
300
|
+
au_uuids_and_emails = {}
|
301
|
+
|
302
|
+
AuthUser.where(uuid: array_of_uuids).inject(au_uuids_and_emails) do |hash, auth_user|
|
303
|
+
hash[auth_user.uuid] = auth_user.email
|
304
|
+
hash
|
305
|
+
end
|
306
|
+
|
307
|
+
{uuids_with_emails: au_uuids_and_emails}
|
308
|
+
end
|
309
|
+
|
310
|
+
def search_for_auth_users(params)
|
311
|
+
act_rel = Fortifier::AuthUser.paged_and_sorted_search(params)
|
312
|
+
hash = {
|
313
|
+
# the following are will_paginate methods
|
314
|
+
total_pages: act_rel.total_pages,
|
315
|
+
current_page: act_rel.current_page,
|
316
|
+
per_page: act_rel.per_page,
|
317
|
+
total_entries: act_rel.total_entries
|
318
|
+
}
|
319
|
+
hash[:auth_users] = act_rel.map do |au|
|
320
|
+
au.public_attribute_hash
|
321
|
+
end
|
322
|
+
|
323
|
+
hash
|
324
|
+
end
|
325
|
+
|
326
|
+
def auth_users_by_uuids(uuids)
|
327
|
+
return [] unless uuids.present?
|
328
|
+
|
329
|
+
formatted_uuids = uuids.map { |uuid| "'#{uuid}'" }.join(',')
|
330
|
+
|
331
|
+
sql = <<-SQL
|
332
|
+
SELECT
|
333
|
+
fortifier_auth_users.*,
|
334
|
+
latest_secrets.last_password_change
|
335
|
+
FROM
|
336
|
+
`fortifier_auth_users` LEFT OUTER JOIN (
|
337
|
+
SELECT
|
338
|
+
auth_user_id,
|
339
|
+
MAX(created_at) AS last_password_change
|
340
|
+
FROM
|
341
|
+
fortifier_secrets
|
342
|
+
GROUP BY
|
343
|
+
auth_user_id) latest_secrets
|
344
|
+
ON
|
345
|
+
fortifier_auth_users.id = latest_secrets.auth_user_id
|
346
|
+
WHERE
|
347
|
+
`fortifier_auth_users`.`uuid` IN (#{formatted_uuids})
|
348
|
+
SQL
|
349
|
+
result = ActiveRecord::Base.connection.execute sql
|
350
|
+
auth_users = [].tap do | arr |
|
351
|
+
result.each(as: :hash) { | row | arr << row }
|
352
|
+
end
|
353
|
+
{ auth_users: auth_users }
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Fortifier
|
2
|
+
module Authentication
|
3
|
+
LOGIN_REGEX = /\A\w[\w\.\-_@']+\z/ # ASCII, strict
|
4
|
+
|
5
|
+
NAME_REGEX = /\A[^[:cntrl:]\\<>\/&]*\z/ # Unicode, permissive
|
6
|
+
|
7
|
+
EMAIL_NAME_REGEX = '[\w\.%\+\-\']+'.freeze
|
8
|
+
DOMAIN_HEAD_REGEX = '(?:[A-Z0-9\-]+\.)+'.freeze
|
9
|
+
DOMAIN_TLD_REGEX = '(?:[A-Z]{2}|com|org|net|edu|gov|mil|biz|info|mobi|name|aero|jobs|museum)'.freeze
|
10
|
+
EMAIL_REGEX = /\A#{EMAIL_NAME_REGEX}@#{DOMAIN_HEAD_REGEX}#{DOMAIN_TLD_REGEX}\z/i
|
11
|
+
|
12
|
+
# Custom regex' (IdS)
|
13
|
+
SECRET_REGEX = /\A(?=.*\d)(?=.*([a-z]|[A-Z]))([\x20-\x7E]){10,40}\z/
|
14
|
+
|
15
|
+
IP_ADDRESS_REGEX = /\A\d{1,3}(\.\d{1,3}){3}\z/
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Fortifier
|
2
|
+
class AuthenticationSteps
|
3
|
+
STEPS = [
|
4
|
+
"InitializeAuthAttempt",
|
5
|
+
"CheckForBlockedUser",
|
6
|
+
"CheckForBlockedIp",
|
7
|
+
"CheckForWhitelistedIp",
|
8
|
+
"CheckForUsExternalIp",
|
9
|
+
]
|
10
|
+
BATCH_SSO_STEPS = [
|
11
|
+
"InitializeBatchSsoAuthAttempt",
|
12
|
+
"CheckForWhitelistedIp",
|
13
|
+
]
|
14
|
+
ON_DEMAND_SSO_STEPS = [
|
15
|
+
"InitializeOnDemandSsoAuthAttempt",
|
16
|
+
"CheckForWhitelistedIp",
|
17
|
+
]
|
18
|
+
|
19
|
+
def self.stepper(params={})
|
20
|
+
# TODO: (DK) dry up below code
|
21
|
+
|
22
|
+
case params[:auth_type]
|
23
|
+
when "standard_auth"
|
24
|
+
STEPS.each do |step|
|
25
|
+
step_class = ("Fortifier::AuthSteps::" + step).constantize
|
26
|
+
next if step_class.respond_to?(:skip_step?) && step_class.skip_step?(params)
|
27
|
+
params = step_class.invoke(params)
|
28
|
+
end
|
29
|
+
when "batch_sso_auth"
|
30
|
+
BATCH_SSO_STEPS.each do |step|
|
31
|
+
step_class = ("Fortifier::AuthSteps::" + step).constantize
|
32
|
+
next if step_class.respond_to?(:skip_step?) && step_class.skip_step?(params)
|
33
|
+
params = step_class.invoke(params)
|
34
|
+
end
|
35
|
+
when "on_demand_sso_auth"
|
36
|
+
ON_DEMAND_SSO_STEPS.each do |step|
|
37
|
+
step_class = ("Fortifier::AuthSteps::" + step).constantize
|
38
|
+
next if step_class.respond_to?(:skip_step?) && step_class.skip_step?(params)
|
39
|
+
params = step_class.invoke(params)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
params
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|