diff --git a/Gemfile b/Gemfile
index 9ac8b7f3ee4..dee8b7bdd59 100644
--- a/Gemfile
+++ b/Gemfile
@@ -57,7 +57,7 @@ gem 'profanity_filter'
gem 'propshaft'
gem 'rack', '>= 3.0'
gem 'rack-attack', github: 'rack/rack-attack', ref: 'd9fedfae4f7f6409f33857763391f4e18a6d7467'
-gem 'rack-cors', '>= 1.0.5', require: 'rack/cors'
+gem 'rack-cors', '>= 1.0.5', '< 2.0.1', require: 'rack/cors'
gem 'rack-headers_filter'
gem 'rack-timeout', require: false
gem 'redacted_struct'
@@ -76,7 +76,8 @@ gem 'stringex', require: false
gem 'strong_migrations', '>= 0.4.2'
gem 'subprocess', require: false
gem 'terminal-table', require: false
-gem 'valid_email', '>= 0.1.3'
+# until a release includes https://github.com/hallelujah/valid_email/pull/126
+gem 'valid_email', '>= 0.1.3', github: 'hallelujah/valid_email', ref: '486b860'
gem 'view_component', '~> 3.0'
gem 'webauthn', '~> 2.5.2'
gem 'xmldsig', '~> 0.6'
diff --git a/Gemfile.lock b/Gemfile.lock
index d31c1a552bd..e912059a4eb 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -44,6 +44,16 @@ GIT
nokogiri (>= 1.10.2)
pkcs11
+GIT
+ remote: https://github.com/hallelujah/valid_email.git
+ revision: 486b860fb40281d1ba99ec7621505abc5c9ad5bb
+ ref: 486b860
+ specs:
+ valid_email (0.2.0)
+ activemodel
+ mail (>= 2.6.1)
+ simpleidn
+
GIT
remote: https://github.com/hashrocket/capybara-webmock.git
revision: d3f3b7c8edbeca7b575e74b256ad22df80d2b420
@@ -216,7 +226,7 @@ GEM
erubi (~> 1.4)
parser (>= 2.4)
smart_properties
- bigdecimal (3.1.5)
+ bigdecimal (3.1.6)
bindata (2.4.15)
bootsnap (1.17.0)
msgpack (~> 1.2)
@@ -249,7 +259,7 @@ GEM
coderay (1.1.3)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
- concurrent-ruby (1.2.2)
+ concurrent-ruby (1.2.3)
connection_pool (2.4.1)
cose (1.3.0)
cbor (~> 0.5.9)
@@ -418,11 +428,11 @@ GEM
mini_histogram (0.3.1)
mini_mime (1.1.5)
mini_portile2 (2.8.5)
- minitest (5.20.0)
+ minitest (5.22.2)
msgpack (1.7.2)
multiset (0.5.3)
mutex_m (0.2.0)
- net-imap (0.4.6)
+ net-imap (0.4.10)
date
net-protocol
net-pop (0.1.2)
@@ -431,7 +441,7 @@ GEM
timeout
net-sftp (3.0.0)
net-ssh (>= 5.0.0, < 7.0.0)
- net-smtp (0.4.0)
+ net-smtp (0.4.0.1)
net-protocol
net-ssh (6.1.0)
newrelic_rpm (9.7.0)
@@ -485,7 +495,7 @@ GEM
raabro (1.4.0)
racc (1.7.3)
rack (3.0.9.1)
- rack-cors (2.0.1)
+ rack-cors (2.0.0)
rack (>= 2.0.0)
rack-headers_filter (0.0.1)
rack-mini-profiler (3.3.0)
@@ -683,13 +693,9 @@ GEM
concurrent-ruby (~> 1.0)
unf (0.1.4)
unf_ext
- unf_ext (0.0.8)
+ unf_ext (0.0.9.1)
unicode-display_width (2.5.0)
uniform_notifier (1.16.0)
- valid_email (0.1.4)
- activemodel
- mail (>= 2.6.1)
- simpleidn
view_component (3.9.0)
activesupport (>= 5.2.0, < 8.0)
concurrent-ruby (~> 1.0)
@@ -729,7 +735,7 @@ GEM
nokogiri (~> 1.11)
xpath (3.2.0)
nokogiri (~> 1.8)
- yard (0.9.34)
+ yard (0.9.35)
zeitwerk (2.6.12)
zlib (3.0.0)
zonebie (0.6.1)
@@ -812,7 +818,7 @@ DEPENDENCIES
puma (~> 6.0)
rack (>= 3.0)
rack-attack!
- rack-cors (>= 1.0.5)
+ rack-cors (>= 1.0.5, < 2.0.1)
rack-headers_filter
rack-mini-profiler (>= 1.1.3)
rack-test (>= 1.1.0)
@@ -850,7 +856,7 @@ DEPENDENCIES
subprocess
tableparser
terminal-table
- valid_email (>= 0.1.3)
+ valid_email (>= 0.1.3)!
view_component (~> 3.0)
webauthn (~> 2.5.2)
webmock
diff --git a/app/assets/images/globe-blue.svg b/app/assets/images/globe-blue.svg
deleted file mode 100644
index a1782e86118..00000000000
--- a/app/assets/images/globe-blue.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/app/assets/images/globe-white.svg b/app/assets/images/globe-white.svg
deleted file mode 100644
index 88a59e3d128..00000000000
--- a/app/assets/images/globe-white.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/app/assets/stylesheets/components/_language-picker.scss b/app/assets/stylesheets/components/_language-picker.scss
index 5910f030e92..39c43f5755a 100644
--- a/app/assets/stylesheets/components/_language-picker.scss
+++ b/app/assets/stylesheets/components/_language-picker.scss
@@ -43,18 +43,6 @@
}
}
- span {
- margin: 0 units(1);
- }
-
- &::after {
- content: '';
- display: block;
- width: 0.8125rem;
- height: 0.8125rem;
- background-size: 0.8125rem;
- }
-
&.usa-accordion__button[aria-expanded='false'],
&.usa-accordion__button[aria-expanded='true'] {
background-image: none;
@@ -64,23 +52,28 @@
&:hover {
background-color: transparent;
}
-
- &::after {
- background-image: url('/angle-arrow-up.svg');
-
- @include at-media('tablet') {
- background-image: url('/angle-arrow-up-white.svg');
- }
- }
}
&.usa-accordion__button[aria-expanded='true'] {
@include u-bg('primary');
color: color('white');
+ }
+}
- &::after {
- background-image: url('/angle-arrow-down-white.svg');
- }
+.language-picker__label-text {
+ margin-left: units(1);
+ margin-right: units(0.5);
+}
+
+.language-picker__expander {
+ transition: transform $project-easing;
+
+ @media (prefers-reduced-motion) {
+ transition: none;
+ }
+
+ .usa-accordion__button[aria-expanded='false'] & {
+ transform: rotate(-180deg);
}
}
diff --git a/app/components/icon_component.html.erb b/app/components/icon_component.html.erb
index 59d6b4b5695..274a84e440e 100644
--- a/app/components/icon_component.html.erb
+++ b/app/components/icon_component.html.erb
@@ -1,10 +1,11 @@
<%= content_tag(
- :svg,
- aria: { hidden: true },
- focusable: 'false',
- role: 'img',
+ :span,
+ content_tag(
+ :style,
+ "#icon-#{unique_id} { mask-image: url(#{icon_path}); -webkit-mask-image: url(#{icon_path}); }",
+ nonce: content_security_policy_nonce,
+ ),
**tag_options,
+ id: "icon-#{unique_id}",
class: css_class,
- ) do %>
-
-<% end %>
+ ) -%>
diff --git a/app/components/icon_component.rb b/app/components/icon_component.rb
index 40250ff994a..4923b3ea551 100644
--- a/app/components/icon_component.rb
+++ b/app/components/icon_component.rb
@@ -255,13 +255,13 @@ def initialize(icon:, size: nil, **tag_options)
end
def css_class
- classes = ['usa-icon', *tag_options[:class]]
+ classes = ['icon', 'usa-icon', *tag_options[:class]]
classes << "usa-icon--size-#{size}" if size
classes
end
def icon_path
- asset_path([asset_path('sprite.svg'), '#', icon].join, host: asset_host)
+ @icon_path ||= asset_path("usa-icons/#{icon}.svg", host: asset_host)
end
private
diff --git a/app/components/icon_component.scss b/app/components/icon_component.scss
index 6b0bdc27e2a..2591b1d1b9c 100644
--- a/app/components/icon_component.scss
+++ b/app/components/icon_component.scss
@@ -3,6 +3,11 @@
@forward 'usa-icon';
+.icon {
+ mask-size: 100%;
+ background-color: currentColor;
+}
+
$icon-min-padding: 2px;
// Upstream: https://github.com/uswds/uswds/pull/4493
diff --git a/app/components/language_picker_component.html.erb b/app/components/language_picker_component.html.erb
index 7caa09ead3a..9aab9f2bb86 100644
--- a/app/components/language_picker_component.html.erb
+++ b/app/components/language_picker_component.html.erb
@@ -7,11 +7,11 @@
expanded: false,
},
) do %>
- <%= image_tag(asset_url('globe-blue.svg'), width: 12, height: 12, alt: '', class: 'tablet:display-none') %>
- <%= image_tag(asset_url('globe-white.svg'), width: 12, height: 12, alt: '', class: 'display-none tablet:display-inline') %>
-
+ <%= render IconComponent.new(icon: :language) %>
+
<%= t('i18n.language') %>
+ <%= render IconComponent.new(icon: :expand_more, size: 3, class: 'language-picker__expander') %>
<% end %>
(idv_session:, user:) { idv_session.flow_path == 'standard' },
+ preconditions: ->(idv_session:, user:) {
+ idv_session.flow_path == 'standard' && (
+ # mobile
+ idv_session.skip_hybrid_handoff ||
+ !idv_session.selfie_check_required || # desktop but selfie not required
+ idv_session.desktop_selfie_test_mode_enabled?
+ )
+ },
undo_step: ->(idv_session:, user:) do
idv_session.pii_from_doc = nil
idv_session.invalidate_in_person_pii_from_user!
@@ -85,6 +92,8 @@ def analytics_arguments
irs_reproofing: irs_reproofing?,
redo_document_capture: idv_session.redo_document_capture,
skip_hybrid_handoff: idv_session.skip_hybrid_handoff,
+ liveness_checking_required: decorated_sp_session.selfie_required?,
+ selfie_check_required: idv_session.selfie_check_required,
}.merge(ab_test_analytics_buckets)
end
diff --git a/app/controllers/idv/hybrid_handoff_controller.rb b/app/controllers/idv/hybrid_handoff_controller.rb
index 61a54ec39ff..f75a238464f 100644
--- a/app/controllers/idv/hybrid_handoff_controller.rb
+++ b/app/controllers/idv/hybrid_handoff_controller.rb
@@ -168,6 +168,7 @@ def analytics_arguments
irs_reproofing: irs_reproofing?,
redo_document_capture: params[:redo] ? true : nil,
skip_hybrid_handoff: idv_session.skip_hybrid_handoff,
+ selfie_check_required: idv_session.selfie_check_required,
}.merge(ab_test_analytics_buckets)
end
diff --git a/app/controllers/idv/hybrid_mobile/capture_complete_controller.rb b/app/controllers/idv/hybrid_mobile/capture_complete_controller.rb
index f66e5823cc5..53e62bb6356 100644
--- a/app/controllers/idv/hybrid_mobile/capture_complete_controller.rb
+++ b/app/controllers/idv/hybrid_mobile/capture_complete_controller.rb
@@ -23,6 +23,7 @@ def analytics_arguments
step: 'capture_complete',
analytics_id: 'Doc Auth',
irs_reproofing: irs_reproofing?,
+ liveness_checking_required: decorated_sp_session.selfie_required?,
}.merge(ab_test_analytics_buckets)
end
end
diff --git a/app/controllers/idv/hybrid_mobile/document_capture_controller.rb b/app/controllers/idv/hybrid_mobile/document_capture_controller.rb
index b554e376e0e..83892d2d038 100644
--- a/app/controllers/idv/hybrid_mobile/document_capture_controller.rb
+++ b/app/controllers/idv/hybrid_mobile/document_capture_controller.rb
@@ -55,6 +55,8 @@ def analytics_arguments
step: 'document_capture',
analytics_id: 'Doc Auth',
irs_reproofing: irs_reproofing?,
+ liveness_checking_required: decorated_sp_session.selfie_required?,
+ selfie_check_required: decorated_sp_session.selfie_required?,
}.merge(
ab_test_analytics_buckets,
)
diff --git a/app/decorators/null_service_provider_session.rb b/app/decorators/null_service_provider_session.rb
index 7c41dd783ee..659ae708b82 100644
--- a/app/decorators/null_service_provider_session.rb
+++ b/app/decorators/null_service_provider_session.rb
@@ -15,7 +15,7 @@ def cancel_link_url
view_context.root_url
end
- def mfa_expiration_interval
+ def mfa_expiration_interval(_authentication_context)
IdentityConfig.store.remember_device_expiration_hours_aal_1.hours
end
diff --git a/app/decorators/service_provider_session.rb b/app/decorators/service_provider_session.rb
index 28487c6bf70..f57e4d4f1fc 100644
--- a/app/decorators/service_provider_session.rb
+++ b/app/decorators/service_provider_session.rb
@@ -88,12 +88,12 @@ def sp_alert(section)
end
end
- def mfa_expiration_interval
+ def mfa_expiration_interval(authorization_context)
aal_1_expiration = IdentityConfig.store.remember_device_expiration_hours_aal_1.hours
aal_2_expiration = IdentityConfig.store.remember_device_expiration_minutes_aal_2.minutes
return aal_2_expiration if sp_aal > 1
return aal_2_expiration if sp_ial > 1
- return aal_2_expiration if requested_aal > 1
+ return aal_2_expiration if authorization_context.aal_level_requested > 1
aal_1_expiration
end
@@ -138,8 +138,8 @@ def sp_ial
sp.ial || 1
end
- def requested_aal
- sp_session[:aal_level_requested] || 1
+ def requested_aal(authorization_context)
+ authorization_context.aal_level_requested
end
def request_url
diff --git a/app/forms/idv/api_image_upload_form.rb b/app/forms/idv/api_image_upload_form.rb
index 37f2bd30269..5d340abb3bd 100644
--- a/app/forms/idv/api_image_upload_form.rb
+++ b/app/forms/idv/api_image_upload_form.rb
@@ -86,6 +86,7 @@ def post_images_to_client
back_image: back_image_bytes,
selfie_image: liveness_checking_required ? selfie_image_bytes : nil,
image_source: image_source,
+ images_cropped: acuant_sdk_autocaptured_id?,
user_uuid: user_uuid,
uuid_prefix: uuid_prefix,
liveness_checking_required: liveness_checking_required,
@@ -205,7 +206,7 @@ def determine_response(form_response:, client_response:, doc_pii_response:)
end
def image_source
- if acuant_sdk_capture?
+ if acuant_sdk_captured_id?
DocAuth::ImageSources::ACUANT_SDK
else
DocAuth::ImageSources::UNKNOWN
@@ -249,6 +250,14 @@ def validate_images
type: :not_a_file
)
end
+
+ if !IdentityConfig.store.doc_auth_selfie_desktop_test_mode &&
+ liveness_checking_required && !acuant_sdk_captured?
+ errors.add(
+ :selfie, t('doc_auth.errors.not_a_file'),
+ type: :not_a_file
+ )
+ end
end
def validate_duplicate_images
@@ -370,12 +379,23 @@ def acuant_sdk_upgrade_ab_test_data
}
end
- def acuant_sdk_capture?
+ def acuant_sdk_captured?
+ acuant_sdk_captured_id? &&
+ (liveness_checking_required ? acuant_sdk_captured_selfie? : true)
+ end
+
+ def acuant_sdk_captured_id?
image_metadata.dig(:front, :source) == Idp::Constants::Vendors::ACUANT &&
- image_metadata.dig(:back, :source) == Idp::Constants::Vendors::ACUANT &&
- (liveness_checking_required ?
- image_metadata.dig(:selfie, :source) == Idp::Constants::Vendors::ACUANT :
- true)
+ image_metadata.dig(:back, :source) == Idp::Constants::Vendors::ACUANT
+ end
+
+ def acuant_sdk_captured_selfie?
+ image_metadata.dig(:selfie, :source) == Idp::Constants::Vendors::ACUANT
+ end
+
+ def acuant_sdk_autocaptured_id?
+ image_metadata.dig(:front, :acuantCaptureMode) == 'AUTO' &&
+ image_metadata.dig(:back, :acuantCaptureMode) == 'AUTO'
end
def image_metadata
diff --git a/app/jobs/get_usps_proofing_results_job.rb b/app/jobs/get_usps_proofing_results_job.rb
index 51355517576..435ddf3a556 100644
--- a/app/jobs/get_usps_proofing_results_job.rb
+++ b/app/jobs/get_usps_proofing_results_job.rb
@@ -227,6 +227,7 @@ def handle_unsupported_id_type(enrollment, response)
primary_id_type: response['primaryIdType'],
reason: 'Unsupported ID type',
job_name: self.class.name,
+ tmx_status: enrollment.profile&.tmx_status,
)
enrollment.update(
status: :failed,
@@ -263,6 +264,7 @@ def handle_expired_status_update(enrollment, response, response_message)
passed: false,
reason: 'Enrollment has expired',
job_name: self.class.name,
+ tmx_status: enrollment.profile&.tmx_status,
)
enrollment.update(
status: :expired,
@@ -334,6 +336,7 @@ def handle_failed_status(enrollment, response)
passed: false,
reason: 'Failed status',
job_name: self.class.name,
+ tmx_status: enrollment.profile&.tmx_status,
)
enrollment.update(
@@ -370,6 +373,7 @@ def handle_successful_status_update(enrollment, response)
passed: true,
reason: 'Successful status update',
job_name: self.class.name,
+ tmx_status: enrollment.profile&.tmx_status,
)
enrollment.update(
status: :passed,
@@ -400,6 +404,7 @@ def handle_passed_with_fraud_review_pending(enrollment, response)
passed: true,
reason: 'Passed with fraud pending',
job_name: self.class.name,
+ tmx_status: enrollment.profile&.tmx_status,
)
enrollment.update(
status: :passed,
@@ -425,6 +430,7 @@ def handle_unsupported_secondary_id(enrollment, response)
passed: false,
reason: 'Provided secondary proof of address',
job_name: self.class.name,
+ tmx_status: enrollment.profile&.tmx_status,
)
enrollment.update(
status: :failed,
diff --git a/app/models/disposable_email_domain.rb b/app/models/disposable_email_domain.rb
index e8835698014..919224a8ec1 100644
--- a/app/models/disposable_email_domain.rb
+++ b/app/models/disposable_email_domain.rb
@@ -1,5 +1,17 @@
class DisposableEmailDomain < ApplicationRecord
def self.disposable?(domain)
- exists?(name: domain)
+ exists?(name: subdomains(domain))
+ end
+
+ # @return [Array]
+ # @example
+ # subdomains("foo.bar.baz.com")
+ # => ["foo.bar.baz.com", "bar.baz.com", "baz.com"]
+ def self.subdomains(domain)
+ parts = domain.split('.')
+
+ parts[...-1].to_enum.with_index.map do |_part, index|
+ parts[index..].join('.')
+ end
end
end
diff --git a/app/models/profile.rb b/app/models/profile.rb
index 31bc0dfff34..b6ee8be51f1 100644
--- a/app/models/profile.rb
+++ b/app/models/profile.rb
@@ -106,6 +106,12 @@ def activate(reason_deactivated: nil)
end
# rubocop:enable Rails/SkipsModelValidations
+ def tmx_status
+ return nil unless IdentityConfig.store.in_person_proofing_enforce_tmx
+
+ fraud_pending_reason || :threatmetrix_pass
+ end
+
def reason_not_to_activate
if pending_reasons.any?
"Attempting to activate profile with pending reasons: #{pending_reasons.join(',')}"
diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb
index 306677b56d3..92510270003 100644
--- a/app/services/analytics_events.rb
+++ b/app/services/analytics_events.rb
@@ -964,6 +964,7 @@ def idv_doc_auth_ssn_visited(**extra)
# @param [Integer] remaining_submit_attempts (previously called "remaining_attempts")
# @param [String] user_id
# @param [String] flow_path
+ # @param [String] liveness_checking_required Whether or not the selfie is required
# @param [String] front_image_fingerprint Fingerprint of front image data
# @param [String] back_image_fingerprint Fingerprint of back image data
# The document capture image uploaded was locally validated during the IDV process
@@ -972,6 +973,7 @@ def idv_doc_auth_submitted_image_upload_form(
errors:,
remaining_submit_attempts:,
flow_path:,
+ liveness_checking_required:,
submit_attempts: nil,
user_id: nil,
front_image_fingerprint: nil,
@@ -988,6 +990,7 @@ def idv_doc_auth_submitted_image_upload_form(
flow_path: flow_path,
front_image_fingerprint: front_image_fingerprint,
back_image_fingerprint: back_image_fingerprint,
+ liveness_checking_required: liveness_checking_required,
**extra,
)
end
@@ -1010,9 +1013,11 @@ def idv_doc_auth_submitted_image_upload_form(
# @param [Boolean] attention_with_barcode
# @param [Boolean] doc_type_supported
# @param [Boolean] doc_auth_success
+ # @param [String] liveness_checking_required Whether or not the selfie is required
# @param [String] selfie_status
# @param [String] vendor
# @param [String] conversation_id
+ # @param [String] request_id RequestId from TrueID
# @param [String] reference
# @param [String] transaction_status
# @param [String] transaction_reason_code
@@ -1050,6 +1055,7 @@ def idv_doc_auth_submitted_image_upload_vendor(
remaining_submit_attempts:,
client_image_metrics:,
flow_path:,
+ liveness_checking_required:,
billed: nil,
doc_auth_result: nil,
vendor_request_time_in_ms: nil,
@@ -1061,6 +1067,7 @@ def idv_doc_auth_submitted_image_upload_vendor(
selfie_status: nil,
vendor: nil,
conversation_id: nil,
+ request_id: nil,
reference: nil,
transaction_status: nil,
transaction_reason_code: nil,
@@ -1097,6 +1104,7 @@ def idv_doc_auth_submitted_image_upload_vendor(
selfie_status:,
vendor:,
conversation_id:,
+ request_id:,
reference:,
transaction_status:,
transaction_reason_code:,
@@ -1108,6 +1116,7 @@ def idv_doc_auth_submitted_image_upload_vendor(
portrait_match_results:,
image_metrics:,
address_line2_present:,
+ liveness_checking_required:,
**extra,
)
end
@@ -1118,6 +1127,7 @@ def idv_doc_auth_submitted_image_upload_vendor(
# @param [Integer] remaining_submit_attempts (previously called "remaining_attempts")
# @param [Hash] pii_like_keypaths
# @param [String] flow_path
+ # @param [String] liveness_checking_required Whether or not the selfie is required
# @param [String] front_image_fingerprint Fingerprint of front image data
# @param [String] back_image_fingerprint Fingerprint of back image data
# @param [Hash] classification_info document image side information, issuing country and type etc
@@ -1128,6 +1138,7 @@ def idv_doc_auth_submitted_pii_validation(
remaining_submit_attempts:,
pii_like_keypaths:,
flow_path:,
+ liveness_checking_required:,
user_id: nil,
front_image_fingerprint: nil,
back_image_fingerprint: nil,
@@ -1145,6 +1156,7 @@ def idv_doc_auth_submitted_pii_validation(
front_image_fingerprint: front_image_fingerprint,
back_image_fingerprint: back_image_fingerprint,
classification_info: classification_info,
+ liveness_checking_required: liveness_checking_required,
**extra,
)
end
diff --git a/app/services/doc_auth/acuant/acuant_client.rb b/app/services/doc_auth/acuant/acuant_client.rb
index 3eec6b365f4..81d76068246 100644
--- a/app/services/doc_auth/acuant/acuant_client.rb
+++ b/app/services/doc_auth/acuant/acuant_client.rb
@@ -41,6 +41,7 @@ def post_images(
front_image:,
back_image:,
image_source:,
+ images_cropped: false,
user_uuid: nil,
uuid_prefix: nil,
selfie_image: nil,
diff --git a/app/services/doc_auth/lexis_nexis/lexis_nexis_client.rb b/app/services/doc_auth/lexis_nexis/lexis_nexis_client.rb
index 8cc25fc34c7..7dfe4ab4e55 100644
--- a/app/services/doc_auth/lexis_nexis/lexis_nexis_client.rb
+++ b/app/services/doc_auth/lexis_nexis/lexis_nexis_client.rb
@@ -17,6 +17,7 @@ def post_images(
back_image:,
selfie_image: nil,
image_source: nil,
+ images_cropped: false,
user_uuid: nil,
uuid_prefix: nil,
liveness_checking_required: false
@@ -29,6 +30,7 @@ def post_images(
back_image: back_image,
selfie_image: selfie_image,
image_source: image_source,
+ images_cropped: images_cropped,
liveness_checking_required: liveness_checking_required,
).fetch
end
diff --git a/app/services/doc_auth/lexis_nexis/request.rb b/app/services/doc_auth/lexis_nexis/request.rb
index 68ca892ec57..a15b1669e9d 100644
--- a/app/services/doc_auth/lexis_nexis/request.rb
+++ b/app/services/doc_auth/lexis_nexis/request.rb
@@ -10,6 +10,7 @@ def initialize(config:, user_uuid: nil, uuid_prefix: nil)
end
def fetch
+ # return DocAuth::Respose with DocAuth:Error if workflow invalid
http_response = send_http_request
return handle_invalid_response(http_response) unless http_response.success?
diff --git a/app/services/doc_auth/lexis_nexis/requests/true_id_request.rb b/app/services/doc_auth/lexis_nexis/requests/true_id_request.rb
index bfe0213e211..45b14623ec2 100644
--- a/app/services/doc_auth/lexis_nexis/requests/true_id_request.rb
+++ b/app/services/doc_auth/lexis_nexis/requests/true_id_request.rb
@@ -12,6 +12,7 @@ def initialize(
back_image:,
selfie_image: nil,
image_source: nil,
+ images_cropped: false,
liveness_checking_required: false
)
super(config: config, user_uuid: user_uuid, uuid_prefix: uuid_prefix)
@@ -19,6 +20,7 @@ def initialize(
@back_image = back_image
@selfie_image = selfie_image
@image_source = image_source
+ @images_cropped = images_cropped
# when set to required, be sure to pass in selfie_image
@liveness_checking_required = liveness_checking_required
end
@@ -70,7 +72,7 @@ def password
end
def workflow
- if acuant_sdk_source?
+ if @images_cropped
include_liveness? ?
config.trueid_liveness_nocropping_workflow :
config.trueid_noliveness_nocropping_workflow
diff --git a/app/services/doc_auth/lexis_nexis/responses/true_id_response.rb b/app/services/doc_auth/lexis_nexis/responses/true_id_response.rb
index aa68c94268d..b8c64a9f8d6 100644
--- a/app/services/doc_auth/lexis_nexis/responses/true_id_response.rb
+++ b/app/services/doc_auth/lexis_nexis/responses/true_id_response.rb
@@ -122,6 +122,10 @@ def conversation_id
@conversation_id ||= parsed_response_body.dig(:Status, :ConversationId)
end
+ def request_id
+ @request_id ||= parsed_response_body.dig(:Status, :RequestId)
+ end
+
def parsed_response_body
@parsed_response_body ||= JSON.parse(http_response.body).with_indifferent_access
end
@@ -193,6 +197,7 @@ def create_response_info
def basic_logging_info
{
conversation_id: conversation_id,
+ request_id: request_id,
reference: reference,
vendor: 'TrueID',
billed: billed?,
diff --git a/app/services/doc_auth/mock/doc_auth_mock_client.rb b/app/services/doc_auth/mock/doc_auth_mock_client.rb
index 739a6617fe0..335af2b004f 100644
--- a/app/services/doc_auth/mock/doc_auth_mock_client.rb
+++ b/app/services/doc_auth/mock/doc_auth_mock_client.rb
@@ -58,6 +58,7 @@ def post_images(
back_image:,
selfie_image: nil,
image_source: nil,
+ images_cropped: false,
user_uuid: nil,
uuid_prefix: nil,
liveness_checking_required: false
diff --git a/app/services/id_token_builder.rb b/app/services/id_token_builder.rb
index cf582448c21..96e7f60a377 100644
--- a/app/services/id_token_builder.rb
+++ b/app/services/id_token_builder.rb
@@ -37,7 +37,7 @@ def jwt_payload
def id_token_claims
{
- acr: acr,
+ acr: (acr if !sp_requests_vot?),
vot: (vot if sp_requests_vot?),
vtm: (IdentityConfig.store.vtm_url if sp_requests_vot?),
nonce: identity.nonce,
@@ -74,7 +74,7 @@ def sp_requests_vot?
end
def vot
- return nil unless identity.vtr.present?
+ return nil unless sp_requests_vot?
resolved_authn_context_result.component_values.map(&:name).join('.')
end
@@ -89,11 +89,16 @@ def determine_ial_max_acr
def resolved_authn_context_result
@resolved_authn_context_result ||= AuthnContextResolver.new(
service_provider: identity.service_provider_record,
- vtr: [identity.vtr],
+ vtr: parsed_vtr_value,
acr_values: identity.acr_values,
).resolve
end
+ def parsed_vtr_value
+ return nil unless sp_requests_vot?
+ JSON.parse(identity.vtr)
+ end
+
def expires
now.to_i + ttl
end
diff --git a/app/services/idv/session.rb b/app/services/idv/session.rb
index 70bfb9a9cde..9a9c7db6171 100644
--- a/app/services/idv/session.rb
+++ b/app/services/idv/session.rb
@@ -23,6 +23,7 @@ class Session
redo_document_capture
resolution_successful
selfie_check_performed
+ selfie_check_required
skip_doc_auth
skip_hybrid_handoff
ssn
@@ -249,6 +250,10 @@ def skip_hybrid_handoff?
!!session[:skip_hybrid_handoff]
end
+ def desktop_selfie_test_mode_enabled?
+ IdentityConfig.store.doc_auth_selfie_desktop_test_mode
+ end
+
private
attr_accessor :user_session
diff --git a/app/services/reporting/active_users_count_report.rb b/app/services/reporting/active_users_count_report.rb
index 215b1f63423..a254a78fd8d 100644
--- a/app/services/reporting/active_users_count_report.rb
+++ b/app/services/reporting/active_users_count_report.rb
@@ -94,34 +94,40 @@ def monthly_active_users
# @return [Array]
def fiscal_year_active_users_per_quarter_cumulative
- @fiscal_year_active_users_per_quarter_cumulative ||= cumulative_quarter_ranges.
- map do |quarter_range|
- Reports::BaseReport.transaction_with_timeout do
- ReportRow.from_hash_time_range(
- time_range: quarter_range,
- hash: Db::Identity::SpActiveUserCounts.overall(
- quarter_range.begin,
- quarter_range.end,
- ).first,
- )
+ @fiscal_year_active_users_per_quarter_cumulative ||= begin
+ data_by_quarter = {}
+ cumulative_quarter_ranges.
+ map do |quarter_range|
+ data_by_quarter[quarter_range] ||= Reports::BaseReport.transaction_with_timeout do
+ ReportRow.from_hash_time_range(
+ time_range: quarter_range,
+ hash: Db::Identity::SpActiveUserCounts.overall(
+ quarter_range.begin,
+ quarter_range.end,
+ ).first,
+ )
+ end
end
- end
+ end
end
# @return [Array]
def apg_fiscal_year_active_users_per_quarter_cumulative
- @apg_fiscal_year_active_users_per_quarter_cumulative ||= cumulative_quarter_ranges.
- map do |quarter_range|
- Reports::BaseReport.transaction_with_timeout do
- ReportRow.from_hash_time_range(
- time_range: quarter_range,
- hash: Db::Identity::SpActiveUserCounts.overall_apg(
- quarter_range.begin,
- quarter_range.end,
- ).first,
- )
+ @apg_fiscal_year_active_users_per_quarter_cumulative ||= begin
+ data_by_quarter = {}
+ cumulative_quarter_ranges.
+ map do |quarter_range|
+ data_by_quarter[quarter_range] ||= Reports::BaseReport.transaction_with_timeout do
+ ReportRow.from_hash_time_range(
+ time_range: quarter_range,
+ hash: Db::Identity::SpActiveUserCounts.overall_apg(
+ quarter_range.begin,
+ quarter_range.end,
+ ).first,
+ )
+ end
end
- end
+ end
end
# rubocop:disable Layout/LineLength
@@ -133,7 +139,7 @@ def cumulative_quarter_ranges
CalendarService.fiscal_start_date(report_date)..CalendarService.fiscal_q4_start(report_date),
CalendarService.fiscal_start_date(report_date)..CalendarService.fiscal_end_date(report_date).next_day(1),
].map do |range|
- range.begin.beginning_of_day..([range.end.prev_day(1).end_of_day, report_date].min)
+ range.begin.in_time_zone('UTC').beginning_of_day..([range.end.prev_day(1).in_time_zone('UTC').end_of_day, report_date.in_time_zone('UTC')].min)
end
end
# rubocop:enable Layout/LineLength
diff --git a/app/services/store_sp_metadata_in_session.rb b/app/services/store_sp_metadata_in_session.rb
index f1dbf5e3ff5..2e1dbc867db 100644
--- a/app/services/store_sp_metadata_in_session.rb
+++ b/app/services/store_sp_metadata_in_session.rb
@@ -34,22 +34,8 @@ def sp_request
@sp_request ||= ServiceProviderRequestProxy.from_uuid(request_id)
end
- def ial2_value
- parsed_vot&.identity_proofing?
- end
-
def aal_level_requested_value
- return nil unless parsed_vot
-
- if parsed_vot.aal2?
- 2
- else
- 1
- end
- end
-
- def piv_cac_requested_value
- parsed_vot&.hspd12?
+ parsed_vot&.aal_level_requested
end
def biometric_comparison_required_value
@@ -63,7 +49,6 @@ def update_session
request_id: sp_request.uuid,
requested_attributes: sp_request.requested_attributes,
aal_level_requested: aal_level_requested_value,
- piv_cac_requested: piv_cac_requested_value,
biometric_comparison_required: biometric_comparison_required_value,
acr_values: sp_request.acr_values,
vtr: sp_request.vtr,
diff --git a/app/services/usps_in_person_proofing/enrollment_helper.rb b/app/services/usps_in_person_proofing/enrollment_helper.rb
index 2b19c2e086b..3173c5494fb 100644
--- a/app/services/usps_in_person_proofing/enrollment_helper.rb
+++ b/app/services/usps_in_person_proofing/enrollment_helper.rb
@@ -30,6 +30,7 @@ def schedule_in_person_enrollment(user, pii, opt_in = nil)
second_address_line_present: pii[:address2].present?,
service_provider: enrollment.service_provider&.issuer,
opted_in_to_in_person_proofing: opt_in,
+ tmx_status: enrollment.profile&.tmx_status,
)
send_ready_to_verify_email(user, enrollment)
diff --git a/app/services/vot/parser.rb b/app/services/vot/parser.rb
index 95e162c498e..f9e2d409f8c 100644
--- a/app/services/vot/parser.rb
+++ b/app/services/vot/parser.rb
@@ -1,6 +1,7 @@
module Vot
class Parser
class ParseException < StandardError; end
+
Result = Data.define(
:component_values,
:aal2?,
@@ -25,6 +26,14 @@ def self.no_sp_result
def identity_proofing_or_ialmax?
identity_proofing? || ialmax?
end
+
+ def aal_level_requested
+ if aal2?
+ 2
+ else
+ 1
+ end
+ end
end
attr_reader :vector_of_trust, :acr_values
diff --git a/app/validators/form_add_email_validator.rb b/app/validators/form_add_email_validator.rb
index 9882e837ba2..93f71232de2 100644
--- a/app/validators/form_add_email_validator.rb
+++ b/app/validators/form_add_email_validator.rb
@@ -12,6 +12,7 @@ module FormAddEmailValidator
email: {
mx_with_fallback: !ENV['RAILS_OFFLINE'],
ban_disposable_email: true,
+ partial: true,
}
validate :validate_domain
end
diff --git a/app/validators/form_email_validator.rb b/app/validators/form_email_validator.rb
index fc37679a33b..96ad1ab3011 100644
--- a/app/validators/form_email_validator.rb
+++ b/app/validators/form_email_validator.rb
@@ -11,6 +11,7 @@ module FormEmailValidator
email: {
mx_with_fallback: !ENV['RAILS_OFFLINE'],
ban_disposable_email: true,
+ partial: true,
}
validate :validate_domain
end
diff --git a/app/views/idv/in_person/state_id.html.erb b/app/views/idv/in_person/state_id.html.erb
index af3bc1e84f2..ef8942e4dc3 100644
--- a/app/views/idv/in_person/state_id.html.erb
+++ b/app/views/idv/in_person/state_id.html.erb
@@ -127,7 +127,7 @@
<% state_id_number_hint = capture do %>
<% [
[:default, state_id_number_hint_default],
- ['FL', t('in_person_proofing.form.state_id.state_id_number_florida_hint')],
+ ['FL', t('in_person_proofing.form.state_id.state_id_number_florida_hint_html')],
['TX', t('in_person_proofing.form.state_id.state_id_number_texas_hint')],
].each do |state, hint| %>
<%= content_tag(
@@ -143,7 +143,7 @@
name: :state_id_number,
form: f,
hint: state_id_number_hint,
- hint_html: { class: ['tablet:grid-col-10', 'jurisdiction-extras'] },
+ hint_html: { class: ['jurisdiction-extras'] },
input_html: { value: pii[:state_id_number] },
label: t('in_person_proofing.form.state_id.state_id_number'),
label_html: { class: 'usa-label' },
diff --git a/bin/oncall/download-piv-certs b/bin/oncall/download-piv-certs
index b0dcac50158..a423c3b3f6d 100755
--- a/bin/oncall/download-piv-certs
+++ b/bin/oncall/download-piv-certs
@@ -125,7 +125,7 @@ class DownloadPivCerts
, properties.event_properties.key_id AS key_id
| sort @timestamp desc
| filter ispresent(properties.event_properties.key_id)
- | filter properties.uuid IN #{quote(uuids)}
+ | filter properties.user_id IN #{quote(uuids)}
| filter properties.event_properties.success = 0
QUERY
@@ -136,7 +136,7 @@ class DownloadPivCerts
Reporting::UnknownProgressBar.wrap(show_bar: progress_bar?, title: 'Querying logs') do
cloudwatch_client.fetch(
query: query,
- from: 1.week.ago,
+ from: 2.weeks.ago,
to: Time.now,
)
end
diff --git a/config/application.yml.default b/config/application.yml.default
index 938086c6a9e..71b56d48dfd 100644
--- a/config/application.yml.default
+++ b/config/application.yml.default
@@ -88,6 +88,7 @@ doc_auth_max_attempts: 5
doc_auth_max_capture_attempts_before_native_camera: 3
doc_auth_max_submission_attempts_before_native_camera: 3
doc_auth_selfie_capture_enabled: false
+doc_auth_selfie_desktop_test_mode: false
doc_auth_sdk_capture_orientation: '{"horizontal": 100, "vertical": 0}'
doc_auth_supported_country_codes: '["US", "GU", "VI", "AS", "MP", "PR", "USA" ,"GUM", "VIR", "ASM", "MNP", "PRI"]'
doc_capture_request_valid_for_minutes: 15
@@ -321,7 +322,6 @@ 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"]'
@@ -386,6 +386,7 @@ development:
database_worker_jobs_password: ''
doc_auth_exit_question_section_enabled: false
doc_auth_selfie_capture_enabled: false
+ doc_auth_selfie_desktop_test_mode: true
doc_auth_vendor: 'mock'
doc_auth_vendor_randomize: false
doc_auth_vendor_randomize_percent: 0
@@ -528,6 +529,7 @@ test:
database_worker_jobs_password: ''
doc_auth_max_attempts: 4
doc_auth_selfie_capture_enabled: false
+ doc_auth_selfie_desktop_test_mode: true
doc_auth_vendor: 'mock'
doc_auth_vendor_randomize: false
doc_auth_vendor_randomize_percent: 0
diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb
index 5f734f347bc..29d2b0e5002 100644
--- a/config/initializers/content_security_policy.rb
+++ b/config/initializers/content_security_policy.rb
@@ -47,5 +47,5 @@
# rubocop:enable Metrics/BlockLength
Rails.application.configure do
config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
- config.content_security_policy_nonce_directives = ['script-src']
+ config.content_security_policy_nonce_directives = ['script-src', 'style-src']
end
diff --git a/config/locales/in_person_proofing/en.yml b/config/locales/in_person_proofing/en.yml
index c65ecbee82a..ecc41e73369 100644
--- a/config/locales/in_person_proofing/en.yml
+++ b/config/locales/in_person_proofing/en.yml
@@ -122,8 +122,8 @@ en:
state_id_jurisdiction_hint: This is the state that issued your ID
state_id_jurisdiction_prompt: '- Select -'
state_id_number: ID number
- state_id_number_florida_hint: This is the number on your ID that begins with 1
- letter followed by 12 numbers. Dashes are not required.
+ state_id_number_florida_hint_html: 'This is the number on your ID with one
+ letter and 12 numbers. Example: D123-456-78-901-2'
state_id_number_hint: 'May include letters, numbers, and the following symbols:'
state_id_number_hint_asterisks: asterisks
state_id_number_hint_dashes: dashes
diff --git a/config/locales/in_person_proofing/es.yml b/config/locales/in_person_proofing/es.yml
index d04c08e4a99..0080118de18 100644
--- a/config/locales/in_person_proofing/es.yml
+++ b/config/locales/in_person_proofing/es.yml
@@ -135,8 +135,8 @@ es:
state_id_jurisdiction_hint: Este es el estado que emitió su identificación
state_id_jurisdiction_prompt: '- Seleccione -'
state_id_number: Número de identidad
- state_id_number_florida_hint: Este es el número de su identificación que
- comienza con 1 letra seguida por 12 números. No se requieren guiones.
+ state_id_number_florida_hint_html: 'Este es el número que figura en su ID con
+ una letra y 12 números. Ejemplo: D123-456-78-901-2'
state_id_number_hint: 'Puede incluir letras, números y los siguientes símbolos:'
state_id_number_hint_asterisks: 'asteriscos'
state_id_number_hint_dashes: guiones
diff --git a/config/locales/in_person_proofing/fr.yml b/config/locales/in_person_proofing/fr.yml
index 5aa7b7af878..3575a27f00b 100644
--- a/config/locales/in_person_proofing/fr.yml
+++ b/config/locales/in_person_proofing/fr.yml
@@ -135,9 +135,9 @@ fr:
state_id_jurisdiction_hint: Il s’agit de l’État qui a émis votre pièce d’identité
state_id_jurisdiction_prompt: '- Sélectionnez -'
state_id_number: Numéro d’identification
- state_id_number_florida_hint: Il s’agit du numéro sur votre pièce d’identité qui
- commence par une lettre suivie de 12 chiffres. Les tirets ne sont pas
- obligatoires.
+ state_id_number_florida_hint_html: 'Il s’agit du numéro de votre carte
+ d’identité comportant une lettre et 12 chiffres.
+ Exemple: D123-456-78-901-2'
state_id_number_hint: 'Il peut s’agir de lettres, de chiffres et des symboles suivants:'
state_id_number_hint_asterisks: 'des astérisques'
state_id_number_hint_dashes: 'des tirets'
diff --git a/lib/identity_config.rb b/lib/identity_config.rb
index 423989b9d75..5ba16daf611 100644
--- a/lib/identity_config.rb
+++ b/lib/identity_config.rb
@@ -187,6 +187,7 @@ def self.build_store(config_map)
config.add(:doc_auth_max_submission_attempts_before_native_camera, type: :integer)
config.add(:doc_auth_s3_request_timeout, type: :integer)
config.add(:doc_auth_selfie_capture_enabled, type: :boolean)
+ config.add(:doc_auth_selfie_desktop_test_mode, type: :boolean)
config.add(:doc_auth_sdk_capture_orientation, type: :json, options: { symbolize_names: true })
config.add(:doc_auth_supported_country_codes, type: :json)
config.add(:doc_auth_vendor, type: :string)
diff --git a/spec/components/button_component_spec.rb b/spec/components/button_component_spec.rb
index ce0ef65f49e..f448f220ce9 100644
--- a/spec/components/button_component_spec.rb
+++ b/spec/components/button_component_spec.rb
@@ -83,7 +83,7 @@
it 'renders an icon' do
rendered = render_inline ButtonComponent.new(icon: :print).with_content(content)
- expect(rendered).to have_css('use[href$="#print"]')
+ expect(rendered).to have_xpath('//style[contains(text(), "/print-")]')
expect(rendered.first_element_child.xpath('./text()').text).to eq(content)
end
@@ -93,7 +93,7 @@
it 'trims text of the content, maintaining html safety' do
rendered = render_inline ButtonComponent.new(icon: :print).with_content(content)
- expect(rendered.to_html).to include('Button')
+ expect(rendered.to_html).to include('
Button')
end
end
@@ -103,7 +103,9 @@
it 'trims text of the content, maintaining html safety' do
rendered = render_inline ButtonComponent.new(icon: :print).with_content(content)
- expect(rendered.to_html).to include('<span class="example">Button</span>')
+ expect(rendered.to_html).to include(
+ '<span class="example">Button</span>',
+ )
end
end
diff --git a/spec/components/icon_component_spec.rb b/spec/components/icon_component_spec.rb
index ea92deadcaa..dc1fce80ed4 100644
--- a/spec/components/icon_component_spec.rb
+++ b/spec/components/icon_component_spec.rb
@@ -12,9 +12,15 @@
it 'renders icon svg' do
rendered = render_inline IconComponent.new(icon: :print)
- expect(rendered).to have_css(
- ".usa-icon use[href^='#{vc_test_request.base_url}'][href$='.svg#print']",
- )
+ icon = rendered.at_css('.icon.usa-icon')
+ id = icon.attr(:id)
+ inline_style = rendered.at_css('style').text.strip
+
+ expect(icon).to be_present
+ expect(inline_style).to match(%r{##{id}\s{.+?}}).
+ and(include('-webkit-mask-image:')).
+ and(include('mask-image:')).
+ and(match(%r{url\([^)]+/print-\w+\.svg\)}))
end
context 'with invalid icon' do
@@ -27,7 +33,7 @@
it 'adds size variant class' do
rendered = render_inline IconComponent.new(icon: :print, size: 2)
- expect(rendered).to have_css('.usa-icon.usa-icon--size-2')
+ expect(rendered).to have_css('.icon.usa-icon.usa-icon--size-2')
end
end
@@ -35,7 +41,7 @@
it 'renders with class' do
rendered = render_inline IconComponent.new(icon: :print, class: 'my-custom-class')
- expect(rendered).to have_css('.usa-icon.my-custom-class')
+ expect(rendered).to have_css('.icon.usa-icon.my-custom-class')
end
end
@@ -43,7 +49,7 @@
it 'renders with attributes' do
rendered = render_inline IconComponent.new(icon: :print, data: { foo: 'bar' })
- expect(rendered).to have_css('.usa-icon[data-foo="bar"]')
+ expect(rendered).to have_css('.icon.usa-icon[data-foo="bar"]')
end
end
@@ -55,9 +61,9 @@
it 'bypasses configured asset_host and uses domain_name instead' do
rendered = render_inline IconComponent.new(icon: :print)
- href = rendered.css('use').first['href']
+ inline_style = rendered.at_css('style').text.strip
- expect(href).to start_with(domain_name)
+ expect(inline_style).to match(%r{url\(#{Regexp.escape(domain_name)}})
end
end
end
diff --git a/spec/components/icon_list_component_spec.rb b/spec/components/icon_list_component_spec.rb
index 599ede1b38c..83acec19eb7 100644
--- a/spec/components/icon_list_component_spec.rb
+++ b/spec/components/icon_list_component_spec.rb
@@ -35,7 +35,7 @@
it 'renders items with default color' do
expect(rendered).to have_css('.usa-icon-list__icon:not([class*="text-"])', count: 2)
- expect(rendered).to have_css('.usa-icon use[href$=".svg#cancel"]', count: 2)
+ expect(rendered).to have_xpath('//style[contains(text(), "/cancel-")]')
end
context 'with icon or color attributes specified on parent component' do
@@ -48,7 +48,7 @@
it 'passes those attributes to slotted items' do
expect(rendered).to have_css('.usa-icon-list__icon.text-error', count: 2)
- expect(rendered).to have_css('.usa-icon use[href$=".svg#cancel"]', count: 2)
+ expect(rendered).to have_xpath('//style[contains(text(), "/cancel-")]', count: 2)
end
end
@@ -62,9 +62,9 @@
it 'renders items with their attributes' do
expect(rendered).to have_css('.usa-icon-list__icon.text-success', count: 1)
- expect(rendered).to have_css('.usa-icon use[href$=".svg#check_circle"]', count: 1)
+ expect(rendered).to have_xpath('//style[contains(text(), "/check_circle-")]', count: 1)
expect(rendered).to have_css('.usa-icon-list__icon.text-error', count: 1)
- expect(rendered).to have_css('.usa-icon use[href$=".svg#cancel"]', count: 1)
+ expect(rendered).to have_xpath('//style[contains(text(), "/cancel-")]', count: 1)
end
end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index e7d44516a9f..a98e5c0811a 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -459,31 +459,56 @@ def index
end
describe '#resolved_authn_context_result' do
- it 'returns a resolved authn context result' do
- sp = build(:service_provider, ial: 2)
- acr_values = [
- 'http://idmanagement.gov/ns/assurance/aal/1',
- ].join(' ')
- sp_session = { vtr: nil, acr_values: acr_values }
+ let(:sp) { build(:service_provider, ial: 2) }
+
+ let(:sp_session) { { vtr: vtr, acr_values: acr_values } }
+
+ let(:result) { subject.resolved_authn_context_result }
+
+ before do
allow(controller).to receive(:sp_from_sp_session).and_return(sp)
allow(controller).to receive(:sp_session).and_return(sp_session)
+ end
+
+ context 'when using acr values' do
+ let(:vtr) { nil }
+ let(:acr_values) do
+ [
+ 'http://idmanagement.gov/ns/assurance/aal/1',
+ ].join(' ')
+ end
- result = subject.resolved_authn_context_result
+ it 'returns a resolved authn context result' do
+ expect(result.aal2?).to eq(true)
+ expect(result.identity_proofing?).to eq(true)
+ end
+
+ context 'without an SP' do
+ let(:sp) { nil }
+ let(:sp_session) { nil }
- expect(result.aal2?).to eq(true)
- expect(result.identity_proofing?).to eq(true)
+ it 'returns a no-SP result' do
+ expect(result).to eq(Vot::Parser::Result.no_sp_result)
+ end
+ end
end
- context 'without an SP' do
- it 'returns a no-SP result' do
- sp = nil
- sp_session = {}
- allow(controller).to receive(:sp_from_sp_session).and_return(sp)
- allow(controller).to receive(:sp_session).and_return(sp_session)
+ context 'when using vot values' do
+ let(:acr_values) { nil }
+ let(:vtr) { ['P1'] }
- result = subject.resolved_authn_context_result
+ it 'returns a resolved authn context result' do
+ expect(result.aal2?).to eq(true)
+ expect(result.identity_proofing?).to eq(true)
+ end
+
+ context 'without an SP' do
+ let(:sp) { nil }
+ let(:sp_session) { nil }
- expect(result).to eq(Vot::Parser::Result.no_sp_result)
+ it 'returns a no-SP result' do
+ expect(result).to eq(Vot::Parser::Result.no_sp_result)
+ end
end
end
end
diff --git a/spec/controllers/concerns/idv/acuant_concern_spec.rb b/spec/controllers/concerns/idv/acuant_concern_spec.rb
index d10cf85358a..cb0523dfbea 100644
--- a/spec/controllers/concerns/idv/acuant_concern_spec.rb
+++ b/spec/controllers/concerns/idv/acuant_concern_spec.rb
@@ -91,5 +91,39 @@ def index; end
expect(csp.style_src).to include("'unsafe-inline'")
expect(csp.img_src).to include('blob:')
end
+
+ context 'with content security policy directives for style-src' do
+ let(:csp_nonce_directives) { ['style-src'] }
+
+ before do
+ request.content_security_policy_nonce_directives = csp_nonce_directives
+ end
+
+ it 'removes style-src nonce directive to allow all unsafe inline styles' do
+ get :index, params: { step: 'document_capture' }
+
+ csp = parse_content_security_policy
+
+ expect(csp['style-src']).to_not include(/'nonce-.+'/)
+
+ # Ensure that the default configuration is not mutated as a result of the request-specific
+ # revisions to the content security policy.
+ expect(csp_nonce_directives).to eq(['style-src'])
+ end
+ end
+ end
+
+ def parse_content_security_policy
+ header = response.request.content_security_policy.build(
+ controller,
+ request.content_security_policy_nonce_generator.call(request),
+ request.content_security_policy_nonce_directives,
+ )
+ header.split(';').each_with_object({}) do |directive, result|
+ tokens = directive.strip.split(/\s+/)
+ key = tokens.first
+ rules = tokens[1..-1]
+ result[key] = rules
+ end
end
end
diff --git a/spec/controllers/concerns/idv/threat_metrix_concern_spec.rb b/spec/controllers/concerns/idv/threat_metrix_concern_spec.rb
index 5acb708f3c9..c0866e3e6a6 100644
--- a/spec/controllers/concerns/idv/threat_metrix_concern_spec.rb
+++ b/spec/controllers/concerns/idv/threat_metrix_concern_spec.rb
@@ -38,6 +38,26 @@ def index; end
expect(csp.directives['img-src']).to include('*.online-metrix.net')
end
end
+
+ context 'with content security policy directives for style-src' do
+ let(:csp_nonce_directives) { ['style-src'] }
+
+ before do
+ request.content_security_policy_nonce_directives = csp_nonce_directives
+ end
+
+ it 'removes style-src nonce directive to allow all unsafe inline styles' do
+ get :index
+
+ csp = parse_content_security_policy
+
+ expect(csp['style-src']).to_not include(/'nonce-.+'/)
+
+ # Ensure that the default configuration is not mutated as a result of the request-specific
+ # revisions to the content security policy.
+ expect(csp_nonce_directives).to eq(['style-src'])
+ end
+ end
end
context 'ff is not set' do
@@ -49,4 +69,18 @@ def index; end
end
end
end
+
+ def parse_content_security_policy
+ header = response.request.content_security_policy.build(
+ controller,
+ request.content_security_policy_nonce_generator.call(request),
+ request.content_security_policy_nonce_directives,
+ )
+ header.split(';').each_with_object({}) do |directive, result|
+ tokens = directive.strip.split(/\s+/)
+ key = tokens.first
+ rules = tokens[1..-1]
+ result[key] = rules
+ end
+ end
end
diff --git a/spec/controllers/idv/document_capture_controller_spec.rb b/spec/controllers/idv/document_capture_controller_spec.rb
index 6f6f8978a6d..dc1c7bec1ae 100644
--- a/spec/controllers/idv/document_capture_controller_spec.rb
+++ b/spec/controllers/idv/document_capture_controller_spec.rb
@@ -20,12 +20,19 @@
{ sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 }
end
+ # selfie related test flags
+ let(:doc_auth_selfie_capture_enabled) { false }
+ let(:sp_selfie_enabled) { false }
+ let(:flow_path) { 'standard' }
+
before do
stub_sign_in(user)
stub_up_to(:hybrid_handoff, idv_session: subject.idv_session)
stub_analytics
subject.idv_session.document_capture_session_uuid = document_capture_session_uuid
-
+ allow(controller.decorated_sp_session).to receive(:selfie_required?).
+ and_return(doc_auth_selfie_capture_enabled && sp_selfie_enabled)
+ subject.idv_session.flow_path = flow_path
allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args)
end
@@ -33,6 +40,32 @@
it 'returns a valid StepInfo object' do
expect(Idv::DocumentCaptureController.step_info).to be_valid
end
+ context 'when selfie feature is enabled system wide' do
+ let(:doc_auth_selfie_capture_enabled) { true }
+ describe 'with sp selfie disabled' do
+ let(:sp_selfie_enabled) { false }
+ it 'does not satisfy precondition' do
+ expect(Idv::DocumentCaptureController.step_info.preconditions.is_a?(Proc))
+ expect(subject).to receive(:render).
+ with(:show, locals: an_instance_of(Hash)).and_call_original
+ get :show
+ expect(response).to render_template :show
+ end
+ end
+ describe 'with sp selfie enabled' do
+ let(:sp_selfie_enabled) { true }
+ before do
+ allow(IdentityConfig.store).to receive(:doc_auth_selfie_desktop_test_mode).
+ and_return(false)
+ end
+ it 'does satisfy precondition' do
+ expect(Idv::DocumentCaptureController.step_info.preconditions.is_a?(Proc))
+ expect(subject).not_to receive(:render).with(:show, locals: an_instance_of(Hash))
+ get :show
+ expect(response).to redirect_to(idv_hybrid_handoff_path)
+ end
+ end
+ end
end
describe 'before_actions' do
@@ -61,6 +94,8 @@
skip_hybrid_handoff: nil,
irs_reproofing: false,
step: 'document_capture',
+ liveness_checking_required: false,
+ selfie_check_required: sp_selfie_enabled && doc_auth_selfie_capture_enabled,
}.merge(ab_test_args)
end
@@ -79,23 +114,44 @@
end
context 'when a selfie is requested' do
+ let(:doc_auth_selfie_capture_enabled) { true }
+ let(:sp_selfie_enabled) { true }
+ let(:desktop_selfie_enabled) { false }
before do
- allow(subject).to receive(:decorated_sp_session).
- and_return(double('decorated_session', { selfie_required?: true, sp_name: 'sp' }))
+ allow(IdentityConfig.store).to receive(:doc_auth_selfie_desktop_test_mode).
+ and_return(desktop_selfie_enabled)
+ end
+ describe 'when desktop selfie disabled' do
+ let(:desktop_selfie_enabled) { false }
+ it 'redirect back to handoff page' do
+ expect(subject).not_to receive(:render).with(
+ :show,
+ locals: hash_including(
+ document_capture_session_uuid: document_capture_session_uuid,
+ doc_auth_selfie_capture: true,
+ ),
+ ).and_call_original
+
+ get :show
+
+ expect(response).to redirect_to(idv_hybrid_handoff_path)
+ end
end
- it 'renders the show template with selfie' do
- expect(subject).to receive(:render).with(
- :show,
- locals: hash_including(
- document_capture_session_uuid: document_capture_session_uuid,
- doc_auth_selfie_capture: true,
- ),
- ).and_call_original
-
- get :show
-
- expect(response).to render_template :show
+ describe 'when desktop selfie enabled' do
+ let(:desktop_selfie_enabled) { true }
+ it 'allows capture' do
+ expect(subject).to receive(:render).with(
+ :show,
+ locals: hash_including(
+ document_capture_session_uuid: document_capture_session_uuid,
+ doc_auth_selfie_capture: true,
+ ),
+ ).and_call_original
+
+ get :show
+ expect(response).to render_template :show
+ end
end
end
@@ -208,6 +264,8 @@
skip_hybrid_handoff: nil,
irs_reproofing: false,
step: 'document_capture',
+ liveness_checking_required: false,
+ selfie_check_required: sp_selfie_enabled && doc_auth_selfie_capture_enabled,
}.merge(ab_test_args)
end
let(:result) { { success: true, errors: {} } }
diff --git a/spec/controllers/idv/hybrid_handoff_controller_spec.rb b/spec/controllers/idv/hybrid_handoff_controller_spec.rb
index 8e35f7808b4..8efff18e1cd 100644
--- a/spec/controllers/idv/hybrid_handoff_controller_spec.rb
+++ b/spec/controllers/idv/hybrid_handoff_controller_spec.rb
@@ -13,18 +13,26 @@
end
let(:in_person_proofing) { false }
let(:ipp_opt_in_enabled) { false }
+ let(:doc_auth_selfie_capture_enabled) { false }
+ let(:sp_selfie_enabled) { false }
before do
+ allow(controller).to receive(:current_sp).
+ and_return(service_provider)
stub_sign_in(user)
stub_up_to(:agreement, idv_session: subject.idv_session)
stub_analytics
stub_attempts_tracker
allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args)
allow(subject.idv_session).to receive(:service_provider).and_return(service_provider)
+ allow(subject.decorated_sp_session).to receive(:selfie_required?).
+ and_return(sp_selfie_enabled && doc_auth_selfie_capture_enabled)
allow(IdentityConfig.store).to receive(:in_person_proofing_enabled) { in_person_proofing }
allow(IdentityConfig.store).to receive(:in_person_proofing_opt_in_enabled) {
ipp_opt_in_enabled
}
+ allow(IdentityConfig.store).to receive(:doc_auth_selfie_capture_enabled).
+ and_return(doc_auth_selfie_capture_enabled)
end
describe '#step_info' do
@@ -58,6 +66,7 @@
redo_document_capture: nil,
skip_hybrid_handoff: nil,
irs_reproofing: false,
+ selfie_check_required: sp_selfie_enabled && doc_auth_selfie_capture_enabled,
}.merge(ab_test_args)
end
@@ -200,6 +209,8 @@
context 'opt in selection is nil' do
before do
+ allow(IdentityConfig.store).to receive(:doc_auth_selfie_desktop_test_mode).
+ and_return(false)
subject.idv_session.skip_doc_auth = nil
end
@@ -221,6 +232,8 @@
context 'opted in to ipp flow' do
before do
+ allow(IdentityConfig.store).to receive(:doc_auth_selfie_desktop_test_mode).
+ and_return(false)
subject.idv_session.skip_doc_auth = true
end
@@ -244,6 +257,28 @@
end
end
end
+
+ context 'with selfie enabled system wide' do
+ let(:doc_auth_selfie_capture_enabled) { true }
+ describe 'when selfie is enabled for sp' do
+ let(:sp_selfie_enabled) { true }
+ it 'pass on correct flags and states and logs correct info' do
+ get :show
+ expect(response).to render_template :show
+ expect(@analytics).to have_logged_event(analytics_name, analytics_args)
+ expect(subject.idv_session.selfie_check_required).to eq(true)
+ end
+ end
+ describe 'when selfie is disabled for sp' do
+ let(:sp_selfie_enabled) { false }
+ it 'pass on correct flags and states and logs correct info' do
+ get :show
+ expect(response).to render_template :show
+ expect(subject.idv_session.selfie_check_required).to eq(false)
+ expect(@analytics).to have_logged_event(analytics_name, analytics_args)
+ end
+ end
+ end
end
describe '#update' do
@@ -260,6 +295,7 @@
analytics_id: 'Doc Auth',
redo_document_capture: nil,
skip_hybrid_handoff: nil,
+ selfie_check_required: sp_selfie_enabled && doc_auth_selfie_capture_enabled,
irs_reproofing: false,
telephony_response: {
errors: {},
@@ -279,6 +315,10 @@
let(:document_capture_session_uuid) { '09228b6d-dd39-4925-bf82-b69104095517' }
+ before do
+ subject.idv_session.document_capture_session_uuid = document_capture_session_uuid
+ end
+
it 'invalidates future steps' do
expect(subject).to receive(:clear_future_steps!)
@@ -292,10 +332,6 @@
expect(@analytics).to have_logged_event(analytics_name, analytics_args)
end
- before do
- subject.idv_session.document_capture_session_uuid = document_capture_session_uuid
- end
-
it 'sends a doc auth link' do
expect(Telephony).to receive(:send_doc_auth_link).with(
hash_including(
@@ -319,6 +355,7 @@
redo_document_capture: nil,
skip_hybrid_handoff: nil,
irs_reproofing: false,
+ selfie_check_required: doc_auth_selfie_capture_enabled && sp_selfie_enabled,
}.merge(ab_test_args)
end
diff --git a/spec/controllers/idv/hybrid_mobile/capture_complete_controller_spec.rb b/spec/controllers/idv/hybrid_mobile/capture_complete_controller_spec.rb
index b52c26a27a1..9aeb83c60a8 100644
--- a/spec/controllers/idv/hybrid_mobile/capture_complete_controller_spec.rb
+++ b/spec/controllers/idv/hybrid_mobile/capture_complete_controller_spec.rb
@@ -51,6 +51,7 @@
flow_path: 'hybrid',
irs_reproofing: false,
step: 'capture_complete',
+ liveness_checking_required: false,
}.merge(ab_test_args)
end
diff --git a/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb b/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb
index f1774b9e0f3..0ad2cc44fc0 100644
--- a/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb
+++ b/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb
@@ -58,6 +58,8 @@
flow_path: 'hybrid',
irs_reproofing: false,
step: 'document_capture',
+ liveness_checking_required: false,
+ selfie_check_required: boolean,
}.merge(ab_test_args)
end
@@ -181,6 +183,8 @@
flow_path: 'hybrid',
irs_reproofing: false,
step: 'document_capture',
+ liveness_checking_required: false,
+ selfie_check_required: boolean,
}.merge(ab_test_args)
end
diff --git a/spec/controllers/idv/image_uploads_controller_spec.rb b/spec/controllers/idv/image_uploads_controller_spec.rb
index 4a45e0ed0c8..157393aa063 100644
--- a/spec/controllers/idv/image_uploads_controller_spec.rb
+++ b/spec/controllers/idv/image_uploads_controller_spec.rb
@@ -369,6 +369,7 @@
user_uuid: an_instance_of(String),
uuid_prefix: nil,
liveness_checking_required: true,
+ images_cropped: false,
).and_call_original
action
@@ -390,6 +391,7 @@
user_uuid: an_instance_of(String),
uuid_prefix: nil,
liveness_checking_required: false,
+ images_cropped: false,
).and_call_original
action
@@ -451,6 +453,7 @@
address_line2_present: nil,
alert_failure_count: nil,
conversation_id: nil,
+ request_id: nil,
decision_product_status: nil,
image_metrics: nil,
log_alert_results: nil,
@@ -651,6 +654,7 @@
address_line2_present: nil,
alert_failure_count: nil,
conversation_id: nil,
+ request_id: nil,
decision_product_status: nil,
image_metrics: nil,
log_alert_results: nil,
@@ -763,6 +767,7 @@
address_line2_present: nil,
alert_failure_count: nil,
conversation_id: nil,
+ request_id: nil,
decision_product_status: nil,
image_metrics: nil,
log_alert_results: nil,
@@ -875,6 +880,7 @@
address_line2_present: nil,
alert_failure_count: nil,
conversation_id: nil,
+ request_id: nil,
decision_product_status: nil,
image_metrics: nil,
log_alert_results: nil,
@@ -984,6 +990,7 @@
address_line2_present: nil,
alert_failure_count: nil,
conversation_id: nil,
+ request_id: nil,
decision_product_status: nil,
image_metrics: nil,
log_alert_results: nil,
@@ -1118,6 +1125,7 @@
address_line2_present: nil,
alert_failure_count: nil,
conversation_id: nil,
+ request_id: nil,
decision_product_status: nil,
image_metrics: nil,
log_alert_results: nil,
@@ -1209,6 +1217,7 @@
address_line2_present: nil,
alert_failure_count: nil,
conversation_id: nil,
+ request_id: nil,
decision_product_status: nil,
image_metrics: nil,
log_alert_results: nil,
@@ -1281,6 +1290,7 @@
user_uuid: an_instance_of(String),
uuid_prefix: nil,
liveness_checking_required: true,
+ images_cropped: false,
).and_call_original
action
diff --git a/spec/controllers/openid_connect/authorization_controller_spec.rb b/spec/controllers/openid_connect/authorization_controller_spec.rb
index a85faf7b05c..bd3ef16221b 100644
--- a/spec/controllers/openid_connect/authorization_controller_spec.rb
+++ b/spec/controllers/openid_connect/authorization_controller_spec.rb
@@ -1080,7 +1080,6 @@
expect(session[:sp]).to eq(
aal_level_requested: 1,
acr_values: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF,
- piv_cac_requested: false,
issuer: 'urn:gov:gsa:openidconnect:test',
request_id: sp_request_id,
request_url: request.original_url,
diff --git a/spec/controllers/saml_idp_controller_spec.rb b/spec/controllers/saml_idp_controller_spec.rb
index 7d37e6d9098..7acf57c79ec 100644
--- a/spec/controllers/saml_idp_controller_spec.rb
+++ b/spec/controllers/saml_idp_controller_spec.rb
@@ -541,12 +541,12 @@ def name_id_version(format_urn)
)
end
- shared_examples 'a verified identity' do |authn_context, ial|
+ context 'with IAL2 and the identity is already verified' do
let(:ial2_settings) do
saml_settings(
overrides: {
issuer: sp1_issuer,
- authn_context: authn_context,
+ authn_context: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF,
},
)
end
@@ -563,7 +563,7 @@ def name_id_version(format_urn)
ial2_authnrequest = saml_authn_request_url(
overrides: {
issuer: sp1_issuer,
- authn_context: authn_context,
+ authn_context: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF,
},
)
raw_req = CGI.unescape ial2_authnrequest.split('SAMLRequest').last
@@ -584,7 +584,7 @@ def name_id_version(format_urn)
before do
stub_sign_in(user)
session[:sign_in_flow] = sign_in_flow
- IdentityLinker.new(user, sp1).link_identity(ial: ial)
+ IdentityLinker.new(user, sp1).link_identity(ial: Idp::Constants::IAL2)
user.identities.last.update!(
verified_attributes: %w[given_name family_name social_security_number address],
)
@@ -606,7 +606,7 @@ def name_id_version(format_urn)
it 'sets identity ial' do
saml_get_auth(ial2_settings)
- expect(user.identities.last.ial).to eq(ial)
+ expect(user.identities.last.ial).to eq(Idp::Constants::IAL2)
end
it 'does not redirect the user to the IdV URL' do
@@ -634,7 +634,7 @@ def name_id_version(format_urn)
stub_analytics
expect(@analytics).to receive(:track_event).
with('SAML Auth Request', {
- requested_ial: authn_context,
+ requested_ial: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF,
service_provider: sp1_issuer,
force_authn: false,
user_fully_authenticated: true,
@@ -644,9 +644,9 @@ def name_id_version(format_urn)
success: true,
errors: {},
nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT,
- authn_context: [authn_context],
+ authn_context: [Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF],
authn_context_comparison: 'exact',
- requested_ial: authn_context,
+ requested_ial: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF,
service_provider: sp1_issuer,
endpoint: "/api/saml/auth#{path_year}",
idv: false,
@@ -656,8 +656,8 @@ def name_id_version(format_urn)
})
expect(@analytics).to receive(:track_event).with(
'SP redirect initiated',
- ial: ial,
- billed_ial: [ial, 2].min,
+ ial: Idp::Constants::IAL2,
+ billed_ial: Idp::Constants::IAL2,
sign_in_flow:,
)
@@ -675,14 +675,8 @@ def name_id_version(format_urn)
end
end
- context 'with IAL2 and the identity is already verified' do
- it_behaves_like 'a verified identity',
- Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF,
- Idp::Constants::IAL2
- end
-
context 'with IAL2 and the profile is reset' do
- it 'redirects to IdV URL for IAL2 proofer' do
+ it 'redirects to reactivate account path' do
user = create(:profile, :verified, :password_reset).user
generate_saml_response(user, ial2_settings)
@@ -1194,8 +1188,7 @@ def name_id_version(format_urn)
sp_request_id = ServiceProviderRequestProxy.last.uuid
expect(session[:sp]).to eq(
issuer: saml_settings.issuer,
- aal_level_requested: aal_level,
- piv_cac_requested: false,
+ aal_level_requested: 1,
acr_values: acr_values,
request_url: @stored_request_url.gsub('authpost', 'auth'),
request_id: sp_request_id,
@@ -1230,9 +1223,8 @@ def name_id_version(format_urn)
expect(session[:sp]).to eq(
issuer: saml_settings.issuer,
- aal_level_requested: aal_level,
+ aal_level_requested: 1,
acr_values: acr_values,
- piv_cac_requested: false,
request_url: @saml_request.request.original_url.gsub('authpost', 'auth'),
request_id: sp_request_id,
requested_attributes: ['email'],
diff --git a/spec/controllers/users/piv_cac_login_controller_spec.rb b/spec/controllers/users/piv_cac_login_controller_spec.rb
index 220184e2027..b3fea8d2cf2 100644
--- a/spec/controllers/users/piv_cac_login_controller_spec.rb
+++ b/spec/controllers/users/piv_cac_login_controller_spec.rb
@@ -44,13 +44,9 @@
context 'with a valid token' do
let(:service_provider) { create(:service_provider) }
- let(:sp_session) do
- {
- acr_values: Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF,
- issuer: service_provider.issuer,
- }
- end
+ let(:sp_session) { { ial: 1, issuer: service_provider.issuer, vtr: vtr } }
let(:nonce) { SecureRandom.base64(20) }
+ let(:vtr) { ['C1'] }
let(:data) do
{
nonce: nonce,
@@ -186,6 +182,13 @@
issuer: service_provider.issuer,
}
end
+ let(:sp_session) do
+ {
+ ial: Idp::Constants::IAL2,
+ issuer: service_provider.issuer,
+ vtr: vtr,
+ }
+ end
it 'redirects to account' do
expect(response).to redirect_to(account_url)
@@ -194,10 +197,7 @@
context 'ial_max service level' do
let(:sp_session) do
- {
- acr_values: Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF,
- issuer: service_provider.issuer,
- }
+ { ial: Idp::Constants::IAL_MAX, issuer: service_provider.issuer, vtr: vtr }
end
it 'redirects to the after_sign_in_path_for' do
diff --git a/spec/decorators/null_service_provider_session_spec.rb b/spec/decorators/null_service_provider_session_spec.rb
index c36fc8b16c1..defddff9085 100644
--- a/spec/decorators/null_service_provider_session_spec.rb
+++ b/spec/decorators/null_service_provider_session_spec.rb
@@ -41,7 +41,7 @@
describe '#mfa_expiration_interval' do
it 'returns the AAL1 expiration interval' do
- expect(subject.mfa_expiration_interval).to eq(30.days)
+ expect(subject.mfa_expiration_interval(nil)).to eq(30.days)
end
end
diff --git a/spec/decorators/service_provider_session_spec.rb b/spec/decorators/service_provider_session_spec.rb
index 696bb43f79c..a09b6c597cc 100644
--- a/spec/decorators/service_provider_session_spec.rb
+++ b/spec/decorators/service_provider_session_spec.rb
@@ -241,12 +241,24 @@
end
describe '#mfa_expiration_interval' do
+ let(:authorization_context) do
+ Vot::Parser::Result.new(
+ component_values: [],
+ aal2?: false,
+ phishing_resistant?: false,
+ hspd12?: false,
+ identity_proofing?: false,
+ biometric_comparison?: false,
+ ialmax?: false,
+ )
+ end
+
context 'with an AAL2 sp' do
before do
allow(sp).to receive(:default_aal).and_return(2)
end
- it { expect(subject.mfa_expiration_interval).to eq(0.hours) }
+ it { expect(subject.mfa_expiration_interval(authorization_context)).to eq(0.hours) }
end
context 'with an IAL2 sp' do
@@ -254,11 +266,11 @@
allow(sp).to receive(:ial).and_return(2)
end
- it { expect(subject.mfa_expiration_interval).to eq(0.hours) }
+ it { expect(subject.mfa_expiration_interval(authorization_context)).to eq(0.hours) }
end
context 'with an sp that is not AAL2 or IAL2' do
- it { expect(subject.mfa_expiration_interval).to eq(30.days) }
+ it { expect(subject.mfa_expiration_interval(authorization_context)).to eq(30.days) }
end
end
diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb
index ffa7aa1cf13..d5e7160a12d 100644
--- a/spec/features/idv/analytics_spec.rb
+++ b/spec/features/idv/analytics_spec.rb
@@ -54,13 +54,13 @@
success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default
},
'IdV: doc auth hybrid handoff visited' => {
- step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, selfie_check_required: boolean
},
'IdV: doc auth hybrid handoff submitted' => {
- success: true, errors: {}, destination: :document_capture, flow_path: 'standard', step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ success: true, errors: {}, destination: :document_capture, flow_path: 'standard', step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, selfie_check_required: boolean
},
'IdV: doc auth document_capture visited' => {
- flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, selfie_check_required: boolean, liveness_checking_required: boolean
},
'Frontend: IdV: front image added' => {
width: 284, height: 38, mimeType: 'image/png', source: 'upload', size: 3694, captureAttempts: 1, flow_path: 'standard', acuant_sdk_upgrade_a_b_testing_enabled: 'false', use_alternate_sdk: anything, acuant_version: anything, acuantCaptureMode: nil, fingerprint: anything, failedImageResubmission: boolean, documentType: nil, dpi: nil, glare: nil, glareScoreThreshold: nil, isAssessedAsBlurry: nil, isAssessedAsGlare: nil, isAssessedAsUnsupported: nil, moire: nil, sharpness: nil, sharpnessScoreThreshold: nil, assessment: nil
@@ -71,11 +71,12 @@
'IdV: doc auth image upload form submitted' => {
success: true, errors: {}, submit_attempts: 1, remaining_submit_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), selfie_image_fingerprint: nil, liveness_checking_required: boolean
},
+ 'IdV: doc auth image upload vendor submitted' => hash_including(success: true, flow_path: 'standard', attention_with_barcode: false, doc_auth_result: 'Passed', liveness_checking_required: boolean),
'IdV: doc auth image upload vendor pii validation' => {
success: true, errors: {}, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: 3, flow_path: 'standard', attention_with_barcode: false, front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), selfie_image_fingerprint: nil, liveness_checking_required: boolean, classification_info: {}
},
'IdV: doc auth document_capture submitted' => {
- success: true, errors: {}, flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ success: true, errors: {}, flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, selfie_check_required: boolean, liveness_checking_required: boolean
},
'IdV: doc auth ssn visited' => {
flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
@@ -162,13 +163,13 @@
success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default
},
'IdV: doc auth hybrid handoff visited' => {
- step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, selfie_check_required: boolean
},
'IdV: doc auth hybrid handoff submitted' => {
- success: true, errors: hash_including(message: nil), destination: :link_sent, flow_path: 'hybrid', step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, telephony_response: hash_including(errors: {}, message_id: 'fake-message-id', request_id: 'fake-message-request-id', success: true)
+ success: true, errors: hash_including(message: nil), destination: :link_sent, flow_path: 'hybrid', step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, telephony_response: hash_including(errors: {}, message_id: 'fake-message-id', request_id: 'fake-message-request-id', success: true), selfie_check_required: boolean
},
'IdV: doc auth document_capture visited' => {
- flow_path: 'hybrid', step: 'document_capture', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false
+ flow_path: 'hybrid', step: 'document_capture', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false, selfie_check_required: boolean, liveness_checking_required: boolean
},
'Frontend: IdV: front image added' => {
width: 284, height: 38, mimeType: 'image/png', source: 'upload', size: 3694, captureAttempts: 1, flow_path: 'hybrid', acuant_sdk_upgrade_a_b_testing_enabled: 'false', use_alternate_sdk: anything, acuant_version: anything, acuantCaptureMode: nil, fingerprint: anything, failedImageResubmission: boolean, documentType: nil, dpi: nil, glare: nil, glareScoreThreshold: nil, isAssessedAsBlurry: nil, isAssessedAsGlare: nil, isAssessedAsUnsupported: nil, moire: nil, sharpness: nil, sharpnessScoreThreshold: nil, assessment: nil
@@ -179,11 +180,12 @@
'IdV: doc auth image upload form submitted' => {
success: true, errors: {}, submit_attempts: 1, remaining_submit_attempts: 3, user_id: user.uuid, flow_path: 'hybrid', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), selfie_image_fingerprint: nil, liveness_checking_required: boolean
},
+ 'IdV: doc auth image upload vendor submitted' => hash_including(success: true, flow_path: 'hybrid', attention_with_barcode: false, doc_auth_result: 'Passed', liveness_checking_required: boolean),
'IdV: doc auth image upload vendor pii validation' => {
success: true, errors: {}, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: 3, flow_path: 'hybrid', attention_with_barcode: false, front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), selfie_image_fingerprint: nil, liveness_checking_required: boolean, classification_info: {}
},
'IdV: doc auth document_capture submitted' => {
- success: true, errors: {}, flow_path: 'hybrid', step: 'document_capture', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false
+ success: true, errors: {}, flow_path: 'hybrid', step: 'document_capture', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false, selfie_check_required: boolean, liveness_checking_required: boolean
},
'IdV: doc auth ssn visited' => {
flow_path: 'hybrid', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
@@ -267,13 +269,13 @@
success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default
},
'IdV: doc auth hybrid handoff visited' => {
- step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, selfie_check_required: boolean
},
'IdV: doc auth hybrid handoff submitted' => {
- success: true, errors: {}, destination: :document_capture, flow_path: 'standard', redo_document_capture: nil, step: 'hybrid_handoff', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ success: true, errors: {}, destination: :document_capture, flow_path: 'standard', redo_document_capture: nil, step: 'hybrid_handoff', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, selfie_check_required: boolean
},
'IdV: doc auth document_capture visited' => {
- flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
+ flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false, selfie_check_required: boolean, liveness_checking_required: boolean
},
'Frontend: IdV: front image added' => {
width: 284, height: 38, mimeType: 'image/png', source: 'upload', size: 3694, captureAttempts: 1, flow_path: 'standard', acuant_sdk_upgrade_a_b_testing_enabled: 'false', use_alternate_sdk: anything, acuant_version: anything, acuantCaptureMode: nil, fingerprint: anything, failedImageResubmission: boolean, documentType: nil, dpi: nil, glare: nil, glareScoreThreshold: nil, isAssessedAsBlurry: nil, isAssessedAsGlare: nil, isAssessedAsUnsupported: nil, moire: nil, sharpness: nil, sharpnessScoreThreshold: nil, assessment: nil
@@ -284,11 +286,12 @@
'IdV: doc auth image upload form submitted' => {
success: true, errors: {}, submit_attempts: 1, remaining_submit_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), selfie_image_fingerprint: nil, liveness_checking_required: boolean
},
+ 'IdV: doc auth image upload vendor submitted' => hash_including(success: true, flow_path: 'standard', attention_with_barcode: false, doc_auth_result: 'Passed', liveness_checking_required: boolean),
'IdV: doc auth image upload vendor pii validation' => {
success: true, errors: {}, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: 3, flow_path: 'standard', attention_with_barcode: false, front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), selfie_image_fingerprint: nil, liveness_checking_required: boolean, classification_info: {}
},
'IdV: doc auth document_capture submitted' => {
- success: true, errors: {}, flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
+ success: true, errors: {}, flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false, selfie_check_required: boolean, liveness_checking_required: boolean
},
'IdV: doc auth ssn visited' => {
flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
@@ -354,13 +357,13 @@
success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default
},
'IdV: doc auth hybrid handoff visited' => {
- step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, selfie_check_required: boolean
},
'IdV: doc auth hybrid handoff submitted' => {
- success: true, errors: {}, destination: :document_capture, flow_path: 'standard', redo_document_capture: nil, step: 'hybrid_handoff', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ success: true, errors: {}, destination: :document_capture, flow_path: 'standard', redo_document_capture: nil, step: 'hybrid_handoff', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, selfie_check_required: boolean
},
'IdV: doc auth document_capture visited' => {
- flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, selfie_check_required: boolean, liveness_checking_required: boolean
},
'Frontend: IdV: front image added' => {
width: 284, height: 38, mimeType: 'image/png', source: 'upload', size: 3694, captureAttempts: 1, flow_path: 'standard', acuant_sdk_upgrade_a_b_testing_enabled: 'false', use_alternate_sdk: anything, acuant_version: anything, acuantCaptureMode: nil, fingerprint: anything, failedImageResubmission: boolean, documentType: nil, dpi: nil, glare: nil, glareScoreThreshold: nil, isAssessedAsBlurry: nil, isAssessedAsGlare: nil, isAssessedAsUnsupported: nil, moire: nil, sharpness: nil, sharpnessScoreThreshold: nil, assessment: nil
@@ -371,7 +374,7 @@
'IdV: doc auth image upload form submitted' => {
success: true, errors: {}, submit_attempts: 1, remaining_submit_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), selfie_image_fingerprint: nil, liveness_checking_required: boolean
},
- 'IdV: doc auth image upload vendor submitted' => hash_including(success: true, flow_path: 'standard', attention_with_barcode: true, doc_auth_result: 'Attention'),
+ 'IdV: doc auth image upload vendor submitted' => hash_including(success: true, flow_path: 'standard', attention_with_barcode: true, doc_auth_result: 'Attention', liveness_checking_required: boolean),
'IdV: verify in person troubleshooting option clicked' => {
flow_path: 'standard', opted_in_to_in_person_proofing: nil
},
@@ -468,32 +471,32 @@
}
end
- let(:happy_selfie_path_events) do
+ let(:happy_mobile_selfie_path_events) do
{
'IdV: intro visited' => {},
'IdV: doc auth welcome visited' => {
- step: 'welcome', analytics_id: 'Doc Auth', irs_reproofing: false, skip_hybrid_handoff: nil, lexisnexis_instant_verify_workflow_ab_test_bucket: :default
+ step: 'welcome', analytics_id: 'Doc Auth', irs_reproofing: false, skip_hybrid_handoff: anything, lexisnexis_instant_verify_workflow_ab_test_bucket: :default
},
'IdV: doc auth welcome submitted' => {
- step: 'welcome', analytics_id: 'Doc Auth', irs_reproofing: false, skip_hybrid_handoff: nil, lexisnexis_instant_verify_workflow_ab_test_bucket: :default
+ step: 'welcome', analytics_id: 'Doc Auth', irs_reproofing: false, skip_hybrid_handoff: anything, lexisnexis_instant_verify_workflow_ab_test_bucket: :default
},
'IdV: doc auth agreement visited' => {
- step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default
+ step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: anything, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default
},
'IdV: consent checkbox toggled' => {
checked: true,
},
'IdV: doc auth agreement submitted' => {
- success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default
+ success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: anything, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default
},
'IdV: doc auth hybrid handoff visited' => {
- step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, selfie_check_required: boolean
},
'IdV: doc auth hybrid handoff submitted' => {
- success: true, errors: {}, destination: :document_capture, flow_path: 'standard', step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ success: true, errors: {}, destination: :document_capture, flow_path: 'standard', step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, selfie_check_required: boolean
},
'IdV: doc auth document_capture visited' => {
- flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, skip_hybrid_handoff: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false
+ flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, skip_hybrid_handoff: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false, selfie_check_required: boolean, liveness_checking_required: true
},
'Frontend: IdV: front image added' => {
width: 284, height: 38, mimeType: 'image/png', source: 'upload', size: 3694, captureAttempts: 1, flow_path: 'standard', acuant_sdk_upgrade_a_b_testing_enabled: 'false', use_alternate_sdk: anything, acuant_version: anything, acuantCaptureMode: nil, fingerprint: anything, failedImageResubmission: boolean, documentType: nil, dpi: nil, glare: nil, glareScoreThreshold: nil, isAssessedAsBlurry: nil, isAssessedAsGlare: nil, isAssessedAsUnsupported: nil, moire: nil, sharpness: nil, sharpnessScoreThreshold: nil, assessment: nil
@@ -504,37 +507,38 @@
'IdV: doc auth image upload form submitted' => {
success: true, errors: {}, submit_attempts: 1, remaining_submit_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), selfie_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean
},
+ 'IdV: doc auth image upload vendor submitted' => hash_including(success: true, flow_path: 'standard', attention_with_barcode: false, doc_auth_result: 'Passed', liveness_checking_required: boolean),
'IdV: doc auth image upload vendor pii validation' => {
success: true, errors: {}, user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: 3, flow_path: 'standard', attention_with_barcode: false, front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), selfie_image_fingerprint: an_instance_of(String), liveness_checking_required: boolean, classification_info: {}
},
'IdV: doc auth document_capture submitted' => {
- success: true, errors: {}, flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, skip_hybrid_handoff: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false
+ success: true, errors: {}, flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, skip_hybrid_handoff: nil, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false, selfie_check_required: boolean, liveness_checking_required: true
},
:idv_selfie_image_file_uploaded => {
captureAttempts: 1, failedImageResubmission: nil, fingerprint: 'aIzxkX_iMtoxFOURZr55qkshs53emQKUOr7VfTf6G1Q', flow_path: 'standard', height: 38, mimeType: 'image/png', size: 3694, source: 'upload', width: 284
},
'IdV: doc auth ssn visited' => {
- flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
+ flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: anything, analytics_id: 'Doc Auth', irs_reproofing: false
},
'IdV: doc auth ssn submitted' => {
- success: true, errors: {}, flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
+ success: true, errors: {}, flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: anything, analytics_id: 'Doc Auth', irs_reproofing: false
},
'IdV: doc auth verify visited' => {
- flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
+ flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: anything, analytics_id: 'Doc Auth', irs_reproofing: false
},
'IdV: doc auth verify submitted' => {
- flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
+ flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: anything, analytics_id: 'Doc Auth', irs_reproofing: false
},
'IdV: doc auth verify proofing results' => {
- success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', ssn_is_unique: true, step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, irs_reproofing: false, skip_hybrid_handoff: nil,
+ success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', ssn_is_unique: true, step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, irs_reproofing: false, skip_hybrid_handoff: anything,
proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, residential_address: { attributes_requiring_additional_verification: [], can_pass_with_additional_verification: false, errors: {}, exception: nil, reference: '', success: true, timed_out: false, transaction_id: '', vendor_name: 'ResidentialAddressNotRequired', vendor_workflow: nil }, state_id: { success: true, errors: {}, exception: nil, mva_exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: threatmetrix_response } } }
},
'IdV: phone of record visited' => {
- acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil,
+ acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: anything,
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass' }
},
'IdV: phone confirmation form' => {
- success: true, errors: {}, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil, otp_delivery_preference: 'sms',
+ success: true, errors: {}, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: anything, otp_delivery_preference: 'sms',
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass' }
},
'IdV: phone confirmation vendor' => {
@@ -549,19 +553,19 @@
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' },
},
'IdV: phone confirmation otp submitted' => {
- success: true, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil, code_expired: false, code_matches: true, second_factor_attempts_count: 0, second_factor_locked_at: nil, errors: {},
+ success: true, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: anything, code_expired: false, code_matches: true, second_factor_attempts_count: 0, second_factor_locked_at: nil, errors: {},
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' }
},
:idv_enter_password_visited => {
- address_verification_method: 'phone', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil,
+ address_verification_method: 'phone', acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: anything,
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' }
},
:idv_enter_password_submitted => {
- success: true, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil, fraud_review_pending: false, fraud_rejection: false, gpo_verification_pending: false, in_person_verification_pending: false, deactivation_reason: nil,
+ success: true, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: anything, fraud_review_pending: false, fraud_rejection: false, gpo_verification_pending: false, in_person_verification_pending: false, deactivation_reason: nil,
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' }
},
'IdV: final resolution' => {
- success: true, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: nil, fraud_review_pending: false, fraud_rejection: false, gpo_verification_pending: false, in_person_verification_pending: false, deactivation_reason: nil,
+ success: true, acuant_sdk_upgrade_ab_test_bucket: :default, lexisnexis_instant_verify_workflow_ab_test_bucket: :default, skip_hybrid_handoff: anything, fraud_review_pending: false, fraud_rejection: false, gpo_verification_pending: false, in_person_verification_pending: false, deactivation_reason: nil,
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' }
},
'IdV: personal key visited' => {
@@ -850,20 +854,15 @@ def wait_for_event(event, wait)
and_return(true)
allow_any_instance_of(FederatedProtocols::Oidc).
to receive(:biometric_comparison_required?).
- and_return({ biometric_comparison_required: true })
-
+ and_return(true)
allow_any_instance_of(DocAuth::Response).to receive(:selfie_status).and_return(:success)
allow_any_instance_of(DocumentCaptureSessionResult).
to receive(:selfie_status).and_return(:success)
- mobile_device = Browser.new(mobile_user_agent)
- allow(BrowserCache).to receive(:parse).and_return(mobile_device)
-
- perform_in_browser(:mobile) do
+ perform_in_browser(:desktop) do
sign_in_and_2fa_user(user)
- visit_idp_from_sp_with_ial2(:oidc)
+ visit_idp_from_sp_with_ial2(:oidc, biometric_comparison_required: true)
complete_doc_auth_steps_before_document_capture_step
-
attach_images
attach_selfie
submit_images
@@ -880,7 +879,7 @@ def wait_for_event(event, wait)
end
it 'records all of the events' do
- happy_selfie_path_events.each do |event, attributes|
+ happy_mobile_selfie_path_events.each do |event, attributes|
expect(fake_analytics).to have_logged_event(event, attributes)
end
end
@@ -901,7 +900,7 @@ def wait_for_event(event, wait)
it 'records all of the events' do
aggregate_failures 'analytics events' do
- happy_selfie_path_events.each do |event, attributes|
+ happy_mobile_selfie_path_events.each do |event, attributes|
expect(fake_analytics).to have_logged_event(event, attributes)
end
end
diff --git a/spec/features/idv/doc_auth/document_capture_spec.rb b/spec/features/idv/doc_auth/document_capture_spec.rb
index db820bf5297..0e2d7451601 100644
--- a/spec/features/idv/doc_auth/document_capture_spec.rb
+++ b/spec/features/idv/doc_auth/document_capture_spec.rb
@@ -240,207 +240,30 @@
before do
allow_any_instance_of(FederatedProtocols::Oidc).
to receive(:biometric_comparison_required?).
- and_return({ biometric_comparison_required: true })
+ and_return(true)
end
- it 'proceeds to the next page with valid info, including a selfie image' do
- perform_in_browser(:mobile) do
- visit_idp_from_oidc_sp_with_ial2
- sign_in_and_2fa_user(user)
- complete_doc_auth_steps_before_document_capture_step
-
- expect(page).to have_current_path(idv_document_capture_url)
- expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_id'))
- expect_doc_capture_page_header(t('doc_auth.headings.document_capture_with_selfie'))
- expect_doc_capture_id_subheader
- expect_doc_capture_selfie_subheader
- attach_liveness_images
- submit_images
-
- expect(page).to have_current_path(idv_ssn_url)
- expect_costing_for_document
- expect(DocAuthLog.find_by(user_id: user.id).state).to eq('MT')
-
- expect(page).to have_current_path(idv_ssn_url)
- fill_out_ssn_form_ok
- click_idv_continue
- complete_verify_step
- expect(page).to have_current_path(idv_phone_url)
- end
- end
-
- context 'selfie with no liveness or poor quality is uploaded', allow_browser_log: true do
- it 'try again and page show no liveness inline error message' do
- visit_idp_from_oidc_sp_with_ial2
- sign_in_and_2fa_user(user)
- complete_doc_auth_steps_before_document_capture_step
- attach_images(
- Rails.root.join(
- 'spec', 'fixtures',
- 'ial2_test_credential_no_liveness.yml'
- ),
- )
- attach_selfie(
- Rails.root.join(
- 'spec', 'fixtures',
- 'ial2_test_credential_no_liveness.yml'
- ),
- )
- submit_images
- message = strip_tags(t('errors.doc_auth.selfie_not_live_or_poor_quality_heading'))
- expect(page).to have_content(message)
- detail_message = strip_tags(t('doc_auth.errors.alerts.selfie_not_live'))
- security_message = strip_tags(
- t(
- 'idv.warning.attempts_html',
- count: IdentityConfig.store.doc_auth_max_attempts - 1,
- ),
- )
- expect(page).to have_content(detail_message << "\n" << security_message)
- review_issues_header = strip_tags(
- t('errors.doc_auth.selfie_not_live_or_poor_quality_heading'),
- )
- expect(page).to have_content(review_issues_header)
- expect(page).to have_current_path(idv_document_capture_path)
- click_try_again
- expect(page).to have_current_path(idv_document_capture_path)
- inline_error = strip_tags(t('doc_auth.errors.general.selfie_failure'))
- expect(page).to have_content(inline_error)
- end
-
- it 'try again and page show poor quality inline error message' do
- visit_idp_from_oidc_sp_with_ial2
- sign_in_and_2fa_user(user)
- complete_doc_auth_steps_before_document_capture_step
- attach_images(
- Rails.root.join(
- 'spec', 'fixtures',
- 'ial2_test_credential_poor_quality.yml'
- ),
- )
- attach_selfie(
- Rails.root.join(
- 'spec', 'fixtures',
- 'ial2_test_credential_poor_quality.yml'
- ),
- )
- submit_images
- message = strip_tags(t('errors.doc_auth.selfie_not_live_or_poor_quality_heading'))
- expect(page).to have_content(message)
- detail_message = strip_tags(t('doc_auth.errors.alerts.selfie_poor_quality'))
- security_message = strip_tags(
- t(
- 'idv.warning.attempts_html',
- count: IdentityConfig.store.doc_auth_max_attempts - 1,
- ),
- )
- expect(page).to have_content(detail_message << "\n" << security_message)
- review_issues_header = strip_tags(
- t('errors.doc_auth.selfie_not_live_or_poor_quality_heading'),
- )
- expect(page).to have_content(review_issues_header)
- expect(page).to have_current_path(idv_document_capture_path)
- click_try_again
- expect(page).to have_current_path(idv_document_capture_path)
- inline_error = strip_tags(t('doc_auth.errors.general.selfie_failure'))
- expect(page).to have_content(inline_error)
- end
-
- it 'try again and page show selfie fail inline error message' do
- visit_idp_from_oidc_sp_with_ial2
- sign_in_and_2fa_user(user)
- complete_doc_auth_steps_before_document_capture_step
- attach_images(
- Rails.root.join(
- 'spec', 'fixtures',
- 'ial2_test_portrait_match_failure.yml'
- ),
- )
- attach_selfie(
- Rails.root.join(
- 'spec', 'fixtures',
- 'ial2_test_portrait_match_failure.yml'
- ),
- )
- submit_images
- message = strip_tags(t('errors.doc_auth.selfie_fail_heading'))
- expect(page).to have_content(message)
- detail_message = strip_tags(t('doc_auth.errors.alerts.selfie_poor_quality'))
- security_message = strip_tags(
- t(
- 'idv.warning.attempts_html',
- count: IdentityConfig.store.doc_auth_max_attempts - 1,
- ),
- )
- expect(page).to have_content(detail_message << "\n" << security_message)
- review_issues_header = strip_tags(
- t('errors.doc_auth.selfie_fail_heading'),
- )
- expect(page).to have_content(review_issues_header)
- expect(page).to have_current_path(idv_document_capture_path)
- click_try_again
- expect(page).to have_current_path(idv_document_capture_path)
- inline_error = strip_tags(t('doc_auth.errors.general.selfie_failure'))
- expect(page).to have_content(inline_error)
- end
-
- context 'with Attention with Barcode' do
- it 'try again and page show selfie fail inline error message' do
- visit_idp_from_oidc_sp_with_ial2
- sign_in_and_2fa_user(user)
- complete_doc_auth_steps_before_document_capture_step
- attach_images(
- Rails.root.join(
- 'spec', 'fixtures',
- 'ial2_test_credential_barcode_attention_liveness_fail.yml'
- ),
- )
- attach_selfie(
- Rails.root.join(
- 'spec', 'fixtures',
- 'ial2_test_credential_barcode_attention_liveness_fail.yml'
- ),
- )
- submit_images
- message = strip_tags(t('errors.doc_auth.selfie_not_live_or_poor_quality_heading'))
- expect(page).to have_content(message)
- detail_message = strip_tags(t('doc_auth.errors.alerts.selfie_not_live'))
- security_message = strip_tags(
- t(
- 'idv.warning.attempts_html',
- count: IdentityConfig.store.doc_auth_max_attempts - 1,
- ),
- )
-
- expect(page).to have_content(detail_message << "\n" << security_message)
- review_issues_header = strip_tags(
- t('errors.doc_auth.selfie_not_live_or_poor_quality_heading'),
- )
- expect(page).to have_content(review_issues_header)
- expect(page).to have_current_path(idv_document_capture_path)
- click_try_again
- expect(page).to have_current_path(idv_document_capture_path)
- inline_error = strip_tags(t('doc_auth.errors.general.selfie_failure'))
- expect(page).to have_content(inline_error)
+ context 'on mobile platform' do
+ before do
+ # mock mobile device as cameraCapable, this allows us to proce
+ allow_any_instance_of(ActionController::Parameters).
+ to receive(:[]).and_wrap_original do |impl, param_name|
+ param_name.to_sym == :skip_hybrid_handoff ? '' : impl.call(param_name)
end
end
- end
- context 'when selfie check is not enabled (flag off, and/or in production)' do
- let(:selfie_check_enabled) { false }
- it 'proceeds to the next page with valid info, excluding a selfie image' do
+ it 'proceeds to the next page with valid info, including a selfie image' do
perform_in_browser(:mobile) do
- visit_idp_from_oidc_sp_with_ial2
+ visit_idp_from_oidc_sp_with_ial2(biometric_comparison_required: true)
sign_in_and_2fa_user(user)
complete_doc_auth_steps_before_document_capture_step
expect(page).to have_current_path(idv_document_capture_url)
- expect(page).not_to have_content(t('doc_auth.headings.document_capture_selfie'))
-
expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_id'))
-
- expect(page).not_to have_content(t('doc_auth.headings.document_capture_selfie'))
- attach_images
+ expect_doc_capture_page_header(t('doc_auth.headings.document_capture_with_selfie'))
+ expect_doc_capture_id_subheader
+ expect_doc_capture_selfie_subheader
+ attach_liveness_images
submit_images
expect(page).to have_current_path(idv_ssn_url)
@@ -454,6 +277,273 @@
expect(page).to have_current_path(idv_phone_url)
end
end
+ context 'when a selfie is required by SP', allow_browser_log: true do
+ before do
+ allow_any_instance_of(FederatedProtocols::Oidc).
+ to receive(:biometric_comparison_required?).
+ and_return(true)
+ end
+ it 'proceeds to the next page with valid info, including a selfie image' do
+ perform_in_browser(:mobile) do
+ visit_idp_from_oidc_sp_with_ial2(biometric_comparison_required: true)
+ sign_in_and_2fa_user(user)
+ complete_doc_auth_steps_before_document_capture_step
+
+ expect(page).to have_current_path(idv_document_capture_url)
+ expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_id'))
+ expect_doc_capture_page_header(t('doc_auth.headings.document_capture_with_selfie'))
+ expect_doc_capture_id_subheader
+ expect_doc_capture_selfie_subheader
+ attach_liveness_images
+ submit_images
+
+ expect(page).to have_current_path(idv_ssn_url)
+ expect_costing_for_document
+ expect(DocAuthLog.find_by(user_id: user.id).state).to eq('MT')
+
+ expect(page).to have_current_path(idv_ssn_url)
+ fill_out_ssn_form_ok
+ click_idv_continue
+ complete_verify_step
+ expect(page).to have_current_path(idv_phone_url)
+ end
+ end
+ context 'selfie with no liveness or poor quality is uploaded',
+ allow_browser_log: true do
+ it 'try again and page show no liveness inline error message' do
+ visit_idp_from_oidc_sp_with_ial2(biometric_comparison_required: true)
+ sign_in_and_2fa_user(user)
+ complete_doc_auth_steps_before_document_capture_step
+ attach_images(
+ Rails.root.join(
+ 'spec', 'fixtures',
+ 'ial2_test_credential_no_liveness.yml'
+ ),
+ )
+ attach_selfie(
+ Rails.root.join(
+ 'spec', 'fixtures',
+ 'ial2_test_credential_no_liveness.yml'
+ ),
+ )
+ submit_images
+ message = strip_tags(t('errors.doc_auth.selfie_not_live_or_poor_quality_heading'))
+ expect(page).to have_content(message)
+ detail_message = strip_tags(t('doc_auth.errors.alerts.selfie_not_live'))
+ security_message = strip_tags(
+ t(
+ 'idv.warning.attempts_html',
+ count: IdentityConfig.store.doc_auth_max_attempts - 1,
+ ),
+ )
+ expect(page).to have_content(detail_message << "\n" << security_message)
+ review_issues_header = strip_tags(
+ t('errors.doc_auth.selfie_not_live_or_poor_quality_heading'),
+ )
+ expect(page).to have_content(review_issues_header)
+ expect(page).to have_current_path(idv_document_capture_path)
+ click_try_again
+ expect(page).to have_current_path(idv_document_capture_path)
+ inline_error = strip_tags(t('doc_auth.errors.general.selfie_failure'))
+ expect(page).to have_content(inline_error)
+ end
+ it 'try again and page show poor quality inline error message' do
+ visit_idp_from_oidc_sp_with_ial2(biometric_comparison_required: true)
+ sign_in_and_2fa_user(user)
+ complete_doc_auth_steps_before_document_capture_step
+ attach_images(
+ Rails.root.join(
+ 'spec', 'fixtures',
+ 'ial2_test_credential_poor_quality.yml'
+ ),
+ )
+ attach_selfie(
+ Rails.root.join(
+ 'spec', 'fixtures',
+ 'ial2_test_credential_poor_quality.yml'
+ ),
+ )
+ submit_images
+ message = strip_tags(t('errors.doc_auth.selfie_not_live_or_poor_quality_heading'))
+ expect(page).to have_content(message)
+ detail_message = strip_tags(t('doc_auth.errors.alerts.selfie_poor_quality'))
+ security_message = strip_tags(
+ t(
+ 'idv.warning.attempts_html',
+ count: IdentityConfig.store.doc_auth_max_attempts - 1,
+ ),
+ )
+ expect(page).to have_content(detail_message << "\n" << security_message)
+ review_issues_header = strip_tags(
+ t('errors.doc_auth.selfie_not_live_or_poor_quality_heading'),
+ )
+ expect(page).to have_content(review_issues_header)
+ expect(page).to have_current_path(idv_document_capture_path)
+ click_try_again
+ expect(page).to have_current_path(idv_document_capture_path)
+ inline_error = strip_tags(t('doc_auth.errors.general.selfie_failure'))
+ expect(page).to have_content(inline_error)
+ end
+
+ it 'try again and page show selfie fail inline error message' do
+ visit_idp_from_oidc_sp_with_ial2(biometric_comparison_required: true)
+ sign_in_and_2fa_user(user)
+ complete_doc_auth_steps_before_document_capture_step
+ attach_images(
+ Rails.root.join(
+ 'spec', 'fixtures',
+ 'ial2_test_portrait_match_failure.yml'
+ ),
+ )
+ attach_selfie(
+ Rails.root.join(
+ 'spec', 'fixtures',
+ 'ial2_test_portrait_match_failure.yml'
+ ),
+ )
+ submit_images
+ message = strip_tags(t('errors.doc_auth.selfie_fail_heading'))
+ expect(page).to have_content(message)
+ detail_message = strip_tags(t('doc_auth.errors.alerts.selfie_poor_quality'))
+ security_message = strip_tags(
+ t(
+ 'idv.warning.attempts_html',
+ count: IdentityConfig.store.doc_auth_max_attempts - 1,
+ ),
+ )
+ expect(page).to have_content(detail_message << "\n" << security_message)
+ review_issues_header = strip_tags(
+ t('errors.doc_auth.selfie_fail_heading'),
+ )
+ expect(page).to have_content(review_issues_header)
+ expect(page).to have_current_path(idv_document_capture_path)
+ click_try_again
+ expect(page).to have_current_path(idv_document_capture_path)
+ inline_error = strip_tags(t('doc_auth.errors.general.selfie_failure'))
+ expect(page).to have_content(inline_error)
+ end
+ end
+ context 'with Attention with Barcode' do
+ it 'try again and page show selfie fail inline error message' do
+ visit_idp_from_oidc_sp_with_ial2
+ sign_in_and_2fa_user(user)
+ complete_doc_auth_steps_before_document_capture_step
+ attach_images(
+ Rails.root.join(
+ 'spec', 'fixtures',
+ 'ial2_test_credential_barcode_attention_liveness_fail.yml'
+ ),
+ )
+ attach_selfie(
+ Rails.root.join(
+ 'spec', 'fixtures',
+ 'ial2_test_credential_barcode_attention_liveness_fail.yml'
+ ),
+ )
+ submit_images
+ message = strip_tags(t('errors.doc_auth.selfie_not_live_or_poor_quality_heading'))
+ expect(page).to have_content(message)
+ detail_message = strip_tags(t('doc_auth.errors.alerts.selfie_not_live'))
+ security_message = strip_tags(
+ t(
+ 'idv.warning.attempts_html',
+ count: IdentityConfig.store.doc_auth_max_attempts - 1,
+ ),
+ )
+
+ expect(page).to have_content(detail_message << "\n" << security_message)
+ review_issues_header = strip_tags(
+ t('errors.doc_auth.selfie_not_live_or_poor_quality_heading'),
+ )
+ expect(page).to have_content(review_issues_header)
+ expect(page).to have_current_path(idv_document_capture_path)
+ click_try_again
+ expect(page).to have_current_path(idv_document_capture_path)
+ inline_error = strip_tags(t('doc_auth.errors.general.selfie_failure'))
+ expect(page).to have_content(inline_error)
+ end
+ end
+ end
+
+ context 'when selfie check is not enabled (flag off, and/or in production)' do
+ let(:selfie_check_enabled) { false }
+ it 'proceeds to the next page with valid info, excluding a selfie image' do
+ perform_in_browser(:mobile) do
+ visit_idp_from_oidc_sp_with_ial2
+ sign_in_and_2fa_user(user)
+ complete_doc_auth_steps_before_document_capture_step
+
+ expect(page).to have_current_path(idv_document_capture_url)
+ expect(page).not_to have_content(t('doc_auth.headings.document_capture_selfie'))
+
+ expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_id'))
+
+ expect(page).not_to have_content(t('doc_auth.headings.document_capture_selfie'))
+ attach_images
+ submit_images
+
+ expect(page).to have_current_path(idv_ssn_url)
+ expect_costing_for_document
+ expect(DocAuthLog.find_by(user_id: user.id).state).to eq('MT')
+
+ expect(page).to have_current_path(idv_ssn_url)
+ fill_out_ssn_form_ok
+ click_idv_continue
+ complete_verify_step
+ expect(page).to have_current_path(idv_phone_url)
+ end
+ end
+ end
+ end
+ context 'on desktop' do
+ let(:desktop_selfie_mode) { false }
+ before do
+ allow(IdentityConfig.store).to receive(:doc_auth_selfie_desktop_test_mode).
+ and_return(desktop_selfie_mode)
+ end
+ describe 'when desktop selfie not allowed' do
+ it 'cannot proceed to document capture page' do
+ perform_in_browser(:desktop) do
+ visit_idp_from_oidc_sp_with_ial2(biometric_comparison_required: true)
+ sign_in_and_2fa_user(user)
+ complete_doc_auth_steps_before_hybrid_handoff_step
+ # we still have option to continue
+ expect(page).to have_current_path(idv_hybrid_handoff_path)
+ click_on t('forms.buttons.upload_photos')
+ expect(page).to have_current_path(idv_hybrid_handoff_path)
+ end
+ end
+ end
+ describe 'when desktop selfie is allowed' do
+ let(:desktop_selfie_mode) { true }
+ it 'proceed to the next page with valid info, including a selfie image' do
+ perform_in_browser(:desktop) do
+ visit_idp_from_oidc_sp_with_ial2(biometric_comparison_required: true)
+ sign_in_and_2fa_user(user)
+ complete_doc_auth_steps_before_hybrid_handoff_step
+ # we still have option to continue on handoff, since it's desktop no skip_hand_off
+ expect(page).to have_current_path(idv_hybrid_handoff_path)
+ click_on t('forms.buttons.upload_photos')
+ expect(page).to have_current_path(idv_document_capture_url)
+ expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_id'))
+ expect_doc_capture_page_header(t('doc_auth.headings.document_capture_with_selfie'))
+ expect_doc_capture_id_subheader
+ expect_doc_capture_selfie_subheader
+ attach_liveness_images
+ submit_images
+
+ expect(page).to have_current_path(idv_ssn_url)
+ expect_costing_for_document
+ expect(DocAuthLog.find_by(user_id: user.id).state).to eq('MT')
+
+ expect(page).to have_current_path(idv_ssn_url)
+ fill_out_ssn_form_ok
+ click_idv_continue
+ complete_verify_step
+ expect(page).to have_current_path(idv_phone_url)
+ end
+ end
+ end
end
end
end
diff --git a/spec/features/idv/steps/in_person/state_id_step_spec.rb b/spec/features/idv/steps/in_person/state_id_step_spec.rb
index 69c5ed42783..63a6117c18b 100644
--- a/spec/features/idv/steps/in_person/state_id_step_spec.rb
+++ b/spec/features/idv/steps/in_person/state_id_step_spec.rb
@@ -442,8 +442,10 @@
t('in_person_proofing.form.state_id.state_id_number_texas_hint'),
)
expect(page).not_to have_content(t('in_person_proofing.form.state_id.state_id_number_hint'))
- expect(page).to have_content(
- t('in_person_proofing.form.state_id.state_id_number_florida_hint'),
+ expect(page).to have_content strip_tags(
+ t('in_person_proofing.form.state_id.state_id_number_florida_hint_html').gsub(
+ / /, ' '
+ ),
)
# select a state without a state specific hint
@@ -454,7 +456,7 @@
t('in_person_proofing.form.state_id.state_id_number_texas_hint'),
)
expect(page).not_to have_content(
- t('in_person_proofing.form.state_id.state_id_number_florida_hint'),
+ t('in_person_proofing.form.state_id.state_id_number_florida_hint_html'),
)
end
end
diff --git a/spec/features/openid_connect/openid_connect_spec.rb b/spec/features/openid_connect/openid_connect_spec.rb
index c38c511fc60..c206cb782bd 100644
--- a/spec/features/openid_connect/openid_connect_spec.rb
+++ b/spec/features/openid_connect/openid_connect_spec.rb
@@ -56,6 +56,11 @@
expect(current_path).to eq(openid_connect_authorize_path)
expect(page).to have_content(t('openid_connect.authorization.errors.prompt_invalid'))
end
+
+ it 'succeeds with a vtr param' do
+ allow(IdentityConfig.store).to receive(:use_vot_in_sp_requests).and_return(true)
+ oidc_end_client_secret_jwt(vtr: 'C1.C2.P1')
+ end
end
context 'with client_secret_jwt' do
@@ -1154,12 +1159,22 @@ def client_private_key
end
end
- def oidc_end_client_secret_jwt(prompt: nil, user: nil, redirs_to: nil)
+ def oidc_end_client_secret_jwt(vtr: nil, prompt: nil, user: nil, redirs_to: nil)
client_id = 'urn:gov:gsa:openidconnect:sp:server'
state = SecureRandom.hex
nonce = SecureRandom.hex
- visit_idp_from_ial2_oidc_sp(prompt: prompt, state: state, nonce: nonce, client_id: client_id)
+ if vtr.present?
+ visit_idp_from_oidc_sp_with_vtr(
+ vtr: vtr,
+ prompt: prompt,
+ state: state,
+ nonce: nonce,
+ client_id: client_id,
+ )
+ else
+ visit_idp_from_ial2_oidc_sp(prompt: prompt, state: state, nonce: nonce, client_id: client_id)
+ end
continue_as(user.email) if user
if redirs_to
expect(URI(oidc_redirect_url).path).to eq(redirs_to)
@@ -1214,12 +1229,19 @@ def oidc_end_client_secret_jwt(prompt: nil, user: nil, redirs_to: nil)
expect(sub).to be_present
expect(decoded_id_token[:nonce]).to eq(nonce)
expect(decoded_id_token[:aud]).to eq(client_id)
- expect(decoded_id_token[:acr]).to eq(Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF)
expect(decoded_id_token[:iss]).to eq(root_url)
expect(decoded_id_token[:email]).to eq(user.email)
expect(decoded_id_token[:given_name]).to eq('John')
expect(decoded_id_token[:social_security_number]).to eq('111223333')
+ if vtr.present?
+ expect(decoded_id_token[:acr]).to eq(nil)
+ expect(decoded_id_token[:vot]).to eq(Array(vtr).first)
+ else
+ expect(decoded_id_token[:acr]).to eq(Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF)
+ expect(decoded_id_token[:vot]).to eq(nil)
+ end
+
access_token = token_response[:access_token]
expect(access_token).to be_present
diff --git a/spec/features/phone/add_phone_spec.rb b/spec/features/phone/add_phone_spec.rb
index 9ee08661408..1b14f111958 100644
--- a/spec/features/phone/add_phone_spec.rb
+++ b/spec/features/phone/add_phone_spec.rb
@@ -6,7 +6,7 @@
phone = '+1 (225) 278-1234'
sign_in_and_2fa_user(user)
- expect(page).to have_link(t('account.index.phone_add'), normalize_ws: true, exact: true)
+ expect(page).to have_link(href: phone_setup_path, text: t('account.index.phone_add'))
within('.sidenav') do
click_on t('account.navigation.add_phone_number')
end
diff --git a/spec/forms/add_user_email_form_spec.rb b/spec/forms/add_user_email_form_spec.rb
index d1c0e150038..18a1b72b0fe 100644
--- a/spec/forms/add_user_email_form_spec.rb
+++ b/spec/forms/add_user_email_form_spec.rb
@@ -51,5 +51,33 @@
expect(response.success?).to eq(false)
end
end
+
+ context 'banned domains' do
+ before do
+ expect(BanDisposableEmailValidator).to receive(:config).and_return(%w[spamdomain.com])
+ end
+
+ context 'with the banned domain' do
+ let(:new_email) { 'test@spamdomain.com' }
+
+ it 'fails and does not send a confirmation email' do
+ expect(SendAddEmailConfirmation).to_not receive(:new)
+
+ response = submit
+ expect(response.success?).to eq(false)
+ end
+ end
+
+ context 'with a subdomain of the banned domain' do
+ let(:new_email) { 'test@abc.def.spamdomain.com' }
+
+ it 'fails and does not send a confirmation email' do
+ expect(SendAddEmailConfirmation).to_not receive(:new)
+
+ response = submit
+ expect(response.success?).to eq(false)
+ end
+ end
+ end
end
end
diff --git a/spec/forms/idv/api_image_upload_form_spec.rb b/spec/forms/idv/api_image_upload_form_spec.rb
index 74fe6d69902..4733457336a 100644
--- a/spec/forms/idv/api_image_upload_form_spec.rb
+++ b/spec/forms/idv/api_image_upload_form_spec.rb
@@ -8,11 +8,11 @@
ActionController::Parameters.new(
{
front: front_image,
- front_image_metadata: front_image_metadata,
+ front_image_metadata: front_image_metadata.to_json,
back: back_image,
- back_image_metadata: back_image_metadata,
+ back_image_metadata: back_image_metadata.to_json,
selfie: selfie_image,
- selfie_image_metadata: selfie_image_metadata,
+ selfie_image_metadata: selfie_image_metadata.to_json,
document_capture_session_uuid: document_capture_session_uuid,
}.compact,
),
@@ -29,10 +29,10 @@
let(:selfie_image) { nil }
let(:liveness_checking_required) { false }
let(:front_image_metadata) do
- { width: 40, height: 40, mimeType: 'image/png', source: 'upload' }.to_json
+ { width: 40, height: 40, mimeType: 'image/png', source: 'upload' }
end
let(:back_image_metadata) do
- { width: 20, height: 20, mimeType: 'image/png', source: 'upload' }.to_json
+ { width: 20, height: 20, mimeType: 'image/png', source: 'upload' }
end
let(:selfie_image_metadata) { nil }
let!(:document_capture_session) { DocumentCaptureSession.create!(user: create(:user)) }
@@ -92,6 +92,43 @@
it 'is valid' do
expect(form.valid?).to eq(true)
end
+
+ context 'validates image source' do
+ let(:selfie_image_metadata) do
+ { width: 40, height: 40, mimeType: 'image/png', source: 'acuant' }
+ end
+ before do
+ allow(IdentityConfig.store).to receive(:doc_auth_selfie_desktop_test_mode).
+ and_return(false)
+ end
+
+ context 'id images are uploaded' do
+ it 'is invalid' do
+ expect(form.valid?).to eq(false)
+ end
+ end
+
+ context 'images sourced by acuant sdk' do
+ let(:front_image_metadata) do
+ { width: 40, height: 40, mimeType: 'image/png', source: 'acuant' }
+ end
+ let(:back_image_metadata) do
+ { width: 20, height: 20, mimeType: 'image/png', source: 'acuant' }
+ end
+ it 'is valid' do
+ expect(form.valid?).to eq(true)
+ end
+
+ context 'selfie is uploaded' do
+ let(:selfie_image_metadata) do
+ { width: 40, height: 40, mimeType: 'image/png', source: 'upload' }
+ end
+ it 'is invalid' do
+ expect(form.valid?).to eq(false)
+ end
+ end
+ end
+ end
end
end
end
@@ -156,6 +193,7 @@
},
image_metrics: nil,
conversation_id: nil,
+ request_id: nil,
doc_auth_result: 'Passed',
decision_product_status: nil,
errors: {},
@@ -205,7 +243,7 @@
let(:back_image) { DocAuthImageFixtures.portrait_match_success_yaml }
let(:selfie_image) { DocAuthImageFixtures.selfie_image_multipart }
let(:selfie_image_metadata) do
- { width: 10, height: 10, mimeType: 'image/png', source: 'upload' }.to_json
+ { width: 10, height: 10, mimeType: 'image/png', source: 'upload' }
end
it 'logs analytics' do
@@ -271,6 +309,7 @@
},
},
conversation_id: nil,
+ request_id: nil,
decision_product_status: nil,
doc_auth_result: 'Passed',
errors: {},
@@ -658,17 +697,18 @@
describe 'image source' do
let(:source) { nil }
let(:front_image_metadata) do
- { width: 40, height: 40, mimeType: 'image/png', source: source }.to_json
+ { width: 40, height: 40, mimeType: 'image/png', source: source }.compact
end
let(:back_image_metadata) do
- { width: 20, height: 20, mimeType: 'image/png', source: source }.to_json
+ { width: 20, height: 20, mimeType: 'image/png', source: source }.compact
end
let(:image_source) { nil }
+ let(:images_cropped) { false }
before do
expect_any_instance_of(DocAuth::Mock::DocAuthMockClient).
to receive(:post_images).
- with(hash_including(image_source: image_source)).
+ with(hash_including(image_source: image_source, images_cropped: images_cropped)).
and_call_original
end
@@ -684,7 +724,7 @@
context 'mixed sources' do
let(:source) { 'upload' }
let(:back_image_metadata) do
- { width: 20, height: 20, mimeType: 'image/png', source: 'acuant' }.to_json
+ { width: 20, height: 20, mimeType: 'image/png', source: 'acuant' }
end
let(:image_source) { DocAuth::ImageSources::UNKNOWN }
@@ -697,8 +737,16 @@
let(:source) { 'acuant' }
let(:image_source) { DocAuth::ImageSources::ACUANT_SDK }
- it 'sets image source to acuant sdk' do
- form.submit
+ context 'when both images are captured via autocapture' do
+ let(:images_cropped) { true }
+ before do
+ front_image_metadata[:acuantCaptureMode] = 'AUTO'
+ back_image_metadata[:acuantCaptureMode] = 'AUTO'
+ end
+
+ it 'sets image source to acuant sdk' do
+ form.submit
+ end
end
context 'selfie is submitted' do
@@ -706,7 +754,11 @@
let(:selfie_image) { DocAuthImageFixtures.selfie_image_multipart }
context 'captured with acuant sdk' do
let(:selfie_image_metadata) do
- { width: 10, height: 10, mimeType: 'image/png', source: source }.to_json
+ { width: 10, height: 10, mimeType: 'image/png', source: source }
+ end
+
+ before do
+ front_image_metadata.merge!(acuantCaptureMode: 'AUTO')
end
it 'sets image source to acuant sdk' do
@@ -716,11 +768,14 @@
context 'add using file upload' do
let(:selfie_image_metadata) do
- { width: 10, height: 10, mimeType: 'image/png', source: 'upload' }.to_json
+ { width: 10, height: 10, mimeType: 'image/png', source: 'upload' }
end
- let(:image_source) { DocAuth::ImageSources::UNKNOWN }
- it 'sets image source to unknown' do
+ before do
+ back_image_metadata.merge!(acuantCaptureMode: 'AUTO')
+ end
+
+ it 'sets image source to acuant sdk' do
form.submit
end
end
@@ -729,7 +784,7 @@
context 'malformed image metadata' do
let(:source) { 'upload' }
- let(:front_image_metadata) { nil.to_json }
+ let(:front_image_metadata) { nil }
let(:image_source) { DocAuth::ImageSources::UNKNOWN }
it 'sets image source to unknown' do
diff --git a/spec/forms/password_reset_email_form_spec.rb b/spec/forms/password_reset_email_form_spec.rb
index 37a7928afbe..a2fe5f10379 100644
--- a/spec/forms/password_reset_email_form_spec.rb
+++ b/spec/forms/password_reset_email_form_spec.rb
@@ -65,5 +65,23 @@
)
end
end
+
+ context 'disposable domains' do
+ before do
+ expect(BanDisposableEmailValidator).to receive(:config).and_return(%w[spamdomain.com])
+ end
+
+ it 'bans direct matches to disposable domains' do
+ form = PasswordResetEmailForm.new('foo@spamdomain.com')
+
+ expect(form.submit).to_not be_success
+ end
+
+ it 'bans subdomains of disposable domains' do
+ form = PasswordResetEmailForm.new('foo@sub.bar.spamdomain.com')
+
+ expect(form.submit).to_not be_success
+ end
+ end
end
end
diff --git a/spec/forms/register_user_email_form_spec.rb b/spec/forms/register_user_email_form_spec.rb
index 495751d73a8..6bc9992f895 100644
--- a/spec/forms/register_user_email_form_spec.rb
+++ b/spec/forms/register_user_email_form_spec.rb
@@ -353,6 +353,25 @@
)
expect_delivered_email_count(0)
end
+
+ it 'returns false and adds errors when subdomain is blocked' do
+ blocked_domain = 'blocked.com'
+ blocked_email = 'test@sub.' + blocked_domain
+
+ errors = { email: [t('valid_email.validations.email.invalid')] }
+ expect(BanDisposableEmailValidator).to receive(:config).and_return([blocked_domain])
+
+ expect(subject.submit(email: blocked_email, terms_accepted: '1').to_h).to include(
+ success: false,
+ errors: errors,
+ error_details: hash_including(*errors.keys),
+ email_already_exists: false,
+ rate_limited: false,
+ user_id: 'anonymous-uuid',
+ domain_name: 'sub.blocked.com',
+ )
+ expect_delivered_email_count(0)
+ end
end
context 'when request_id is invalid' do
diff --git a/spec/jobs/get_usps_proofing_results_job_spec.rb b/spec/jobs/get_usps_proofing_results_job_spec.rb
index 15012943d94..bc82dcd5d5d 100644
--- a/spec/jobs/get_usps_proofing_results_job_spec.rb
+++ b/spec/jobs/get_usps_proofing_results_job_spec.rb
@@ -44,6 +44,7 @@
transaction_end_date_time: anything,
transaction_start_date_time: anything,
job_name: 'GetUspsProofingResultsJob',
+ tmx_status: :threatmetrix_pass,
)
end
end
@@ -194,6 +195,7 @@
'%m/%d/%Y %H%M%S',
).in_time_zone('UTC')
end
+ let(:in_person_proofing_enforce_tmx) { true }
before do
allow(IdentityConfig.store).
@@ -207,6 +209,8 @@
allow(job).to receive(:analytics).and_return(job_analytics)
allow(IdentityConfig.store).to receive(:get_usps_proofing_results_job_reprocess_delay_minutes).
and_return(reprocess_delay_minutes)
+ allow(IdentityConfig.store).to receive(:in_person_proofing_enforce_tmx).
+ and_return(in_person_proofing_enforce_tmx)
stub_const(
'GetUspsProofingResultsJob::REQUEST_DELAY_IN_SECONDS',
request_delay_ms / GetUspsProofingResultsJob::MILLISECONDS_PER_SECOND,
diff --git a/spec/models/disposable_email_domain_spec.rb b/spec/models/disposable_email_domain_spec.rb
index 68d161ad285..8aa5c47741e 100644
--- a/spec/models/disposable_email_domain_spec.rb
+++ b/spec/models/disposable_email_domain_spec.rb
@@ -9,9 +9,17 @@
end
context 'when the domain exists' do
- it 'returns true' do
+ it 'returns true for an exact domain match' do
expect(DisposableEmailDomain.disposable?(domain)).to eq true
end
+
+ it 'returns true for an first subdomain match' do
+ expect(DisposableEmailDomain.disposable?("temp1.#{domain}")).to eq true
+ end
+
+ it 'returns true for an sub-sub-subdomain match' do
+ expect(DisposableEmailDomain.disposable?("foo.bar.temp1.#{domain}")).to eq true
+ end
end
context 'when the domain does not exist' do
@@ -20,4 +28,16 @@
end
end
end
+
+ describe '.subdomains' do
+ it 'breaks a domain into subdomains' do
+ expect(DisposableEmailDomain.subdomains('foo.bar.baz.com')).to eq(
+ %w[
+ foo.bar.baz.com
+ bar.baz.com
+ baz.com
+ ],
+ )
+ end
+ end
end
diff --git a/spec/requests/csp_spec.rb b/spec/requests/csp_spec.rb
index 73440a443ac..cbe92e3b7b2 100644
--- a/spec/requests/csp_spec.rb
+++ b/spec/requests/csp_spec.rb
@@ -31,7 +31,7 @@
expect(content_security_policy['script-src']).to match(
/'self' 'unsafe-eval' 'nonce-[\w\d=\/+]+'/,
)
- expect(content_security_policy['style-src']).to eq("'self'")
+ expect(content_security_policy['style-src']).to match(/'self' 'nonce-[\w\d=\/+]+'/)
end
it 'uses logout SP to override CSP form action that will allow a redirect to the CSP' do
@@ -75,7 +75,7 @@
expect(content_security_policy['script-src']).to match(
/'self' 'unsafe-eval' 'nonce-[\w\d=\/+]+'/,
)
- expect(content_security_policy['style-src']).to eq("'self'")
+ expect(content_security_policy['style-src']).to match(/'self' 'nonce-[\w\d=\/+]+'/)
end
it 'uses logout SP to override CSP form action that will allow a redirect to the CSP' do
@@ -111,7 +111,7 @@
expect(content_security_policy['script-src']).to match(
/'self' 'unsafe-eval' 'nonce-[\w\d=\/+]+'/,
)
- expect(content_security_policy['style-src']).to eq("'self'")
+ expect(content_security_policy['style-src']).to match(/'self' 'nonce-[\w\d=\/+]+'/)
end
end
diff --git a/spec/services/analytics_spec.rb b/spec/services/analytics_spec.rb
index 1d4a949e8c0..af9b0a66ad8 100644
--- a/spec/services/analytics_spec.rb
+++ b/spec/services/analytics_spec.rb
@@ -249,6 +249,7 @@
remaining_submit_attempts: nil,
client_image_metrics: nil,
flow_path: nil,
+ liveness_checking_required: nil,
'DocumentName' => 'some_name',
)
end
diff --git a/spec/services/doc_auth/lexis_nexis/lexis_nexis_client_spec.rb b/spec/services/doc_auth/lexis_nexis/lexis_nexis_client_spec.rb
index 52d13e68842..deaf4844f4a 100644
--- a/spec/services/doc_auth/lexis_nexis/lexis_nexis_client_spec.rb
+++ b/spec/services/doc_auth/lexis_nexis/lexis_nexis_client_spec.rb
@@ -1,7 +1,7 @@
require 'rails_helper'
RSpec.describe DocAuth::LexisNexis::LexisNexisClient do
- let(:image_source) { nil }
+ let(:images_cropped) { false }
let(:workflow) { 'NOLIVENESS.CROPPING.WORKFLOW' }
let(:image_upload_url) do
URI.join(
@@ -35,15 +35,15 @@
)
end
- context 'with acuant image source' do
- let(:image_source) { DocAuth::ImageSources::ACUANT_SDK }
+ context 'with cropped images' do
+ let(:images_cropped) { true }
let(:workflow) { 'NOLIVENESS.NOCROPPING.WORKFLOW' }
it 'sends an upload image request for the front and back DL images' do
result = client.post_images(
front_image: DocAuthImageFixtures.document_front_image,
back_image: DocAuthImageFixtures.document_back_image,
- image_source: image_source,
+ images_cropped: images_cropped,
)
expect(result.success?).to eq(true)
@@ -51,15 +51,14 @@
end
end
- context 'with unknown image source' do
- let(:image_source) { DocAuth::ImageSources::UNKNOWN }
+ context 'with non-cropped images' do
let(:workflow) { 'NOLIVENESS.CROPPING.WORKFLOW' }
it 'sends an upload image request for the front and back DL images' do
result = client.post_images(
front_image: DocAuthImageFixtures.document_front_image,
back_image: DocAuthImageFixtures.document_back_image,
- image_source: image_source,
+ images_cropped: images_cropped,
)
expect(result.success?).to eq(true)
@@ -76,7 +75,7 @@
result = client.post_images(
front_image: DocAuthImageFixtures.document_front_image,
back_image: DocAuthImageFixtures.document_back_image,
- image_source: image_source,
+ images_cropped: images_cropped,
)
expect(result.success?).to eq(false)
@@ -91,7 +90,7 @@
result = client.post_images(
front_image: DocAuthImageFixtures.document_front_image,
back_image: DocAuthImageFixtures.document_back_image,
- image_source: image_source,
+ images_cropped: images_cropped,
)
expect(result.success?).to eq(false)
@@ -109,7 +108,7 @@
result = client.post_images(
front_image: DocAuthImageFixtures.document_front_image,
back_image: DocAuthImageFixtures.document_back_image,
- image_source: image_source,
+ images_cropped: images_cropped,
)
expect(result.success?).to eq(false)
@@ -136,7 +135,7 @@
result = client.post_images(
front_image: DocAuthImageFixtures.document_front_image,
back_image: DocAuthImageFixtures.document_back_image,
- image_source: image_source,
+ images_cropped: images_cropped,
selfie_image: DocAuthImageFixtures.selfie_image,
liveness_checking_required: true,
)
@@ -160,7 +159,7 @@
result = client.post_images(
front_image: DocAuthImageFixtures.document_front_image,
back_image: DocAuthImageFixtures.document_back_image,
- image_source: image_source,
+ images_cropped: images_cropped,
selfie_image: DocAuthImageFixtures.selfie_image,
liveness_checking_required: true,
)
@@ -194,7 +193,7 @@
result = client.post_images(
front_image: DocAuthImageFixtures.document_front_image,
back_image: DocAuthImageFixtures.document_back_image,
- image_source: image_source,
+ images_cropped: images_cropped,
selfie_image: DocAuthImageFixtures.selfie_image,
liveness_checking_required: true,
)
@@ -225,7 +224,7 @@
result = client.post_images(
front_image: DocAuthImageFixtures.document_front_image,
back_image: DocAuthImageFixtures.document_back_image,
- image_source: image_source,
+ images_cropped: images_cropped,
selfie_image: DocAuthImageFixtures.selfie_image,
liveness_checking_required: true,
)
diff --git a/spec/services/doc_auth/lexis_nexis/requests/true_id_request_spec.rb b/spec/services/doc_auth/lexis_nexis/requests/true_id_request_spec.rb
index 2bc01966414..17f641bcab4 100644
--- a/spec/services/doc_auth/lexis_nexis/requests/true_id_request_spec.rb
+++ b/spec/services/doc_auth/lexis_nexis/requests/true_id_request_spec.rb
@@ -12,6 +12,7 @@
let(:cropping_non_liveness_flow) { 'test_workflow_cropping' }
let(:non_cropping_liveness_flow) { 'test_workflow_liveness' }
let(:cropping_liveness_flow) { 'test_workflow_liveness_cropping' }
+ let(:images_cropped) { false }
let(:config) do
DocAuth::LexisNexis::Config.new(
@@ -31,6 +32,7 @@
front_image: DocAuthImageFixtures.document_front_image,
back_image: DocAuthImageFixtures.document_back_image,
image_source: image_source,
+ images_cropped: images_cropped,
user_uuid: applicant[:uuid],
uuid_prefix: applicant[:uuid_prefix],
selfie_image: selfie_image,
@@ -104,41 +106,49 @@ def include_liveness_expected
context 'with liveness_checking_enabled as false' do
context 'when liveness checking is NOT required' do
let(:liveness_checking_required) { false }
- context 'with acuant image source' do
- let(:image_source) { DocAuth::ImageSources::ACUANT_SDK }
- it_behaves_like 'a successful request'
- it 'uses non-cropping non-liveness workflow' do
- expect(subject.send(:workflow)).to eq(non_cropping_non_liveness_flow)
- end
- it 'does not include a nil selfie in the request body sent to TrueID' do
- body_as_json = subject.send(:body)
- body_as_hash = JSON.parse(body_as_json)
- expect(body_as_hash['Document']).not_to have_key('Selfie')
- end
- end
- context 'with unknown image source' do
- let(:image_source) { DocAuth::ImageSources::UNKNOWN }
+
+ it_behaves_like 'a successful request'
+
+ context 'with non-cropped images' do
it 'uses cropping non-liveness workflow' do
expect(subject.send(:workflow)).to eq(cropping_non_liveness_flow)
end
+ end
+
+ it 'does not include a nil selfie in the request body sent to TrueID' do
+ body_as_json = subject.send(:body)
+ body_as_hash = JSON.parse(body_as_json)
+ expect(body_as_hash['Document']).not_to have_key('Selfie')
+ end
+
+ context 'with cropped images' do
+ let(:images_cropped) { true }
+
+ it 'uses non-cropping non-liveness workflow' do
+ expect(subject.send(:workflow)).to eq(non_cropping_non_liveness_flow)
+ end
+
it_behaves_like 'a successful request'
end
end
context 'when liveness checking is required' do
let(:liveness_checking_required) { true }
- context 'with acuant image source' do
- let(:image_source) { DocAuth::ImageSources::ACUANT_SDK }
+
+ context 'with non-cropped images' do
it 'uses non-cropping non-liveness workflow' do
- expect(subject.send(:workflow)).to eq(non_cropping_non_liveness_flow)
+ expect(subject.send(:workflow)).to eq(cropping_non_liveness_flow)
end
+
it_behaves_like 'a successful request'
end
- context 'with unknown image source' do
- let(:image_source) { DocAuth::ImageSources::UNKNOWN }
- it 'uses cropping non-liveness workflow' do
- expect(subject.send(:workflow)).to eq(cropping_non_liveness_flow)
+
+ context 'with cropped images' do
+ let(:images_cropped) { true }
+ it 'uses non-cropping non-liveness workflow' do
+ expect(subject.send(:workflow)).to eq(non_cropping_non_liveness_flow)
end
+
it_behaves_like 'a successful request'
end
end
@@ -153,17 +163,16 @@ def include_liveness_expected
context 'when liveness checking is NOT required' do
let(:liveness_checking_required) { false }
- context 'with acuant image source' do
- let(:image_source) { DocAuth::ImageSources::ACUANT_SDK }
- it 'use non-cropping non-liveness workflow' do
- expect(subject.send(:workflow)).to eq(non_cropping_non_liveness_flow)
+ context 'with non-cropped images' do
+ it 'use cropping non-liveness workflow' do
+ expect(subject.send(:workflow)).to eq(cropping_non_liveness_flow)
end
it_behaves_like 'a successful request'
end
- context 'with unknown image source' do
- let(:image_source) { DocAuth::ImageSources::UNKNOWN }
- it 'use cropping non-liveness workflow' do
- expect(subject.send(:workflow)).to eq(cropping_non_liveness_flow)
+ context 'with cropped images' do
+ let(:images_cropped) { true }
+ it 'use non-cropping non-liveness workflow' do
+ expect(subject.send(:workflow)).to eq(non_cropping_non_liveness_flow)
end
it_behaves_like 'a successful request'
end
@@ -171,35 +180,40 @@ def include_liveness_expected
context 'when liveness checking is required' do
let(:liveness_checking_required) { true }
- context 'with acuant image source' do
- let(:image_source) { DocAuth::ImageSources::ACUANT_SDK }
- it 'use non-cropping liveness workflow' do
- expect(subject.send(:workflow)).to eq(non_cropping_liveness_flow)
+
+ context 'with non-cropped images' do
+ it 'use cropping liveness workflow' do
+ expect(subject.send(:workflow)).to eq(cropping_liveness_flow)
end
+
it_behaves_like 'a successful request'
end
- context 'with unknown image source' do
- let(:image_source) { DocAuth::ImageSources::UNKNOWN }
- it 'use cropping liveness workflow' do
- expect(subject.send(:workflow)).to eq(cropping_liveness_flow)
+
+ context 'with cropped images' do
+ let(:images_cropped) { true }
+ it 'use non-cropping liveness workflow' do
+ expect(subject.send(:workflow)).to eq(non_cropping_liveness_flow)
end
it_behaves_like 'a successful request'
end
context 'when hosted env is prod' do
let(:selfie_check_allowed) { false }
- context 'with acuant image source' do
- let(:image_source) { DocAuth::ImageSources::ACUANT_SDK }
- it 'use non-cropping non-liveness workflow' do
- expect(subject.send(:workflow)).to eq(non_cropping_non_liveness_flow)
+
+ context 'with non-cropped images' do
+ it 'use cropping non-liveness workflow' do
+ expect(subject.send(:workflow)).to eq(cropping_non_liveness_flow)
end
it_behaves_like 'a successful request'
end
- context 'with unknown image source' do
- let(:image_source) { DocAuth::ImageSources::UNKNOWN }
- it 'use cropping non-liveness workflow' do
- expect(subject.send(:workflow)).to eq(cropping_non_liveness_flow)
+
+ context 'with cropped images' do
+ let(:images_cropped) { true }
+
+ it 'use non-cropping non-liveness workflow' do
+ expect(subject.send(:workflow)).to eq(non_cropping_non_liveness_flow)
end
+
it_behaves_like 'a successful request'
end
end
@@ -207,7 +221,6 @@ def include_liveness_expected
end
context 'with non 200 http status code' do
- let(:image_source) { DocAuth::ImageSources::ACUANT_SDK }
it 'is a network error with 5xx status' do
stub_request(:post, full_url).to_return(body: '{}', status: 500)
response = subject.fetch
diff --git a/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb b/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb
index 382015c3b32..40479f87e60 100644
--- a/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb
+++ b/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb
@@ -111,6 +111,7 @@
errors: {},
attention_with_barcode: false,
conversation_id: a_kind_of(String),
+ request_id: a_kind_of(String),
doc_type_supported: true,
reference: a_kind_of(String),
vendor: 'TrueID',
@@ -357,6 +358,7 @@ def get_decision_product(resp)
attention_with_barcode: false,
doc_type_supported: true,
conversation_id: a_kind_of(String),
+ request_id: a_kind_of(String),
reference: a_kind_of(String),
vendor: 'TrueID',
billed: true,
diff --git a/spec/services/id_token_builder_spec.rb b/spec/services/id_token_builder_spec.rb
index e575d70602e..39e546511b0 100644
--- a/spec/services/id_token_builder_spec.rb
+++ b/spec/services/id_token_builder_spec.rb
@@ -62,47 +62,45 @@
expect(decoded_payload[:nonce]).to eq(identity.nonce)
end
- context 'it sets the vot' do
- context 'sp requests vot' do
- before do
- allow(IdentityConfig.store).to receive(:use_vot_in_sp_requests).
- and_return(true)
- allow(IdentityConfig.store).to receive(:vtm_url).
- and_return(vtm_url)
- end
+ context 'sp request includes VTR' do
+ before do
+ allow(IdentityConfig.store).to receive(:use_vot_in_sp_requests).
+ and_return(true)
+ allow(IdentityConfig.store).to receive(:vtm_url).
+ and_return(vtm_url)
+ end
- it 'sets the vot if the sp requests it' do
- identity.vtr = 'Pb'
- expect(decoded_payload[:vot]).to eq('C1.C2.P1.Pb')
- end
+ it 'sets the vot if the sp requests it' do
+ identity.vtr = ['Pb'].to_json
+ expect(decoded_payload[:vot]).to eq('C1.C2.P1.Pb')
+ end
- it 'sets the vtm' do
- identity.vtr = 'Pb'
- expect(decoded_payload[:vtm]).to eq(vtm_url)
- end
+ it 'sets the vtm' do
+ identity.vtr = ['Pb'].to_json
+ expect(decoded_payload[:vtm]).to eq(vtm_url)
end
+ end
- context 'sp does not request vot' do
- before do
- allow(IdentityConfig.store).to receive(:use_vot_in_sp_requests).
- and_return(false)
- allow(IdentityConfig.store).to receive(:vtm_url).
- and_return(vtm_url)
- end
+ context 'vtr is disabled' do
+ before do
+ allow(IdentityConfig.store).to receive(:use_vot_in_sp_requests).
+ and_return(false)
+ allow(IdentityConfig.store).to receive(:vtm_url).
+ and_return(vtm_url)
+ end
- it 'does not set the vot if the sp does not request it' do
- identity.vtr = 'Pb'
- expect(decoded_payload[:vot]).to eq nil
- end
+ it 'does not set the vot if the sp does not request it' do
+ identity.vtr = ['Pb'].to_json
+ expect(decoded_payload[:vot]).to eq nil
+ end
- it 'does not set the vtm' do
- identity.vtr = nil
- expect(decoded_payload[:vtm]).to eq nil
- end
+ it 'does not set the vtm' do
+ identity.vtr = nil
+ expect(decoded_payload[:vtm]).to eq nil
end
end
- context 'it sets the acr' do
+ context 'context sp requests ACR values' do
context 'aal and ial request' do
before do
identity.aal = 2
@@ -162,6 +160,25 @@
end
end
+ context 'sp requests includes ACR values and VTR' do
+ before do
+ allow(IdentityConfig.store).to receive(:use_vot_in_sp_requests).
+ and_return(true)
+ allow(IdentityConfig.store).to receive(:vtm_url).
+ and_return(vtm_url)
+
+ identity.ial = 1
+ identity.vtr = ['C1'].to_json
+ identity.acr_values = Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF
+ end
+
+ it 'sets the vot and vtm and it does not set an acr' do
+ expect(decoded_payload[:vot]).to eq('C1')
+ expect(decoded_payload[:vtm]).to eq(vtm_url)
+ expect(decoded_payload[:acr]).to eq(nil)
+ end
+ end
+
it 'sets the jti to something meaningful' do
expect(decoded_payload[:jti]).to be_present
end
diff --git a/spec/services/reporting/active_users_count_report_spec.rb b/spec/services/reporting/active_users_count_report_spec.rb
index c339ba58bce..9a6b09452d1 100644
--- a/spec/services/reporting/active_users_count_report_spec.rb
+++ b/spec/services/reporting/active_users_count_report_spec.rb
@@ -12,7 +12,7 @@
end
describe '#active_users_count_emailable_report' do
- it 'returns a report for active users', aggregate_failures: true do
+ it 'returns a report for active users' do
create(
:service_provider_identity,
user_id: 1,
@@ -62,19 +62,25 @@
['Fiscal year Q4 cumulative', 1, 1, 2, Date.new(2022, 10, 1), Date.new(2023, 3, 31)],
]
+ allow(Db::Identity::SpActiveUserCounts).to receive(:overall).and_call_original
+
emailable_report = report.active_users_count_emailable_report
- emailable_report.table.zip(expected_table).each do |actual, expected|
- expect(actual).to eq(expected)
- end
+ expect(Db::Identity::SpActiveUserCounts).to have_received(:overall).exactly(3).times
- expect(emailable_report.title).to eq('Active Users')
- expect(emailable_report.filename).to eq 'active_users_count'
+ aggregate_failures do
+ emailable_report.table.zip(expected_table).each do |actual, expected|
+ expect(actual).to eq(expected)
+ end
+
+ expect(emailable_report.title).to eq('Active Users')
+ expect(emailable_report.filename).to eq 'active_users_count'
+ end
end
end
describe '#active_users_count_apg_emailable_report' do
- it 'returns a report for active users using APG math', aggregate_failures: true do
+ it 'returns a report for active users using APG math' do
create(
:service_provider_identity,
user_id: 1,
@@ -96,14 +102,20 @@
['Fiscal year Q4 cumulative', 2, 0, 2, Date.new(2022, 10, 1), Date.new(2023, 3, 31)],
]
+ allow(Db::Identity::SpActiveUserCounts).to receive(:overall_apg).and_call_original
+
emailable_report = report.active_users_count_apg_emailable_report
- emailable_report.table.zip(expected_table).each do |actual, expected|
- expect(actual).to eq(expected)
- end
+ expect(Db::Identity::SpActiveUserCounts).to have_received(:overall_apg).exactly(2).times
- expect(emailable_report.title).to eq('Active Users (APG)')
- expect(emailable_report.filename).to eq 'active_users_count_apg'
+ aggregate_failures do
+ emailable_report.table.zip(expected_table).each do |actual, expected|
+ expect(actual).to eq(expected)
+ end
+
+ expect(emailable_report.title).to eq('Active Users (APG)')
+ expect(emailable_report.filename).to eq 'active_users_count_apg'
+ end
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 057ada83968..48ad5ddcdf6 100644
--- a/spec/services/store_sp_metadata_in_session_spec.rb
+++ b/spec/services/store_sp_metadata_in_session_spec.rb
@@ -47,7 +47,6 @@
issuer: issuer,
aal_level_requested: 1,
acr_values: request_acr,
- piv_cac_requested: false,
request_url: request_url,
request_id: request_id,
requested_attributes: requested_attributes,
@@ -70,7 +69,6 @@
issuer: issuer,
aal_level_requested: 2,
acr_values: request_acr,
- piv_cac_requested: false,
request_url: request_url,
request_id: request_id,
requested_attributes: requested_attributes,
@@ -80,6 +78,7 @@
)
end
end
+
context 'when IAL2 and phishing-resistant are requested with ACRs' do
let(:request_acr) do
[Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF,
@@ -92,7 +91,6 @@
issuer: issuer,
aal_level_requested: 2,
acr_values: request_acr,
- piv_cac_requested: false,
request_url: request_url,
request_id: request_id,
requested_attributes: requested_attributes,
@@ -116,7 +114,6 @@
issuer: issuer,
aal_level_requested: 2,
acr_values: request_acr,
- piv_cac_requested: false,
request_url: request_url,
request_id: request_id,
requested_attributes: requested_attributes,
@@ -136,7 +133,6 @@
issuer: issuer,
aal_level_requested: 1,
acr_values: request_acr,
- piv_cac_requested: false,
request_url: request_url,
request_id: request_id,
requested_attributes: requested_attributes,
@@ -157,7 +153,6 @@
issuer: issuer,
aal_level_requested: 2,
acr_values: request_acr,
- piv_cac_requested: false,
request_url: request_url,
request_id: request_id,
requested_attributes: requested_attributes,
@@ -178,7 +173,6 @@
issuer: issuer,
aal_level_requested: 2,
acr_values: request_acr,
- piv_cac_requested: false,
request_url: request_url,
request_id: request_id,
requested_attributes: requested_attributes,
@@ -198,7 +192,6 @@
issuer: issuer,
aal_level_requested: 2,
acr_values: request_acr,
- piv_cac_requested: false,
request_url: request_url,
request_id: request_id,
requested_attributes: requested_attributes,
@@ -218,7 +211,6 @@
issuer: issuer,
aal_level_requested: 2,
acr_values: request_acr,
- piv_cac_requested: false,
request_url: request_url,
request_id: request_id,
requested_attributes: requested_attributes,
@@ -236,9 +228,8 @@
expect(app_session[:sp]).to eq(
{
issuer: issuer,
- acr_values: request_acr,
aal_level_requested: 2,
- piv_cac_requested: false,
+ acr_values: request_acr,
request_url: request_url,
request_id: request_id,
requested_attributes: requested_attributes,
@@ -258,7 +249,6 @@
issuer: issuer,
aal_level_requested: 2,
acr_values: request_acr,
- piv_cac_requested: true,
request_url: request_url,
request_id: request_id,
requested_attributes: requested_attributes,
@@ -278,7 +268,6 @@
issuer: issuer,
aal_level_requested: 2,
acr_values: request_acr,
- piv_cac_requested: true,
request_url: request_url,
request_id: request_id,
requested_attributes: requested_attributes,
@@ -298,7 +287,6 @@
issuer: issuer,
aal_level_requested: 2,
acr_values: request_acr,
- piv_cac_requested: false,
request_url: request_url,
request_id: request_id,
requested_attributes: requested_attributes,
diff --git a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb
index 07909fb2ce2..6f72187974c 100644
--- a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb
+++ b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb
@@ -198,6 +198,7 @@
opted_in_to_in_person_proofing: nil,
second_address_line_present: false,
service_provider: nil,
+ tmx_status: nil,
)
end
end
@@ -216,6 +217,7 @@
opted_in_to_in_person_proofing: nil,
second_address_line_present: false,
service_provider: issuer,
+ tmx_status: nil,
)
end
end
@@ -242,6 +244,7 @@
opted_in_to_in_person_proofing: nil,
second_address_line_present: false,
service_provider: nil,
+ tmx_status: nil,
)
end
@@ -261,6 +264,7 @@
opted_in_to_in_person_proofing: nil,
second_address_line_present: true,
service_provider: nil,
+ tmx_status: nil,
)
end
end
@@ -279,6 +283,7 @@
opted_in_to_in_person_proofing: true,
second_address_line_present: false,
service_provider: nil,
+ tmx_status: nil,
)
end
end
diff --git a/spec/support/oidc_auth_helper.rb b/spec/support/oidc_auth_helper.rb
index 4e533751983..3c236979331 100644
--- a/spec/support/oidc_auth_helper.rb
+++ b/spec/support/oidc_auth_helper.rb
@@ -16,6 +16,13 @@ def visit_idp_from_ial1_oidc_sp(**args)
oidc_path
end
+ def visit_idp_from_oidc_sp_with_vtr(vtr:, **args)
+ params = vtr_params(vtr: vtr, **args)
+ oidc_path = openid_connect_authorize_path params
+ visit oidc_path
+ oidc_path
+ end
+
def visit_idp_from_ial_max_oidc_sp(**args)
args[:acr_values] = Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF
params = ial2_params(**args)
@@ -55,11 +62,12 @@ def visit_idp_from_ial1_oidc_sp_defaulting_to_aal3(**args)
oidc_path
end
- def ial1_params(prompt: nil,
- state: SecureRandom.hex,
- nonce: SecureRandom.hex,
- client_id: OIDC_IAL1_ISSUER,
- tid: nil)
+ def ial1_params(
+ prompt: nil,
+ state: SecureRandom.hex,
+ nonce: SecureRandom.hex,
+ client_id: OIDC_IAL1_ISSUER
+ )
ial1_params = {
client_id: client_id,
response_type: 'code',
@@ -69,18 +77,18 @@ def ial1_params(prompt: nil,
state: state,
nonce: nonce,
}
- ial1_params[:tid] = tid if tid
ial1_params[:prompt] = prompt if prompt
ial1_params
end
- def ial2_params(prompt: nil,
- state: SecureRandom.hex,
- nonce: SecureRandom.hex,
- client_id: OIDC_ISSUER,
- acr_values: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF,
- tid: nil,
- biometric_comparison_required: false)
+ def ial2_params(
+ prompt: nil,
+ state: SecureRandom.hex,
+ nonce: SecureRandom.hex,
+ client_id: OIDC_ISSUER,
+ acr_values: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF,
+ biometric_comparison_required: false
+ )
ial2_params = {
client_id: client_id,
response_type: 'code',
@@ -90,7 +98,6 @@ def ial2_params(prompt: nil,
state: state,
nonce: nonce,
}
- ial2_params[:tid] = tid if tid
ial2_params[:prompt] = prompt if prompt
if biometric_comparison_required
ial2_params[:biometric_comparison_required] = 'true'
@@ -98,6 +105,27 @@ def ial2_params(prompt: nil,
ial2_params
end
+ def vtr_params(
+ vtr:,
+ prompt: nil,
+ state: SecureRandom.hex,
+ nonce: SecureRandom.hex,
+ client_id: OIDC_ISSUER,
+ scope: 'openid email profile:name social_security_number'
+ )
+ vtr_params = {
+ client_id: client_id,
+ response_type: 'code',
+ vtr: Array(vtr).to_json,
+ scope: scope,
+ redirect_uri: 'http://localhost:7654/auth/result',
+ state: state,
+ nonce: nonce,
+ }
+ vtr_params[:prompt] = prompt if prompt
+ vtr_params
+ end
+
def include_phishing_resistant(params)
params[:acr_values] = "#{params[:acr_values]} " +
Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF
diff --git a/spec/support/shared_examples_for_email_validation.rb b/spec/support/shared_examples_for_email_validation.rb
index 493f0358234..e1b70535ff6 100644
--- a/spec/support/shared_examples_for_email_validation.rb
+++ b/spec/support/shared_examples_for_email_validation.rb
@@ -4,6 +4,6 @@
detect { |v| v.instance_of?(EmailValidator) }
expect(email_validator.options).
- to eq(mx_with_fallback: true, ban_disposable_email: true)
+ to eq(mx_with_fallback: true, ban_disposable_email: true, partial: true)
end
end