diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f9c8c89bf71..85bb625cd77 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -428,6 +428,16 @@ def analytics_exception_info(exception) } end + def add_sp_cost(token) + Db::SpCost::AddSpCost.call( + current_sp, + sp_session_ial, + token, + transaction_id: nil, + user: current_user, + ) + end + def mobile? BrowserCache.parse(request.user_agent).mobile? end diff --git a/app/controllers/concerns/billable_event_trackable.rb b/app/controllers/concerns/billable_event_trackable.rb index 573bb965f16..1b6798c4ea1 100644 --- a/app/controllers/concerns/billable_event_trackable.rb +++ b/app/controllers/concerns/billable_event_trackable.rb @@ -6,6 +6,7 @@ def track_billing_events increment_sp_monthly_auths create_sp_return_log(billable: true) mark_current_session_billed + add_sp_cost(:authentication) end end diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index d409eac260a..03d582431f4 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -127,6 +127,7 @@ def process_locked_out_user def handle_valid_authentication sign_in(resource_name, resource) cache_active_profile(auth_params[:password]) + add_sp_cost(:digest) create_user_event(:sign_in_before_2fa) EmailAddress.update_last_sign_in_at_on_user_id_and_email( user_id: current_user.id, diff --git a/app/controllers/users/two_factor_authentication_controller.rb b/app/controllers/users/two_factor_authentication_controller.rb index 5da262882e3..678e60002d8 100644 --- a/app/controllers/users/two_factor_authentication_controller.rb +++ b/app/controllers/users/two_factor_authentication_controller.rb @@ -172,7 +172,7 @@ def handle_valid_otp_params(method, default = nil) end def handle_telephony_result(method:, default:) - track_events + track_events(method) if @telephony_result.success? redirect_to login_two_factor_url( otp_delivery_preference: method, @@ -189,8 +189,9 @@ def handle_telephony_result(method:, default:) end end - def track_events + def track_events(method) analytics.track_event(Analytics::TELEPHONY_OTP_SENT, @telephony_result.to_h) + add_sp_cost(method) if @telephony_result.success? end def exceeded_otp_send_limit? diff --git a/app/services/db/sp_cost/add_sp_cost.rb b/app/services/db/sp_cost/add_sp_cost.rb index 6c4ef015e18..f7f4fe606aa 100644 --- a/app/services/db/sp_cost/add_sp_cost.rb +++ b/app/services/db/sp_cost/add_sp_cost.rb @@ -9,9 +9,15 @@ class SpCostTypeError < StandardError; end acuant_back_image acuant_result acuant_selfie + authentication + digest lexis_nexis_resolution lexis_nexis_address gpo_letter + phone_otp + sms + user_added + voice ].freeze def self.call(service_provider, ial, token, transaction_id: nil, user: nil) diff --git a/app/services/identity_linker.rb b/app/services/identity_linker.rb index fa52e198fc4..254f0326ec8 100644 --- a/app/services/identity_linker.rb +++ b/app/services/identity_linker.rb @@ -45,6 +45,7 @@ def identity def find_or_create_identity_with_costing identity_record = identity_relation.first return identity_record if identity_record + Db::SpCost::AddSpCost.call(service_provider, @ial, :user_added) user.identities.create(service_provider: service_provider.issuer) end diff --git a/app/services/idv/send_phone_confirmation_otp.rb b/app/services/idv/send_phone_confirmation_otp.rb index abc9e8bc235..0a960e51f6a 100644 --- a/app/services/idv/send_phone_confirmation_otp.rb +++ b/app/services/idv/send_phone_confirmation_otp.rb @@ -73,6 +73,7 @@ def otp_sent_response def add_cost Db::ProofingCost::AddUserProofingCost.call(user.id, :phone_otp) + Db::SpCost::AddSpCost.call(idv_session.service_provider, 2, :phone_otp) end def extra_analytics_attributes diff --git a/spec/controllers/saml_idp_controller_spec.rb b/spec/controllers/saml_idp_controller_spec.rb index fe557c6cc4d..5ca2a1a6045 100644 --- a/spec/controllers/saml_idp_controller_spec.rb +++ b/spec/controllers/saml_idp_controller_spec.rb @@ -1753,6 +1753,8 @@ def stub_requested_attributes ial: 1) generate_saml_response(user) + + expect_sp_authentication_cost end end @@ -1787,6 +1789,8 @@ def stub_requested_attributes ial: 1) generate_saml_response(user) + + expect_sp_authentication_cost end end end @@ -1813,4 +1817,12 @@ def stub_requested_attributes expect(subject.external_saml_request?).to eq false end end + + def expect_sp_authentication_cost + sp_cost = SpCost.where( + issuer: 'http://localhost:3000', + cost_type: 'authentication', + ).first + expect(sp_cost).to be_present + end end diff --git a/spec/features/sp_cost_tracking_spec.rb b/spec/features/sp_cost_tracking_spec.rb index 410f5b4abc2..a239452995e 100644 --- a/spec/features/sp_cost_tracking_spec.rb +++ b/spec/features/sp_cost_tracking_spec.rb @@ -12,21 +12,32 @@ let(:email) { 'test@test.com' } let(:password) { Features::SessionHelper::VALID_PASSWORD } + it 'logs the correct costs for an ial1 user creation from sp with oidc' do + create_ial1_user_from_sp(email) + + expect_sp_cost_type(0, 1, 'sms') + expect_sp_cost_type(1, 1, 'user_added') + expect_sp_cost_type(2, 1, 'authentication') + end + it 'logs the correct costs for an ial2 user creation from sp with oidc' do create_ial2_user_from_sp(email) - expect_sp_cost_type(0, 2, 'acuant_front_image') - expect_sp_cost_type(1, 2, 'acuant_back_image') - expect_sp_cost_type(2, 2, 'acuant_result') + expect_sp_cost_type(0, 2, 'sms') + expect_sp_cost_type(1, 2, 'acuant_front_image') + expect_sp_cost_type(2, 2, 'acuant_back_image') + expect_sp_cost_type(3, 2, 'acuant_result') expect_sp_cost_type( - 3, 2, 'lexis_nexis_resolution', + 4, 2, 'lexis_nexis_resolution', transaction_id: Proofing::Mock::ResolutionMockClient::TRANSACTION_ID ) expect_sp_cost_type( - 4, 2, 'aamva', + 5, 2, 'aamva', transaction_id: Proofing::Mock::StateIdMockClient::TRANSACTION_ID ) - expect_sp_cost_type(5, 2, 'lexis_nexis_address') + expect_sp_cost_type(6, 2, 'lexis_nexis_address') + expect_sp_cost_type(7, 2, 'user_added') + expect_sp_cost_type(8, 2, 'authentication') end it 'logs the cost to the SP for reproofing' do @@ -65,6 +76,56 @@ end end + it 'logs the correct costs for an ial1 authentication' do + create_ial1_user_from_sp(email) + SpCost.delete_all + + # track costs without dealing with 'remember device' + Capybara.reset_session! + + visit_idp_from_sp_with_ial1(:oidc) + fill_in_credentials_and_submit(email, password) + fill_in_code_with_last_phone_otp + click_submit_default + + expect_sp_cost_type(0, 1, 'digest') + expect_sp_cost_type(1, 1, 'sms') + expect_sp_cost_type(2, 1, 'authentication') + end + + it 'logs the correct costs for an ial2 authentication' do + create_ial2_user_from_sp(email) + SpCost.delete_all + + # track costs without dealing with 'remember device' + Capybara.reset_session! + + visit_idp_from_sp_with_ial2(:oidc) + fill_in_credentials_and_submit(email, password) + fill_in_code_with_last_phone_otp + click_submit_default + + expect_sp_cost_type(0, 2, 'digest') + expect_sp_cost_type(1, 2, 'sms') + expect_sp_cost_type(2, 2, 'authentication') + end + + it 'logs the correct costs for a direct authentication' do + visit root_path + create_ial1_user_directly(email) + SpCost.delete_all + + # track costs without dealing with 'remember device' + Capybara.reset_session! + + visit root_path + fill_in_credentials_and_submit(email, password) + fill_in_code_with_last_phone_otp + click_submit_default + + expect_direct_cost_type(0, 'digest') + end + def expect_sp_cost_type(sp_cost_index, ial, token, transaction_id: nil) sp_cost = sp_costs(sp_cost_index) expect(sp_cost.ial).to eq(ial) @@ -74,6 +135,13 @@ def expect_sp_cost_type(sp_cost_index, ial, token, transaction_id: nil) expect(sp_cost.transaction_id).to(eq(transaction_id)) if transaction_id end + def expect_direct_cost_type(sp_cost_index, token) + sp_cost = sp_costs(sp_cost_index) + expect(sp_cost.issuer).to eq('') + expect(sp_cost.agency_id).to eq(0) + expect(sp_cost.cost_type).to eq(token) + end + def sp_costs(index) SpCost.order('id asc')[index] end