fortifier 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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,148 @@
|
|
1
|
+
module Fortifier
|
2
|
+
|
3
|
+
class BatchUpdater
|
4
|
+
|
5
|
+
attr_reader :users_to_update, :application_uuid, :account_uuid, :user_status
|
6
|
+
|
7
|
+
def initialize(users_to_upd)
|
8
|
+
@account_uuid = users_to_upd['account_uuid']
|
9
|
+
@application_uuid = users_to_upd['application_uuid']
|
10
|
+
@users_to_update = users_to_upd['users']
|
11
|
+
@user_status = Hash.new { | h, k | h[k] = { status: :new, messages: [] }}
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform_updates
|
15
|
+
identify_deltas # Determine what we need to do
|
16
|
+
users_with_unidentified_status = []
|
17
|
+
|
18
|
+
user_status.each_pair do | login, status_info |
|
19
|
+
case status_info[:status]
|
20
|
+
when :new
|
21
|
+
add_new_user(login, users_to_update[login], status_info)
|
22
|
+
when :delete
|
23
|
+
unlink_user(login, status_info)
|
24
|
+
when :update
|
25
|
+
update_user(login, users_to_update[login], status_info)
|
26
|
+
when :error
|
27
|
+
# Not doing anything with users that have an error
|
28
|
+
else
|
29
|
+
# Need to log and email users in this state
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def set_error_info(status_info, errors)
|
37
|
+
status_info[:status] = :error
|
38
|
+
status_info[:messages] += errors
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_new_user(login, user_info, status_info)
|
42
|
+
auth_user = AuthUser.where(login: login).includes(:secrets).references(:secrets).first
|
43
|
+
auth_user ||= AuthUser.new
|
44
|
+
auth_user.login = login
|
45
|
+
auth_user.email = user_info['email']
|
46
|
+
auth_user.name = user_info['name']
|
47
|
+
auth_user.app_uuids = [ application_uuid ]
|
48
|
+
auth_user.account_uuids = [ account_uuid ]
|
49
|
+
auth_user.secrets << Secret.new(secret: user_info['token'],
|
50
|
+
secret_confirmation: user_info['token'],
|
51
|
+
enc_type: Secret::SSO_TOKEN)
|
52
|
+
if auth_user.valid?
|
53
|
+
auth_user.save
|
54
|
+
status_info[:uuid] = auth_user.uuid
|
55
|
+
else
|
56
|
+
set_error_info(status_info, auth_user.errors.full_messages)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def unlink_user(login, status_info)
|
61
|
+
auth_user = AuthUser.where(login: login).includes(:secrets).references(:secrets).first
|
62
|
+
if auth_user.present?
|
63
|
+
# In the case of batch SSO, the way we 'unlink' a user is to remove
|
64
|
+
# their secret (token). Removal of their secret disallows the possibility
|
65
|
+
# of begin authenticated. Leaving the auth user is necessary to allow
|
66
|
+
# app-user ressurection.
|
67
|
+
auth_user.secrets.destroy_all
|
68
|
+
status_info[:uuid] = auth_user.uuid
|
69
|
+
else
|
70
|
+
set_error_info(status_info, [ "Unable to find user to unlink: #{login}" ])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def update_user(login, user_info, status_info)
|
75
|
+
auth_user = AuthUser.where(login: login).includes(:secrets).references(:secrets).first
|
76
|
+
if auth_user.present?
|
77
|
+
auth_user.email = user_info['email']
|
78
|
+
auth_user.name = user_info['name']
|
79
|
+
status_info[:uuid] = auth_user.uuid
|
80
|
+
secret = auth_user.secrets.first
|
81
|
+
if secret.present?
|
82
|
+
secret.update_attributes(secret: user_info['token'], secret_confirmation: user_info['token'])
|
83
|
+
else
|
84
|
+
auth_user.secrets << Secret.new(secret: user_info['token'],
|
85
|
+
secret_confirmation: user_info['token'],
|
86
|
+
enc_type: Secret::SSO_TOKEN)
|
87
|
+
end
|
88
|
+
if auth_user.valid?
|
89
|
+
auth_user.save
|
90
|
+
else
|
91
|
+
set_error_info(status_info, auth_user.errors.full_messages)
|
92
|
+
end
|
93
|
+
else
|
94
|
+
set_error_info(status_info, [ "Unable to find user to update: #{login}" ])
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def identify_deltas
|
99
|
+
account_auth_user_logins = AuthUser.joins(:secrets).where("FIND_IN_SET(?, account_uuids_csv) > 0", account_uuid).pluck(:login).uniq
|
100
|
+
|
101
|
+
identify_existing_users
|
102
|
+
identify_users_to_be_deleted account_auth_user_logins
|
103
|
+
identify_users_to_be_added account_auth_user_logins
|
104
|
+
|
105
|
+
user_status
|
106
|
+
end
|
107
|
+
|
108
|
+
def identify_existing_users
|
109
|
+
# Find the auth users that currently exist
|
110
|
+
existing_auth_users = AuthUser.where("login in (?)", logins)
|
111
|
+
existing_auth_users.reduce(user_status) do | u_status, auth_user |
|
112
|
+
u_status[auth_user.login][:status] = :update
|
113
|
+
unless auth_user.account_uuids.include?(account_uuid)
|
114
|
+
u_status[auth_user.login][:status] = :error
|
115
|
+
u_status[auth_user.login][:messages] << "Login already used"
|
116
|
+
end
|
117
|
+
u_status
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def identify_users_to_be_deleted(account_auth_user_logins)
|
122
|
+
auth_users_to_delete = (account_auth_user_logins - logins)
|
123
|
+
auth_users_to_delete.reduce(user_status) do | u_status, login_to_delete |
|
124
|
+
u_status[login_to_delete][:status] = :delete
|
125
|
+
u_status
|
126
|
+
end
|
127
|
+
account_auth_user_logins
|
128
|
+
end
|
129
|
+
|
130
|
+
def identify_users_to_be_added(account_auth_user_logins)
|
131
|
+
# Find the auth users that need to be added
|
132
|
+
auth_users_to_add = (logins - account_auth_user_logins)
|
133
|
+
auth_users_to_add.reduce(user_status) do | u_status, login_to_add |
|
134
|
+
unless u_status.has_key?(login_to_add)
|
135
|
+
u_status[login_to_add][:status] = :new
|
136
|
+
end
|
137
|
+
u_status
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
def logins
|
143
|
+
users_to_update.keys
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Fortifier
|
2
|
+
class MaxMind
|
3
|
+
|
4
|
+
def self.valid_ip?(auth_log)
|
5
|
+
return true unless Rails.env=="production"
|
6
|
+
|
7
|
+
ip_address = auth_log[:remote_addr]
|
8
|
+
|
9
|
+
return true if local_ip?(ip_address)
|
10
|
+
return true if previously_validated_ip?(ip_address)
|
11
|
+
return true if auth_log.auth_user.try(:matching_whitelist_ip?,ip_address)
|
12
|
+
return false if previously_rejected_ip?(ip_address)
|
13
|
+
|
14
|
+
fields = [:country_code, :error]
|
15
|
+
options = { :license => "KqA72c39qtQs", :ip => ip_address }
|
16
|
+
|
17
|
+
uri = URI::HTTP.build(:scheme => 'http',
|
18
|
+
:host => 'geoip.maxmind.com',
|
19
|
+
:path => '/a',
|
20
|
+
:query => URI.encode_www_form(:l => options[:license],
|
21
|
+
:i => ip_address))
|
22
|
+
begin
|
23
|
+
response = Net::HTTP.get_response(uri)
|
24
|
+
|
25
|
+
raise Exception.new("MaxMind Request failed with status #{response.code}") unless response.is_a?(Net::HTTPSuccess)
|
26
|
+
|
27
|
+
info = Hash[fields.zip(response.body.encode('utf-8', 'iso-8859-1').parse_csv)]
|
28
|
+
country = info[:country_code]
|
29
|
+
|
30
|
+
raise Exception.new("MaxMind Request failed - IP not found: #{ip_address}") if info[:error]=="IP_NOT_FOUND"
|
31
|
+
|
32
|
+
if country=="CA" || country=="US" || country=="UM" || country=="PR" || country==nil
|
33
|
+
MaxMindReferenceIp.create(ip_address: ip_address, country: country, allowed: true)
|
34
|
+
return true
|
35
|
+
else
|
36
|
+
NotifierMailer.foreign_access(auth_log.auth_user, auth_log).deliver
|
37
|
+
MaxMindReferenceIp.create(ip_address: ip_address, country: country, allowed: false)
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
rescue Exception=>e
|
41
|
+
NotifierMailer.task_exception(e).deliver
|
42
|
+
return true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def self.local_ip?(ip)
|
49
|
+
ip_regex = /(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1)|(^0:0:0:0:0:0:0:1)$/
|
50
|
+
return true if ip_regex.match(ip)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.previously_validated_ip?(ip)
|
54
|
+
match = MaxMindReferenceIp.where(ip_address: ip).where("updated_at > ? and allowed = ?", Date.today - 6.months, true).first
|
55
|
+
return match.present?
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.previously_rejected_ip?(ip)
|
59
|
+
match = MaxMindReferenceIp.where(ip_address: ip).where("updated_at > ? and allowed = ?", Date.today - 6.months, false).first
|
60
|
+
return match.present?
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# .---------------- minute (0 - 59)
|
2
|
+
# | .------------- hour (0 - 23)
|
3
|
+
# | | .---------- day of month (1 - 31)
|
4
|
+
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
|
5
|
+
# | | | | .----- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
|
6
|
+
# | | | | |
|
7
|
+
# * * * * * command to be executed
|
8
|
+
|
9
|
+
module Fortifier
|
10
|
+
class RufusNotificationDigest
|
11
|
+
|
12
|
+
def self.schedule(scheduler)
|
13
|
+
|
14
|
+
# DAILY - every day at half past midnight
|
15
|
+
scheduler.cron '30 0 * * * America/Denver', :blocking => true do
|
16
|
+
ActiveRecord::Base.establish_connection
|
17
|
+
ScheduledJob.log("Daily Password Expiration") do
|
18
|
+
Password.evaluate_for_expiration
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
module Fortifier
|
2
|
+
class Secret < ActiveRecord::Base
|
3
|
+
|
4
|
+
EXPIRATION_PERIOD = 90 # days
|
5
|
+
RESTRICTION_PERIOD = 1 # year
|
6
|
+
GRACE_PERIOD = EXPIRATION_PERIOD - 8 # days
|
7
|
+
SHA = "SHA"
|
8
|
+
BCRYPT = "BCRYPT"
|
9
|
+
SSO_TOKEN = "SSO_TOKEN" # i.e. no encryption
|
10
|
+
RESET_TOKEN = 'RESET_TOKEN'
|
11
|
+
|
12
|
+
attr_accessor :secret, :secret_confirmation
|
13
|
+
attr_accessor :skip_validation
|
14
|
+
attr_accessor :reset_token
|
15
|
+
|
16
|
+
belongs_to :auth_user
|
17
|
+
|
18
|
+
scope :active, -> { where(expired: 0) }
|
19
|
+
scope :past_expiration, -> do
|
20
|
+
active.joins(:user, :account)
|
21
|
+
.where("accounts.id in (select accounts.id from accounts left outer join configurations c on c.account_id = accounts.id
|
22
|
+
where c.add_on_id=#{AddOn::PW_ENHANCEMENTS_ADD_ON_ID} and c.kee='enabled' and c.value='true')")
|
23
|
+
.where("secrets.created_at < ?", Time.now - EXPIRATION_PERIOD.days)
|
24
|
+
end
|
25
|
+
scope :week_to_expiration, -> do
|
26
|
+
active.select("users.*, secrets.*")
|
27
|
+
.joins(:user, :account)
|
28
|
+
.where("accounts.id in (select accounts.id from accounts left outer join configurations c on c.account_id = accounts.id
|
29
|
+
where c.add_on_id=#{AddOn::PW_ENHANCEMENTS_ADD_ON_ID} and c.kee='enabled' and c.value='true')")
|
30
|
+
.where("DATEDIFF(secrets.created_at, '#{Time.now - EXPIRATION_PERIOD.days}') = 7")
|
31
|
+
end
|
32
|
+
|
33
|
+
# NOTE: These custom validations are used so that symbols/codes
|
34
|
+
# can be returned to applications, rather than explicit text.
|
35
|
+
# Fortifier provides the symbols and the apps are responsible
|
36
|
+
# for handling error messaging.
|
37
|
+
validate :secret_is_present,
|
38
|
+
:unless => :skip_validation
|
39
|
+
validate :secret_confirmation_is_present,
|
40
|
+
:unless => :skip_validation
|
41
|
+
validate :secret_matches_confirmation,
|
42
|
+
:unless => :skip_validation
|
43
|
+
validate :secret_matches_regex,
|
44
|
+
:unless => :skip_validation
|
45
|
+
# TODO: (DK) turn back on when ready:
|
46
|
+
# validate :secret_not_used_recently,
|
47
|
+
# :unless=>:sso_auth_user?
|
48
|
+
|
49
|
+
before_save :encrypt_secret, :unless => :skip_validation
|
50
|
+
before_save :set_sso_token, :if => :sso_auth_user?
|
51
|
+
# TODO: (DK) remove when no longer needed: before_save :set_auth_user_id
|
52
|
+
before_save :expire_previous_secret
|
53
|
+
|
54
|
+
def secret_is_present
|
55
|
+
return if secret.present?
|
56
|
+
errors[:base] << :blank_password
|
57
|
+
end
|
58
|
+
|
59
|
+
def secret_confirmation_is_present
|
60
|
+
return if secret_confirmation.present?
|
61
|
+
errors[:base] << :blank_password_confirmation
|
62
|
+
end
|
63
|
+
|
64
|
+
def secret_matches_confirmation
|
65
|
+
return if secret == secret_confirmation
|
66
|
+
errors[:base] << :passwords_do_not_match
|
67
|
+
end
|
68
|
+
|
69
|
+
def secret_matches_regex
|
70
|
+
# 10 to 40 characters, one letter, one number
|
71
|
+
errors[:base] << :bad_password if secret and secret.match(Fortifier::Authentication::SECRET_REGEX).nil?
|
72
|
+
end
|
73
|
+
|
74
|
+
# TODO: (DK)
|
75
|
+
# def secret_not_used_recently
|
76
|
+
# # enhanced secret validation
|
77
|
+
# errors[:base] << :password_previously_used if matches_previous_secret?(secret)
|
78
|
+
# end
|
79
|
+
|
80
|
+
def sso_auth_user?; self.enc_type==SSO_TOKEN; end
|
81
|
+
|
82
|
+
def password_reset?; self.enc_type==RESET_TOKEN || self.reset_token; end
|
83
|
+
|
84
|
+
def skip_validation?
|
85
|
+
self.skip_validation || self.sso_auth_user? || self.password_reset?
|
86
|
+
end
|
87
|
+
|
88
|
+
def expire!; update_column(:expired, 1); end
|
89
|
+
|
90
|
+
def expired?; expired == 1; end
|
91
|
+
|
92
|
+
def enable!; expired == 0; end
|
93
|
+
|
94
|
+
def expiration_date; (created_at.to_time + EXPIRATION_PERIOD.days); end
|
95
|
+
|
96
|
+
def within_a_week_of_expiration?
|
97
|
+
t = (expiration_date.to_date - Date.today).to_i
|
98
|
+
t <= 7 && t > 0
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.reset_token_unique?(token)
|
102
|
+
return false if token.blank?
|
103
|
+
Fortifier::Secret.where("enc_type='#{RESET_TOKEN}'
|
104
|
+
AND secret_value='#{token}'
|
105
|
+
AND (expired IS NULL OR expired=false)").blank?
|
106
|
+
end
|
107
|
+
|
108
|
+
def expire_previous_secret
|
109
|
+
return if (self.reset_token || auth_user.current_secret.blank?)
|
110
|
+
old_secrets = Secret.where("auth_user_id='#{auth_user.id}' AND (expired IS NULL OR expired=false)")
|
111
|
+
old_secrets.each{|s| s.expire!}
|
112
|
+
end
|
113
|
+
|
114
|
+
# This is called by the rufus job to determine whether users should be locked for not changing secrets
|
115
|
+
def self.evaluate_for_expiration
|
116
|
+
Secret.past_expiration.each do |secret|
|
117
|
+
secret.user.disable!
|
118
|
+
secret.expire!
|
119
|
+
end
|
120
|
+
Secret.week_to_expiration.each do |secret|
|
121
|
+
NotifierMailer.secret_expiration(secret, secret.user.email).deliver
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def matches?(secret_string)
|
126
|
+
if enc_type == SHA
|
127
|
+
# deprecated pw hashing
|
128
|
+
secret_value == Digest::SHA1.hexdigest("--#{salt}--#{secret_string}--")
|
129
|
+
elsif enc_type == Secret::SSO_TOKEN
|
130
|
+
secret_value == secret_string
|
131
|
+
else
|
132
|
+
BCrypt::Password.new(secret_value) == secret_string
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def update_encryption_method(secret_string)
|
137
|
+
# TODO: dave, test if this works: return if enc_type == (BCRYPT || SSO_TOKEN)
|
138
|
+
return if self.enc_type == Secret::BCRYPT || self.enc_type == Secret::SSO_TOKEN or secret_string.blank?
|
139
|
+
new_secret = Secret.new
|
140
|
+
new_secret.auth_user = self.auth_user
|
141
|
+
new_secret.secret_value = secret_string
|
142
|
+
new_secret.salt = nil
|
143
|
+
new_secret.enc_type = BCRYPT
|
144
|
+
new_secret.save!(validate: false)
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.make_token
|
148
|
+
token=rand(36**30).to_s(36) while Secret.reset_token_unique?(token)==false
|
149
|
+
token
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.make_sso_token
|
153
|
+
token=rand(36**30).to_s(36) while Secret.reset_token_unique?(token)==false
|
154
|
+
token
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
# before_save filter
|
160
|
+
def encrypt_secret
|
161
|
+
# TODO: (DK) make sure this works for old encryption changes and new records, etc
|
162
|
+
secret = new_record? ? self.secret : self.secret_value
|
163
|
+
if self.reset_token
|
164
|
+
self.secret_value = secret
|
165
|
+
self.enc_type = RESET_TOKEN
|
166
|
+
elsif self.enc_type==SSO_TOKEN
|
167
|
+
self.secret_value = secret
|
168
|
+
self.enc_type = SSO_TOKEN
|
169
|
+
else
|
170
|
+
self.secret_value = BCrypt::Password.create(secret)
|
171
|
+
self.enc_type = "BCRYPT"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def set_sso_token; self.secret_value = secret; end
|
176
|
+
|
177
|
+
def matches_previous_secret?(secret_string)
|
178
|
+
return false unless new_record?
|
179
|
+
return if !auth_user || auth_user.new_record? # || !Account.enhanced_secret_protection_enabled?(user.account_id) TODO: still necessary?
|
180
|
+
past_secrets = auth_user.secrets.where("id IS NOT NULL and created_at > '#{Time.now.utc - RESTRICTION_PERIOD.year}'")
|
181
|
+
past_secrets.detect { |past_secret| past_secret.matches?(secret_string) }
|
182
|
+
end
|
183
|
+
|
184
|
+
# TODO: (DK) remove when no longer needed
|
185
|
+
# def set_auth_user_id
|
186
|
+
# self.auth_user_id ||= auth_user.id
|
187
|
+
# end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<!-- TODO: (DK) make appropriate changes here to specify generic address and no specific app (including logo) -->
|
2
|
+
|
3
|
+
<html style="font-family:helvetica,arial,sans-serif; color:#888">
|
4
|
+
<head>
|
5
|
+
<title>Security Alert</title>
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<div style="border-bottom:3px solid #AFAFAF;padding:0 20px 10px 20px;position:relative;height:52px;min-width:340px">
|
9
|
+
<h1 style="font-size:23px;color:#7F8084;line-height:52px;margin:0;padding:0;">abaqis Security Alert</h1>
|
10
|
+
<img style="position:absolute;right:20px;top:0" src="http://www.providigm.com/wp-content/uploads/2011/08/abaqis_logo_transparent2.png"/>
|
11
|
+
</div>
|
12
|
+
<div style="padding:0 20px;font-size:14px;position:relative;color:#404040;overflow:hidden;min-width:340px">
|
13
|
+
<div style="width:340px;padding:10px 20px 10px 0;float:left;">
|
14
|
+
<h2 style="font-size:16px;margin:2% 0 1% 0;">IP Block</h2>
|
15
|
+
<p style="line-height:18px">At <%=Time.now.in_time_zone(DISPLAY_TIME_ZONE).strftime("%m/%-d/%Y %H:%M:%S")%> MST we detected numerous failed attempts to access your account. The sudden increase in failed logins indicates the possibility that an unauthorized party is trying to gain access to your account.</p>
|
16
|
+
<p style="line-height:18px">To protect you from potentially fraudulent activity, we've placed a 10 minute block on the IP address(es) from where the login attempts originated: </p>
|
17
|
+
<p style="line-height:18px"><%= @remote_addr %></p>
|
18
|
+
<p style="line-height:18px">Access will be restored at <strong><%=(Time.now+600).in_time_zone(DISPLAY_TIME_ZONE).strftime("%m/%-d/%Y %H:%M:%S")%> MST</strong>.</p>
|
19
|
+
<p style="line-height:18px"> –– The Providigm team</p>
|
20
|
+
</div>
|
21
|
+
<p style="width:179px;float:left;font-size:12px;line-height:20px;border-left:1px solid #ddd;padding-left:20px;">
|
22
|
+
<strong>Need further assistance?</strong><br/>
|
23
|
+
Client Support<br/>
|
24
|
+
<strong>866-922-8655</strong><br/>
|
25
|
+
<a href="mailto:abaqis_support@providigm.com" style="color:#369">abaqis_support@providigm.com</a><br/>
|
26
|
+
</p>
|
27
|
+
</div>
|
28
|
+
</body>
|
29
|
+
</html>
|
30
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<!-- TODO: (DK) make appropriate changes here to specify generic address and no specific app (including logo) -->
|
2
|
+
|
3
|
+
<html style="font-family:helvetica,arial,sans-serif; color:#888">
|
4
|
+
<head>
|
5
|
+
<title>Security Alert</title>
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<div style="border-bottom:3px solid #AFAFAF;padding:0 20px 10px 20px;position:relative;height:52px;min-width:340px">
|
9
|
+
<h1 style="font-size:23px;color:#7F8084;line-height:52px;margin:0;padding:0;">abaqis Security Alert</h1>
|
10
|
+
<img style="position:absolute;right:20px;top:0" src="http://www.providigm.com/wp-content/uploads/2011/08/abaqis_logo_transparent2.png"/>
|
11
|
+
</div>
|
12
|
+
<div style="padding:0 20px;font-size:14px;position:relative;color:#404040;overflow:hidden;min-width:340px">
|
13
|
+
<div style="width:340px;padding:10px 20px 10px 0;float:left;">
|
14
|
+
<h2 style="font-size:16px;margin:2% 0 1% 0;">IP Block - <%= @account[:organization] || @account[:name] %></h2>
|
15
|
+
<p style="line-height:18px">At <%=Time.now.in_time_zone(DISPLAY_TIME_ZONE).strftime("%m/%-d/%Y %H:%M:%S")%> MST we blocked access to the following IP(s) for 10 minutes:</p>
|
16
|
+
<p style="line-height:18px"><%= @remote_addr %></p>
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
</body>
|
20
|
+
</html>
|
@@ -0,0 +1,88 @@
|
|
1
|
+
<%
|
2
|
+
user = User.find_by_id(@request.session[:user_id])
|
3
|
+
prov_user = User.where(id: @request.session[:sudoer]).first if @request.session[:sudo]
|
4
|
+
facility = Facility.find_by_id(@request.session[:facility_id])
|
5
|
+
def filter_params(params)
|
6
|
+
(params && params["password"]) ? params.merge!({"password"=>"*******"}) : params
|
7
|
+
(params && params["password_confirmation"]) ? params.merge!({"password_confirmation"=>"*******"}) : params
|
8
|
+
(params && params["current"]) ? params.merge!({"current"=>"*******"}) : params
|
9
|
+
(params && params["user"] && params["user"]["password"]) ? params["user"].merge!({"password"=>"*******"}) : params
|
10
|
+
(params && params["user"] && params["user"]["password_confirmation"]) ? params["user"].merge!({"password_confirmation"=>"*******"}) : params
|
11
|
+
end
|
12
|
+
%>
|
13
|
+
<img src="http://<%= @request.env["HTTP_HOST"] %>/images/kablooie.gif"/>
|
14
|
+
|
15
|
+
<h1>Transcendent Kablooie!!!</h1>
|
16
|
+
<p><strong>Something just blew up at:</strong> http://<%= @request.env["HTTP_HOST"] %></p>
|
17
|
+
|
18
|
+
<table border="0" cellpadding="5" cellspacing="0">
|
19
|
+
<tr>
|
20
|
+
<td>Facility:</td>
|
21
|
+
<td><%= "(#{facility.id}) #{facility.name}" if facility %></td>
|
22
|
+
</tr>
|
23
|
+
<tr>
|
24
|
+
<td>Account (:</td>
|
25
|
+
<td><%= "(#{user.account.id}) #{user.account.organization}" if user%></td>
|
26
|
+
</tr>
|
27
|
+
<tr>
|
28
|
+
<td>User:</td>
|
29
|
+
<td><%= "(#{user.id}) #{user.login}" if user%><%= " - via (#{prov_user.id}) #{prov_user.login}" if prov_user %></td>
|
30
|
+
</tr>
|
31
|
+
<tr>
|
32
|
+
<td>Exception Message:</td>
|
33
|
+
<td><%= @exception.message if @exception%></td>
|
34
|
+
</tr>
|
35
|
+
<tr>
|
36
|
+
<td>Controller:</td>
|
37
|
+
<td><%= @request.params["controller"].to_s if controller%></td>
|
38
|
+
</tr>
|
39
|
+
<tr>
|
40
|
+
<td>Action:</td>
|
41
|
+
<td><%= @request.params["action"].to_s.capitalize if controller %></td>
|
42
|
+
</tr>
|
43
|
+
<tr>
|
44
|
+
<td>Params:</td>
|
45
|
+
<td><%= filter_params(@request.params) if @request %></td>
|
46
|
+
</tr>
|
47
|
+
<% if @request && @request.headers -%>
|
48
|
+
<tr>
|
49
|
+
<td>Accept:</td>
|
50
|
+
<td><%= @request.headers["Accept"] %></td>
|
51
|
+
</tr>
|
52
|
+
<tr>
|
53
|
+
<td>Request Method:</td>
|
54
|
+
<td><%= @request.headers["REQUEST_METHOD"] %></td>
|
55
|
+
</tr>
|
56
|
+
<tr>
|
57
|
+
<td>Content Type:</td>
|
58
|
+
<td><%= @request.headers["CONTENT_TYPE"] %></td>
|
59
|
+
</tr>
|
60
|
+
<tr>
|
61
|
+
<td>Request URI:</td>
|
62
|
+
<td><%= @request.headers["REQUEST_URI"] %></td>
|
63
|
+
</tr>
|
64
|
+
<tr>
|
65
|
+
<td>User Agent:</td>
|
66
|
+
<td><%= @request.headers["HTTP_USER_AGENT"] %></td>
|
67
|
+
</tr>
|
68
|
+
<tr>
|
69
|
+
<td>Remote IP:</td>
|
70
|
+
<td><%= @request.headers["REMOTE_ADDR"] %></td>
|
71
|
+
</tr>
|
72
|
+
<tr>
|
73
|
+
<td>Http X Request:</td>
|
74
|
+
<td><%= @request.headers["X_REQUESTED_WITH"] %></td>
|
75
|
+
</tr>
|
76
|
+
<tr>
|
77
|
+
<td>Request Time (UTC):</td>
|
78
|
+
<td><%= Time.now.utc.to_s %></td>
|
79
|
+
</tr>
|
80
|
+
<tr>
|
81
|
+
<td>Request Time (Mountain):</td>
|
82
|
+
<td><%= Time.now.in_time_zone(DISPLAY_TIME_ZONE) %></td>
|
83
|
+
</tr>
|
84
|
+
<% end -%>
|
85
|
+
</table>
|
86
|
+
|
87
|
+
<p><strong>Exception Backtrace:</strong></p>
|
88
|
+
<%= @exception.backtrace.join("<br />\n").html_safe if @exception %> <%# DS Verified XSS potential; nothing bad in back trace %>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<html style="font-family:helvetica,arial,sans-serif; color:#888">
|
2
|
+
<head>
|
3
|
+
<title>abaqis Security Alert</title>
|
4
|
+
</head>
|
5
|
+
<body>
|
6
|
+
<div style="border-bottom:3px solid #AFAFAF;padding:0 20px 10px 20px;position:relative;height:52px;min-width:340px">
|
7
|
+
<h1 style="font-size:23px;color:#7F8084;line-height:52px;margin:0;padding:0;">abaqis Security Alert</h1>
|
8
|
+
<img style="position:absolute;right:20px;top:0" src="http://www.providigm.com/wp-content/uploads/2011/08/abaqis_logo_transparent2.png"/>
|
9
|
+
</div>
|
10
|
+
<div style="padding:0 20px;font-size:14px;position:relative;color:#404040;overflow:hidden;min-width:340px">
|
11
|
+
<div style="width:340px;padding:10px 20px 10px 0;float:left;">
|
12
|
+
<h2 style="font-size:16px;margin:2% 0 1% 0;">abaqis® foreign IP access attempt - <%#TODO: (DK) fix reference to account = @user.account.organization || @user.account.name %></h2>
|
13
|
+
<p>Foreign IP access attempt at: <%#TODO: (DK) fix reference to Abaqis = Abaqis.get_host_uri %></p>
|
14
|
+
<p style="line-height:18px">At <%#TODO: (DK) fix reference to constant =Time.now.in_time_zone(DISPLAY_TIME_ZONE).strftime("%m/%-d/%Y %H:%M:%S")%> MST there was an attempt to access abaqis from a foreign IP address.</p>
|
15
|
+
<p style="line-height:18px">User: <%= @user.try(:login) %></p>
|
16
|
+
<p style="line-height:18px">Organization: <%#TODO: (DK) fix reference to account = @user.account.organization || @user.account.name %></p>
|
17
|
+
<p style="line-height:18px">IP: <%= @visitor_log.try(:remote_addr) %></p>
|
18
|
+
<p style="line-height:18px">Country Code: <%= @visitor_log.try(:country) %></p>
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
</body>
|
22
|
+
</html>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<% url = defined?(HOST_URI) ? HOST_URI : "http://abaqis.com" %>
|
2
|
+
<% path = url + new_password_path + "?pu=t" %>
|
3
|
+
<html style="font-family:helvetica,arial,sans-serif; color:#888">
|
4
|
+
<head>
|
5
|
+
<title>Password Expiration Notice</title>
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<div style="border-bottom:3px solid #AFAFAF;padding:0 20px 10px 20px;position:relative;height:52px;min-width:340px">
|
9
|
+
<img style="position:absolute;right:20px;top:0" src="http://www.providigm.com/wp-content/uploads/2011/08/abaqis_logo_transparent2.png"/>
|
10
|
+
</div>
|
11
|
+
<div style="padding:0 20px;font-size:14px;position:relative;color:#404040;overflow:hidden;min-width:340px">
|
12
|
+
<div style="width:340px;padding:10px 20px 10px 0;float:left;">
|
13
|
+
<h2 style="font-size:16px;margin:2% 0 1% 0;">Password Expiration Notice</h2>
|
14
|
+
<p style="line-height:18px">Your abaqis password will expire on <%= @pw_expiration_date.strftime("%B %d, %Y") %>.</p>
|
15
|
+
<p style="line-height:18px">You can change your password by clicking on the link below or by copying and pasting it into your browser's address bar.</p>
|
16
|
+
<p style="line-height:18px">Password reset:<br/>
|
17
|
+
<a href='<%= path %>'><%= path %></a></p>
|
18
|
+
<p style ="line-height:18px">–– The abaqis team</p>
|
19
|
+
</div>
|
20
|
+
<p style="width:179px;float:left;font-size:12px;line-height:20px;border-left:1px solid #ddd;padding-left:20px;">
|
21
|
+
<strong>Need further assistance?</strong><br/>
|
22
|
+
Client Support<br/>
|
23
|
+
<strong>866-922-8655</strong><br/>
|
24
|
+
<a href="mailto:abaqis_support@providigm.com" style="color:#369">abaqis_support@providigm.com</a><br/>
|
25
|
+
</p>
|
26
|
+
</div>
|
27
|
+
</body>
|
28
|
+
</html>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<% url = defined?(HOST_URI) ? HOST_URI : "http://abaqis.com" %>
|
2
|
+
<html style="font-family:helvetica,arial,sans-serif; color:#888">
|
3
|
+
<head>
|
4
|
+
<title>abaqis Support Request</title>
|
5
|
+
</head>
|
6
|
+
<body>
|
7
|
+
<div style="border-bottom:3px solid #AFAFAF;padding:0 20px 10px 20px;position:relative;height:52px;min-width:340px">
|
8
|
+
<h1 style="font-size:23px;color:#7F8084;line-height:52px;margin:0;padding:0;">Support Request</h1>
|
9
|
+
<img style="position:absolute;right:20px;top:0" src="http://www.providigm.com/wp-content/uploads/2011/08/abaqis_logo_transparent2.png"/>
|
10
|
+
</div>
|
11
|
+
<div style="padding:0 20px;font-size:14px;position:relative;color:#404040;overflow:hidden;min-width:340px">
|
12
|
+
<div style="width:340px;padding:10px 20px 10px 0;float:left;">
|
13
|
+
<h2 style="font-size:16px;margin:2% 0 1% 0;">abaqis Password Reset</h2>
|
14
|
+
<p style="line-height:18px">A request to change your password was made at <%= format_time_12hr(Time.now) %> MST on <%= format_date_long(Date.today) %>, from IP address <%= @request.try(:remote_ip) %>.</p>
|
15
|
+
<p style="line-height:18px">If you want to keep your current password, please disregard this message.</p>
|
16
|
+
<p style="line-height:18px">To change your password, please click the link below or copy and paste it into your browser's address bar:</p>
|
17
|
+
<p style="line-height:18px"><a href="<%= url + "/setpw?token=#{@user.token}" %>" style="color:#369"><%= url + "/setpw?token=#{@user.token}" %></a></p>
|
18
|
+
<p style ="line-height:18px">The abaqis team</p>
|
19
|
+
</div>
|
20
|
+
<p style="width:179px;float:left;font-size:12px;line-height:20px;border-left:1px solid #ddd;padding-left:20px;">
|
21
|
+
<strong>Need further assistance?</strong><br/>
|
22
|
+
Client Support<br/>
|
23
|
+
<strong>866-922-8655</strong><br/>
|
24
|
+
<a href="mailto:abaqis_support@providigm.com" style="color:#369">abaqis_support@providigm.com</a><br/>
|
25
|
+
</p>
|
26
|
+
</div>
|
27
|
+
</body>
|
28
|
+
</html>
|