Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
parallelism: 4
docker:
# Specify the Ruby version you desire here
- image: circleci/ruby:2.5.1-node-browsers
- image: circleci/ruby:2.5.3-node-browsers
environment:
RAILS_ENV: test
CC_TEST_REPORTER_ID: faecd27e9aed532634b3f4d3e251542d7de9457cfca96a94208a63270ef9b42e
Expand Down Expand Up @@ -46,7 +46,7 @@ jobs:
- v1-identity-idp-yarn-
- run:
name: Install Yarn
command: yarn install --cache-folder ~/.cache/yarn
command: yarn install --ignore-engines --cache-folder ~/.cache/yarn
- save-cache:
key: v1-identity-idp-yarn-{{ checksum "yarn.lock" }}
paths:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Vagrantfile
!/cert/*.crt.example
/config/application.yml
/config/aws.yml
/geo_data/*
/keys/*.key.enc
!/keys/*.key.enc.example
/keys/equifax_rsa
Expand Down
3 changes: 3 additions & 0 deletions .reek.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,13 @@ detectors:
- TwoFactorLoginOptionsPresenter
UncommunicativeMethodName:
exclude:
- Deploy::Activate#download_application_yml_from_s3
- Deploy::Activate#download_geocoding_database_from_s3
- PhoneConfirmationFlow
- render_401
- SessionDecorator#registration_bullet_1
- ServiceProviderSessionDecorator#registration_bullet_1

UncommunicativeModuleName:
exclude:
- X509
Expand Down
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Metrics/ClassLength:
- app/services/analytics.rb
- app/services/idv/session.rb
- app/presenters/two_factor_auth_code/phone_delivery_presenter.rb
- app/view_models/account_show.rb
- lib/cloudhsm/cloudhsm_key_generator.rb

Metrics/LineLength:
Expand Down
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
source 'https://rubygems.org'
git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }

ruby '~> 2.5.1'
ruby '~> 2.5.3'

gem 'rails', '~> 5.1.3'

Expand All @@ -27,6 +27,7 @@ gem 'identity-hostdata', github: '18F/identity-hostdata', branch: 'master'
gem 'json-jwt'
gem 'local_time'
gem 'lograge'
gem 'maxminddb'
gem 'net-sftp'
gem 'newrelic_rpm'
gem 'pg'
Expand Down
6 changes: 4 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ GEM
systemu (~> 2.6.2)
mail (2.7.1)
mini_mime (>= 0.1.1)
maxminddb (0.1.22)
memory_profiler (0.9.11)
method_source (0.9.2)
mime-types (3.2.2)
Expand Down Expand Up @@ -710,6 +711,7 @@ DEPENDENCIES
lexisnexis!
local_time
lograge
maxminddb
net-sftp
newrelic_rpm
overcommit
Expand Down Expand Up @@ -768,7 +770,7 @@ DEPENDENCIES
zxcvbn-js

RUBY VERSION
ruby 2.5.1p57
ruby 2.5.3p105

BUNDLED WITH
1.16.6
1.17.1
2 changes: 2 additions & 0 deletions app/controllers/accounts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ def show
personal_key: flash[:personal_key],
decorated_user: current_user.decorate
)

@login_presenter = LoginPresenter.new(user: current_user)
end

private
Expand Down
17 changes: 8 additions & 9 deletions app/controllers/analytics_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,21 @@ def results

def platform_authenticator_result
return unless current_user
return if platform_authenticator_results_saved? || !platform_authenticator_params_valid?
return if platform_authenticator_results_saved? || platform_authenticator_available?.nil?

session[:platform_authenticator_analytics_saved] = true
platform_authenticator_available = params[:available] ||
params.dig(:platform_authenticator, :available)
extra = { platform_authenticator: (platform_authenticator_available == 'true') }
extra = { platform_authenticator: platform_authenticator_available? }
FormResponse.new(success: true, errors: {}, extra: extra)
end

def platform_authenticator_params_valid?
result = params[:available] || params.dig(:platform_authenticator, :available)
%w[true false].include?(result)
def platform_authenticator_available?
@platform_authenticator_available ||= begin
available = params.dig(:platform_authenticator, :available)
available == 'true' if %w[true false].include?(available)
end
end

def platform_authenticator_results_saved?
session[:platform_authenticator_analytics_saved] == true ||
session[:platform_authenticator] == true
session[:platform_authenticator_analytics_saved] == true
end
end
21 changes: 21 additions & 0 deletions app/controllers/sign_up/cancellations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module SignUp
class CancellationsController < ApplicationController
before_action :ensure_in_setup

def new
properties = ParseControllerFromReferer.new(request.referer).call
analytics.track_event(Analytics::USER_REGISTRATION_CANCELLATION, properties)
@presenter = CancellationPresenter.new(view_context: view_context)
end

private

def ensure_in_setup
redirect_to root_url if !session[:user_confirmation_token] && two_factor_enabled
end

def two_factor_enabled
current_user && MfaPolicy.new(current_user).two_factor_enabled?
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ def show
end

def create
result = OtpVerificationForm.new(current_user, form_params[:code].strip).submit
result = OtpVerificationForm.new(current_user, sanitized_otp_code).submit
properties = result.to_h.merge(analytics_properties)

analytics.track_event(Analytics::MULTI_FACTOR_AUTH, result.to_h.merge(analytics_properties))
analytics.track_event(mfa_event_name, properties)

if result.success?
handle_valid_otp
Expand Down Expand Up @@ -62,10 +63,20 @@ def phone
user_session[:unconfirmed_phone]
end

def sanitized_otp_code
form_params[:code].strip
end

def form_params
params.permit(:code)
end

def mfa_event_name
return Analytics::MULTI_FACTOR_AUTH_SETUP if context == 'confirmation'

Analytics::MULTI_FACTOR_AUTH
end

def analytics_properties
{
context: context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def two_factor_enabled?

def process_piv_cac_setup
result = user_piv_cac_form.submit
analytics.track_event(Analytics::USER_REGISTRATION_PIV_CAC_ENABLED, result.to_h)
analytics.track_event(Analytics::MULTI_FACTOR_AUTH_SETUP, result.to_h)
if result.success?
process_valid_submission
else
Expand Down Expand Up @@ -97,7 +97,8 @@ def process_invalid_submission
end

def authorize_piv_cac_disable
redirect_to account_url unless piv_cac_enabled?
return redirect_to account_url unless piv_cac_enabled? &&
MfaPolicy.new(current_user).multiple_factors_enabled?
end

def authorize_piv_cac_setup
Expand Down
16 changes: 10 additions & 6 deletions app/controllers/users/totp_setup_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def new
def confirm
result = TotpSetupForm.new(current_user, new_totp_secret, params[:code].strip).submit

analytics.track_event(Analytics::TOTP_SETUP, result.to_h)
analytics.track_event(Analytics::MULTI_FACTOR_AUTH_SETUP, result.to_h)

if result.success?
process_valid_code
Expand All @@ -26,11 +26,8 @@ def confirm
end

def disable
if current_user.totp_enabled?
analytics.track_event(Analytics::TOTP_USER_DISABLED)
create_user_event(:authenticator_disabled)
UpdateUser.new(user: current_user, attributes: { otp_secret_key: nil }).call
flash[:success] = t('notices.totp_disabled')
if current_user.totp_enabled? && MfaPolicy.new(current_user).multiple_factors_enabled?
process_successful_disable
end
redirect_to account_url
end
Expand Down Expand Up @@ -60,6 +57,13 @@ def process_valid_code
user_session.delete(:new_totp_secret)
end

def process_successful_disable
analytics.track_event(Analytics::TOTP_USER_DISABLED)
create_user_event(:authenticator_disabled)
UpdateUser.new(user: current_user, attributes: { otp_secret_key: nil }).call
flash[:success] = t('notices.totp_disabled')
end

def mark_user_as_fully_authenticated
user_session[TwoFactorAuthentication::NEED_AUTHENTICATION] = false
user_session[:authn_at] = Time.zone.now
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/users/webauthn_setup_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def new
def confirm
form = WebauthnSetupForm.new(current_user, user_session)
result = form.submit(request.protocol, params)
analytics.track_event(Analytics::WEBAUTHN_SETUP_SUBMITTED, result.to_h)
analytics.track_event(Analytics::MULTI_FACTOR_AUTH_SETUP, result.to_h)
if result.success?
process_valid_webauthn
else
Expand Down
10 changes: 10 additions & 0 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class UsersController < ApplicationController
before_action :ensure_in_setup

def destroy
track_account_deletion_event
url_after_cancellation = decorated_session.cancel_link_url
Expand All @@ -19,4 +21,12 @@ def destroy_user
user&.destroy!
sign_out if user
end

def ensure_in_setup
redirect_to root_url if !session[:user_confirmation_token] && two_factor_enabled
end

def two_factor_enabled
current_user && MfaPolicy.new(current_user).two_factor_enabled?
end
end
5 changes: 4 additions & 1 deletion app/forms/totp_setup_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ def process_valid_submission
end

def extra_analytics_attributes
{ totp_secret_present: secret.present? }
{
totp_secret_present: secret.present?,
multi_factor_auth_method: 'totp',
}
end
end
12 changes: 11 additions & 1 deletion app/forms/user_piv_cac_setup_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ class UserPivCacSetupForm
def submit
success = valid? && valid_token?

FormResponse.new(success: success && process_valid_submission, errors: {})
FormResponse.new(
success: success && process_valid_submission,
errors: {},
extra: extra_analytics_attributes
)
end

private
Expand Down Expand Up @@ -76,4 +80,10 @@ def user_has_no_piv_cac
true
end
end

def extra_analytics_attributes
{
multi_factor_auth_method: 'piv_cac',
}
end
end
5 changes: 4 additions & 1 deletion app/forms/webauthn_setup_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ def create_user_event
end

def extra_analytics_attributes
{ mfa_method_counts: MfaContext.new(user).enabled_two_factor_configuration_counts_hash }
{
mfa_method_counts: MfaContext.new(user).enabled_two_factor_configuration_counts_hash,
multi_factor_auth_method: 'webauthn',
}
end
end
4 changes: 2 additions & 2 deletions app/models/identity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ def decorate
end

def piv_cac_available?
PivCacService.piv_cac_available_for_agency?(
sp_metadata[:agency],
PivCacService.piv_cac_available_for_sp?(
ServiceProvider.from_issuer(service_provider),
user.email_addresses.map(&:email)
)
end
Expand Down
2 changes: 1 addition & 1 deletion app/models/service_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def live?
end

def piv_cac_available?(user = nil)
PivCacService.piv_cac_available_for_agency?(agency, user&.email_addresses&.map(&:email))
PivCacService.piv_cac_available_for_sp?(self, user&.email_addresses&.map(&:email))
end

private
Expand Down
48 changes: 48 additions & 0 deletions app/presenters/cancellation_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
class CancellationPresenter < FailurePresenter
include ActionView::Helpers::TranslationHelper
include Rails.application.routes.url_helpers

delegate :request, to: :view_context

attr_reader :view_context

def initialize(view_context:)
super(:warning)
@view_context = view_context
end

def title
t('headings.cancellations.prompt')
end

def header
t('headings.cancellations.prompt')
end

def cancellation_warnings
[
t('users.delete.bullet_1', app: APP_NAME),
t('users.delete.bullet_2_loa1'),
t('users.delete.bullet_3', app: APP_NAME),
]
end

def go_back_path
referer_path || two_factor_options_path
end

private

def referer_path
referer_string = request.env['HTTP_REFERER']
return if referer_string.blank?
referer_uri = URI.parse(referer_string)
return if referer_uri.scheme == 'javascript'
return unless referer_uri.host == Figaro.env.domain_name.split(':')[0]
extract_path_and_query_from_uri(referer_uri)
end

def extract_path_and_query_from_uri(uri)
[uri.path, uri.query].compact.join('?')
end
end
Loading