Skip to content

Commit 24122f1

Browse files
committed
Warn on suspicious login, after long period of inactivity
1 parent a8eff2f commit 24122f1

File tree

4 files changed

+70
-2
lines changed

4 files changed

+70
-2
lines changed

app/lib/ldap_authenticatable_tiny/strategy.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ def authenticate_without_consistent_timing!
6060
ldap_login = PerDistrict.new.ldap_login_for_educator(educator)
6161
return fail!(:invalid) unless is_authorized_by_ldap?(ldap_login, password_text)
6262

63-
# Success, run password checks and store results encrypted and noised,
64-
# ignoring any errors in the process.
63+
# Success, run security checks
6564
store_password_check(password_text)
65+
warn_if_suspicious(educator)
6666

6767
# Return success
6868
return success!(educator)
@@ -106,6 +106,10 @@ def store_password_check(password_text)
106106
nil
107107
end
108108

109+
def warn_if_suspicious(educator)
110+
LoginChecker.new(educator).warn_if_suspicious
111+
end
112+
109113
def logger
110114
Rails.logger
111115
end

app/lib/login_checker.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Check for suspicious bits about the login, for warning.
2+
class LoginChecker
3+
def initialize(educator, options = {})
4+
@educator = educator
5+
@time_now = options.fetch(:time_now, Time.now)
6+
end
7+
8+
def warn_if_suspicious
9+
flags = infer_flags
10+
warn_about(flags)
11+
flags
12+
end
13+
14+
private
15+
def infer_flags
16+
last_login_at = LoginActivity.last_login_at(@educator)
17+
18+
flags = []
19+
flags << :first_login_month_after_creation if last_login_at.nil? && @educator.created_at < (@time_now - 30.days)
20+
flags << :first_login_after_year if last_login_at.present? && last_login_at < (@time_now - 1.year)
21+
flags.sort
22+
end
23+
24+
def warn_about(flags)
25+
if flags.size > 0
26+
Rollbar.warn('LoginChecker#warn_if_suspicious', flags: flags)
27+
end
28+
end
29+
end

app/models/login_activity.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,13 @@ class LoginActivity < ApplicationRecord
1414
belongs_to :user,
1515
polymorphic: true,
1616
optional: true
17+
18+
# Return ActiveSupport::TimeWithZone or nil
19+
def self.last_login_at(educator_id)
20+
LoginActivity.where(user_id: educator_id)
21+
.order(created_at: :desc)
22+
.limit(1)
23+
.first
24+
.try(:created_at)
25+
end
1726
end

spec/lib/login_checker_spec.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
require 'spec_helper'
2+
3+
RSpec.describe LoginChecker do
4+
let!(:pals) { TestPals.create! }
5+
6+
it '#warn_if_suspicious reports to Rollbar on :first_login_after_year' do
7+
LoginActivity.create!({
8+
user_id: pals.healey_vivian_teacher.id,
9+
created_at: pals.time_now - 2.years
10+
})
11+
12+
allow(Rollbar).to receive(:warn)
13+
expect(Rollbar).to receive(:warn).once.with('LoginChecker#warn_if_suspicious', flags: [:first_login_after_year])
14+
checker = LoginChecker.new(pals.healey_vivian_teacher, time_now: pals.time_now)
15+
expect(checker.warn_if_suspicious).to eq [:first_login_after_year]
16+
end
17+
18+
it '#warn_if_suspicious reports to Rollbar on :first_login_month_after_creation' do
19+
pals.healey_vivian_teacher.update!(created_at: pals.time_now - 32.days)
20+
21+
allow(Rollbar).to receive(:warn)
22+
expect(Rollbar).to receive(:warn).once.with('LoginChecker#warn_if_suspicious', flags: [:first_login_month_after_creation])
23+
checker = LoginChecker.new(pals.healey_vivian_teacher, time_now: pals.time_now)
24+
expect(checker.warn_if_suspicious).to eq [:first_login_month_after_creation]
25+
end
26+
end

0 commit comments

Comments
 (0)