Skip to content

Commit

Permalink
Merge pull request #11323 from 18F/stages/rc-2024-10-07
Browse files Browse the repository at this point in the history
Deploy RC 420 to Production
  • Loading branch information
zachmargolis authored Oct 8, 2024
2 parents ff42349 + 4b189a8 commit 9ac762c
Show file tree
Hide file tree
Showing 40 changed files with 430 additions and 330 deletions.
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

0 comments on commit 9ac762c

Please sign in to comment.