From d246b9d40040e63fe2e5e4477749f0dad2fbaee7 Mon Sep 17 00:00:00 2001 From: Lauren George Date: Wed, 2 Oct 2024 16:54:59 -0400 Subject: [PATCH 1/4] Use a list to log resolved ACRs in events Why === - Deter using the resolved ACRs directly in reporting. By nature, the resolved list of ACRs (or Vectors of Trust) is volatile and subject to change as service features are added over time. Instead, most users should use the resolved attribute flags (e.g., `facial_match` or `enhanced_ipp`) for event filtering. In effect, the purpose of the sp_request.component_values field is to support investigating unexpected behavior. - Eliminate unexpected "reformatting" or "truncation" of resolved ACR names. While truncating these strings makes composing queries simpler, it introduces complexity and confusion when triaging and debugging requests. Originally, the purpose was to be able to do simple filters like `properties.sp_request.component_values.Pb`, but with the deprecation of the VoT interfaces, the filter becomes 'sp_request.component_values.`urn:acr.login.gov:verified`', which is much less legible than: `'urn:acr.login.gov:verified' IN sp_request.component_values` - Why not store component values as a string? Basically, VoT/vtr style components and ACR style components "stringify" differently, which would make query composition even more complex, instead of less. How === - Instead of using a hash to map the resolved ACRs to a boolean, an array of strings containing the ACR name will be stored in `sp_request.component_values`. - No other changes have been made to structure of `sp_request`. changelog: Internal, Analytics, Streamlining inclusion of resolved ACR names resolves: https://gitlab.login.gov/lg-people/Melba/backlog-fy24/-/issues/112 --- app/services/analytics.rb | 4 +- spec/services/analytics_spec.rb | 76 +++++++++++++++++++++++++-------- 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/app/services/analytics.rb b/app/services/analytics.rb index 378a89712e7..1b2f24fb8ec 100644 --- a/app/services/analytics.rb +++ b/app/services/analytics.rb @@ -130,9 +130,7 @@ def sp_request_attributes return if resolved_result.nil? attributes = resolved_result.to_h - attributes[:component_values] = resolved_result.component_values.map do |v| - [v.name.sub('http://idmanagement.gov/ns/assurance/', ''), true] - end.to_h + attributes[:component_values] = resolved_result.component_names attributes.reject! { |_key, value| value == false } if differentiator.present? diff --git a/spec/services/analytics_spec.rb b/spec/services/analytics_spec.rb index 76b797e27d4..24a1eab0e7b 100644 --- a/spec/services/analytics_spec.rb +++ b/spec/services/analytics_spec.rb @@ -221,7 +221,7 @@ { sp_request: { aal2: true, - component_values: { 'C1' => true, 'C2' => true, 'P1' => true }, + component_values: ['C1', 'C2', 'P1'], identity_proofing: true, component_separator: '.', }, @@ -238,15 +238,7 @@ context 'phishing resistant and requiring facial match comparison' do let(:session) { { sp: { vtr: ['Ca.Pb'] } } } - let(:component_values) do - { - 'C1' => true, - 'C2' => true, - 'Ca' => true, - 'P1' => true, - 'Pb' => true, - } - end + let(:component_values) { ['C1', 'C2', 'Ca', 'P1', 'Pb'] } let(:expected_attributes) do { @@ -277,7 +269,7 @@ let(:expected_attributes) do { sp_request: { - component_values: { 'ial/1' => true }, + component_values: [Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF], component_separator: ' ', }, } @@ -297,7 +289,7 @@ { sp_request: { aal2: true, - component_values: { 'ial/2' => true }, + component_values: [Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF], identity_proofing: true, component_separator: ' ', }, @@ -322,7 +314,7 @@ aal2: true, facial_match: true, two_pieces_of_fair_evidence: true, - component_values: { 'ial/2?bio=required' => true }, + component_values: [Saml::Idp::Constants::IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF], identity_proofing: true, component_separator: ' ', }, @@ -336,14 +328,64 @@ analytics.track_event('Trackable Event') end end + context 'and requests facial match preferred' do + let(:session) do + { sp: { acr_values: Saml::Idp::Constants::IAL_VERIFIED_FACIAL_MATCH_PREFERRED_ACR } } + end + context 'when the verified user has proofed with facial match' do + let(:current_user) { build_stubbed(:user, :proofed_with_selfie) } + let(:expected_attributes) do + { + sp_request: { + aal2: true, + facial_match: true, + two_pieces_of_fair_evidence: true, + component_values: [Saml::Idp::Constants::IAL_VERIFIED_FACIAL_MATCH_PREFERRED_ACR], + identity_proofing: true, + component_separator: ' ', + }, + } + end + + it 'successfully tracks the sp_request' do + expect(ahoy).to receive(:track). + with('Trackable Event', hash_including(expected_attributes)) + + analytics.track_event('Trackable Event') + end + end + + context 'when the verified user has not proofed with facial match' do + let(:current_user) { build(:user) } + let(:expected_attributes) do + { + sp_request: { + aal2: true, + facial_match: false, + two_pieces_of_fair_evidence: false, + component_values: [Saml::Idp::Constants::IAL_VERIFIED_FACIAL_MATCH_PREFERRED_ACR], + identity_proofing: true, + component_separator: ' ', + }, + } + end + + it 'can successfully track the sp_request' do + expect(ahoy).to receive(:track). + with('Trackable Event', hash_including(expected_attributes)) + + analytics.track_event('Trackable Event') + end + end + end context 'acr_values IALMAX' do let(:session) { { sp: { acr_values: Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF } } } let(:expected_attributes) do { sp_request: { aal2: true, - component_values: { 'ial/0' => true }, + component_values: [Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF], component_separator: ' ', ialmax: true, }, @@ -366,7 +408,7 @@ let(:expected_attributes) do { sp_request: { - component_values: { 'ial/1' => true }, + component_values: [Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF], component_separator: ' ', }, } @@ -393,7 +435,7 @@ let(:expected_attributes) do { sp_request: { - component_values: { 'ial/1' => true }, + component_values: [Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF], component_separator: ' ', }, } @@ -421,7 +463,7 @@ let(:expected_attributes) do { sp_request: { - component_values: { 'ial/1' => true }, + component_values: [Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF], component_separator: ' ', app_differentiator: 'NY', }, From 498db9bd9c5d2081e4a4c178467a0180ea1ff794 Mon Sep 17 00:00:00 2001 From: Lauren George Date: Thu, 3 Oct 2024 10:22:17 -0400 Subject: [PATCH 2/4] Use a non-facial-matched proofed user in spec --- spec/services/analytics_spec.rb | 207 ++++++++++++++++++-------------- 1 file changed, 117 insertions(+), 90 deletions(-) diff --git a/spec/services/analytics_spec.rb b/spec/services/analytics_spec.rb index 24a1eab0e7b..162aede3f5f 100644 --- a/spec/services/analytics_spec.rb +++ b/spec/services/analytics_spec.rb @@ -183,6 +183,7 @@ context 'when should_log says not to' do let(:should_log) { /some other event/ } + it 'does not include ab_test in logged event' do expect(ahoy).to receive(:track).with( 'Trackable Event', @@ -263,19 +264,22 @@ end end - context 'with SP request acr_values saved in the session' do - context 'IAL1' do - let(:session) { { sp: { acr_values: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF } } } - let(:expected_attributes) do - { - sp_request: { - component_values: [Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF], - component_separator: ' ', - }, - } - end + context 'when acr_values are saved in the session' do + let(:acr_values) { [] } + let(:sp_request) { {} } + let(:session) { { sp: { acr_values: acr_values.join(' ') } } } + let(:expected_attributes) do + { + sp_request: { + component_separator: ' ', + component_values: acr_values, + **sp_request, + }, + } + end - it 'includes the sp_request' do + shared_examples 'commit trackable event' do + it 'then includes sp_request in the event' do expect(ahoy).to receive(:track). with('Trackable Event', hash_including(expected_attributes)) @@ -283,102 +287,125 @@ end end - context 'IAL2' do - let(:session) { { sp: { acr_values: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF } } } - let(:expected_attributes) do - { - sp_request: { - aal2: true, - component_values: [Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF], - identity_proofing: true, - component_separator: ' ', - }, - } - end + shared_examples 'for all user scenarios' do |acr_values_list| + let(:acr_values) { acr_values_list } - it 'includes the sp_request' do - expect(ahoy).to receive(:track). - with('Trackable Event', hash_including(expected_attributes)) + context "using #{acr_values_list}" do + context 'when the user has not been identity verified' do + let(:current_user) { build(:user, :fully_registered) } - analytics.track_event('Trackable Event') + include_examples 'commit trackable event' + end + + context 'when the identity verified user has not proofed with facial match' do + let(:current_user) { build(:user, :proofed) } + + include_examples 'commit trackable event' + end + + context 'when the identity verified user has proofed with facial match' do + let(:current_user) { build(:user, :proofed_with_selfie) } + + include_examples 'commit trackable event' + end end end - context 'IAL2 with facial match' do - let(:session) do - { sp: { acr_values: Saml::Idp::Constants::IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF } } - end - let(:expected_attributes) do + context 'and does not require any identity proofing' do + include_examples 'for all user scenarios', + [Saml::Idp::Constants::IAL_AUTH_ONLY_ACR] + include_examples 'for all user scenarios', + [Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF] + include_examples 'for all user scenarios', + [Saml::Idp::Constants::LOA1_AUTHN_CONTEXT_CLASSREF] + end + + context 'and selects any variant of identity proofing' do + let(:sp_request) do { - sp_request: { - aal2: true, - facial_match: true, - two_pieces_of_fair_evidence: true, - component_values: [Saml::Idp::Constants::IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF], - identity_proofing: true, - component_separator: ' ', - }, + aal2: true, + identity_proofing: true, } end - it 'includes the sp_request' do - expect(ahoy).to receive(:track). - with('Trackable Event', hash_including(expected_attributes)) - - analytics.track_event('Trackable Event') - end + include_examples 'for all user scenarios', + [Saml::Idp::Constants::IAL_VERIFIED_ACR] + include_examples 'for all user scenarios', + [Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF] + include_examples 'for all user scenarios', + [Saml::Idp::Constants::LOA3_AUTHN_CONTEXT_CLASSREF] end - context 'and requests facial match preferred' do - let(:session) do - { sp: { acr_values: Saml::Idp::Constants::IAL_VERIFIED_FACIAL_MATCH_PREFERRED_ACR } } - end - - context 'when the verified user has proofed with facial match' do - let(:current_user) { build_stubbed(:user, :proofed_with_selfie) } - let(:expected_attributes) do - { - sp_request: { - aal2: true, - facial_match: true, - two_pieces_of_fair_evidence: true, - component_values: [Saml::Idp::Constants::IAL_VERIFIED_FACIAL_MATCH_PREFERRED_ACR], - identity_proofing: true, - component_separator: ' ', - }, - } - end - - it 'successfully tracks the sp_request' do - expect(ahoy).to receive(:track). - with('Trackable Event', hash_including(expected_attributes)) - analytics.track_event('Trackable Event') - end + context 'and selects required facial match identity proofing' do + let(:sp_request) do + { + aal2: true, + facial_match: true, + two_pieces_of_fair_evidence: true, + identity_proofing: true, + } end - context 'when the verified user has not proofed with facial match' do - let(:current_user) { build(:user) } - let(:expected_attributes) do - { - sp_request: { - aal2: true, - facial_match: false, - two_pieces_of_fair_evidence: false, - component_values: [Saml::Idp::Constants::IAL_VERIFIED_FACIAL_MATCH_PREFERRED_ACR], - identity_proofing: true, - component_separator: ' ', - }, - } - end + include_examples 'for all user scenarios', + [Saml::Idp::Constants::IAL_VERIFIED_FACIAL_MATCH_REQUIRED_ACR] - it 'can successfully track the sp_request' do - expect(ahoy).to receive(:track). - with('Trackable Event', hash_including(expected_attributes)) + include_examples 'for all user scenarios', + [Saml::Idp::Constants::IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF] + end - analytics.track_event('Trackable Event') + context 'and selects optional facial match identity proofing' do + shared_examples 'with user scenarios' do |acr_values_list| + context "using #{acr_values_list}" do + let(:acr_values) { acr_values_list } + + context 'when the user has not been identity verified' do + let(:sp_request) do + { + aal2: true, + facial_match: true, + two_pieces_of_fair_evidence: true, + identity_proofing: true, + } + end + let(:current_user) { build(:user, :fully_registered) } + + include_examples 'commit trackable event' + end + + context 'when the identity verified user has not proofed with facial match' do + let(:current_user) { build(:user, :proofed) } + let(:sp_request) do + { + aal2: true, + identity_proofing: true, + } + end + + include_examples 'commit trackable event' + end + + context 'when the identity verified user has proofed with facial match' do + let(:sp_request) do + { + aal2: true, + facial_match: true, + two_pieces_of_fair_evidence: true, + identity_proofing: true, + } + end + let(:current_user) { build(:user, :proofed_with_selfie) } + + include_examples 'commit trackable event' + end end end + + include_examples 'with user scenarios', + [Saml::Idp::Constants::IAL_VERIFIED_FACIAL_MATCH_PREFERRED_ACR] + include_examples 'with user scenarios', + [Saml::Idp::Constants::IAL2_BIO_PREFERRED_AUTHN_CONTEXT_CLASSREF] end + context 'acr_values IALMAX' do let(:session) { { sp: { acr_values: Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF } } } let(:expected_attributes) do From 0a1f5e74f37aadad51e0a3f80538b20c5f787bc4 Mon Sep 17 00:00:00 2001 From: Lauren George Date: Thu, 3 Oct 2024 22:32:48 -0400 Subject: [PATCH 3/4] do not build the authn context when vtr and acr_values are both blank --- app/services/analytics.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/services/analytics.rb b/app/services/analytics.rb index 1b2f24fb8ec..0eb6487ddcb 100644 --- a/app/services/analytics.rb +++ b/app/services/analytics.rb @@ -155,7 +155,9 @@ def differentiator end def resolved_authn_context_result - return nil if sp.nil? || session[:sp].blank? + return nil if sp.blank? || + session[:sp].blank? || + session[:sp][:vtr].blank? && session[:sp][:acr_values].blank? return @resolved_authn_context_result if defined?(@resolved_authn_context_result) service_provider = ServiceProvider.find_by(issuer: sp) From 0080ca49214db3e6b6cf7f034ec3bcd250b3b590 Mon Sep 17 00:00:00 2001 From: Lauren George Date: Mon, 7 Oct 2024 19:12:07 -0400 Subject: [PATCH 4/4] Revert changes to sp_request.component_values and add sp_request.component_names, fix up tests --- app/services/analytics.rb | 7 +- lib/saml_idp_constants.rb | 13 ++- spec/services/analytics_spec.rb | 168 +++++++++++++------------------- 3 files changed, 82 insertions(+), 106 deletions(-) diff --git a/app/services/analytics.rb b/app/services/analytics.rb index 0eb6487ddcb..d03005fcddd 100644 --- a/app/services/analytics.rb +++ b/app/services/analytics.rb @@ -130,7 +130,10 @@ def sp_request_attributes return if resolved_result.nil? attributes = resolved_result.to_h - attributes[:component_values] = resolved_result.component_names + attributes[:component_values] = resolved_result.component_values.map do |v| + [v.name.sub("#{Saml::Idp::Constants::LEGACY_ACR_PREFIX}/", ''), true] + end.to_h + attributes[:component_names] = resolved_result.component_names attributes.reject! { |_key, value| value == false } if differentiator.present? @@ -157,7 +160,7 @@ def differentiator def resolved_authn_context_result return nil if sp.blank? || session[:sp].blank? || - session[:sp][:vtr].blank? && session[:sp][:acr_values].blank? + (session[:sp][:vtr].blank? && session[:sp][:acr_values].blank?) return @resolved_authn_context_result if defined?(@resolved_authn_context_result) service_provider = ServiceProvider.find_by(issuer: sp) diff --git a/lib/saml_idp_constants.rb b/lib/saml_idp_constants.rb index 67a738998a8..d02892bec02 100644 --- a/lib/saml_idp_constants.rb +++ b/lib/saml_idp_constants.rb @@ -6,10 +6,13 @@ module Saml module Idp module Constants - LOA1_AUTHN_CONTEXT_CLASSREF = 'http://idmanagement.gov/ns/assurance/loa/1' - LOA3_AUTHN_CONTEXT_CLASSREF = 'http://idmanagement.gov/ns/assurance/loa/3' + LEGACY_ACR_NS = 'http://idmanagement.gov/ns' + LEGACY_ACR_PREFIX = "#{LEGACY_ACR_NS}/assurance".freeze - IAL_AUTHN_CONTEXT_PREFIX = 'http://idmanagement.gov/ns/assurance/ial' + LOA1_AUTHN_CONTEXT_CLASSREF = "#{LEGACY_ACR_PREFIX}/loa/1".freeze + LOA3_AUTHN_CONTEXT_CLASSREF = "#{LEGACY_ACR_PREFIX}/loa/3".freeze + + IAL_AUTHN_CONTEXT_PREFIX = "#{LEGACY_ACR_PREFIX}/ial".freeze IAL1_AUTHN_CONTEXT_CLASSREF = "#{IAL_AUTHN_CONTEXT_PREFIX}/1".freeze IAL2_AUTHN_CONTEXT_CLASSREF = "#{IAL_AUTHN_CONTEXT_PREFIX}/2".freeze IALMAX_AUTHN_CONTEXT_CLASSREF = "#{IAL_AUTHN_CONTEXT_PREFIX}/0".freeze @@ -29,7 +32,7 @@ module Constants ].freeze DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF = 'urn:gov:gsa:ac:classes:sp:PasswordProtectedTransport:duo' - AAL_AUTHN_CONTEXT_PREFIX = 'http://idmanagement.gov/ns/assurance/aal' + AAL_AUTHN_CONTEXT_PREFIX = "#{LEGACY_ACR_PREFIX}/aal".freeze AAL1_AUTHN_CONTEXT_CLASSREF = "#{AAL_AUTHN_CONTEXT_PREFIX}/1".freeze AAL2_AUTHN_CONTEXT_CLASSREF = "#{AAL_AUTHN_CONTEXT_PREFIX}/2".freeze AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF = "#{AAL_AUTHN_CONTEXT_PREFIX}/2?phishing_resistant=true".freeze @@ -42,7 +45,7 @@ module Constants NAME_ID_FORMAT_UNSPECIFIED = 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified' VALID_NAME_ID_FORMATS = [NAME_ID_FORMAT_PERSISTENT, NAME_ID_FORMAT_EMAIL].freeze - REQUESTED_ATTRIBUTES_CLASSREF = 'http://idmanagement.gov/ns/requested_attributes?ReqAttr=' + REQUESTED_ATTRIBUTES_CLASSREF = "#{LEGACY_ACR_NS}/requested_attributes?ReqAttr=".freeze VALID_AUTHN_CONTEXTS = (if FeatureManagement.use_semantic_authn_contexts? IdentityConfig.store.valid_authn_contexts_semantic diff --git a/spec/services/analytics_spec.rb b/spec/services/analytics_spec.rb index 162aede3f5f..23143e426fd 100644 --- a/spec/services/analytics_spec.rb +++ b/spec/services/analytics_spec.rb @@ -218,11 +218,14 @@ context 'with an SP request vtr saved in the session' do context 'identity verified' do let(:session) { { sp: { vtr: ['C1.P1'] } } } + let(:component_names) { ['C1', 'C2', 'P1'] } + let(:component_values) { component_names.index_with(true) } let(:expected_attributes) do { sp_request: { aal2: true, - component_values: ['C1', 'C2', 'P1'], + component_names:, + component_values:, identity_proofing: true, component_separator: '.', }, @@ -239,7 +242,8 @@ context 'phishing resistant and requiring facial match comparison' do let(:session) { { sp: { vtr: ['Ca.Pb'] } } } - let(:component_values) { ['C1', 'C2', 'Ca', 'P1', 'Pb'] } + let(:component_names) { ['C1', 'C2', 'Ca', 'P1', 'Pb'] } + let(:component_values) { component_names.index_with(true) } let(:expected_attributes) do { @@ -248,6 +252,7 @@ facial_match: true, two_pieces_of_fair_evidence: true, component_values:, + component_names:, identity_proofing: true, phishing_resistant: true, component_separator: '.', @@ -264,59 +269,76 @@ end end - context 'when acr_values are saved in the session' do + shared_context '#sp_request_attributes[acr_values]' do let(:acr_values) { [] } let(:sp_request) { {} } - let(:session) { { sp: { acr_values: acr_values.join(' ') } } } + let(:session) do + { + sp: { + acr_values: acr_values.join(' '), + }, + } + end let(:expected_attributes) do { sp_request: { component_separator: ' ', - component_values: acr_values, + component_names: acr_values, + component_values: acr_values.map do |v| + v.sub("#{Saml::Idp::Constants::LEGACY_ACR_PREFIX}/", '') + end.index_with(true), **sp_request, }, } end - shared_examples 'commit trackable event' do - it 'then includes sp_request in the event' do + shared_examples 'track event with :sp_request' do + it 'then #sp_request_attributes() matches :sp_request' do + expect(analytics.sp_request_attributes).to match(expected_attributes) + end + + it 'then includes :sp_request in the event' do expect(ahoy).to receive(:track). with('Trackable Event', hash_including(expected_attributes)) analytics.track_event('Trackable Event') end end + end - shared_examples 'for all user scenarios' do |acr_values_list| + context 'when acr_values are saved in the session' do + include_context '#sp_request_attributes[acr_values]' + + shared_examples 'using acrs for all user scenarios' do |acr_values_list| let(:acr_values) { acr_values_list } context "using #{acr_values_list}" do context 'when the user has not been identity verified' do let(:current_user) { build(:user, :fully_registered) } - include_examples 'commit trackable event' + include_examples 'track event with :sp_request' end context 'when the identity verified user has not proofed with facial match' do let(:current_user) { build(:user, :proofed) } - include_examples 'commit trackable event' + include_examples 'track event with :sp_request' end context 'when the identity verified user has proofed with facial match' do let(:current_user) { build(:user, :proofed_with_selfie) } - include_examples 'commit trackable event' + include_examples 'track event with :sp_request' end end end context 'and does not require any identity proofing' do - include_examples 'for all user scenarios', + include_examples 'using acrs for all user scenarios', [Saml::Idp::Constants::IAL_AUTH_ONLY_ACR] - include_examples 'for all user scenarios', + include_examples 'using acrs for all user scenarios', [Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF] - include_examples 'for all user scenarios', + include_examples 'using acrs for all user scenarios', [Saml::Idp::Constants::LOA1_AUTHN_CONTEXT_CLASSREF] end @@ -328,11 +350,11 @@ } end - include_examples 'for all user scenarios', + include_examples 'using acrs for all user scenarios', [Saml::Idp::Constants::IAL_VERIFIED_ACR] - include_examples 'for all user scenarios', + include_examples 'using acrs for all user scenarios', [Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF] - include_examples 'for all user scenarios', + include_examples 'using acrs for all user scenarios', [Saml::Idp::Constants::LOA3_AUTHN_CONTEXT_CLASSREF] end @@ -346,10 +368,10 @@ } end - include_examples 'for all user scenarios', + include_examples 'using acrs for all user scenarios', [Saml::Idp::Constants::IAL_VERIFIED_FACIAL_MATCH_REQUIRED_ACR] - include_examples 'for all user scenarios', + include_examples 'using acrs for all user scenarios', [Saml::Idp::Constants::IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF] end @@ -369,7 +391,7 @@ end let(:current_user) { build(:user, :fully_registered) } - include_examples 'commit trackable event' + include_examples 'track event with :sp_request' end context 'when the identity verified user has not proofed with facial match' do @@ -381,7 +403,7 @@ } end - include_examples 'commit trackable event' + include_examples 'track event with :sp_request' end context 'when the identity verified user has proofed with facial match' do @@ -395,7 +417,7 @@ end let(:current_user) { build(:user, :proofed_with_selfie) } - include_examples 'commit trackable event' + include_examples 'track event with :sp_request' end end end @@ -406,103 +428,51 @@ [Saml::Idp::Constants::IAL2_BIO_PREFERRED_AUTHN_CONTEXT_CLASSREF] end - context 'acr_values IALMAX' do - let(:session) { { sp: { acr_values: Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF } } } - let(:expected_attributes) do + context 'and selects the IALMax step-up flow' do + let(:sp_request) do { - sp_request: { - aal2: true, - component_values: [Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF], - component_separator: ' ', - ialmax: true, - }, + aal2: true, + ialmax: true, } end - it 'includes the sp_request' do - expect(ahoy).to receive(:track). - with('Trackable Event', hash_including(expected_attributes)) - - analytics.track_event('Trackable Event') - end + include_examples 'using acrs for all user scenarios', + [Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF] end end context 'with an SP request_url saved in the session' do - context 'no request_url' do - let(:session) { { sp: { acr_values: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF } } } - - let(:expected_attributes) do - { - sp_request: { - component_values: [Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF], - component_separator: ' ', - }, - } - end - - it 'includes the sp_request' do - expect(ahoy).to receive(:track). - with('Trackable Event', hash_including(expected_attributes)) + include_context '#sp_request_attributes[acr_values]' + let(:acr_values) { [Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF] } + let(:request_url) { nil } + let(:session) do + { + sp: { + acr_values: acr_values.join(' '), + request_url:, + }.compact, + } + end - analytics.track_event('Trackable Event') - end + context 'no request_url' do + include_examples 'track event with :sp_request' end context 'a request_url without login_gov_app_differentiator ' do - let(:session) do - { - sp: { - acr_values: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, - request_url: 'http://localhost:3000/openid_connect/authorize?whatever=something_else', - }, - } - end - - let(:expected_attributes) do - { - sp_request: { - component_values: [Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF], - component_separator: ' ', - }, - } - end - - it 'includes the sp_request' do - expect(ahoy).to receive(:track). - with('Trackable Event', hash_including(expected_attributes)) + let(:request_url) { 'http://localhost:3000/openid_connect/authorize?whatever=something_else' } - analytics.track_event('Trackable Event') - end + include_examples 'track event with :sp_request' end context 'a request_url with login_gov_app_differentiator ' do - let(:session) do - { - sp: { - acr_values: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, - request_url: - 'http://localhost:3000/openid_connect/authorize?login_gov_app_differentiator=NY', - }, - } - end - - let(:expected_attributes) do + let(:request_url) { 'http://localhost:3000/openid_connect/authorize?login_gov_app_differentiator=NY' } + let(:sp_request) do { - sp_request: { - component_values: [Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF], - component_separator: ' ', - app_differentiator: 'NY', - }, + app_differentiator: 'NY', } end - it 'includes the sp_request' do - expect(ahoy).to receive(:track). - with('Trackable Event', hash_including(expected_attributes)) - - analytics.track_event('Trackable Event') - end + include_examples 'track event with :sp_request' end end end