diff --git a/app/controllers/concerns/saml_idp_auth_concern.rb b/app/controllers/concerns/saml_idp_auth_concern.rb index 3e4a139b7a1..a4373d9ee05 100644 --- a/app/controllers/concerns/saml_idp_auth_concern.rb +++ b/app/controllers/concerns/saml_idp_auth_concern.rb @@ -55,9 +55,7 @@ def check_sp_active def validate_service_provider_and_authn_context return if result.success? - analytics.saml_auth( - **result.to_h.merge(request_signed: saml_request.signed?), - ) + capture_analytics render 'saml_idp/auth/error', status: :bad_request end diff --git a/app/controllers/openid_connect/authorization_controller.rb b/app/controllers/openid_connect/authorization_controller.rb index d057729a6ad..9c3b3483c15 100644 --- a/app/controllers/openid_connect/authorization_controller.rb +++ b/app/controllers/openid_connect/authorization_controller.rb @@ -156,6 +156,7 @@ def pre_validate_authorize_form **result.to_h.except(:redirect_uri, :code_digest).merge( user_fully_authenticated: user_fully_authenticated?, referer: request.referer, + vtr_param: params[:vtr], ), ) return if result.success? @@ -214,6 +215,8 @@ def track_events ial: event_ial_context.ial, billed_ial: event_ial_context.bill_for_ial_1_or_2, sign_in_flow: session[:sign_in_flow], + vtr: sp_session[:vtr], + acr_values: sp_session[:acr_values], ) track_billing_events end diff --git a/app/controllers/saml_idp_controller.rb b/app/controllers/saml_idp_controller.rb index e2d1ac49c3d..a09148c54af 100644 --- a/app/controllers/saml_idp_controller.rb +++ b/app/controllers/saml_idp_controller.rb @@ -136,6 +136,7 @@ def log_external_saml_auth_request analytics.saml_auth_request( requested_ial: requested_ial, + authn_context: saml_request&.requested_authn_contexts, requested_aal_authn_context: saml_request&.requested_aal_authn_context, requested_vtr_authn_context: saml_request&.requested_vtr_authn_context, force_authn: saml_request&.force_authn?, @@ -181,6 +182,8 @@ def track_events ial: resolved_authn_context_int_ial, billed_ial: ial_context.bill_for_ial_1_or_2, sign_in_flow: session[:sign_in_flow], + vtr: sp_session[:vtr], + acr_values: sp_session[:acr_values], ) track_billing_events end diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 9b5923a4da6..26d756c8ae9 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -3800,6 +3800,7 @@ def openid_connect_bearer_token(success:, ial:, client_id:, errors:, **extra) # @param [String] scope # @param [Array] acr_values # @param [Array] vtr + # @param [String, nil] vtr_param # @param [Boolean] unauthorized_scope # @param [Boolean] user_fully_authenticated def openid_connect_request_authorization( @@ -3807,6 +3808,7 @@ def openid_connect_request_authorization( scope:, acr_values:, vtr:, + vtr_param:, unauthorized_scope:, user_fully_authenticated:, **extra @@ -3817,6 +3819,7 @@ def openid_connect_request_authorization( scope: scope, acr_values: acr_values, vtr: vtr, + vtr_param: vtr_param, unauthorized_scope: unauthorized_scope, user_fully_authenticated: user_fully_authenticated, **extra, @@ -4459,6 +4462,12 @@ def rules_of_use_visit # @param [Array] authn_context # @param [String] authn_context_comparison # @param [String] service_provider + # @param [String] endpoint + # @param [Boolean] idv + # @param [Boolean] finish_profile + # @param [Integer] requested_ial + # @param [Boolean] request_signed + # @param [String] matching_cert_serial def saml_auth( success:, errors:, @@ -4466,6 +4475,12 @@ def saml_auth( authn_context:, authn_context_comparison:, service_provider:, + endpoint:, + idv:, + finish_profile:, + requested_ial:, + request_signed:, + matching_cert_serial:, **extra ) track_event( @@ -4476,29 +4491,47 @@ def saml_auth( authn_context: authn_context, authn_context_comparison: authn_context_comparison, service_provider: service_provider, + endpoint: endpoint, + idv: idv, + finish_profile: finish_profile, + requested_ial: requested_ial, + request_signed: request_signed, + matching_cert_serial: matching_cert_serial, **extra, ) end # @param [Integer] requested_ial - # @param [String,nil] requested_aal_authn_context - # @param [Boolean,nil] force_authn + # @param [Array] authn_context + # @param [String, nil] requested_aal_authn_context + # @param [String, nil] requested_vtr_authn_context + # @param [Boolean] force_authn + # @param [Boolean] final_auth_request # @param [String] service_provider + # @param [Boolean] user_fully_authenticated # An external request for SAML Authentication was received def saml_auth_request( requested_ial:, + authn_context:, requested_aal_authn_context:, + requested_vtr_authn_context:, force_authn:, + final_auth_request:, service_provider:, + user_fully_authenticated:, **extra ) track_event( 'SAML Auth Request', { requested_ial: requested_ial, + authn_context: authn_context, requested_aal_authn_context: requested_aal_authn_context, + requested_vtr_authn_context: requested_vtr_authn_context, force_authn: force_authn, + final_auth_request: final_auth_request, service_provider: service_provider, + user_fully_authenticated: user_fully_authenticated, **extra, }.compact, ) @@ -4624,12 +4657,16 @@ def sp_inactive_visit # @param [Integer] ial # @param [Integer] billed_ial # @param [String, nil] sign_in_flow - def sp_redirect_initiated(ial:, billed_ial:, sign_in_flow:, **extra) + # @param [String, nil] vtr + # @param [String, nil] acr_values + def sp_redirect_initiated(ial:, billed_ial:, sign_in_flow:, vtr:, acr_values:, **extra) track_event( 'SP redirect initiated', ial:, billed_ial:, sign_in_flow:, + vtr: vtr, + acr_values: acr_values, **extra, ) end diff --git a/spec/controllers/openid_connect/authorization_controller_spec.rb b/spec/controllers/openid_connect/authorization_controller_spec.rb index 36462739077..7099b2809c2 100644 --- a/spec/controllers/openid_connect/authorization_controller_spec.rb +++ b/spec/controllers/openid_connect/authorization_controller_spec.rb @@ -119,7 +119,8 @@ code_challenge_present: false, service_provider_pkce: nil, scope: 'openid', - vtr: nil) + vtr: nil, + vtr_param: '') expect(@analytics).to receive(:track_event). with('OpenID Connect: authorization request handoff', success: true, @@ -132,6 +133,8 @@ ial: 1, billed_ial: 1, sign_in_flow:, + acr_values: 'http://idmanagement.gov/ns/assurance/ial/1', + vtr: nil, ) IdentityLinker.new(user, service_provider).link_identity(ial: 1) @@ -168,7 +171,8 @@ code_challenge_present: false, service_provider_pkce: nil, scope: 'openid', - vtr: ['C1']) + vtr: ['C1'], + vtr_param: ['C1'].to_json) expect(@analytics).to receive(:track_event). with('OpenID Connect: authorization request handoff', success: true, @@ -181,6 +185,8 @@ ial: 1, billed_ial: 1, sign_in_flow:, + acr_values: '', + vtr: ['C1'], ) IdentityLinker.new(user, service_provider).link_identity(ial: 1) @@ -354,7 +360,8 @@ code_challenge_present: false, service_provider_pkce: nil, scope: 'openid profile', - vtr: nil) + vtr: nil, + vtr_param: '') expect(@analytics).to receive(:track_event). with('OpenID Connect: authorization request handoff', success: true, @@ -367,6 +374,8 @@ ial: 2, billed_ial: 2, sign_in_flow:, + acr_values: 'http://idmanagement.gov/ns/assurance/ial/2', + vtr: nil, ) IdentityLinker.new(user, service_provider).link_identity(ial: 2) @@ -728,7 +737,8 @@ code_challenge_present: false, service_provider_pkce: nil, scope: 'openid profile', - vtr: nil) + vtr: nil, + vtr_param: '') expect(@analytics).to receive(:track_event). with('OpenID Connect: authorization request handoff', success: true, @@ -741,6 +751,8 @@ ial: 0, billed_ial: 2, sign_in_flow:, + acr_values: 'http://idmanagement.gov/ns/assurance/ial/0', + vtr: nil, ) IdentityLinker.new(user, service_provider).link_identity(ial: 2) @@ -813,7 +825,8 @@ code_challenge_present: false, service_provider_pkce: nil, scope: 'openid profile', - vtr: nil) + vtr: nil, + vtr_param: '') expect(@analytics).to receive(:track_event). with('OpenID Connect: authorization request handoff', success: true, @@ -825,6 +838,8 @@ ial: 0, billed_ial: 1, sign_in_flow:, + acr_values: 'http://idmanagement.gov/ns/assurance/ial/0', + vtr: nil, ) IdentityLinker.new(user, service_provider).link_identity(ial: 1) @@ -899,7 +914,8 @@ code_challenge_present: false, service_provider_pkce: nil, scope: 'openid profile', - vtr: nil) + vtr: nil, + vtr_param: '') expect(@analytics).to receive(:track_event). with('OpenID Connect: authorization request handoff', success: true, @@ -911,6 +927,8 @@ ial: 0, billed_ial: 1, sign_in_flow:, + acr_values: 'http://idmanagement.gov/ns/assurance/ial/0', + vtr: nil, ) IdentityLinker.new(user, service_provider).link_identity(ial: 1) @@ -1102,7 +1120,8 @@ code_challenge_present: false, service_provider_pkce: nil, scope: 'openid', - vtr: nil) + vtr: nil, + vtr_param: '') expect(@analytics).to_not receive(:track_event).with('sp redirect initiated') action @@ -1136,7 +1155,8 @@ code_challenge_present: false, service_provider_pkce: nil, scope: 'openid', - vtr: nil) + vtr: nil, + vtr_param: '') expect(@analytics).to_not receive(:track_event).with('SP redirect initiated') action @@ -1254,6 +1274,7 @@ service_provider_pkce: nil, scope: 'openid', vtr: nil, + vtr_param: '', ) action diff --git a/spec/controllers/saml_idp_controller_spec.rb b/spec/controllers/saml_idp_controller_spec.rb index 59172daafc5..3f44b292a67 100644 --- a/spec/controllers/saml_idp_controller_spec.rb +++ b/spec/controllers/saml_idp_controller_spec.rb @@ -781,6 +781,7 @@ def name_id_version(format_urn) stub_analytics expect(@analytics).to receive(:track_event). with('SAML Auth Request', { + authn_context: [Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF], requested_ial: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, service_provider: sp1_issuer, force_authn: false, @@ -806,6 +807,8 @@ def name_id_version(format_urn) ial: Idp::Constants::IAL2, billed_ial: Idp::Constants::IAL2, sign_in_flow:, + acr_values: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, + vtr: nil, ) allow(controller).to receive(:identity_needs_verification?).and_return(false) @@ -926,6 +929,7 @@ def name_id_version(format_urn) stub_analytics expect(@analytics).to receive(:track_event). with('SAML Auth Request', { + authn_context: [Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF], requested_ial: 'ialmax', service_provider: sp1_issuer, force_authn: false, @@ -951,6 +955,8 @@ def name_id_version(format_urn) ial: 0, billed_ial: 2, sign_in_flow:, + acr_values: Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF, + vtr: nil, ) allow(controller).to receive(:identity_needs_verification?).and_return(false) @@ -991,6 +997,11 @@ def name_id_version(format_urn) authn_context_comparison: 'exact', service_provider: 'http://localhost:3000', request_signed: true, + requested_ial: 'http://idmanagement.gov/ns/assurance/loa/5', + endpoint: "/api/saml/auth#{path_year}", + idv: false, + finish_profile: false, + matching_cert_serial: saml_test_sp_cert_serial, } expect(@analytics).to have_received(:track_event). @@ -1162,6 +1173,7 @@ def name_id_version(format_urn) stub_analytics expect(@analytics).to receive(:track_event). with('SAML Auth Request', { + authn_context: request_authn_contexts, requested_ial: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, service_provider: 'http://localhost:3000', requested_aal_authn_context: Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF, @@ -1210,6 +1222,11 @@ def name_id_version(format_urn) authn_context_comparison: 'exact', service_provider: nil, request_signed: true, + requested_ial: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, + endpoint: "/api/saml/auth#{path_year}", + idv: false, + finish_profile: false, + matching_cert_serial: nil, } expect(@analytics).to have_received(:track_event). @@ -1254,6 +1271,11 @@ def name_id_version(format_urn) authn_context_comparison: 'exact', service_provider: nil, request_signed: true, + requested_ial: 'http://idmanagement.gov/ns/assurance/loa/5', + endpoint: "/api/saml/auth#{path_year}", + idv: false, + finish_profile: false, + matching_cert_serial: nil, } expect(@analytics).to have_received(:track_event). @@ -1516,18 +1538,6 @@ def name_id_version(format_urn) context 'cert element in SAML request is blank' do let(:user) { create(:user, :fully_registered) } let(:service_provider) { build(:service_provider, issuer: 'http://localhost:3000') } - let(:analytics_hash) do - { - success: false, - errors: { service_provider: ['We cannot detect a certificate in your request.'] }, - error_details: { service_provider: { blank_cert_element_req: true } }, - nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT, - authn_context: [Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF], - authn_context_comparison: 'exact', - service_provider: 'http://localhost:3000', - request_signed: true, - } - end before do stub_analytics @@ -1582,6 +1592,22 @@ def name_id_version(format_urn) it 'notes it in the analytics event' do generate_saml_response(user, saml_settings) + analytics_hash = { + success: false, + errors: { service_provider: ['We cannot detect a certificate in your request.'] }, + error_details: { service_provider: { blank_cert_element_req: true } }, + nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT, + authn_context: [Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF], + authn_context_comparison: 'exact', + service_provider: 'http://localhost:3000', + request_signed: true, + requested_ial: 'none', + endpoint: "/api/saml/auth#{path_year}", + idv: false, + finish_profile: false, + matching_cert_serial: nil, + } + expect(@analytics).to have_received(:track_event). with('SAML Auth', analytics_hash) end @@ -1730,6 +1756,11 @@ def name_id_version(format_urn) authn_context_comparison: 'exact', service_provider: 'http://localhost:3000', request_signed: true, + requested_ial: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, + endpoint: "/api/saml/auth#{path_year}", + idv: false, + finish_profile: false, + matching_cert_serial: saml_test_sp_cert_serial, } expect(@analytics).to have_received(:track_event). @@ -1880,6 +1911,7 @@ def name_id_version(format_urn) stub_analytics expect(@analytics).to receive(:track_event). with('SAML Auth Request', { + authn_context: request_authn_contexts, requested_ial: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, service_provider: 'http://localhost:3000', requested_aal_authn_context: Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF, @@ -2326,6 +2358,10 @@ def name_id_version(format_urn) expect(@analytics).to receive(:track_event). with('SAML Auth Request', { + authn_context: [ + Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, + ], requested_ial: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, service_provider: 'http://localhost:3000', requested_aal_authn_context: Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF, @@ -2375,6 +2411,7 @@ def stub_requested_attributes expect(@analytics).to receive(:track_event). with('SAML Auth Request', { + authn_context: request_authn_contexts, requested_ial: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, service_provider: 'http://localhost:3000', requested_aal_authn_context: Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF, @@ -2387,6 +2424,11 @@ def stub_requested_attributes ial: 1, billed_ial: 1, sign_in_flow: :sign_in, + acr_values: [ + Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, + ].join(' '), + vtr: nil, ) generate_saml_response(user) @@ -2419,6 +2461,7 @@ def stub_requested_attributes expect(@analytics).to receive(:track_event). with('SAML Auth Request', { + authn_context: request_authn_contexts, requested_ial: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, service_provider: 'http://localhost:3000', requested_aal_authn_context: Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF, @@ -2431,6 +2474,11 @@ def stub_requested_attributes ial: 1, billed_ial: 1, sign_in_flow: :sign_in, + acr_values: [ + Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, + ].join(' '), + vtr: nil, ) generate_saml_response(user) diff --git a/spec/features/saml/saml_spec.rb b/spec/features/saml/saml_spec.rb index c9d75609158..9a38081eacd 100644 --- a/spec/features/saml/saml_spec.rb +++ b/spec/features/saml/saml_spec.rb @@ -503,7 +503,8 @@ click_submit_default_twice expect(fake_analytics.events['SAML Auth Request']).to eq( - [{ requested_ial: 'http://idmanagement.gov/ns/assurance/ial/1', + [{ authn_context: request_authn_contexts, + requested_ial: 'http://idmanagement.gov/ns/assurance/ial/1', service_provider: 'http://localhost:3000', requested_aal_authn_context: Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF, force_authn: false, @@ -537,11 +538,22 @@ click_agree_and_continue click_submit_default_twice + expected_analytics_authn_context = [ + Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, + "#{Saml::Idp::Constants::REQUESTED_ATTRIBUTES_CLASSREF}first_name:last_name email, ssn", + "#{Saml::Idp::Constants::REQUESTED_ATTRIBUTES_CLASSREF}phone", + ] + expect(fake_analytics.events['SAML Auth Request']).to eq( - [{ requested_ial: 'http://idmanagement.gov/ns/assurance/ial/2', - service_provider: 'saml_sp_ial2', - force_authn: false, - user_fully_authenticated: false }], + [ + { + authn_context: expected_analytics_authn_context, + requested_ial: 'http://idmanagement.gov/ns/assurance/ial/2', + service_provider: 'saml_sp_ial2', + force_authn: false, + user_fully_authenticated: false, + }, + ], ) expect(fake_analytics.events['SAML Auth'].count).to eq 2 @@ -564,7 +576,8 @@ click_submit_default_twice expect(fake_analytics.events['SAML Auth Request']).to eq( - [{ requested_ial: 'http://idmanagement.gov/ns/assurance/ial/1', + [{ authn_context: request_authn_contexts, + requested_ial: 'http://idmanagement.gov/ns/assurance/ial/1', service_provider: 'http://localhost:3000', requested_aal_authn_context: Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF, force_authn: false, diff --git a/spec/features/users/sign_up_spec.rb b/spec/features/users/sign_up_spec.rb index a5810561a03..5631fb54df1 100644 --- a/spec/features/users/sign_up_spec.rb +++ b/spec/features/users/sign_up_spec.rb @@ -486,6 +486,8 @@ def clipboard_text ial: 1, billed_ial: 1, sign_in_flow: 'create_account', + acr_values: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, + vtr: nil, ) end diff --git a/spec/support/shared_examples/sign_in.rb b/spec/support/shared_examples/sign_in.rb index d19d0e5984e..16548dcb977 100644 --- a/spec/support/shared_examples/sign_in.rb +++ b/spec/support/shared_examples/sign_in.rb @@ -49,6 +49,8 @@ ial: 1, billed_ial: 1, sign_in_flow: 'sign_in', + acr_values: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, + vtr: nil, ) end end