From bef6538cf7b63a655cdd4574dda0ded6f05e2b8b Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Tue, 6 Feb 2024 12:37:35 -0500 Subject: [PATCH 1/3] Revert "Revert "LG-12190 Store vtr and acr_values in sp_session (#10004)" (#10044)" This reverts commit 04361ce31d91fa4a8d01f71270220d14c715c702. --- app/forms/openid_connect_authorize_form.rb | 55 ++- app/models/federated_protocols/oidc.rb | 8 + app/models/federated_protocols/saml.rb | 8 + app/services/analytics_events.rb | 3 + .../service_provider_request_handler.rb | 2 + app/services/store_sp_metadata_in_session.rb | 2 + config/application.yml.default | 2 + config/locales/openid_connect/en.yml | 1 + config/locales/openid_connect/es.yml | 1 + config/locales/openid_connect/fr.yml | 1 + lib/identity_config.rb | 1 + .../authorization_controller_spec.rb | 26 +- spec/controllers/saml_idp_controller_spec.rb | 16 + .../openid_connect_authorize_form_spec.rb | 411 +++++++++++------- .../store_sp_metadata_in_session_spec.rb | 66 +++ 15 files changed, 435 insertions(+), 168 deletions(-) diff --git a/app/forms/openid_connect_authorize_form.rb b/app/forms/openid_connect_authorize_form.rb index 9000ec7c518..ff076f99513 100644 --- a/app/forms/openid_connect_authorize_form.rb +++ b/app/forms/openid_connect_authorize_form.rb @@ -20,6 +20,7 @@ class OpenidConnectAuthorizeForm ATTRS = [ :unauthorized_scope, :acr_values, + :vtr, :scope, :verified_within, :biometric_comparison_required, @@ -37,7 +38,7 @@ class OpenidConnectAuthorizeForm RANDOM_VALUE_MINIMUM_LENGTH = 22 MINIMUM_REPROOF_VERIFIED_WITHIN_DAYS = 30 - validates :acr_values, presence: true + validates :acr_values, presence: true, if: ->(form) { form.vtr.empty? } validates :client_id, presence: true validates :redirect_uri, presence: true validates :scope, presence: true @@ -49,6 +50,7 @@ class OpenidConnectAuthorizeForm validates :code_challenge_method, inclusion: { in: %w[S256] }, if: :code_challenge validate :validate_acr_values + validate :validate_vtr validate :validate_client_id validate :validate_scope validate :validate_unauthorized_scope @@ -59,6 +61,7 @@ class OpenidConnectAuthorizeForm def initialize(params) @acr_values = parse_to_values(params[:acr_values], Saml::Idp::Constants::VALID_AUTHN_CONTEXTS) + @vtr = parse_vtr(params[:vtr]) SIMPLE_ATTRS.each { |key| instance_variable_set(:"@#{key}", params[key]) } @prompt ||= 'select_account' @scope = parse_to_values(params[:scope], scopes) @@ -119,7 +122,13 @@ def ial_context end def ial - Saml::Idp::Constants::AUTHN_CONTEXT_CLASSREF_TO_IAL[ial_values.sort.max] + if parsed_vector_of_trust&.identity_proofing? + 2 + elsif parsed_vector_of_trust.present? + 1 + else + Saml::Idp::Constants::AUTHN_CONTEXT_CLASSREF_TO_IAL[ial_values.sort.max] + end end def aal_values @@ -127,7 +136,13 @@ def aal_values end def aal - Saml::Idp::Constants::AUTHN_CONTEXT_CLASSREF_TO_AAL[requested_aal_value] + if parsed_vector_of_trust&.aal2? + 2 + elsif parsed_vector_of_trust.present? + 1 + else + Saml::Idp::Constants::AUTHN_CONTEXT_CLASSREF_TO_AAL[requested_aal_value] + end end def requested_aal_value @@ -163,7 +178,18 @@ def parse_to_values(param_value, possible_values) param_value.split(' ').compact & possible_values end + def parse_vtr(param_value) + return if !IdentityConfig.store.use_vot_in_sp_requests + return [] if param_value.blank? + + JSON.parse(param_value) + rescue JSON::ParserError + nil + end + def validate_acr_values + return if vtr.present? + if acr_values.empty? errors.add( :acr_values, t('openid_connect.authorization.errors.no_valid_acr_values'), @@ -177,6 +203,15 @@ def validate_acr_values end end + def validate_vtr + return if vtr.blank? + return if parsed_vector_of_trust.present? + errors.add( + :vtr, t('openid_connect.authorization.errors.no_valid_vtr'), + type: :no_valid_vtr + ) + end + # This checks that the SP matches something in the database # OpenidConnect::AuthorizationController#check_sp_active checks that it's currently active def validate_client_id @@ -246,6 +281,7 @@ def extra_analytics_attributes redirect_uri: result_uri, scope: scope&.sort&.join(' '), acr_values: acr_values&.sort&.join(' '), + vtr: vtr, unauthorized_scope: @unauthorized_scope, code_digest: code ? Digest::SHA256.hexdigest(code) : nil, code_challenge_present: code_challenge.present?, @@ -275,6 +311,19 @@ def scopes OpenidConnectAttributeScoper::VALID_IAL1_SCOPES end + def parsed_vector_of_trust + return @parsed_vector_of_trust if defined?(@parsed_vector_of_trust) + return @parsed_vector_of_trust = nil if vtr.blank? + + @parsed_vector_of_trust = begin + if vtr.is_a?(Array) && !vtr.empty? + Vot::Parser.new(vector_of_trust: vtr.first).parse + end + rescue Vot::Parser::ParseException + nil + end + end + def validate_privileges if (ial2_requested? && !ial_context.ial2_service_provider?) || (ial_context.ialmax_requested? && diff --git a/app/models/federated_protocols/oidc.rb b/app/models/federated_protocols/oidc.rb index 33b92251cf3..be1605d9b6d 100644 --- a/app/models/federated_protocols/oidc.rb +++ b/app/models/federated_protocols/oidc.rb @@ -16,6 +16,14 @@ def aal request.aal_values.sort.max end + def acr_values + [aal, ial].compact.join(' ') + end + + def vtr + request.vtr + end + def requested_attributes OpenidConnectAttributeScoper.new(request.scope).requested_attributes end diff --git a/app/models/federated_protocols/saml.rb b/app/models/federated_protocols/saml.rb index ecc0dea6569..7659d466abd 100644 --- a/app/models/federated_protocols/saml.rb +++ b/app/models/federated_protocols/saml.rb @@ -16,6 +16,14 @@ def aal request.requested_aal_authn_context end + def acr_values + [aal, ial].compact.join(' ') + end + + def vtr + nil + end + def requested_attributes @requested_attributes ||= SamlRequestPresenter.new( request: request, service_provider: current_service_provider, diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 7f392be8655..d7e5e29ef14 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -3636,12 +3636,14 @@ def openid_connect_bearer_token(success:, ial:, client_id:, errors:, **extra) # @param [String] client_id # @param [String] scope # @param [Array] acr_values + # @param [Array] vtr # @param [Boolean] unauthorized_scope # @param [Boolean] user_fully_authenticated def openid_connect_request_authorization( client_id:, scope:, acr_values:, + vtr:, unauthorized_scope:, user_fully_authenticated:, **extra @@ -3651,6 +3653,7 @@ def openid_connect_request_authorization( client_id: client_id, scope: scope, acr_values: acr_values, + vtr: vtr, unauthorized_scope: unauthorized_scope, user_fully_authenticated: user_fully_authenticated, **extra, diff --git a/app/services/service_provider_request_handler.rb b/app/services/service_provider_request_handler.rb index 089293b8f77..bd62d915a9c 100644 --- a/app/services/service_provider_request_handler.rb +++ b/app/services/service_provider_request_handler.rb @@ -63,6 +63,8 @@ def attributes issuer: protocol.issuer, ial: protocol.ial, aal: protocol.aal, + acr_values: protocol.acr_values, + vtr: protocol.vtr, requested_attributes: protocol.requested_attributes, biometric_comparison_required: protocol.biometric_comparison_required?, uuid: request_id, diff --git a/app/services/store_sp_metadata_in_session.rb b/app/services/store_sp_metadata_in_session.rb index 13c052b2646..c3c0577504e 100644 --- a/app/services/store_sp_metadata_in_session.rb +++ b/app/services/store_sp_metadata_in_session.rb @@ -37,6 +37,8 @@ def update_session request_id: sp_request.uuid, requested_attributes: sp_request.requested_attributes, biometric_comparison_required: sp_request.biometric_comparison_required, + acr_values: sp_request.acr_values, + vtr: sp_request.vtr, } end diff --git a/config/application.yml.default b/config/application.yml.default index e848e7bc7fe..e599da246eb 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -321,6 +321,7 @@ team_ursula_email: '' test_ssn_allowed_list: '' totp_code_interval: 30 unauthorized_scope_enabled: false +use_vot_in_sp_requests: true usps_upload_enabled: false usps_upload_sftp_timeout: 5 valid_authn_contexts: '["http://idmanagement.gov/ns/assurance/loa/1", "http://idmanagement.gov/ns/assurance/loa/3", "http://idmanagement.gov/ns/assurance/ial/1", "http://idmanagement.gov/ns/assurance/ial/2", "http://idmanagement.gov/ns/assurance/ial/0", "http://idmanagement.gov/ns/assurance/ial/2?strict=true", "urn:gov:gsa:ac:classes:sp:PasswordProtectedTransport:duo", "http://idmanagement.gov/ns/assurance/aal/2", "http://idmanagement.gov/ns/assurance/aal/3", "http://idmanagement.gov/ns/assurance/aal/3?hspd12=true","http://idmanagement.gov/ns/assurance/aal/2?phishing_resistant=true","http://idmanagement.gov/ns/assurance/aal/2?hspd12=true"]' @@ -494,6 +495,7 @@ production: state_tracking_enabled: false telephony_adapter: pinpoint use_kms: true + use_vot_in_sp_requests: false usps_auth_token_refresh_job_enabled: true usps_confirmation_max_days: 30 usps_upload_sftp_directory: '' diff --git a/config/locales/openid_connect/en.yml b/config/locales/openid_connect/en.yml index 3c68137adbd..bd5eb70fd1c 100644 --- a/config/locales/openid_connect/en.yml +++ b/config/locales/openid_connect/en.yml @@ -12,6 +12,7 @@ en: no_auth: The acr_values are not authorized no_valid_acr_values: No acceptable acr_values found no_valid_scope: No valid scope values found + no_valid_vtr: No acceptable vots found prompt_invalid: No valid prompt values found redirect_uri_invalid: redirect_uri is invalid redirect_uri_no_match: redirect_uri does not match registered redirect_uri diff --git a/config/locales/openid_connect/es.yml b/config/locales/openid_connect/es.yml index 51ea9ba75b2..7aac8a1747c 100644 --- a/config/locales/openid_connect/es.yml +++ b/config/locales/openid_connect/es.yml @@ -12,6 +12,7 @@ es: no_auth: Los acr_values no están autorizados no_valid_acr_values: ial_valores encontrados no aceptables no_valid_scope: No se han encontrado valores de magnitud válidos + no_valid_vtr: vots encontrados no aceptables prompt_invalid: Prompt no es válido redirect_uri_invalid: Redirect_uri no es válido redirect_uri_no_match: Redirect_uri no coincide con redirect_uri registrado diff --git a/config/locales/openid_connect/fr.yml b/config/locales/openid_connect/fr.yml index 2d8f7eefe24..2ee66ca55c1 100644 --- a/config/locales/openid_connect/fr.yml +++ b/config/locales/openid_connect/fr.yml @@ -12,6 +12,7 @@ fr: no_auth: Les acr_values ne sont pas autorisées no_valid_acr_values: Valeurs acr_values inacceptables trouvées no_valid_scope: Aucune étendue de données valide trouvée + no_valid_vtr: vots encontrados no aceptables prompt_invalid: prompt est non valide redirect_uri_invalid: redirect_uri est non valide redirect_uri_no_match: redirect_uri ne correspond pas au redirect_uri enregistré diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 8d07bde22cf..894ae4ebd83 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -461,6 +461,7 @@ def self.build_store(config_map) config.add(:unauthorized_scope_enabled, type: :boolean) config.add(:use_dashboard_service_providers, type: :boolean) config.add(:use_kms, type: :boolean) + config.add(:use_vot_in_sp_requests, type: :boolean) config.add(:usps_auth_token_refresh_job_enabled, type: :boolean) config.add(:usps_confirmation_max_days, type: :integer) config.add(:usps_ipp_client_id, type: :string) diff --git a/spec/controllers/openid_connect/authorization_controller_spec.rb b/spec/controllers/openid_connect/authorization_controller_spec.rb index da601abc81e..fb09c85af67 100644 --- a/spec/controllers/openid_connect/authorization_controller_spec.rb +++ b/spec/controllers/openid_connect/authorization_controller_spec.rb @@ -110,7 +110,8 @@ acr_values: 'http://idmanagement.gov/ns/assurance/ial/1', code_challenge_present: false, service_provider_pkce: nil, - scope: 'openid') + scope: 'openid', + vtr: []) expect(@analytics).to receive(:track_event). with('OpenID Connect: authorization request handoff', success: true, @@ -257,7 +258,8 @@ acr_values: 'http://idmanagement.gov/ns/assurance/ial/2', code_challenge_present: false, service_provider_pkce: nil, - scope: 'openid profile') + scope: 'openid profile', + vtr: []) expect(@analytics).to receive(:track_event). with('OpenID Connect: authorization request handoff', success: true, @@ -495,7 +497,8 @@ acr_values: 'http://idmanagement.gov/ns/assurance/ial/0', code_challenge_present: false, service_provider_pkce: nil, - scope: 'openid profile') + scope: 'openid profile', + vtr: []) expect(@analytics).to receive(:track_event). with('OpenID Connect: authorization request handoff', success: true, @@ -578,7 +581,8 @@ acr_values: 'http://idmanagement.gov/ns/assurance/ial/0', code_challenge_present: false, service_provider_pkce: nil, - scope: 'openid profile') + scope: 'openid profile', + vtr: []) expect(@analytics).to receive(:track_event). with('OpenID Connect: authorization request handoff', success: true, @@ -663,7 +667,8 @@ acr_values: 'http://idmanagement.gov/ns/assurance/ial/0', code_challenge_present: false, service_provider_pkce: nil, - scope: 'openid profile') + scope: 'openid profile', + vtr: []) expect(@analytics).to receive(:track_event). with('OpenID Connect: authorization request handoff', success: true, @@ -865,7 +870,8 @@ acr_values: 'http://idmanagement.gov/ns/assurance/ial/1', code_challenge_present: false, service_provider_pkce: nil, - scope: 'openid') + scope: 'openid', + vtr: []) expect(@analytics).to_not receive(:track_event).with('sp redirect initiated') action @@ -898,7 +904,8 @@ acr_values: 'http://idmanagement.gov/ns/assurance/ial/1', code_challenge_present: false, service_provider_pkce: nil, - scope: 'openid') + scope: 'openid', + vtr: []) expect(@analytics).to_not receive(:track_event).with('SP redirect initiated') action @@ -1013,7 +1020,8 @@ acr_values: 'http://idmanagement.gov/ns/assurance/ial/1', code_challenge_present: false, service_provider_pkce: nil, - scope: 'openid') + scope: 'openid', + vtr: []) action sp_request_id = ServiceProviderRequestProxy.last.uuid @@ -1028,6 +1036,7 @@ expect(session[:sp]).to eq( aal_level_requested: nil, + acr_values: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, piv_cac_requested: false, phishing_resistant_requested: false, ial: 1, @@ -1038,6 +1047,7 @@ request_url: request.original_url, requested_attributes: %w[], biometric_comparison_required: false, + vtr: [], ) end diff --git a/spec/controllers/saml_idp_controller_spec.rb b/spec/controllers/saml_idp_controller_spec.rb index 511195be937..d07d0858dfa 100644 --- a/spec/controllers/saml_idp_controller_spec.rb +++ b/spec/controllers/saml_idp_controller_spec.rb @@ -1166,6 +1166,12 @@ def name_id_version(format_urn) end context 'POST to auth correctly stores SP in session' do + let(:acr_values) do + Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF + + ' ' + + Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF + end + before do @user = create(:user, :fully_registered) @saml_request = saml_request(saml_settings) @@ -1181,6 +1187,7 @@ def name_id_version(format_urn) issuer: saml_settings.issuer, aal_level_requested: aal_level, piv_cac_requested: false, + acr_values: acr_values, phishing_resistant_requested: false, ial: 1, ial2: false, @@ -1189,6 +1196,7 @@ def name_id_version(format_urn) request_id: sp_request_id, requested_attributes: ['email'], biometric_comparison_required: false, + vtr: nil, ) end @@ -1201,6 +1209,12 @@ def name_id_version(format_urn) end context 'service provider is valid' do + let(:acr_values) do + Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF + + ' ' + + Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF + end + before do @user = create(:user, :fully_registered) @saml_request = saml_get_auth(saml_settings) @@ -1212,6 +1226,7 @@ def name_id_version(format_urn) expect(session[:sp]).to eq( issuer: saml_settings.issuer, aal_level_requested: aal_level, + acr_values: acr_values, piv_cac_requested: false, phishing_resistant_requested: false, ial: 1, @@ -1221,6 +1236,7 @@ def name_id_version(format_urn) request_id: sp_request_id, requested_attributes: ['email'], biometric_comparison_required: false, + vtr: nil, ) end diff --git a/spec/forms/openid_connect_authorize_form_spec.rb b/spec/forms/openid_connect_authorize_form_spec.rb index fafe7188723..a3542a9b9ef 100644 --- a/spec/forms/openid_connect_authorize_form_spec.rb +++ b/spec/forms/openid_connect_authorize_form_spec.rb @@ -4,6 +4,7 @@ subject(:form) do OpenidConnectAuthorizeForm.new( acr_values: acr_values, + vtr: vtr, client_id: client_id, nonce: nonce, prompt: prompt, @@ -18,12 +19,8 @@ ) end - let(:acr_values) do - [ - Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, - ].join(' ') - end - + let(:acr_values) { nil } + let(:vtr) { ['C1'].to_json } let(:client_id) { 'urn:gov:gsa:openidconnect:test' } let(:nonce) { SecureRandom.hex } let(:prompt) { 'select_account' } @@ -49,7 +46,8 @@ allow_prompt_login: true, redirect_uri: nil, unauthorized_scope: true, - acr_values: 'http://idmanagement.gov/ns/assurance/ial/1', + acr_values: '', + vtr: JSON.parse(vtr), scope: 'openid', code_digest: nil, code_challenge_present: false, @@ -73,7 +71,8 @@ redirect_uri: "#{redirect_uri}?error=invalid_request&error_description=" \ "Response+type+is+not+included+in+the+list&state=#{state}", unauthorized_scope: true, - acr_values: 'http://idmanagement.gov/ns/assurance/ial/1', + acr_values: '', + vtr: JSON.parse(vtr), scope: 'openid', code_digest: nil, code_challenge_present: false, @@ -93,6 +92,18 @@ expect(result.extra[:redirect_uri]).to be_nil end end + + context 'when use_vot_in_sp_requests flag is false' do + before do + allow(IdentityConfig.store).to receive(:use_vot_in_sp_requests).and_return(false) + end + + let(:vtr) { ['C1.P1'].to_json } + + it 'does not consume the VTR param' do + expect(form.vtr).to be_nil + end + end end describe '#valid?' do @@ -106,8 +117,18 @@ end end + context 'with an invalid vtr' do + let(:vtr) { ['A1.B2.C3'].to_json } + it 'has errors' do + expect(valid?).to eq(false) + expect(form.errors[:vtr]). + to include(t('openid_connect.authorization.errors.no_valid_vtr')) + end + end + context 'with no valid acr_values' do let(:acr_values) { 'abc def' } + let(:vtr) { nil } it 'has errors' do expect(valid?).to eq(false) expect(form.errors[:acr_values]). @@ -115,8 +136,19 @@ end end + context 'with no authorized vtr components' do + let(:vtr) { ['C1.P1'].to_json } + let(:client_id) { 'urn:gov:gsa:openidconnect:test:loa1' } + it 'has errors' do + expect(valid?).to eq(false) + expect(form.errors[:acr_values]). + to include(t('openid_connect.authorization.errors.no_auth')) + end + end + context 'with no authorized acr_values' do let(:acr_values) { Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF } + let(:vtr) { nil } let(:client_id) { 'urn:gov:gsa:openidconnect:test:loa1' } it 'has errors' do expect(valid?).to eq(false) @@ -127,6 +159,7 @@ context 'with ialmax requested' do let(:acr_values) { Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF } + let(:vtr) { nil } context 'with a service provider not in the allow list' do it 'has errors' do @@ -150,6 +183,7 @@ context 'with aal but not ial requested via acr_values' do let(:acr_values) { Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF } + let(:vtr) { nil } it 'has errors' do expect(valid?).to eq(false) expect(form.errors[:acr_values]). @@ -248,7 +282,7 @@ context 'when scope includes profile:verified_at but the sp is only ial1' do let(:client_id) { 'urn:gov:gsa:openidconnect:test:loa1' } - let(:acr_values) { Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF } + let(:vtr) { ['C1'].to_json } let(:scope) { 'email profile:verified_at' } it 'has errors' do @@ -336,6 +370,7 @@ 'fake_value', ].join(' ') end + let(:vtr) { nil } it 'is parsed into an array of valid ACR values' do expect(form.acr_values).to eq( @@ -348,209 +383,252 @@ end describe '#ial' do - context 'when IAL1 passed' do - let(:acr_values) { Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF } + context 'with vtr param' do + let(:acr_values) { nil } + + context 'when proofing is requested' do + let(:vtr) { ['C1.P1'].to_json } - it 'returns 1' do - expect(form.ial).to eq(1) + it { expect(form.ial).to eq(2) } end - end - context 'when IAL2 passed' do - let(:acr_values) { Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF } + context 'when proofing is not requested' do + let(:vtr) { ['C1'].to_json } - it 'returns 2' do - expect(form.ial).to eq(2) + it { expect(form.ial).to eq(1) } end end - context 'when IALMAX passed' do - let(:acr_values) { Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF } + context 'with acr_values param' do + let(:vtr) { nil } + + context 'when IAL1 passed' do + let(:acr_values) { Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF } + + it 'returns 1' do + expect(form.ial).to eq(1) + end + end - it 'returns 0' do - expect(form.ial).to eq(0) + context 'when IAL2 passed' do + let(:acr_values) { Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF } + + it 'returns 2' do + expect(form.ial).to eq(2) + end end - end - context 'when LOA1 passed' do - let(:acr_values) { Saml::Idp::Constants::LOA1_AUTHN_CONTEXT_CLASSREF } + context 'when IALMAX passed' do + let(:acr_values) { Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF } - it 'returns 1' do - expect(form.ial).to eq(1) + it 'returns 0' do + expect(form.ial).to eq(0) + end end - end - context 'when LOA3 passed' do - let(:acr_values) { Saml::Idp::Constants::LOA3_AUTHN_CONTEXT_CLASSREF } + context 'when LOA1 passed' do + let(:acr_values) { Saml::Idp::Constants::LOA1_AUTHN_CONTEXT_CLASSREF } - it 'returns 2' do - expect(form.ial).to eq(2) + it 'returns 1' do + expect(form.ial).to eq(1) + end + end + + context 'when LOA3 passed' do + let(:acr_values) { Saml::Idp::Constants::LOA3_AUTHN_CONTEXT_CLASSREF } + + it 'returns 2' do + expect(form.ial).to eq(2) + end end end end describe '#aal' do - context 'when no AAL passed' do - let(:acr_values) { Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF } + context 'with vtr param' do + let(:acr_values) { nil } + + context 'when AAL2 is requested' do + let(:vtr) { ['C2'].to_json } - it 'returns 0' do - expect(form.aal).to eq(0) + it { expect(form.aal).to eq(2) } end - end - context 'when DEFAULT_AAL passed' do - let(:acr_values) { Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF } + context 'when AAL2 is not requested' do + let(:vtr) { ['C1'].to_json } - it 'returns 0' do - expect(form.aal).to eq(0) + it { expect(form.aal).to eq(1) } end end - context 'when AAL2 passed' do - let(:acr_values) { Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF } + context 'with acr_values param' do + let(:vtr) { nil } - it 'returns 2' do - expect(form.aal).to eq(2) + context 'when no AAL passed' do + let(:acr_values) { Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF } + + it 'returns 0' do + expect(form.aal).to eq(0) + end end - end - context 'when AAL2_PHISHING_RESISTANT passed' do - let(:acr_values) { Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF } + context 'when DEFAULT_AAL passed' do + let(:acr_values) { Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF } - it 'returns 2' do - expect(form.aal).to eq(2) + it 'returns 0' do + expect(form.aal).to eq(0) + end end - end - context 'when AAL2_HSPD12 passed' do - let(:acr_values) { Saml::Idp::Constants::AAL2_HSPD12_AUTHN_CONTEXT_CLASSREF } + context 'when AAL2 passed' do + let(:acr_values) { Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF } - it 'returns 2' do - expect(form.aal).to eq(2) + it 'returns 2' do + expect(form.aal).to eq(2) + end end - end - context 'when AAL3 passed' do - let(:acr_values) { Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF } + context 'when AAL2_PHISHING_RESISTANT passed' do + let(:acr_values) { Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF } - it 'returns 3' do - expect(form.aal).to eq(3) + it 'returns 2' do + expect(form.aal).to eq(2) + end end - end - context 'when AAL3_HSPD12 passed' do - let(:acr_values) { Saml::Idp::Constants::AAL3_HSPD12_AUTHN_CONTEXT_CLASSREF } + context 'when AAL2_HSPD12 passed' do + let(:acr_values) { Saml::Idp::Constants::AAL2_HSPD12_AUTHN_CONTEXT_CLASSREF } - it 'returns 3' do - expect(form.aal).to eq(3) + it 'returns 2' do + expect(form.aal).to eq(2) + end end - end - context 'when IAL and AAL passed' do - aal2 = Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF - ial2 = Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF + context 'when AAL3 passed' do + let(:acr_values) { Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF } - let(:acr_values) do - "#{aal2} #{ial2}" + it 'returns 3' do + expect(form.aal).to eq(3) + end end - it 'returns ial and aal' do - expect(form.aal).to eq(2) - expect(form.ial).to eq(2) + context 'when AAL3_HSPD12 passed' do + let(:acr_values) { Saml::Idp::Constants::AAL3_HSPD12_AUTHN_CONTEXT_CLASSREF } + + it 'returns 3' do + expect(form.aal).to eq(3) + end + end + + context 'when IAL and AAL passed' do + aal2 = Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF + ial2 = Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF + + let(:acr_values) do + "#{aal2} #{ial2}" + end + + it 'returns ial and aal' do + expect(form.aal).to eq(2) + expect(form.ial).to eq(2) + end end end end describe '#requested_aal_value' do - context 'when AAL2 passed' do - let(:acr_values) { Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF } + context 'with ACR values' do + let(:vtr) { nil } + context 'when AAL2 passed' do + let(:acr_values) { Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF } - it 'returns AAL2' do - expect(form.requested_aal_value).to eq(Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF) + it 'returns AAL2' do + expect(form.requested_aal_value).to eq(Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF) + end end - end - context 'when AAL2_PHISHING_RESISTANT passed' do - let(:acr_values) { Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF } + context 'when AAL2_PHISHING_RESISTANT passed' do + let(:acr_values) { Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF } - it 'returns AAL2+Phishing Resistant' do - expect(form.requested_aal_value).to eq( - Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF, - ) + it 'returns AAL2+Phishing Resistant' do + expect(form.requested_aal_value).to eq( + Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF, + ) + end end - end - context 'when AAL2_HSPD12 passed' do - let(:acr_values) { Saml::Idp::Constants::AAL2_HSPD12_AUTHN_CONTEXT_CLASSREF } + context 'when AAL2_HSPD12 passed' do + let(:acr_values) { Saml::Idp::Constants::AAL2_HSPD12_AUTHN_CONTEXT_CLASSREF } - it 'returns AAL2+HSPD12' do - expect(form.requested_aal_value).to eq( - Saml::Idp::Constants::AAL2_HSPD12_AUTHN_CONTEXT_CLASSREF, - ) + it 'returns AAL2+HSPD12' do + expect(form.requested_aal_value).to eq( + Saml::Idp::Constants::AAL2_HSPD12_AUTHN_CONTEXT_CLASSREF, + ) + end end - end - context 'when AAL3 passed' do - let(:acr_values) { Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF } + context 'when AAL3 passed' do + let(:acr_values) { Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF } - it 'returns AAL3' do - expect(form.requested_aal_value).to eq(Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF) + it 'returns AAL3' do + expect(form.requested_aal_value).to eq(Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF) + end end - end - context 'when AAL3_HSPD12 passed' do - let(:acr_values) { Saml::Idp::Constants::AAL3_HSPD12_AUTHN_CONTEXT_CLASSREF } + context 'when AAL3_HSPD12 passed' do + let(:acr_values) { Saml::Idp::Constants::AAL3_HSPD12_AUTHN_CONTEXT_CLASSREF } - it 'returns AAL3+HSPD12' do - expect(form.requested_aal_value).to eq( - Saml::Idp::Constants::AAL3_HSPD12_AUTHN_CONTEXT_CLASSREF, - ) + it 'returns AAL3+HSPD12' do + expect(form.requested_aal_value).to eq( + Saml::Idp::Constants::AAL3_HSPD12_AUTHN_CONTEXT_CLASSREF, + ) + end end - end - context 'when AAL3_HSPD12 and AAL2_HSPD12 passed' do - let(:acr_values) do - [Saml::Idp::Constants::AAL3_HSPD12_AUTHN_CONTEXT_CLASSREF, - Saml::Idp::Constants::AAL2_HSPD12_AUTHN_CONTEXT_CLASSREF].join(' ') - end + context 'when AAL3_HSPD12 and AAL2_HSPD12 passed' do + let(:acr_values) do + [Saml::Idp::Constants::AAL3_HSPD12_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::AAL2_HSPD12_AUTHN_CONTEXT_CLASSREF].join(' ') + end - it 'returns AAL2+HSPD12' do - expect(form.requested_aal_value).to eq( - Saml::Idp::Constants::AAL2_HSPD12_AUTHN_CONTEXT_CLASSREF, - ) + it 'returns AAL2+HSPD12' do + expect(form.requested_aal_value).to eq( + Saml::Idp::Constants::AAL2_HSPD12_AUTHN_CONTEXT_CLASSREF, + ) + end end - end - context 'when AAL2 and AAL2_PHISHING_RESISTANT passed' do - let(:phishing_resistant) do - Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF - end + context 'when AAL2 and AAL2_PHISHING_RESISTANT passed' do + let(:phishing_resistant) do + Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF + end - let(:acr_values) do - "#{Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF} - #{phishing_resistant}" - end + let(:acr_values) do + "#{Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF} + #{phishing_resistant}" + end - it 'returns AAL2+HSPD12' do - expect(form.requested_aal_value).to eq(phishing_resistant) + it 'returns AAL2+HSPD12' do + expect(form.requested_aal_value).to eq(phishing_resistant) + end end - end - context 'when AAL2_PHISHING_RESISTANT and AAL2 passed' do - # this is the same as the previous test, just reverse ordered - # AAL values, to ensure it doesn't just take the 2nd AAL. - let(:phishing_resistant) do - Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF - end + context 'when AAL2_PHISHING_RESISTANT and AAL2 passed' do + # this is the same as the previous test, just reverse ordered + # AAL values, to ensure it doesn't just take the 2nd AAL. + let(:phishing_resistant) do + Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF + end - let(:acr_values) do - "#{phishing_resistant} - #{Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF}" - end + let(:acr_values) do + "#{phishing_resistant} + #{Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF}" + end - it 'returns AAL2+HSPD12' do - requested_aal_value = form.requested_aal_value - expect(requested_aal_value).to eq(phishing_resistant) + it 'returns AAL2+HSPD12' do + requested_aal_value = form.requested_aal_value + expect(requested_aal_value).to eq(phishing_resistant) + end end end end @@ -646,29 +724,48 @@ describe '#ial2_requested?' do subject(:ial2_requested?) { form.ial2_requested? } - context 'with ial1' do - let(:acr_values) { Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF } - it { expect(ial2_requested?).to eq(false) } - end - context 'with ial2' do - let(:acr_values) { Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF } - it { expect(ial2_requested?).to eq(true) } - end + context 'with vtr params' do + let(:acr_values) { nil } + + context 'when identity proofing is requested' do + let(:vtr) { ['P1'].to_json } + it { expect(ial2_requested?).to eq(true) } + end - context 'with ial1 and ial2' do - let(:acr_values) do - [ - Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, - Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, - ].join(' ') + context 'when identity proofing is not requested' do + let(:vtr) { ['C1'].to_json } + it { expect(ial2_requested?).to eq(false) } end - it { expect(ial2_requested?).to eq(true) } end - context 'with a malformed ial' do - let(:acr_values) { 'foobarbaz' } - it { expect(ial2_requested?).to eq(false) } + context 'with acr_values param' do + let(:vtr) { nil } + + context 'with ial1' do + let(:acr_values) { Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF } + it { expect(ial2_requested?).to eq(false) } + end + + context 'with ial2' do + let(:acr_values) { Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF } + it { expect(ial2_requested?).to eq(true) } + end + + context 'with ial1 and ial2' do + let(:acr_values) do + [ + Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, + ].join(' ') + end + it { expect(ial2_requested?).to eq(true) } + end + + context 'with a malformed ial' do + let(:acr_values) { 'foobarbaz' } + it { expect(ial2_requested?).to eq(false) } + end end end diff --git a/spec/services/store_sp_metadata_in_session_spec.rb b/spec/services/store_sp_metadata_in_session_spec.rb index 6504e43d929..d1486bb4258 100644 --- a/spec/services/store_sp_metadata_in_session_spec.rb +++ b/spec/services/store_sp_metadata_in_session_spec.rb @@ -18,6 +18,7 @@ ServiceProviderRequestProxy.find_or_create_by(uuid: request_id) do |sp_request| sp_request.issuer = 'issuer' sp_request.ial = Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF + sp_request.acr_values = Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF sp_request.url = 'http://issuer.gov' sp_request.requested_attributes = %w[email] sp_request.biometric_comparison_required = false @@ -27,6 +28,7 @@ app_session_hash = { issuer: 'issuer', aal_level_requested: nil, + acr_values: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, piv_cac_requested: false, phishing_resistant_requested: false, ial: 1, @@ -36,6 +38,7 @@ request_id: request_id, requested_attributes: %w[email], biometric_comparison_required: false, + vtr: nil, } instance.call @@ -51,6 +54,10 @@ sp_request.issuer = 'issuer' sp_request.ial = Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF sp_request.aal = Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF + sp_request.acr_values = [ + Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF, + ].join(' ') sp_request.url = 'http://issuer.gov' sp_request.requested_attributes = %w[email] sp_request.biometric_comparison_required = false @@ -60,6 +67,10 @@ app_session_hash = { issuer: 'issuer', aal_level_requested: 3, + acr_values: [ + Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF, + ].join(' '), piv_cac_requested: false, phishing_resistant_requested: true, ial: 2, @@ -69,6 +80,7 @@ request_id: request_id, requested_attributes: %w[email], biometric_comparison_required: false, + vtr: nil, } instance.call @@ -84,6 +96,10 @@ sp_request.issuer = 'issuer' sp_request.ial = Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF sp_request.aal = Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF + sp_request.acr_values = [ + Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF, + ].join(' ') sp_request.url = 'http://issuer.gov' sp_request.requested_attributes = %w[email] sp_request.biometric_comparison_required = false @@ -93,6 +109,10 @@ app_session_hash = { issuer: 'issuer', aal_level_requested: 2, + acr_values: [ + Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF, + ].join(' '), piv_cac_requested: false, phishing_resistant_requested: true, ial: 2, @@ -102,6 +122,7 @@ request_id: request_id, requested_attributes: %w[email], biometric_comparison_required: false, + vtr: nil, } instance.call @@ -117,6 +138,10 @@ sp_request.issuer = 'issuer' sp_request.ial = Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF sp_request.aal = Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF + sp_request.acr_values = [ + Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF, + ].join(' ') sp_request.url = 'http://issuer.gov' sp_request.requested_attributes = %w[email] sp_request.biometric_comparison_required = true @@ -126,6 +151,10 @@ app_session_hash = { issuer: 'issuer', aal_level_requested: 3, + acr_values: [ + Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF, + ].join(' '), piv_cac_requested: false, phishing_resistant_requested: true, ial: 2, @@ -135,6 +164,43 @@ request_id: request_id, requested_attributes: %w[email], biometric_comparison_required: true, + vtr: nil, + } + + instance.call + expect(app_session[:sp]).to eq app_session_hash + end + end + + context 'when a vtr is present' do + it 'sets the session[:sp] hash' do + app_session = {} + request_id = SecureRandom.uuid + ServiceProviderRequestProxy.find_or_create_by(uuid: request_id) do |sp_request| + sp_request.issuer = 'issuer' + sp_request.ial = nil + sp_request.aal = nil + sp_request.vtr = ['C2.P1'] + sp_request.url = 'http://issuer.gov' + sp_request.requested_attributes = %w[email] + sp_request.biometric_comparison_required = false + end + instance = StoreSpMetadataInSession.new(session: app_session, request_id: request_id) + + app_session_hash = { + issuer: 'issuer', + aal_level_requested: nil, + acr_values: nil, + piv_cac_requested: false, + phishing_resistant_requested: false, + ial: nil, + ial2: false, + ialmax: nil, + request_url: 'http://issuer.gov', + request_id: request_id, + requested_attributes: %w[email], + biometric_comparison_required: false, + vtr: ['C2.P1'], } instance.call From c5c198a23d18cd5a07a6804fde81b61420b485ca Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Tue, 6 Feb 2024 12:48:59 -0500 Subject: [PATCH 2/3] Swap `#empty?` with `#blank?` and add tests The commit that was un-reverted was problematic because of a `NoMethodError` when calling `empty?` on `nil` here: ```ruby validates :acr_values, presence: true, if: ->(form) { form.vtr.empty? } ``` This commit replaces `#empty?` with `#blank?` which will work on nil. Additionally the `#parse_vtr` method was modified to consistently return `nil` if either a `vtr` param is not provided or the `vtr` param is not enabled. Tests were added to cover this case. [skip changelog] --- app/forms/openid_connect_authorize_form.rb | 4 +-- .../openid_connect_authorize_form_spec.rb | 32 +++++++++++++++++-- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/app/forms/openid_connect_authorize_form.rb b/app/forms/openid_connect_authorize_form.rb index ff076f99513..bb2c5afb04d 100644 --- a/app/forms/openid_connect_authorize_form.rb +++ b/app/forms/openid_connect_authorize_form.rb @@ -38,7 +38,7 @@ class OpenidConnectAuthorizeForm RANDOM_VALUE_MINIMUM_LENGTH = 22 MINIMUM_REPROOF_VERIFIED_WITHIN_DAYS = 30 - validates :acr_values, presence: true, if: ->(form) { form.vtr.empty? } + validates :acr_values, presence: true, if: ->(form) { form.vtr.blank? } validates :client_id, presence: true validates :redirect_uri, presence: true validates :scope, presence: true @@ -180,7 +180,7 @@ def parse_to_values(param_value, possible_values) def parse_vtr(param_value) return if !IdentityConfig.store.use_vot_in_sp_requests - return [] if param_value.blank? + return if param_value.blank? JSON.parse(param_value) rescue JSON::ParserError diff --git a/spec/forms/openid_connect_authorize_form_spec.rb b/spec/forms/openid_connect_authorize_form_spec.rb index a3542a9b9ef..a019462b577 100644 --- a/spec/forms/openid_connect_authorize_form_spec.rb +++ b/spec/forms/openid_connect_authorize_form_spec.rb @@ -98,10 +98,36 @@ allow(IdentityConfig.store).to receive(:use_vot_in_sp_requests).and_return(false) end - let(:vtr) { ['C1.P1'].to_json } + context 'with only a vtr param' do + let(:vtr) { ['C1.P1'].to_json } + let(:acr_values) { nil } + + it 'is invalid' do + expect(form.vtr).to be_nil + expect(form.valid?).to eq(false) + expect(form.errors[:acr_values]). + to include(t('openid_connect.authorization.errors.no_valid_acr_values')) + expect(form.errors[:vtr]).to be_empty + end + end + + context 'with a vtr and acr_values param' do + let(:vtr) { ['C1.P1'].to_json } + let(:acr_values) { Saml::Idp::Constants::LOA3_AUTHN_CONTEXT_CLASSREF } + + it 'uses the acr_values param and ignores vtr' do + expect(form.vtr).to be_nil + expect(form.valid?).to eq(true) + end + end - it 'does not consume the VTR param' do - expect(form.vtr).to be_nil + context 'with only an acr_values param' do + let(:vtr) { nil } + let(:acr_values) { Saml::Idp::Constants::LOA3_AUTHN_CONTEXT_CLASSREF } + + it 'uses the acr_values param' do + expect(form.valid?).to eq(true) + end end end end From a3114511fab205162febb83be1189e2b6fae1610 Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Tue, 6 Feb 2024 13:08:16 -0500 Subject: [PATCH 3/3] new analytics args --- .../authorization_controller_spec.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/controllers/openid_connect/authorization_controller_spec.rb b/spec/controllers/openid_connect/authorization_controller_spec.rb index fb09c85af67..284024ef17a 100644 --- a/spec/controllers/openid_connect/authorization_controller_spec.rb +++ b/spec/controllers/openid_connect/authorization_controller_spec.rb @@ -111,7 +111,7 @@ code_challenge_present: false, service_provider_pkce: nil, scope: 'openid', - vtr: []) + vtr: nil) expect(@analytics).to receive(:track_event). with('OpenID Connect: authorization request handoff', success: true, @@ -259,7 +259,7 @@ code_challenge_present: false, service_provider_pkce: nil, scope: 'openid profile', - vtr: []) + vtr: nil) expect(@analytics).to receive(:track_event). with('OpenID Connect: authorization request handoff', success: true, @@ -498,7 +498,7 @@ code_challenge_present: false, service_provider_pkce: nil, scope: 'openid profile', - vtr: []) + vtr: nil) expect(@analytics).to receive(:track_event). with('OpenID Connect: authorization request handoff', success: true, @@ -582,7 +582,7 @@ code_challenge_present: false, service_provider_pkce: nil, scope: 'openid profile', - vtr: []) + vtr: nil) expect(@analytics).to receive(:track_event). with('OpenID Connect: authorization request handoff', success: true, @@ -668,7 +668,7 @@ code_challenge_present: false, service_provider_pkce: nil, scope: 'openid profile', - vtr: []) + vtr: nil) expect(@analytics).to receive(:track_event). with('OpenID Connect: authorization request handoff', success: true, @@ -871,7 +871,7 @@ code_challenge_present: false, service_provider_pkce: nil, scope: 'openid', - vtr: []) + vtr: nil) expect(@analytics).to_not receive(:track_event).with('sp redirect initiated') action @@ -905,7 +905,7 @@ code_challenge_present: false, service_provider_pkce: nil, scope: 'openid', - vtr: []) + vtr: nil) expect(@analytics).to_not receive(:track_event).with('SP redirect initiated') action @@ -1021,7 +1021,7 @@ code_challenge_present: false, service_provider_pkce: nil, scope: 'openid', - vtr: []) + vtr: nil) action sp_request_id = ServiceProviderRequestProxy.last.uuid @@ -1047,7 +1047,7 @@ request_url: request.original_url, requested_attributes: %w[], biometric_comparison_required: false, - vtr: [], + vtr: nil, ) end