diff --git a/app/models/auth_app_configuration.rb b/app/models/auth_app_configuration.rb index 4789c319229..a0dc3abe526 100644 --- a/app/models/auth_app_configuration.rb +++ b/app/models/auth_app_configuration.rb @@ -13,7 +13,7 @@ def mfa_enabled? def selection_presenters if mfa_enabled? - [TwoFactorAuthentication::AuthAppSelectionPresenter.new(user:, configuration: self)] + [TwoFactorAuthentication::SignInAuthAppSelectionPresenter.new(user:, configuration: self)] else [] end diff --git a/app/presenters/two_factor_authentication/auth_app_selection_presenter.rb b/app/presenters/two_factor_authentication/auth_app_selection_presenter.rb deleted file mode 100644 index de6a7be3f69..00000000000 --- a/app/presenters/two_factor_authentication/auth_app_selection_presenter.rb +++ /dev/null @@ -1,11 +0,0 @@ -module TwoFactorAuthentication - class AuthAppSelectionPresenter < SelectionPresenter - def method - :auth_app - end - - def mfa_configuration_count - user.auth_app_configurations.count - end - end -end diff --git a/app/presenters/two_factor_authentication/selection_presenter.rb b/app/presenters/two_factor_authentication/selection_presenter.rb index ddae517c1e7..00ebae8b49d 100644 --- a/app/presenters/two_factor_authentication/selection_presenter.rb +++ b/app/presenters/two_factor_authentication/selection_presenter.rb @@ -69,8 +69,6 @@ def disabled? def login_label(type) case type - when 'auth_app' - t('two_factor_authentication.login_options.auth_app') when 'backup_code' t('two_factor_authentication.login_options.backup_code') when 'personal_key' @@ -92,8 +90,6 @@ def login_label(type) def setup_label(type) case type - when 'auth_app' - t('two_factor_authentication.two_factor_choice_options.auth_app') when 'backup_code' t('two_factor_authentication.two_factor_choice_options.backup_code') when 'piv_cac' diff --git a/app/presenters/two_factor_authentication/set_up_auth_app_selection_presenter.rb b/app/presenters/two_factor_authentication/set_up_auth_app_selection_presenter.rb new file mode 100644 index 00000000000..0763712895d --- /dev/null +++ b/app/presenters/two_factor_authentication/set_up_auth_app_selection_presenter.rb @@ -0,0 +1,19 @@ +module TwoFactorAuthentication + class SetUpAuthAppSelectionPresenter < SetUpSelectionPresenter + def method + :auth_app + end + + def mfa_configuration_count + user.auth_app_configurations.count + end + + def label + t('two_factor_authentication.two_factor_choice_options.auth_app') + end + + def info + t('two_factor_authentication.two_factor_choice_options.auth_app_info') + end + end +end diff --git a/app/presenters/two_factor_authentication/set_up_selection_presenter.rb b/app/presenters/two_factor_authentication/set_up_selection_presenter.rb new file mode 100644 index 00000000000..0c4e798eb89 --- /dev/null +++ b/app/presenters/two_factor_authentication/set_up_selection_presenter.rb @@ -0,0 +1,59 @@ +module TwoFactorAuthentication + class SetUpSelectionPresenter + include ActionView::Helpers::TranslationHelper + + attr_reader :user + + def initialize(user:) + @user = user + end + + def render_in(view_context, &block) + view_context.capture(&block) + end + + def type + method.to_s + end + + def label + raise "Unsupported setup method: #{type}" + end + + def info + raise "Unsupported setup method: #{type}" + end + + def mfa_added_label + if single_configuration_only? + '' + else + "(#{mfa_configuration_description})" + end + end + + def single_configuration_only? + false + end + + def mfa_configuration_count + 0 + end + + def mfa_configuration_description + return '' if mfa_configuration_count == 0 + if single_configuration_only? + t('two_factor_authentication.two_factor_choice_options.no_count_configuration_added') + else + t( + 'two_factor_authentication.two_factor_choice_options.configurations_added', + count: mfa_configuration_count, + ) + end + end + + def disabled? + single_configuration_only? && mfa_configuration_count > 0 + end + end +end diff --git a/app/presenters/two_factor_authentication/sign_in_auth_app_selection_presenter.rb b/app/presenters/two_factor_authentication/sign_in_auth_app_selection_presenter.rb new file mode 100644 index 00000000000..59e268502ba --- /dev/null +++ b/app/presenters/two_factor_authentication/sign_in_auth_app_selection_presenter.rb @@ -0,0 +1,15 @@ +module TwoFactorAuthentication + class SignInAuthAppSelectionPresenter < SignInSelectionPresenter + def method + :auth_app + end + + def label + t('two_factor_authentication.login_options.auth_app') + end + + def info + t('two_factor_authentication.login_options.auth_app_info') + end + end +end diff --git a/app/presenters/two_factor_authentication/sign_in_selection_presenter.rb b/app/presenters/two_factor_authentication/sign_in_selection_presenter.rb new file mode 100644 index 00000000000..fbffd1a0960 --- /dev/null +++ b/app/presenters/two_factor_authentication/sign_in_selection_presenter.rb @@ -0,0 +1,32 @@ +module TwoFactorAuthentication + class SignInSelectionPresenter + include ActionView::Helpers::TranslationHelper + + attr_reader :configuration, :user + + def initialize(user:, configuration:) + @user = user + @configuration = configuration + end + + def render_in(view_context, &block) + view_context.capture(&block) + end + + def type + method.to_s + end + + def label + raise "Unsupported login method: #{type}" + end + + def info + raise "Unsupported login method: #{type}" + end + + def disabled? + false + end + end +end diff --git a/app/presenters/two_factor_options_presenter.rb b/app/presenters/two_factor_options_presenter.rb index 4e76fd9d586..daa1bcc7af1 100644 --- a/app/presenters/two_factor_options_presenter.rb +++ b/app/presenters/two_factor_options_presenter.rb @@ -99,7 +99,7 @@ def phone_options def totp_option return [] if piv_cac_required? || phishing_resistant_only? - [TwoFactorAuthentication::AuthAppSelectionPresenter.new(user: user)] + [TwoFactorAuthentication::SetUpAuthAppSelectionPresenter.new(user: user)] end def backup_code_option diff --git a/spec/presenters/two_factor_authentication/auth_app_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/set_up_auth_app_selection_presenter_spec.rb similarity index 76% rename from spec/presenters/two_factor_authentication/auth_app_selection_presenter_spec.rb rename to spec/presenters/two_factor_authentication/set_up_auth_app_selection_presenter_spec.rb index 58695461877..ada58bceb8d 100644 --- a/spec/presenters/two_factor_authentication/auth_app_selection_presenter_spec.rb +++ b/spec/presenters/two_factor_authentication/set_up_auth_app_selection_presenter_spec.rb @@ -1,14 +1,14 @@ require 'rails_helper' -RSpec.describe TwoFactorAuthentication::AuthAppSelectionPresenter do +RSpec.describe TwoFactorAuthentication::SetUpAuthAppSelectionPresenter do let(:configuration) {} let(:user_without_mfa) { create(:user) } let(:user_with_mfa) { create(:user, :with_authentication_app) } let(:presenter_without_mfa) do - described_class.new(configuration: configuration, user: user_without_mfa) + described_class.new(user: user_without_mfa) end let(:presenter_with_mfa) do - described_class.new(configuration: configuration, user: user_with_mfa) + described_class.new(user: user_with_mfa) end describe '#type' do @@ -17,7 +17,7 @@ end end - describe '#mfa_configruation' do + describe '#mfa_configuration' do it 'return an empty string when user has not configured this authenticator' do expect(presenter_without_mfa.mfa_configuration_description).to eq('') end diff --git a/spec/presenters/two_factor_authentication/set_up_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/set_up_selection_presenter_spec.rb new file mode 100644 index 00000000000..768ab03ede8 --- /dev/null +++ b/spec/presenters/two_factor_authentication/set_up_selection_presenter_spec.rb @@ -0,0 +1,105 @@ +require 'rails_helper' + +RSpec.describe TwoFactorAuthentication::SetUpSelectionPresenter do + class PlaceholderPresenter < TwoFactorAuthentication::SetUpSelectionPresenter + def method + :missing + end + end + + let(:user) { build(:user) } + + subject(:presenter) { described_class.new(user: user) } + + describe '#render_in' do + it 'renders captured block content' do + view_context = ActionController::Base.new.view_context + + expect(view_context).to receive(:capture) do |*args, &block| + expect(block.call).to eq('content') + end + + presenter.render_in(view_context) { 'content' } + end + end + + describe '#disabled?' do + let(:single_configuration_only) {} + let(:mfa_configuration_count) {} + + before do + allow(presenter).to receive(:single_configuration_only?).and_return(single_configuration_only) + allow(presenter).to receive(:mfa_configuration_count).and_return(mfa_configuration_count) + end + + context 'without single configuration restriction' do + let(:single_configuration_only) { false } + + it 'is an mfa that allows multiple configurations' do + expect(presenter.disabled?).to eq(false) + end + end + + context 'with single configuration only' do + let(:single_configuration_only) { true } + + context 'with default mfa count implementation' do + before do + allow(presenter).to receive(:mfa_configuration_count).and_call_original + end + + it 'is mfa with unimplemented mfa count and single config' do + expect(presenter.disabled?).to eq(false) + end + end + + context 'with no configured mfas' do + let(:mfa_configuration_count) { 0 } + + it 'is configured with no mfa' do + expect(presenter.disabled?).to eq(false) + end + end + + context 'with at least one configured mfa' do + let(:mfa_configuration_count) { 1 } + + it 'is mfa with at least one configured' do + expect(presenter.disabled?).to eq(true) + end + end + end + end + + describe '#mfa_added_label' do + subject(:mfa_added_label) { presenter.mfa_added_label } + before do + allow(presenter).to receive(:mfa_configuration_count).and_return('1') + end + it 'is a count of configured MFAs' do + expect(presenter.mfa_added_label).to include('added') + end + + context 'with single configuration only' do + before do + allow(presenter).to receive(:single_configuration_only?).and_return(true) + end + + it 'is an empty string' do + expect(presenter.mfa_added_label).to eq('') + end + end + end + + describe '#label' do + it 'raises with missing translation' do + expect { PlaceholderPresenter.new(user: user).label }.to raise_error(RuntimeError) + end + end + + describe '#info' do + it 'raises with missing translation' do + expect { PlaceholderPresenter.new(user: user).info }.to raise_error(RuntimeError) + end + end +end diff --git a/spec/presenters/two_factor_authentication/sign_in_auth_app_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/sign_in_auth_app_selection_presenter_spec.rb new file mode 100644 index 00000000000..5fe3027a668 --- /dev/null +++ b/spec/presenters/two_factor_authentication/sign_in_auth_app_selection_presenter_spec.rb @@ -0,0 +1,28 @@ +require 'rails_helper' + +RSpec.describe TwoFactorAuthentication::SignInAuthAppSelectionPresenter do + let(:user) { create(:user) } + let(:configuration) { create(:auth_app_configuration, user: user) } + + let(:presenter) do + described_class.new(user: user, configuration: configuration) + end + + describe '#type' do + it 'returns auth_app' do + expect(presenter.type).to eq 'auth_app' + end + end + + describe '#label' do + it 'raises with missing translation' do + expect(presenter.label).to eq(t('two_factor_authentication.login_options.auth_app')) + end + end + + describe '#info' do + it 'raises with missing translation' do + expect(presenter.info).to eq(t('two_factor_authentication.login_options.auth_app_info')) + end + end +end diff --git a/spec/presenters/two_factor_authentication/sign_in_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/sign_in_selection_presenter_spec.rb new file mode 100644 index 00000000000..d1c1ccbe709 --- /dev/null +++ b/spec/presenters/two_factor_authentication/sign_in_selection_presenter_spec.rb @@ -0,0 +1,48 @@ +require 'rails_helper' + +RSpec.describe TwoFactorAuthentication::SignInSelectionPresenter do + class PlaceholderPresenter < TwoFactorAuthentication::SignInSelectionPresenter + def method + :missing + end + end + + let(:user) { build(:user) } + let(:configuration) { create(:phone_configuration, user: user) } + + subject(:presenter) { PlaceholderPresenter.new(user: user, configuration: configuration) } + + describe '#render_in' do + it 'renders captured block content' do + view_context = ActionController::Base.new.view_context + + expect(view_context).to receive(:capture) do |*args, &block| + expect(block.call).to eq('content') + end + + presenter.render_in(view_context) { 'content' } + end + end + + describe '#label' do + it 'raises with missing translation' do + expect do + presenter.label + end.to raise_error(RuntimeError) + end + end + + describe '#type' do + it 'returns missing as type' do + expect(presenter.type).to eq('missing') + end + end + + describe '#info' do + it 'raises with missing translation' do + expect do + presenter.info + end.to raise_error(RuntimeError) + end + end +end diff --git a/spec/presenters/two_factor_options_presenter_spec.rb b/spec/presenters/two_factor_options_presenter_spec.rb index ff6aba336cc..0ae9a4bc26e 100644 --- a/spec/presenters/two_factor_options_presenter_spec.rb +++ b/spec/presenters/two_factor_options_presenter_spec.rb @@ -30,7 +30,7 @@ describe '#options' do it 'supplies all the options for a user' do expect(presenter.options.map(&:class)).to eq [ - TwoFactorAuthentication::AuthAppSelectionPresenter, + TwoFactorAuthentication::SetUpAuthAppSelectionPresenter, TwoFactorAuthentication::PhoneSelectionPresenter, TwoFactorAuthentication::BackupCodeSelectionPresenter, TwoFactorAuthentication::WebauthnSelectionPresenter, @@ -61,7 +61,7 @@ it 'supplies all the options except phone' do expect(presenter.options.map(&:class)).to eq [ - TwoFactorAuthentication::AuthAppSelectionPresenter, + TwoFactorAuthentication::SetUpAuthAppSelectionPresenter, TwoFactorAuthentication::BackupCodeSelectionPresenter, TwoFactorAuthentication::WebauthnSelectionPresenter, TwoFactorAuthentication::PivCacSelectionPresenter, @@ -77,7 +77,7 @@ it 'supplies all the options except webauthn' do expect(presenter.options.map(&:class)).to eq [ TwoFactorAuthentication::WebauthnPlatformSelectionPresenter, - TwoFactorAuthentication::AuthAppSelectionPresenter, + TwoFactorAuthentication::SetUpAuthAppSelectionPresenter, TwoFactorAuthentication::PhoneSelectionPresenter, TwoFactorAuthentication::BackupCodeSelectionPresenter, TwoFactorAuthentication::WebauthnSelectionPresenter, diff --git a/spec/views/partials/multi_factor_authentication/_mfa_selection.html.erb_spec.rb b/spec/views/partials/multi_factor_authentication/_mfa_selection.html.erb_spec.rb index 05f196cbdea..0ad3bfa59d0 100644 --- a/spec/views/partials/multi_factor_authentication/_mfa_selection.html.erb_spec.rb +++ b/spec/views/partials/multi_factor_authentication/_mfa_selection.html.erb_spec.rb @@ -41,7 +41,7 @@ render partial: 'mfa_selection', locals: { form: form_builder, option: presenter.options.find do |option| - option.is_a?(TwoFactorAuthentication::AuthAppSelectionPresenter) + option.is_a?(TwoFactorAuthentication::SetUpAuthAppSelectionPresenter) end, } end