Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
414729a
create job
mdiarra3 Apr 1, 2024
d03e751
changelog: Upcoming Features, Auth, Migration for password compromise…
mdiarra3 Apr 1, 2024
3100c3f
Merge remote-tracking branch 'origin/LG-12788-migration' into LG-1278…
mdiarra3 Apr 2, 2024
85d9c35
Merge remote-tracking branch 'origin/main' into LG-12788-compare-pass…
mdiarra3 Apr 3, 2024
d0eca40
job
mdiarra3 Apr 3, 2024
0ba0b30
Merge remote-tracking branch 'origin/main' into LG-12788-compare-pass…
mdiarra3 Apr 3, 2024
030714a
add query for user
mdiarra3 Apr 3, 2024
c3b8998
test change
mdiarra3 Apr 5, 2024
fd2346a
changelog: Upcoming Features, notify random amount of users that thei…
mdiarra3 Apr 9, 2024
d34d056
Merge remote-tracking branch 'origin/main' into LG-12788-compare-pass…
mdiarra3 Apr 9, 2024
3dd6535
sessions controller
mdiarra3 Apr 10, 2024
58f00c3
changelog: Upcoming Features, authentication, look up if user comprom…
mdiarra3 Apr 11, 2024
da50886
LG-12788: check password compromised
mdiarra3 Apr 12, 2024
89081e1
Merge remote-tracking branch 'origin/main' into LG-12788-migration
mdiarra3 Apr 12, 2024
1425996
fix schema from merge conflict
mdiarra3 Apr 12, 2024
c90573e
address comments
mdiarra3 Apr 15, 2024
c8029ec
deal with lint
mdiarra3 Apr 15, 2024
9f2271f
rubocop
mdiarra3 Apr 16, 2024
e2460fd
fix analytic event
mdiarra3 Apr 16, 2024
6d5ed63
reword migration to follow naming convention
mdiarra3 Apr 16, 2024
dabefb4
Merge remote-tracking branch 'origin/LG-12788-migration' into LG-1278…
mdiarra3 Apr 16, 2024
9f4b87d
rename to address migration change
mdiarra3 Apr 16, 2024
c49b984
remove question mark
mdiarra3 Apr 16, 2024
67c25f6
update default value
mdiarra3 Apr 16, 2024
559a9cb
Merge remote-tracking branch 'origin/main' into LG-12788-migration
mdiarra3 Apr 16, 2024
a298e37
Merge remote-tracking branch 'origin/main' into LG-12788-migration
mdiarra3 Apr 16, 2024
5016297
reshake migration
mdiarra3 Apr 16, 2024
45f7814
Merge remote-tracking branch 'origin/LG-12788-migration' into LG-1278…
mdiarra3 Apr 16, 2024
1f6e601
Merge remote-tracking branch 'origin/main' into LG-12788-migration
mdiarra3 Apr 17, 2024
8397bf1
Merge remote-tracking branch 'origin/LG-12788-migration' into LG-1278…
mdiarra3 Apr 17, 2024
431a0b4
address comments
mdiarra3 Apr 18, 2024
28b6b61
Merge remote-tracking branch 'origin/main' into LG-12788-compare-pass…
mdiarra3 Apr 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ def fix_broken_personal_key_url
def after_sign_in_path_for(_user)
return rules_of_use_path if !current_user.accepted_rules_of_use_still_valid?
return user_please_call_url if current_user.suspended?
return user_password_compromised_url if session[:redirect_to_password_compromised].present?
return authentication_methods_setup_url if user_needs_sp_auth_method_setup?
return login_add_piv_cac_prompt_url if session[:needs_to_setup_piv_cac_after_sign_in].present?
return fix_broken_personal_key_url if current_user.broken_personal_key?
Expand Down
19 changes: 19 additions & 0 deletions app/controllers/users/password_compromised_controller.rb
Comment thread
mdiarra3 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module Users
class PasswordCompromisedController < ApplicationController
before_action :confirm_two_factor_authenticated
before_action :verify_feature_toggle_on

def show
session.delete(:redirect_to_password_compromised)
@after_sign_in_path = after_sign_in_path_for(current_user)
analytics.user_password_compromised_visited
end

def verify_feature_toggle_on
redirect_to after_sign_in_path_for(current_user) unless
FeatureManagement.check_password_enabled?
end
end
end
27 changes: 27 additions & 0 deletions app/controllers/users/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ def handle_valid_authentication
user_id: current_user.id,
email: auth_params[:email],
)
check_password_compromised
user_session[:platform_authenticator_available] =
params[:platform_authenticator_available] == 'true'
redirect_to next_url_after_valid_authentication
Expand Down Expand Up @@ -204,6 +205,32 @@ def override_csp_for_google_analytics
def sign_in_params
params[resource_name]&.permit(:email) if request.post?
end

def check_password_compromised
return if current_user.password_compromised_checked_at.present? ||
!eligible_for_password_lookup?

session[:redirect_to_password_compromised] =
PwnedPasswords::LookupPassword.call(auth_params[:password])
update_user_password_compromised_checked_at
end

def eligible_for_password_lookup?
FeatureManagement.check_password_enabled? &&
randomize_check_password?
end

def update_user_password_compromised_checked_at
UpdateUser.new(
user: current_user,
attributes: { password_compromised_checked_at: Time.zone.now },
).call
end

def randomize_check_password?
SecureRandom.random_number(IdentityConfig.store.compromised_password_randomizer_value) >=
IdentityConfig.store.compromised_password_randomizer_threshold
end
end

def unsafe_redirect_error(_exception)
Expand Down
8 changes: 8 additions & 0 deletions app/services/analytics_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5134,6 +5134,14 @@ def user_marked_authed(authentication_type:, **extra)
)
end

# Tracks when the user is notified their password is compromised
def user_password_compromised_visited(**extra)
track_event(
:user_password_compromised_visited,
**extra,
)
end

# @param [Boolean] success
# @param [Hash] errors
# @param [Integer] enabled_mfa_methods_count
Expand Down
15 changes: 15 additions & 0 deletions app/views/users/password_compromised/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<%= render(
'idv/shared/error',
heading: 'Password Compromised',
) do %>
<p>
PASSWORD COMPROMISED
</p>
<% end %>

<div class="col-12">
<%= link_to(
@after_sign_in_path,
class: 'usa-button usa-button--wide usa-button--big margin-bottom-3',
) { 'SKIP' } %>
</div>
6 changes: 6 additions & 0 deletions config/application.yml.default
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ broken_personal_key_window_start: '2021-07-29T00:00:00Z'
broken_personal_key_window_finish: '2021-09-22T00:00:00Z'
component_previews_enabled: false
component_previews_embed_frame_ancestors: '[]'
compromised_password_randomizer_value: 1000
compromised_password_randomizer_threshold: 900
Comment thread
mdiarra3 marked this conversation as resolved.
check_user_password_compromised_enabled: false
country_phone_number_overrides: '{}'
database_pool_idp: 5
database_socket: ''
Expand Down Expand Up @@ -378,8 +381,11 @@ development:
attribute_encryption_key: 2086dfbd15f5b0c584f3664422a1d3409a0d2aa6084f65b6ba57d64d4257431c124158670c7655e45cabe64194f7f7b6c7970153c285bdb8287ec0c4f7553e25
attribute_encryption_key_queue: '[{ "key": "11111111111111111111111111111111" }, { "key": "22222222222222222222222222222222" }]'
aws_logo_bucket: ''
check_user_password_compromised_enabled: true
component_previews_enabled: true
component_previews_embed_frame_ancestors: '["http://localhost:4000"]'
compromised_password_randomizer_value: 1
compromised_password_randomizer_threshold: 0
dashboard_api_token: test_token
dashboard_url: http://localhost:3001/api/service_providers
database_host: ''
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@
get '/confirm_backup_codes' => 'users/backup_code_setup#confirm_backup_codes'

get '/user_please_call' => 'users/please_call#show'
get '/user_password_compromised' => 'users/password_compromised#show'

post '/sign_up/create_password' => 'sign_up/passwords#create', as: :sign_up_create_password
get '/sign_up/email/confirm' => 'sign_up/email_confirmations#create',
Expand Down
4 changes: 4 additions & 0 deletions lib/feature_management.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ def self.identity_pki_local_dev?
Rails.env.development? && IdentityConfig.store.identity_pki_local_dev
end

def self.check_password_enabled?
IdentityConfig.store.check_user_password_compromised_enabled
end

def self.doc_capture_polling_enabled?
IdentityConfig.store.doc_capture_polling_enabled
end
Expand Down
3 changes: 3 additions & 0 deletions lib/identity_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,11 @@ def self.build_store(config_map)
config.add(:backup_code_cost, type: :string)
config.add(:broken_personal_key_window_finish, type: :timestamp)
config.add(:broken_personal_key_window_start, type: :timestamp)
config.add(:check_user_password_compromised_enabled, type: :boolean)
config.add(:component_previews_embed_frame_ancestors, type: :json)
config.add(:component_previews_enabled, type: :boolean)
config.add(:compromised_password_randomizer_value, type: :integer)
config.add(:compromised_password_randomizer_threshold, type: :integer)
config.add(:country_phone_number_overrides, type: :json)
config.add(:dashboard_api_token, type: :string)
config.add(:dashboard_url, type: :string)
Expand Down
96 changes: 96 additions & 0 deletions spec/controllers/users/sessions_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,102 @@
end
end

context 'Password Compromised toggle is set to true' do
before do
allow(FeatureManagement).to receive(:check_password_enabled?).and_return(true)
end

context 'User has a compromised password' do
let(:user) { create(:user, :fully_registered) }
before do
allow(PwnedPasswords::LookupPassword).to receive(:call).and_return true
end

context 'user randomly chosen to be tested' do
before do
allow(SecureRandom).to receive(:random_number).and_return(5)
allow(IdentityConfig.store).to receive(:compromised_password_randomizer_threshold).
and_return(2)
end

it 'updates user attribute password_compromised_checked_at' do
expect(user.password_compromised_checked_at).to be_falsey
post :create, params: { user: { email: user.email, password: user.password } }
user.reload
expect(user.password_compromised_checked_at).to be_truthy
end

it 'stores in session redirect to check compromise' do
post :create, params: { user: { email: user.email, password: user.password } }
expect(controller.session[:redirect_to_password_compromised]).to be_truthy
end
end

context 'user not chosen to be tested' do
before do
allow(SecureRandom).to receive(:random_number).and_return(1)
allow(IdentityConfig.store).to receive(:compromised_password_randomizer_threshold).
and_return(5)
end

it 'does not store anything in user_session' do
post :create, params: { user: { email: user.email, password: user.password } }
expect(user.password_compromised_checked_at).to be_falsey
end

it 'does not update the user ' do
post :create, params: { user: { email: user.email, password: user.password } }
expect(controller.session[:redirect_to_password_compromised]).to be_falsey
end
end
end

context 'user does not have a compromised password' do
let(:user) { create(:user, :fully_registered) }
before do
allow(PwnedPasswords::LookupPassword).to receive(:call).and_return false
end

context 'user randomly chosen to be tested' do
before do
allow(SecureRandom).to receive(:random_number).and_return(5)
allow(IdentityConfig.store).to receive(:compromised_password_randomizer_threshold).
and_return(2)
end

it 'updates user attribute password_compromised_checked_at' do
expect(user.password_compromised_checked_at).to be_falsey
post :create, params: { user: { email: user.email, password: user.password } }
user.reload
expect(user.password_compromised_checked_at).to be_truthy
end

it 'stores in session false to attempt to redirect password compromised' do
post :create, params: { user: { email: user.email, password: user.password } }
expect(controller.session[:redirect_to_password_compromised]).to be_falsey
end
end

context 'user not chosen to be tested' do
before do
allow(SecureRandom).to receive(:random_number).and_return(1)
allow(IdentityConfig.store).to receive(:compromised_password_randomizer_threshold).
and_return(5)
end

it 'does not store anything in user_session' do
post :create, params: { user: { email: user.email, password: user.password } }
expect(user.password_compromised_checked_at).to be_falsey
end

it 'does not update the user ' do
post :create, params: { user: { email: user.email, password: user.password } }
expect(controller.session[:redirect_to_password_compromised]).to be_falsey
end
end
end
end

context 'IAL2 user' do
before do
allow(FeatureManagement).to receive(:use_kms?).and_return(false)
Expand Down
80 changes: 80 additions & 0 deletions spec/features/users/sign_in_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,86 @@
end
end

context 'check_password_compromised feature toggle is true' do
before do
allow(FeatureManagement).to receive(:check_password_enabled?).and_return(true)
end

context 'user has a compromised password' do
let(:user) { create(:user, :fully_registered, password: '3.141592653589793238') }
context 'user is chosen to check if password compromised' do
before do
allow(SecureRandom).to receive(:random_number).and_return(5)
allow(IdentityConfig.store).to receive(:compromised_password_randomizer_threshold).
and_return(2)
end
it 'should bring user to compromised password page' do
visit new_user_session_path
fill_in_credentials_and_submit(user.email, user.password)
fill_in_code_with_last_phone_otp
click_submit_default

expect(current_path).to eq user_password_compromised_path
end
end

context 'user is not chosen to check if password compromised' do
before do
allow(SecureRandom).to receive(:random_number).and_return(2)
allow(IdentityConfig.store).to receive(:compromised_password_randomizer_threshold).
and_return(5)
end
it 'should continue without issue' do
visit new_user_session_path
fill_in_credentials_and_submit(user.email, user.password)
fill_in_code_with_last_phone_otp
click_submit_default

expect(current_path).to eq account_path
end
end
end

context 'user does not have compromised password' do
let(:user) { create(:user, :fully_registered) }
context 'user is chosen to check if password compromised' do
before do
allow(SecureRandom).to receive(:random_number).and_return(5)
allow(IdentityConfig.store).to receive(:compromised_password_randomizer_threshold).
and_return(2)
end
it 'should bring user to account page and set password compromised attr' do
visit new_user_session_path
fill_in_credentials_and_submit(user.email, user.password)
fill_in_code_with_last_phone_otp
click_submit_default

expect(current_path).to eq account_path
user.reload
expect(user.password_compromised_checked_at).to be_truthy
end
end

context 'user is not chosen to check if password compromised' do
before do
allow(SecureRandom).to receive(:random_number).and_return(2)
allow(IdentityConfig.store).to receive(:compromised_password_randomizer_threshold).
and_return(5)
end
it 'should continue without issue and does not set password compromised attr' do
visit new_user_session_path
fill_in_credentials_and_submit(user.email, user.password)
fill_in_code_with_last_phone_otp
click_submit_default

expect(current_path).to eq account_path
user.reload
expect(user.password_compromised_checked_at).to be_falsey
end
end
end
end

context 'when piv/cac is required' do
before do
visit_idp_from_oidc_sp_with_hspd12_and_require_piv_cac
Expand Down