-
Notifications
You must be signed in to change notification settings - Fork 166
LG-5184: error page on Acuant or LexisNexis full outage #5551
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
Changes from all commits
de34d61
ec1a7b8
80396db
8d6fb7e
245713d
46cc2ae
21bf47e
982ab69
ccc783a
47c221c
c9b1835
a3eee3f
ac9a31f
d7565c8
2b43f2f
e5bda74
08df70d
436de09
a7f78bc
a731c55
d1c1cb6
a7ebd67
c60cafe
f7f8d74
bb76f75
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| class VendorOutageController < ApplicationController | ||
| def show | ||
| vendor_status = VendorStatus.new( | ||
| sp: current_sp, | ||
| from: session.delete(:vendor_outage_redirect), | ||
| from_idv: session.delete(:vendor_outage_redirect_from_idv), | ||
| ) | ||
| @specific_message = vendor_status.outage_message | ||
| vendor_status.track_event(analytics) | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| class StatusPage | ||
| BASE_URL = 'https://logingov.statuspage.io/'.freeze | ||
|
|
||
| def self.base_url | ||
| BASE_URL | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| class VendorStatus | ||
| def initialize(from: nil, from_idv: nil, sp: nil) | ||
| @from = from | ||
| @from_idv = from_idv | ||
| @sp = sp | ||
| end | ||
|
|
||
| IAL2_VENDORS = %i[acuant lexisnexis_instant_verify lexisnexis_trueid].freeze | ||
| ALL_VENDORS = (IAL2_VENDORS + %i[sms voice]).freeze | ||
|
|
||
| def vendor_outage?(vendor) | ||
| status = case vendor | ||
| when :acuant | ||
| IdentityConfig.store.vendor_status_acuant | ||
| when :lexisnexis_instant_verify | ||
| IdentityConfig.store.vendor_status_lexisnexis_instant_verify | ||
| when :lexisnexis_trueid | ||
| IdentityConfig.store.vendor_status_lexisnexis_trueid | ||
| when :sms | ||
| IdentityConfig.store.vendor_status_sms | ||
| when :voice | ||
| IdentityConfig.store.vendor_status_voice | ||
|
solipet marked this conversation as resolved.
|
||
| else | ||
| raise ArgumentError, "invalid vendor #{vendor}" | ||
| end | ||
| status != :operational | ||
| end | ||
|
|
||
| def any_vendor_outage?(vendors = ALL_VENDORS) | ||
| vendors.any? { |vendor| vendor_outage?(vendor) } | ||
| end | ||
|
|
||
| def any_ial2_vendor_outage? | ||
| any_vendor_outage?(IAL2_VENDORS) | ||
| end | ||
|
|
||
| def from_idv? | ||
| from_idv | ||
| end | ||
|
|
||
| # Returns an appropriate error message based upon the type of outage or what the user was doing | ||
| # when they encountered the outage. | ||
| # | ||
| # @return [String, nil] the localized message. | ||
| def outage_message | ||
|
solipet marked this conversation as resolved.
|
||
| if any_ial2_vendor_outage? | ||
| if from_idv? | ||
| if sp | ||
| return I18n.t( | ||
| 'vendor_outage.idv_blocked.with_sp', | ||
| service_provider: sp.friendly_name, | ||
| ) | ||
| else | ||
| return I18n.t('vendor_outage.idv_blocked.without_sp') | ||
| end | ||
| end | ||
|
|
||
| return I18n.t('vendor_outage.idv_blocked.generic') | ||
| end | ||
| end | ||
|
|
||
| def track_event(analytics) | ||
| raise ArgumentError, 'analytics instance required' if analytics.nil? | ||
|
|
||
| tracking_data = { | ||
| vendor_status: { | ||
| acuant: IdentityConfig.store.vendor_status_acuant, | ||
| lexisnexis_instant_verify: IdentityConfig.store.vendor_status_lexisnexis_instant_verify, | ||
| lexisnexis_trueid: IdentityConfig.store.vendor_status_lexisnexis_trueid, | ||
| }, | ||
| redirect_from: from, | ||
| } | ||
| analytics.track_event(Analytics::VENDOR_OUTAGE, tracking_data) | ||
| end | ||
|
|
||
| private | ||
|
|
||
| attr_reader :from, :from_idv, :sp | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| <%= render( | ||
| 'idv/shared/error', | ||
| heading: t('vendor_outage.working'), | ||
| options: [ | ||
| { | ||
| text: t('vendor_outage.get_updates_on_status_page'), | ||
| url: StatusPage.base_url, | ||
| new_tab: true, | ||
| }, | ||
| { | ||
| text: t('idv.troubleshooting.options.contact_support', app_name: APP_NAME), | ||
| url: MarketingSite.contact_url, | ||
| new_tab: true, | ||
| }, | ||
| ], | ||
| ) do %> | ||
| <p><%= @specific_message %></p> | ||
| <% end %> | ||
|
|
||
| <%= render( | ||
| 'idv/doc_auth/back', | ||
| ) %> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| --- | ||
| en: | ||
| vendor_outage: | ||
| get_updates_on_status_page: Get updates on our status page | ||
| idv_blocked: | ||
| generic: We are having technical difficulties on our end and cannot verify your | ||
| identity at this time. Please try again later. | ||
| with_sp: '%{service_provider} needs to make sure you are you — not someone | ||
| pretending to be you. Unfortunately, we are having technical | ||
| difficulties and cannot verify your identity at this time. Please try | ||
| again later.' | ||
| without_sp: The agency that you are trying to access needs to make sure you are | ||
| you — not someone pretending to be you. Unfortunately, we are having | ||
| technical difficulties and cannot verify your identity at this time. | ||
| Please try again later. | ||
| working: We are working to resolve an error |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| --- | ||
| es: | ||
| vendor_outage: | ||
| get_updates_on_status_page: Reciba actualizaciones en nuestra página de estado | ||
| idv_blocked: | ||
| generic: Debido a problemas técnicos por nuestra parte, no podemos verificar su | ||
| identidad en estos momentos. Por favor, inténtelo nuevamente más tarde. | ||
| with_sp: '%{service_provider} necesita asegurarse de que es usted realmente y no | ||
| alguien que se hace pasar por usted. Lamentablemente, debido a problemas | ||
| técnicos por nuestra parte, tal vez no podamos verificar su identidad en | ||
| estos momentos. Por favor, inténtelo nuevamente más tarde.' | ||
| without_sp: La agencia a la que está intentando acceder debe asegurarse de que | ||
| usted sea quien dice ser, y no alguien que se hace pasar por usted. | ||
| Lamentablemente, debido a problemas técnicos por nuestra parte, tal vez | ||
| no podamos verificar su identidad en estos momentos. Por favor, | ||
| inténtelo nuevamente más tarde. | ||
| working: Estamos trabajando para corregir un error |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| --- | ||
| fr: | ||
| vendor_outage: | ||
| get_updates_on_status_page: Obtenez des mises à jour sur notre page de statut | ||
| idv_blocked: | ||
| generic: Nous rencontrons des difficultés techniques et ne pouvons pas vérifier | ||
| votre identité pour le moment. Veuillez réessayer plus tard. | ||
| with_sp: "%{service_provider} doit s'assurer que c'est bien vous — et non | ||
| quelqu'un qui se fait passer pour vous. Malheureusement, nous | ||
| rencontrons des difficultés techniques et ne pouvons pas vérifier votre | ||
| identité pour le moment. Veuillez réessayer plus tard." | ||
| without_sp: L’agence à laquelle vous essayez d’accéder doit s’assurer qu’il | ||
| s’agit bien de vous, et non de quelqu’un qui se fait passer pour vous. | ||
| Malheureusement, nous rencontrons des difficultés techniques et ne | ||
| pouvons pas vérifier votre identité pour le moment. Veuillez réessayer | ||
| plus tard. | ||
| working: "Nous travaillons à la résolution d'une erreur" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,12 +3,15 @@ class IdentityConfig | |
| GIT_TAG = `git tag --points-at HEAD`.chomp.split("\n").first | ||
| GIT_BRANCH = `git rev-parse --abbrev-ref HEAD`.chomp | ||
|
|
||
| VENDOR_STATUS_OPTIONS = %i[operational partial_outage full_outage] | ||
|
|
||
| class << self | ||
| attr_reader :store | ||
| end | ||
|
|
||
| CONVERTERS = { | ||
| string: proc { |value| value.to_s }, | ||
| symbol: proc { |value| value.to_sym }, | ||
| comma_separated_string_list: proc do |value| | ||
| value.split(',') | ||
| end, | ||
|
|
@@ -44,11 +47,14 @@ def initialize(read_env) | |
| @written_env = {} | ||
| end | ||
|
|
||
| def add(key, type: :string, is_sensitive: false, allow_nil: false, options: {}) | ||
| def add(key, type: :string, is_sensitive: false, allow_nil: false, enum: nil, options: {}) | ||
| value = @read_env[key] | ||
|
|
||
| converted_value = CONVERTERS.fetch(type).call(value, options: options) if !value.nil? | ||
| raise "#{key} is required but is not present" if converted_value.nil? && !allow_nil | ||
| if enum && !enum.include?(converted_value) | ||
| raise "unexpected #{key}: #{value}, expected one of #{enum}" | ||
| end | ||
|
Comment on lines
+55
to
+57
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. codeclimate is warning this raise is not covered, can we add some tests in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Erm... I don't see
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh dang I thought we had one 😭 |
||
|
|
||
| @written_env[key] = converted_value | ||
| @written_env | ||
|
|
@@ -199,6 +205,11 @@ def self.build_store(config_map) | |
| config.add(:otps_per_ip_limit, type: :integer) | ||
| config.add(:otps_per_ip_period, type: :integer) | ||
| config.add(:otps_per_ip_track_only_mode, type: :boolean) | ||
| config.add(:vendor_status_acuant, type: :symbol, enum: VENDOR_STATUS_OPTIONS) | ||
|
solipet marked this conversation as resolved.
|
||
| config.add(:vendor_status_lexisnexis_instant_verify, type: :symbol, enum: VENDOR_STATUS_OPTIONS) | ||
| config.add(:vendor_status_lexisnexis_trueid, type: :symbol, enum: VENDOR_STATUS_OPTIONS) | ||
| config.add(:vendor_status_sms, type: :symbol, enum: VENDOR_STATUS_OPTIONS) | ||
| config.add(:vendor_status_voice, type: :symbol, enum: VENDOR_STATUS_OPTIONS) | ||
| config.add(:outbound_connection_check_retry_count, type: :integer) | ||
| config.add(:outbound_connection_check_timeout, type: :integer) | ||
| config.add(:outbound_connection_check_url) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| require 'rails_helper' | ||
|
|
||
| describe VendorOutageController do | ||
| before do | ||
| stub_analytics | ||
| allow(@analytics).to receive(:track_event) | ||
| end | ||
|
|
||
| let(:redirect_from) { nil } | ||
| let(:tracking_data) do | ||
| { | ||
| vendor_status: { | ||
| acuant: IdentityConfig.store.vendor_status_acuant, | ||
| lexisnexis_instant_verify: IdentityConfig.store.vendor_status_lexisnexis_instant_verify, | ||
| lexisnexis_trueid: IdentityConfig.store.vendor_status_lexisnexis_trueid, | ||
| }, | ||
| redirect_from: redirect_from, | ||
| } | ||
| end | ||
|
|
||
| it 'tracks an analytics event' do | ||
| get :show | ||
|
|
||
| expect(@analytics).to have_received(:track_event).with( | ||
| Analytics::VENDOR_OUTAGE, | ||
| tracking_data, | ||
| ) | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| require 'rails_helper' | ||
|
|
||
| feature 'vendor_outage_spec' do | ||
| include PersonalKeyHelper | ||
| include IdvStepHelper | ||
|
|
||
| let(:user) { create(:user, :signed_up) } | ||
| let(:new_password) { 'some really awesome new password' } | ||
| let(:pii) { { ssn: '666-66-1234', dob: '1920-01-01', first_name: 'alice' } } | ||
|
|
||
| %w[acuant lexisnexis_instant_verify lexisnexis_trueid].each do |service| | ||
| context "full outage on #{service}" do | ||
| before do | ||
| allow(IdentityConfig.store).to receive("vendor_status_#{service}".to_sym). | ||
| and_return(:full_outage) | ||
| end | ||
|
|
||
| it 'prevents an existing ial1 user from verifying their identity' do | ||
| visit_idp_from_sp_with_ial2(:oidc) | ||
| sign_in_user(user_with_2fa) | ||
| fill_in_code_with_last_phone_otp | ||
| click_submit_default | ||
| expect(current_path).to eq vendor_outage_path | ||
| expect(page).to have_content( | ||
| t('vendor_outage.idv_blocked.with_sp', service_provider: 'Test SP'), | ||
| ) | ||
| end | ||
|
|
||
| it 'prevents a user who reset their password from reactivating profile with no personal key', | ||
| email: true, js: true do | ||
| personal_key_from_pii(user, pii) | ||
| trigger_reset_password_and_click_email_link(user.email) | ||
| reset_password(user, new_password) | ||
|
|
||
| visit new_user_session_path | ||
| signin(user.email, new_password) | ||
| fill_in_code_with_last_phone_otp | ||
| click_submit_default | ||
|
|
||
| click_link t('account.index.reactivation.link') | ||
| click_on t('links.account.reactivate.without_key') | ||
| click_on t('forms.buttons.continue') | ||
|
|
||
| expect(current_path).to eq vendor_outage_path | ||
| expect(page).to have_content(t('vendor_outage.idv_blocked.without_sp')) | ||
| end | ||
|
|
||
| it 'prevents a user from creating an account' do | ||
| visit_idp_from_sp_with_ial2(:oidc) | ||
| click_link t('links.create_account') | ||
| expect(current_path).to eq vendor_outage_path | ||
| expect(page).to have_content(t('vendor_outage.idv_blocked.generic')) | ||
| end | ||
| end | ||
| end | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
had to disable this cop since
:showis not explicitly defined on this controller but via magic in the FSM