Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deploy RC 420 to Production #11323

Merged
merged 11 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 0 additions & 51 deletions app/controllers/concerns/idv/threat_metrix_concern.rb

This file was deleted.

49 changes: 49 additions & 0 deletions app/controllers/concerns/threat_metrix_concern.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

module ThreatMetrixConcern
THREAT_METRIX_DOMAIN = 'h.online-metrix.net'
THREAT_METRIX_WILDCARD_DOMAIN = '*.online-metrix.net'

def override_csp_for_threat_metrix
return unless FeatureManagement.proofing_device_profiling_collecting_enabled?

threat_metrix_csp_overrides
end

def threat_metrix_csp_overrides
policy = current_content_security_policy

# ThreatMetrix requires additional Content Security Policy (CSP)
# directives to be added to the response to enable its JS to run
# in the browser.

# `script-src` must be updated to enable:
# - The domain hosting ThreatMetrix JS (so it can be included on the page)
# - `unsafe-eval`, since the ThreatMetrix JS uses eval() internally.
policy.script_src(*policy.script_src.to_set.merge([THREAT_METRIX_DOMAIN, :unsafe_eval]))

# `style-src` must be updated to enable:
# - `unsafe-inline`, since the ThreatMetrix library applies inline
# styles to elements it inserts into the DOM
request.content_security_policy_nonce_directives =
request.content_security_policy_nonce_directives.without('style-src')
policy.style_src(*(policy.style_src.to_set << :unsafe_inline))

# `img-src` must be updated to enable:
# - A wildcard domain, since the JS loads images from different
# subdomains of the main ThreatMetrix domain.
policy.img_src(*(policy.img_src.to_set << THREAT_METRIX_WILDCARD_DOMAIN))

# `connect-src` must be updated to enable:
# - The domain hosting ThreatMetrix JS, since ThreatMetrix makes XHR
# requests to this domain.
policy.connect_src(*(policy.connect_src.to_set << THREAT_METRIX_DOMAIN))

# `child-src` must be updated to enable:
# - The domain hosting ThreatMetrix JS, which used to load a fallback
# `<iframe>` element when Javascript is disabled.
policy.child_src(*(policy.child_src.to_set << THREAT_METRIX_DOMAIN))

request.content_security_policy = policy
end
end
2 changes: 1 addition & 1 deletion app/controllers/idv/in_person_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class InPersonController < ApplicationController

include IdvSessionConcern
include Flow::FlowStateMachine
include Idv::ThreatMetrixConcern
include ThreatMetrixConcern

before_action :redirect_if_flow_completed

Expand Down
20 changes: 19 additions & 1 deletion app/controllers/sign_up/registrations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
module SignUp
class RegistrationsController < ApplicationController
include ApplicationHelper # for ial2_requested?
include ThreatMetrixHelper
include ThreatMetrixConcern

before_action :confirm_two_factor_authenticated, only: [:destroy_confirm]
before_action :require_no_authentication
before_action :redirect_if_ial2_and_idv_unavailable
before_action :override_csp_for_threat_metrix

CREATE_ACCOUNT = 'create_account'

def new
@register_user_email_form = RegisterUserEmailForm.new(analytics:)
analytics.user_registration_enter_email_visit
render :new, formats: :html
render :new, formats: :html, locals: threatmetrix_variables
end

def create
Expand Down Expand Up @@ -64,5 +67,20 @@ def redirect_if_ial2_and_idv_unavailable
redirect_to idv_unavailable_path(from: CREATE_ACCOUNT)
end
end

def threatmetrix_variables
return {} unless FeatureManagement.account_creation_device_profiling_collecting_enabled?
session_id = generate_threatmetrix_session_id

{
threatmetrix_session_id: session_id,
threatmetrix_javascript_urls: threatmetrix_javascript_urls(session_id),
threatmetrix_iframe_url: threatmetrix_iframe_url(session_id),
}
end

def generate_threatmetrix_session_id
session[:threatmetrix_session_id] ||= SecureRandom.uuid
end
end
end
10 changes: 5 additions & 5 deletions app/controllers/test/oidc_test_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Test
class OidcTestController < ApplicationController
include OidcAuthHelper

BIOMETRIC_REQUIRED = 'biometric-comparison-required'
FACIAL_MATCH_REQUIRED = 'facial-match-required'

def initialize
@client_id = 'urn:gov:gsa:openidconnect:sp:sinatra'
Expand All @@ -14,7 +14,7 @@ def initialize

def index
# default to require
@start_url_selfie = "#{test_oidc_auth_request_url}?ial=biometric-comparison-required"
@start_url_selfie = "#{test_oidc_auth_request_url}?ial=#{FACIAL_MATCH_REQUIRED}"
@start_url_ial2 = "#{test_oidc_auth_request_url}?ial=2"
@start_url_ial1 = "#{test_oidc_auth_request_url}?ial=1"
update_service_provider
Expand Down Expand Up @@ -46,7 +46,7 @@ def authorization_url(ial:, aal: nil)
params = ial2_params(
client_id: client_id,
acr_values: acr_values(ial: ial, aal: aal),
biometric_comparison_required: ial == BIOMETRIC_REQUIRED,
facial_match_required: ial == FACIAL_MATCH_REQUIRED,
state: random_value,
nonce: random_value,
)
Expand All @@ -70,7 +70,7 @@ def scopes_for(ial)
'openid email social_security_number'
when '1', nil
'openid email'
when '2', BIOMETRIC_REQUIRED
when '2', FACIAL_MATCH_REQUIRED
'openid email profile social_security_number phone address'
else
raise ArgumentError.new("Unexpected IAL: #{ial.inspect}")
Expand All @@ -84,7 +84,7 @@ def acr_values(ial:, aal:)
'' => 'http://idmanagement.gov/ns/assurance/ial/1',
'1' => 'http://idmanagement.gov/ns/assurance/ial/1',
'2' => 'http://idmanagement.gov/ns/assurance/ial/2',
'biometric-comparison-required' => 'http://idmanagement.gov/ns/assurance/ial/2',
'facial-match-required' => 'http://idmanagement.gov/ns/assurance/ial/2',
}[ial]
aal_value = {
'2' => 'http://idmanagement.gov/ns/assurance/aal/2',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
border-width: 3px;
}

usa-file-input:not(
.usa-file-input--has-value,
.usa-file-input--value-pending,
.usa-file-input--is-id-capture
)
.usa-form-group--success
.usa-file-input
.usa-file-input__target {
height: 21rem;
width: 12rem;
}

.usa-file-input:not(.usa-file-input--has-value, .usa-file-input--value-pending) {
.usa-file-input__target {
border-color: color('primary');
Expand Down Expand Up @@ -75,7 +87,17 @@
width: 100%;
}
}

.usa-file-input.usa-file-input--single-value:not(.usa-file-input--is-id-capture) {
.usa-file-input__preview {
width: 12rem;
}
.usa-file-input__target {
width: 12rem;
}
.usa-file-input__preview-image {
width: 12rem;
}
}
.usa-file-input__input:not([disabled]):focus {
outline: 3px solid color('primary');
outline-offset: 6px;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { useContext } from 'react';
import { useI18n } from '@18f/identity-react-i18n';
import { FormStepComponentProps, FormStepsButton } from '@18f/identity-form-steps';
import { PageHeading } from '@18f/identity-components';
import { Cancel } from '@18f/identity-verify-flow';
import HybridDocCaptureWarning from './hybrid-doc-capture-warning';
import TipList from './tip-list';
import { DeviceContext, SelfieCaptureContext, UploadContext } from '../context';
import { DeviceContext, UploadContext } from '../context';
import {
ImageValue,
DefaultSideProps,
Expand Down Expand Up @@ -41,13 +40,7 @@ export function DocumentsCaptureStep({

export function DocumentCaptureSubheaderOne() {
const { t } = useI18n();
return (
<h2>
<hr className="margin-y-5" />
{'1. '}
{t('doc_auth.headings.document_capture_subheader_id')}
</h2>
);
return <h1>{t('doc_auth.headings.document_capture')}</h1>;
}

export default function DocumentsStep({
Expand All @@ -60,10 +53,6 @@ export default function DocumentsStep({
const { t } = useI18n();
const { isMobile } = useContext(DeviceContext);
const { flowPath } = useContext(UploadContext);
const { isSelfieCaptureEnabled } = useContext(SelfieCaptureContext);
const pageHeaderText = isSelfieCaptureEnabled
? t('doc_auth.headings.document_capture_with_selfie')
: t('doc_auth.headings.document_capture');
const defaultSideProps: DefaultSideProps = {
registerField,
onChange,
Expand All @@ -73,7 +62,6 @@ export default function DocumentsStep({
return (
<>
{flowPath === 'hybrid' && <HybridDocCaptureWarning className="margin-bottom-4" />}
<PageHeading>{pageHeaderText}</PageHeading>
<DocumentCaptureSubheaderOne />
<TipList
titleClassName="margin-bottom-0 text-bold"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ function FileInput(props: FileInputProps, ref: ForwardedRef<any>) {
// they don't have a preview. This shows the name of the file in the upload
// box (using the existing preview) when the file name ends with .yml
const isYAMLFile: boolean = value instanceof window.File && value.name.endsWith('.yml');
const isIdCapture: boolean = !(label === t('doc_auth.headings.document_capture_selfie'));

/**
* In response to a file input change event, confirms that the file is valid before calling
Expand Down Expand Up @@ -387,6 +388,7 @@ function FileInput(props: FileInputProps, ref: ForwardedRef<any>) {
isDraggingOver && 'usa-file-input--drag',
value && !isValuePending && 'usa-file-input--has-value',
isValuePending && 'usa-file-input--value-pending',
isIdCapture && 'usa-file-input--is-id-capture',
]
.filter(Boolean)
.join(' ')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
FormStepsButton,
FormStepsContext,
} from '@18f/identity-form-steps';
import { PageHeading } from '@18f/identity-components';
import { Cancel } from '@18f/identity-verify-flow';
import HybridDocCaptureWarning from './hybrid-doc-capture-warning';
import DocumentSideAcuantCapture from './document-side-acuant-capture';
Expand All @@ -29,8 +28,7 @@ export function SelfieCaptureStep({
const { t } = useI18n();
return (
<>
<hr className="margin-y-5" />
<h2>2. {t('doc_auth.headings.document_capture_subheader_selfie')}</h2>
<h1>{t('doc_auth.headings.document_capture_subheader_selfie')}</h1>
<p>{t('doc_auth.info.selfie_capture_content')}</p>
<TipList
title={t('doc_auth.tips.document_capture_selfie_selfie_text')}
Expand Down Expand Up @@ -60,10 +58,8 @@ export default function SelfieStep({
onError = () => {},
registerField = () => undefined,
}: FormStepComponentProps<DocumentsAndSelfieStepValue>) {
const { t } = useI18n();
const { isLastStep } = useContext(FormStepsContext);
const { flowPath } = useContext(UploadContext);
const pageHeaderText = t('doc_auth.headings.document_capture_with_selfie');

const defaultSideProps: DefaultSideProps = {
registerField,
Expand All @@ -74,7 +70,6 @@ export default function SelfieStep({
return (
<>
{flowPath === 'hybrid' && <HybridDocCaptureWarning className="margin-bottom-4" />}
<PageHeading>{pageHeaderText}</PageHeading>
<SelfieCaptureStep
defaultSideProps={defaultSideProps}
selfieValue={value.selfie}
Expand Down
4 changes: 3 additions & 1 deletion app/models/profile.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

class Profile < ApplicationRecord
FACIAL_MATCH_IDV_LEVELS = %w[unsupervised_with_selfie in_person].to_set.freeze

belongs_to :user
# rubocop:disable Rails/InverseOf
belongs_to :initiating_service_provider,
Expand Down Expand Up @@ -310,7 +312,7 @@ def profile_age_in_seconds
end

def facial_match?
::User::FACIAL_MATCH_IDV_LEVELS.include?(idv_level)
FACIAL_MATCH_IDV_LEVELS.include?(idv_level)
end

private
Expand Down
4 changes: 1 addition & 3 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ class User < ApplicationRecord
MAX_RECENT_EVENTS = 5
MAX_RECENT_DEVICES = 5

FACIAL_MATCH_IDV_LEVELS = %w[unsupervised_with_selfie in_person].to_set.freeze

enum otp_delivery_preference: { sms: 0, voice: 1 }

# rubocop:disable Rails/HasManyOrHasOneDependent
Expand Down Expand Up @@ -377,7 +375,7 @@ def identity_verified?
end

def identity_verified_with_facial_match?
FACIAL_MATCH_IDV_LEVELS.include?(active_profile&.idv_level)
active_profile.present? && active_profile.facial_match?
end

# This user's most recently activated profile that has also been deactivated
Expand Down
Loading