diff --git a/app/controllers/concerns/saml_idp_auth_concern.rb b/app/controllers/concerns/saml_idp_auth_concern.rb index adf162a5af9..e37d60f8c18 100644 --- a/app/controllers/concerns/saml_idp_auth_concern.rb +++ b/app/controllers/concerns/saml_idp_auth_concern.rb @@ -123,6 +123,7 @@ def ial_context ial: requested_ial_authn_context, service_provider: saml_request_service_provider, authn_context_comparison: saml_request.requested_authn_context_comparison, + user: current_user, ) end diff --git a/app/controllers/openid_connect/authorization_controller.rb b/app/controllers/openid_connect/authorization_controller.rb index adcc1f1836d..0040e9683da 100644 --- a/app/controllers/openid_connect/authorization_controller.rb +++ b/app/controllers/openid_connect/authorization_controller.rb @@ -147,7 +147,17 @@ def pii_requested_but_locked? end def track_events - analytics.track_event(Analytics::SP_REDIRECT_INITIATED, ial: sp_session_ial) + event_ial_context = IalContext.new( + ial: @authorize_form.ial, + service_provider: @authorize_form.service_provider, + user: current_user, + ) + + analytics.track_event( + Analytics::SP_REDIRECT_INITIATED, + ial: event_ial_context.ial, + billed_ial: event_ial_context.bill_for_ial_1_or_2, + ) track_billing_events end end diff --git a/app/controllers/saml_idp_controller.rb b/app/controllers/saml_idp_controller.rb index d5c5cd090a5..7c5dc0e3470 100644 --- a/app/controllers/saml_idp_controller.rb +++ b/app/controllers/saml_idp_controller.rb @@ -162,7 +162,11 @@ def render_template_for(message, action_url, type) end def track_events - analytics.track_event(Analytics::SP_REDIRECT_INITIATED, ial: ial_context.ial) + analytics.track_event( + Analytics::SP_REDIRECT_INITIATED, + ial: ial_context.ial, + billed_ial: ial_context.bill_for_ial_1_or_2, + ) track_billing_events end end diff --git a/app/forms/openid_connect_authorize_form.rb b/app/forms/openid_connect_authorize_form.rb index f4aa6076e90..06af6c3f1a0 100644 --- a/app/forms/openid_connect_authorize_form.rb +++ b/app/forms/openid_connect_authorize_form.rb @@ -99,6 +99,10 @@ def ial_context @ial_context ||= IalContext.new(ial: ial, service_provider: service_provider) end + def ial + Saml::Idp::Constants::AUTHN_CONTEXT_CLASSREF_TO_IAL[ial_values.sort.max] + end + def_delegators :ial_context, :ial2_or_greater?, :ial2_requested?, @@ -195,10 +199,6 @@ def validate_verified_within_duration false end - def ial - Saml::Idp::Constants::AUTHN_CONTEXT_CLASSREF_TO_IAL[ial_values.sort.max] - end - def extra_analytics_attributes { client_id: client_id, diff --git a/spec/controllers/openid_connect/authorization_controller_spec.rb b/spec/controllers/openid_connect/authorization_controller_spec.rb index b284baa8d11..8c20d909644 100644 --- a/spec/controllers/openid_connect/authorization_controller_spec.rb +++ b/spec/controllers/openid_connect/authorization_controller_spec.rb @@ -59,8 +59,11 @@ acr_values: 'http://idmanagement.gov/ns/assurance/ial/1', scope: 'openid') expect(@analytics).to receive(:track_event). - with(Analytics::SP_REDIRECT_INITIATED, - ial: 1) + with( + Analytics::SP_REDIRECT_INITIATED, + ial: 1, + billed_ial: 1, + ) IdentityLinker.new(user, service_provider).link_identity(ial: 1) user.identities.last.update!(verified_attributes: %w[given_name family_name birthdate]) @@ -115,8 +118,11 @@ acr_values: 'http://idmanagement.gov/ns/assurance/ial/2', scope: 'openid profile') expect(@analytics).to receive(:track_event). - with(Analytics::SP_REDIRECT_INITIATED, - ial: 2) + with( + Analytics::SP_REDIRECT_INITIATED, + ial: 2, + billed_ial: 2, + ) IdentityLinker.new(user, service_provider).link_identity(ial: 2) user.identities.last.update!( @@ -167,6 +173,151 @@ end end + context 'with ialmax requested' do + before { params[:acr_values] = Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF } + + context 'account is already verified' do + let(:user) do + create( + :profile, :active, :verified, proofing_components: { liveness_check: true } + ).user + end + + it 'redirects to the redirect_uri immediately when pii is unlocked' do + IdentityLinker.new(user, service_provider).link_identity(ial: 3) + user.identities.last.update!( + verified_attributes: %w[given_name family_name birthdate verified_at], + ) + allow(controller).to receive(:pii_requested_but_locked?).and_return(false) + action + + expect(response).to redirect_to(/^#{params[:redirect_uri]}/) + end + + it 'redirects to the password capture url when pii is locked' do + IdentityLinker.new(user, service_provider).link_identity(ial: 3) + user.identities.last.update!( + verified_attributes: %w[given_name family_name birthdate verified_at], + ) + allow(controller).to receive(:pii_requested_but_locked?).and_return(true) + action + + expect(response).to redirect_to(capture_password_url) + end + + it 'tracks IAL2 authentication event' do + stub_analytics + expect(@analytics).to receive(:track_event). + with(Analytics::OPENID_CONNECT_REQUEST_AUTHORIZATION, + success: true, + client_id: client_id, + errors: {}, + unauthorized_scope: false, + user_fully_authenticated: true, + acr_values: 'http://idmanagement.gov/ns/assurance/ial/0', + scope: 'openid profile') + expect(@analytics).to receive(:track_event). + with( + Analytics::SP_REDIRECT_INITIATED, + ial: 0, + billed_ial: 2, + ) + + IdentityLinker.new(user, service_provider).link_identity(ial: 2) + user.identities.last.update!( + verified_attributes: %w[given_name family_name birthdate verified_at], + ) + allow(controller).to receive(:pii_requested_but_locked?).and_return(false) + action + + sp_return_log = SpReturnLog.find_by(issuer: client_id) + expect(sp_return_log.ial).to eq(2) + end + end + + context 'account is not already verified' do + it 'redirects to the redirect_uri immediately without proofing' do + IdentityLinker.new(user, service_provider).link_identity(ial: 1) + user.identities.last.update!( + verified_attributes: %w[given_name family_name birthdate verified_at], + ) + + action + expect(response).to redirect_to(/^#{params[:redirect_uri]}/) + end + + it 'tracks IAL1 authentication event' do + stub_analytics + expect(@analytics).to receive(:track_event). + with(Analytics::OPENID_CONNECT_REQUEST_AUTHORIZATION, + success: true, + client_id: client_id, + errors: {}, + unauthorized_scope: false, + user_fully_authenticated: true, + acr_values: 'http://idmanagement.gov/ns/assurance/ial/0', + scope: 'openid profile') + expect(@analytics).to receive(:track_event). + with( + Analytics::SP_REDIRECT_INITIATED, + ial: 0, + billed_ial: 1, + ) + + IdentityLinker.new(user, service_provider).link_identity(ial: 1) + user.identities.last.update!( + verified_attributes: %w[given_name family_name birthdate verified_at], + ) + action + + sp_return_log = SpReturnLog.find_by(issuer: client_id) + expect(sp_return_log.ial).to eq(1) + end + end + + context 'profile is reset' do + let(:user) { create(:profile, :password_reset).user } + + it 'redirects to the redirect_uri immediately without proofing' do + IdentityLinker.new(user, service_provider).link_identity(ial: 1) + user.identities.last.update!( + verified_attributes: %w[given_name family_name birthdate verified_at], + ) + + action + expect(response).to redirect_to(/^#{params[:redirect_uri]}/) + end + + it 'tracks IAL1 authentication event' do + stub_analytics + expect(@analytics).to receive(:track_event). + with(Analytics::OPENID_CONNECT_REQUEST_AUTHORIZATION, + success: true, + client_id: client_id, + errors: {}, + unauthorized_scope: false, + user_fully_authenticated: true, + acr_values: 'http://idmanagement.gov/ns/assurance/ial/0', + scope: 'openid profile') + expect(@analytics).to receive(:track_event). + with( + Analytics::SP_REDIRECT_INITIATED, + ial: 0, + billed_ial: 1, + ) + + IdentityLinker.new(user, service_provider).link_identity(ial: 1) + user.identities.last.update!( + verified_attributes: %w[given_name family_name birthdate verified_at], + ) + action + + sp_return_log = SpReturnLog.find_by(issuer: client_id) + expect(sp_return_log.ial).to eq(1) + end + end + end + context 'user has not approved this application' do it 'redirects verify shared attributes page' do action diff --git a/spec/controllers/saml_idp_controller_spec.rb b/spec/controllers/saml_idp_controller_spec.rb index 096413627e1..2f59340c5f9 100644 --- a/spec/controllers/saml_idp_controller_spec.rb +++ b/spec/controllers/saml_idp_controller_spec.rb @@ -553,8 +553,11 @@ def name_id_version(format_urn) idv: false, finish_profile: false) expect(@analytics).to receive(:track_event). - with(Analytics::SP_REDIRECT_INITIATED, - ial: ial) + with( + Analytics::SP_REDIRECT_INITIATED, + ial: ial, + billed_ial: [ial, 2].min, + ) allow(controller).to receive(:identity_needs_verification?).and_return(false) saml_get_auth(ial2_settings) @@ -707,8 +710,11 @@ def name_id_version(format_urn) idv: false, finish_profile: false) expect(@analytics).to receive(:track_event). - with(Analytics::SP_REDIRECT_INITIATED, - ial: 0) + with( + Analytics::SP_REDIRECT_INITIATED, + ial: 0, + billed_ial: 2, + ) allow(controller).to receive(:identity_needs_verification?).and_return(false) saml_get_auth(ialmax_settings) @@ -1869,8 +1875,11 @@ def stub_requested_attributes service_provider: 'http://localhost:3000') expect(@analytics).to receive(:track_event).with(Analytics::SAML_AUTH, analytics_hash) expect(@analytics).to receive(:track_event). - with(Analytics::SP_REDIRECT_INITIATED, - ial: 1) + with( + Analytics::SP_REDIRECT_INITIATED, + ial: 1, + billed_ial: 1, + ) generate_saml_response(user) end @@ -1903,8 +1912,11 @@ def stub_requested_attributes service_provider: 'http://localhost:3000') expect(@analytics).to receive(:track_event).with(Analytics::SAML_AUTH, analytics_hash) expect(@analytics).to receive(:track_event). - with(Analytics::SP_REDIRECT_INITIATED, - ial: 1) + with( + Analytics::SP_REDIRECT_INITIATED, + ial: 1, + billed_ial: 1, + ) generate_saml_response(user) end diff --git a/spec/forms/openid_connect_authorize_form_spec.rb b/spec/forms/openid_connect_authorize_form_spec.rb index 1c3150c34aa..ba955229c9a 100644 --- a/spec/forms/openid_connect_authorize_form_spec.rb +++ b/spec/forms/openid_connect_authorize_form_spec.rb @@ -312,6 +312,48 @@ end end + describe '#ial' do + 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 + + 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 + + context 'when IALMAX passed' do + let(:acr_values) { Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF } + + it 'returns 0' do + expect(form.ial).to eq(0) + end + end + + context 'when LOA1 passed' do + let(:acr_values) { Saml::Idp::Constants::LOA1_AUTHN_CONTEXT_CLASSREF } + + 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 + describe '#verified_within' do context 'without a verified_within' do let(:verified_within) { nil }