Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LG-12636: Show recommended PIV option for .gov/.mil email #10252

Merged
merged 24 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e07258e
Sort MFA selection order PIV option by email
aduth Mar 14, 2024
45ad1c1
Add tag component
aduth Mar 14, 2024
c7df9e4
Show tag for recommended MFA option
aduth Mar 14, 2024
a7aaf3c
Add TagComponent specs
aduth Mar 14, 2024
8213d09
Add TagComponent preview
aduth Mar 14, 2024
2895c4b
Add EmailAddress#gov_or_mil? specs
aduth Mar 14, 2024
037fd69
Add SetUpSelectionPresenter#recommended? specs
aduth Mar 14, 2024
16a3721
Add SetUpPivCacSelectionPresenter#recommended? specs
aduth Mar 14, 2024
0000eb3
Remove unused method
aduth Mar 14, 2024
04e56fb
Move visibility consideration to presenter classes
aduth Mar 14, 2024
a7bc8d7
Add specs for setup presenters phishing_resistant?
aduth Mar 14, 2024
7a1239d
Fix missing method desktop
aduth Mar 14, 2024
02bb360
Add SetUpSelectionPresenter#visible? specs
aduth Mar 14, 2024
fe437b0
Allow option parameters for SetUpSelectionPresenter
aduth Mar 14, 2024
3982792
Add SetUpPhoneSelectionPresenter#visible? specs
aduth Mar 14, 2024
52b78f5
Fix SetUpWebauthnPlatformSelectionPresenter specs
aduth Mar 14, 2024
97d365b
Fix SetUpAuthAppSelectionPresenter specs
aduth Mar 14, 2024
8819535
Fix SetUpWebauthnSelectionPresenter specs
aduth Mar 14, 2024
b69ecfd
Add SetUpBackupCodeSelectionPresenter specs
aduth Mar 20, 2024
375e353
Fix and add spec for piv desktop_only?
aduth Mar 20, 2024
a8a4a7f
Add specs for TwoFactorOptionsPresenter#all_options_sorted
aduth Mar 20, 2024
06661bb
Add specs for _mfa_selection recommended tag
aduth Mar 20, 2024
3e2531c
Make browser method private
aduth Mar 20, 2024
ec43d0b
Log analytics property for visit with gov or mil email
aduth Mar 20, 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
24 changes: 24 additions & 0 deletions app/components/tag_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class TagComponent < BaseComponent
attr_reader :big, :informative, :tag_options
alias_method :big?, :big
alias_method :informative?, :informative

def initialize(big: false, informative: false, **tag_options)
@big = big
@informative = informative
@tag_options = tag_options
end

def css_class
classes = ['usa-tag', *tag_options[:class]]
classes << 'usa-tag--big' if big?
classes << 'usa-tag--informative' if informative?
classes
end

def call
content_tag(:span, content, **tag_options, class: css_class)
end

private :big, :informative
end
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ class TwoFactorAuthenticationSetupController < ApplicationController
def index
two_factor_options_form
@presenter = two_factor_options_presenter
analytics.user_registration_2fa_setup_visit(enabled_mfa_methods_count:)
analytics.user_registration_2fa_setup_visit(
enabled_mfa_methods_count:,
gov_or_mil_email: gov_or_mil_email?,
)
end

def create
Expand Down Expand Up @@ -42,6 +45,10 @@ def two_factor_options_form

private

def gov_or_mil_email?
current_user.confirmed_email_addresses.any?(&:gov_or_mil?)
end

def mfa_context
@mfa_context ||= MfaContext.new(current_user)
end
Expand Down
4 changes: 4 additions & 0 deletions app/models/email_address.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ def confirmation_period_expired?
Time.zone.now > expiration_time
end

def gov_or_mil?
email.end_with?('.gov', '.mil')
end

class << self
def find_with_email(email)
return nil if !email.is_a?(String) || email.empty?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,9 @@ def label
def info
t('two_factor_authentication.two_factor_choice_options.auth_app_info')
end

def phishing_resistant?
false
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ def info
t('two_factor_authentication.two_factor_choice_options.backup_code_info')
end

def phishing_resistant?
false
end

def single_configuration_only?
true
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ def info
t('two_factor_authentication.two_factor_choice_options.phone_info')
end

def phishing_resistant?
false
end

def visible?
return false if IdentityConfig.store.hide_phone_mfa_signup
super
end

def mfa_configuration_count
user.phone_configurations.count
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ def info
t('two_factor_authentication.two_factor_choice_options.piv_cac_info')
end

def phishing_resistant?
true
end

def recommended?
user.confirmed_email_addresses.any?(&:gov_or_mil?)
end

def desktop_only?
true
end

def single_configuration_only?
true
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@ module TwoFactorAuthentication
class SetUpSelectionPresenter
include ActionView::Helpers::TranslationHelper

attr_reader :user
attr_reader :user, :piv_cac_required, :phishing_resistant_required, :user_agent
alias_method :piv_cac_required?, :piv_cac_required
alias_method :phishing_resistant_required?, :phishing_resistant_required

def initialize(user:)
def initialize(
user:,
piv_cac_required: false,
phishing_resistant_required: false,
user_agent: nil
)
@user = user
@piv_cac_required = piv_cac_required
@phishing_resistant_required = phishing_resistant_required
@user_agent = user_agent
end

def render_in(view_context, &block)
Expand All @@ -24,6 +34,10 @@ def info
raise NotImplementedError
end

def phishing_resistant?
raise NotImplementedError
end

def mfa_added_label
if single_configuration_only?
''
Expand All @@ -32,6 +46,26 @@ def mfa_added_label
end
end

def visible?
if piv_cac_required?
type == :piv_cac
elsif phishing_resistant_required?
phishing_resistant?
elsif desktop_only?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this line be considered as a bit of mental gymnastics? I was thinking !browser.mobile? could go into something like is_browser_mobile? for readability.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, to the readability point, I initially tried browser.desktop? to avoid the negation, before realizing that the method doesn't actually exist and fixing in 7a1239d.

Personally I think the negation isn't too bad to warrant adding another method here.

!browser.mobile?
else
true
end
end

def recommended?
false
end

def desktop_only?
false
end

def single_configuration_only?
false
end
Expand All @@ -55,5 +89,13 @@ def mfa_configuration_description
def disabled?
single_configuration_only? && mfa_configuration_count > 0
end

private :piv_cac_required, :phishing_resistant_required

private

def browser
@browser ||= BrowserCache.parse(user_agent)
end
end
end
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
module TwoFactorAuthentication
class SetUpWebauthnPlatformSelectionPresenter < SetUpSelectionPresenter
def initialize(user:, configuration: nil)
@user = user
@configuration = configuration
end

def type
:webauthn_platform
end
Expand Down Expand Up @@ -32,6 +27,10 @@ def info
)
end

def phishing_resistant?
true
end

def single_configuration_only?
true
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ def info
t('two_factor_authentication.two_factor_choice_options.webauthn_info')
end

def phishing_resistant?
true
end

def mfa_configuration_count
user.webauthn_configurations.where(platform_authenticator: [false, nil]).count
end
Expand Down
69 changes: 19 additions & 50 deletions app/presenters/two_factor_options_presenter.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
class TwoFactorOptionsPresenter
include ActionView::Helpers::TranslationHelper

attr_reader :user, :after_mfa_setup_path, :return_to_sp_cancel_path
attr_reader :user,
:after_mfa_setup_path,
:return_to_sp_cancel_path,
:phishing_resistant_required,
:piv_cac_required,
:user_agent

delegate :two_factor_enabled?, to: :mfa_policy

Expand All @@ -24,21 +29,22 @@ def initialize(
end

def options
webauthn_platform_option + totp_option + phone_options +
backup_code_option + webauthn_option + piv_cac_option
all_options_sorted.select(&:visible?)
end

# Array of possible options selected by the user to display on the
# add additional MFA page
def all_user_selected_options
def all_options_sorted
[
TwoFactorAuthentication::SetUpWebauthnPlatformSelectionPresenter.new(user: user),
TwoFactorAuthentication::SetUpAuthAppSelectionPresenter.new(user: user),
TwoFactorAuthentication::SetUpPhoneSelectionPresenter.new(user: user),
TwoFactorAuthentication::SetUpBackupCodeSelectionPresenter.new(user: user),
TwoFactorAuthentication::SetUpWebauthnSelectionPresenter.new(user: user),
TwoFactorAuthentication::SetUpPivCacSelectionPresenter.new(user: user),
]
TwoFactorAuthentication::SetUpWebauthnPlatformSelectionPresenter,
TwoFactorAuthentication::SetUpAuthAppSelectionPresenter,
TwoFactorAuthentication::SetUpPhoneSelectionPresenter,
TwoFactorAuthentication::SetUpBackupCodeSelectionPresenter,
TwoFactorAuthentication::SetUpWebauthnSelectionPresenter,
TwoFactorAuthentication::SetUpPivCacSelectionPresenter,
].map do |klass|
klass.new(user:, piv_cac_required:, phishing_resistant_required:, user_agent:)
end.
partition(&:recommended?).
flatten
end

def icon
Expand Down Expand Up @@ -97,43 +103,6 @@ def skip_label

private

def piv_cac_option
return [] unless current_device_is_desktop?
[TwoFactorAuthentication::SetUpPivCacSelectionPresenter.new(user: user)]
end

def webauthn_option
return [] if piv_cac_required?
[TwoFactorAuthentication::SetUpWebauthnSelectionPresenter.new(user: user)]
end

def webauthn_platform_option
return [] if piv_cac_required?
[TwoFactorAuthentication::SetUpWebauthnPlatformSelectionPresenter.new(user: user)]
end

def phone_options
if piv_cac_required? || phishing_resistant_only? || IdentityConfig.store.hide_phone_mfa_signup
return []
else
[TwoFactorAuthentication::SetUpPhoneSelectionPresenter.new(user: user)]
end
end

def totp_option
return [] if piv_cac_required? || phishing_resistant_only?
[TwoFactorAuthentication::SetUpAuthAppSelectionPresenter.new(user: user)]
end

def backup_code_option
return [] if piv_cac_required? || phishing_resistant_only?
[TwoFactorAuthentication::SetUpBackupCodeSelectionPresenter.new(user: user)]
end

def current_device_is_desktop?
!BrowserCache.parse(@user_agent).mobile?
end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how you refactored all of that.

def piv_cac_required?
@piv_cac_required
end
Expand Down
8 changes: 7 additions & 1 deletion app/services/analytics_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4744,10 +4744,16 @@ def user_registration_2fa_setup(

# Tracks when user visits MFA selection page
# @param [Integer] enabled_mfa_methods_count Number of MFAs associated with user at time of visit
def user_registration_2fa_setup_visit(enabled_mfa_methods_count:, **extra)
# @param [Boolean] gov_or_mil_email Whether registered user has government email
def user_registration_2fa_setup_visit(
enabled_mfa_methods_count:,
gov_or_mil_email:,
**extra
)
track_event(
'User Registration: 2FA Setup visited',
enabled_mfa_methods_count:,
gov_or_mil_email:,
**extra,
)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
<% end %>
<span id="two_factor_options_form_selection_info_<%= option.type %>" aria-hidden="true" class="usa-checkbox__label-description">
<%= option.info %>
<% if option.recommended? %>
<br />
<%= render TagComponent.new(
informative: true,
class: 'margin-top-1',
).with_content(t('two_factor_authentication.recommended')) %>
<% end %>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'Added' tag is similar to this but has the aria-hidden="true" attribute. I'm wondering if it should be on the Recommended tag also, or should it not be on the Added tag either.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ARIA is a little hard to follow here, but I think this is correct as-is.

Example how this announces in Safari + VoiceOver:

image

</span>
</div>
<% end %>
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
</h2>

<%= render IconListComponent.new(icon: :check_circle, color: :success) do |c| %>
<% @presenter.all_user_selected_options.each do |option| %>
<% @presenter.all_options_sorted.each do |option| %>
<% if option.mfa_configuration_count > 0 %>
<% c.with_item do %>
<%= option.label %> <%= option.mfa_added_label %>
Expand Down
1 change: 1 addition & 0 deletions config/locales/two_factor_authentication/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ en:
google_policy_link: Privacy Policy
google_tos_link: Terms of Service
login_tos_link: Mobile Terms of Use
recommended: Recommended
totp_header_text: Enter your authentication app code
two_factor_aal3_choice: Additional authentication required
two_factor_aal3_choice_intro: This app requires a higher level of security. You
Expand Down
1 change: 1 addition & 0 deletions config/locales/two_factor_authentication/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ es:
google_policy_link: aplican la política de privacidad
google_tos_link: las condiciones de servicio
login_tos_link: las condiciones de uso
recommended: Recomendado
totp_header_text: Ingrese su código de la app de autenticación
two_factor_aal3_choice: Se requiere autenticación adicional
two_factor_aal3_choice_intro: Esta aplicación requiere un mayor nivel de
Expand Down
1 change: 1 addition & 0 deletions config/locales/two_factor_authentication/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ fr:
google_policy_link: règles de confidentialité
google_tos_link: conditions de service
login_tos_link: conditions d’utilisation
recommended: Recommandation
totp_header_text: Entrez votre code d’application d’authentification
two_factor_aal3_choice: Authentification supplémentaire requise
two_factor_aal3_choice_intro: Cette application nécessite un niveau de sécurité
Expand Down
Loading