Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e1e3504
Modify `OpenidConnect::AuthorizationController#link_identity_to_servi…
jmhooper May 20, 2024
5cb708a
LG-13345 | FormObject fix in AgreementController (#10652)
n1zyy May 20, 2024
da4e985
LG-13214: Log presence of issue date and exp date (#10658)
solipet May 20, 2024
00db905
Remove unused Step Indicator component styles (#10661)
aduth May 20, 2024
731fa7d
LG-13200: DOS translations (#10651)
mdiarra3 May 21, 2024
15a5301
Remove unused YML file flatten helpers (#10579)
zachmargolis May 21, 2024
55e2076
Use design system centered variant for banner (#10663)
aduth May 21, 2024
ca1ce25
LG-13216: AAMVA issue date + expiration validation (log only) (#10653)
matthinz May 21, 2024
9863562
Fix command for review app configuration in GitLab job (#10667)
May 21, 2024
56cb759
feat: LG13235 consume eipp data from sinatra (#10618)
KeithNava May 21, 2024
f50fcc8
feat: updated translations for DOS IPP (#10428)
KeithNava May 21, 2024
f747744
Prevent USPS Proofing Results Job from failing when in_person_enrollm…
jack-ryan-nava-pbc May 21, 2024
82e5056
LG-13171: i18n zh text (#10557)
dawei-nava May 21, 2024
db7f26b
Avoid outputting font-face for unused light font weight (#10673)
aduth May 22, 2024
d753d55
Subset fonts to used character data (#10655)
aduth May 22, 2024
43ed171
LG-13220: Fix aggregated new device sign-in for expired session (#10628)
aduth May 22, 2024
888e76e
LG-13043 Update translations for "How verifiying your identity works"…
theabrad May 22, 2024
4cc947a
Use NewDeviceConcern consistently for session value (#10678)
aduth May 22, 2024
4ebfb83
Fix logging envs all being put in the same log group (#10670)
voidlily May 22, 2024
e2921f6
LG-13117: Add tests for resubmit h1 and body copy (#10674)
May 22, 2024
9250d04
LG-13038 Add user context to the `​​AuthnContextResolver` (#10517)
jmhooper May 22, 2024
940d9f3
lg-13006 rename skip_doc_auth (#10672)
racingspider May 22, 2024
9f9c910
LG-13363: Fix Screenreader Focus Jumping During Selfie Capture (#10668)
charleyf May 22, 2024
c5b25f6
Flatten public packs directory (#10677)
aduth May 22, 2024
96032fc
Rm unusued argument in general error message (#10660)
May 22, 2024
97e5c06
LG-13005: use TransactionStatus to determine if document passed TrueI…
amirbey May 22, 2024
096aceb
Agnes/lg 13352 billing model refresh v2 (#10683)
colter-nattrass May 22, 2024
d2505df
LG-13313 Add multiple vector of trust support for SAML (#10680)
jmhooper May 22, 2024
4b080a1
LG-13216: Gaze into the AAMVA SOAP abyss. (#10676)
matthinz May 22, 2024
070534f
LG-13402: Prompt user to confirm setting up backup codes from account…
aduth May 23, 2024
6af2802
LG-13199: Sprint 309 Translations (#10642)
mdiarra3 May 23, 2024
9baa05e
Revert "LG-13005: use TransactionStatus to determine if document pass…
amirbey May 23, 2024
1a4b8eb
LG-13216: Include requested_attributes in StateIdResult::to_h (#10684)
matthinz May 23, 2024
4a9ebd5
Delete and regenerate backup codes in a single transaction (#10686)
May 23, 2024
268b502
Verify and consume backup code in single database transaction (#10687)
May 23, 2024
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
4 changes: 2 additions & 2 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ trigger_devops:
- >-
helm upgrade --install --namespace review-apps
--debug
--set env="reviewapps"
--set env="reviewapps-$CI_ENVIRONMENT_SLUG"
--set idp.image.repository="${ECR_REGISTRY}/identity-idp/review"
--set idp.image.tag="${CI_COMMIT_SHA}"
--set worker.image.repository="${ECR_REGISTRY}/identity-idp/review"
Expand All @@ -490,7 +490,7 @@ trigger_devops:
--set-json dashboard.ingress.hosts="[{\"host\": \"$CI_ENVIRONMENT_SLUG-review-app-dashboard.review-app.identitysandbox.gov\", \"paths\": [{\"path\": \"/\", \"pathType\": \"Prefix\"}]}]"
$CI_ENVIRONMENT_SLUG ./identity-idp-helm-chart
- echo "DNS may take a while to propagate, so be patient if it doesn't show up right away"
- echo "To access the rails console, first run 'aws-vault exec sandbox-power -- aws eks update-kubeconfig --name review_app'"
- echo "To access the rails console, first run 'aws-vault exec sandbox-power -- aws eks update-kubeconfig --name reviewapps'"
- echo "Then run aws-vault exec sandbox-power -- kubectl exec -it service/$CI_ENVIRONMENT_SLUG-login-chart-idp -n review-apps -- /app/bin/rails console"
- echo "Address of IDP review app:"
- echo https://$CI_ENVIRONMENT_SLUG.review-app.identitysandbox.gov
Expand Down
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ gem 'rqrcode'
gem 'ruby-progressbar'
gem 'ruby-saml'
gem 'safe_target_blank', '>= 1.0.2'
gem 'saml_idp', github: '18F/saml_idp', tag: '0.20.2-18f'
gem 'saml_idp', github: '18F/saml_idp', tag: '0.21.0-18f'
gem 'scrypt'
gem 'simple_form', '>= 5.0.2'
gem 'stringex', require: false
Expand Down
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ GIT

GIT
remote: https://github.com/18F/saml_idp.git
revision: dd8643b16c8214f7b791763538180d043af7ef65
tag: 0.20.2-18f
revision: 33275d69f7609e448942d6e3ce5c27779920995f
tag: 0.21.0-18f
specs:
saml_idp (0.20.2.pre.18f)
saml_idp (0.21.0.pre.18f)
activesupport
builder
faraday
Expand Down
14 changes: 13 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ ARTIFACT_DESTINATION_FILE ?= ./tmp/idp.tar.gz
lint_analytics_events_sorted \
lint_country_dialing_codes \
lint_erb \
lint_font_glyphs \
lint_lockfiles \
lint_new_typescript_files \
lint_optimized_assets \
Expand Down Expand Up @@ -87,6 +88,8 @@ endif
# Other
@echo "--- lint yaml ---"
make lint_yaml
@echo "--- lint font glyphs ---"
make lint_font_glyphs
@echo "--- lint Yarn workspaces ---"
make lint_yarn_workspaces
@echo "--- lint new TypeScript files ---"
Expand All @@ -110,6 +113,15 @@ lint_erb: ## Lints ERB files
lint_yaml: normalize_yaml ## Lints YAML files
(! git diff --name-only | grep "^config/.*\.yml$$") || (echo "Error: Run 'make normalize_yaml' to normalize YAML"; exit 1)

lint_font_glyphs: ## Lints to validate content glyphs match expectations from fonts
scripts/yaml_characters \
--exclude-locale=zh \
--exclude-gem-path=faker \
--exclude-gem-path=good_job \
--exclude-gem-path=i18n-tasks \
> app/assets/fonts/glyphs.txt
(! git diff --name-only | grep "glyphs\.txt$$") || (echo "Error: New character data found. Follow 'Fonts' instructions in 'docs/frontend.md' to regenerate fonts."; exit 1)

lint_yarn_workspaces: ## Lints Yarn workspace packages
scripts/validate-workspaces.mjs

Expand All @@ -121,7 +133,7 @@ lint_asset_bundle_size: ## Lints JavaScript and CSS compiled bundle size
@# budget and accept the fact that this will force end-users to endure longer load times, you
@# should set the new budget to within a few thousand bytes of the production-compiled size.
find app/assets/builds/application.css -size -185000c | grep .
find public/packs/js/application-*.digested.js -size -5000c | grep .
find public/packs/application-*.digested.js -size -5000c | grep .

lint_migrations:
scripts/migration_check
Expand Down
1 change: 1 addition & 0 deletions app/assets/fonts/glyphs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!"#$%&'(),-./0123456789:;>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ «»¿ÀÁÈÉÊÎÓÚàáâãçèéêëíîïñóôùúû ‑—‘’“”…‹中体文简
Binary file removed app/assets/fonts/public-sans/PublicSans-Black.woff
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-Black.woff2
Binary file not shown.
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-BlackItalic.woff2
Binary file not shown.
Binary file removed app/assets/fonts/public-sans/PublicSans-Bold.woff
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-Bold.woff2
Binary file not shown.
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-BoldItalic.woff2
Binary file not shown.
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-ExtraBold.woff2
Binary file not shown.
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-ExtraBoldItalic.woff2
Binary file not shown.
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-ExtraLight.woff2
Binary file not shown.
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-ExtraLightItalic.woff2
Binary file not shown.
Binary file removed app/assets/fonts/public-sans/PublicSans-Italic.woff
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-Italic.woff2
Binary file not shown.
Binary file removed app/assets/fonts/public-sans/PublicSans-Light.woff
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-Light.woff2
Binary file not shown.
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-LightItalic.woff2
Binary file not shown.
Binary file removed app/assets/fonts/public-sans/PublicSans-Medium.woff
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-Medium.woff2
Binary file not shown.
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-MediumItalic.woff2
Binary file not shown.
Binary file removed app/assets/fonts/public-sans/PublicSans-Regular.woff
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-Regular.woff2
Binary file not shown.
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-SemiBold.woff2
Binary file not shown.
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-SemiBoldItalic.woff2
Binary file not shown.
Binary file removed app/assets/fonts/public-sans/PublicSans-Thin.woff
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-Thin.woff2
Binary file not shown.
Binary file not shown.
Binary file modified app/assets/fonts/public-sans/PublicSans-ThinItalic.woff2
Binary file not shown.
1 change: 1 addition & 0 deletions app/assets/stylesheets/_uswds-core.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
$theme-body-font-size: 'sm',
$theme-button-icon-gap: 0.5,
$theme-font-path: '',
$theme-font-weight-light: false,
$theme-image-path: '',
$theme-global-border-box-sizing: true,
$theme-global-link-styles: true,
Expand Down
7 changes: 0 additions & 7 deletions app/assets/stylesheets/components/_banner.scss

This file was deleted.

1 change: 0 additions & 1 deletion app/assets/stylesheets/components/_index.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
@forward 'account-header';
@forward 'alert-icon';
@forward 'alert';
@forward 'banner';
@forward 'block-link';
@forward 'btn';
@forward 'card';
Expand Down
11 changes: 0 additions & 11 deletions app/assets/stylesheets/components/_step-indicator.scss
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,3 @@ lg-step-indicator {
.step-indicator__step--current .step-indicator__step-title {
font-weight: bold;
}

.step-indicator__step-subtitle {
@include at-media-max('tablet') {
@include sr-only;
}

@include at-media('tablet') {
display: block;
font-style: italic;
}
}
1 change: 1 addition & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ def resolved_authn_context_result
@resolved_authn_context_result = Vot::Parser::Result.no_sp_result
else
@resolved_authn_context_result = AuthnContextResolver.new(
user: current_user,
service_provider: service_provider,
vtr: sp_session[:vtr],
acr_values: sp_session[:acr_values],
Expand Down
11 changes: 11 additions & 0 deletions app/controllers/concerns/new_device_concern.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module NewDeviceConcern
def set_new_device_session
user_session[:new_device] = !current_user.authenticated_device?(cookie_uuid: cookies[:device])
end

def new_device?
user_session[:new_device] != false
end
end
9 changes: 6 additions & 3 deletions app/controllers/concerns/saml_idp_auth_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ module SamlIdpAuthConcern
private

def block_biometric_requests_in_production
if @saml_request_validator.parsed_vector_of_trust&.biometric_comparison? &&
if @saml_request_validator.biometric_comparison_requested? &&
!FeatureManagement.idv_allow_selfie_check?
render_not_acceptable
end
Expand Down Expand Up @@ -130,9 +130,12 @@ def default_ial_context
end

def response_authn_context
saml_request.requested_vtr_authn_context ||
if saml_request.requested_vtr_authn_contexts.present?
resolved_authn_context_result.expanded_component_values
else
saml_request.requested_aal_authn_context ||
default_aal_context
default_aal_context
end
end

def requested_ial_authn_context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module TwoFactorAuthenticatableMethods
include RememberDeviceConcern
include SecureHeadersConcern
include MfaSetupConcern
include NewDeviceConcern

def auth_methods_session
@auth_methods_session ||= AuthMethodsSession.new(user_session:)
Expand All @@ -14,8 +15,7 @@ def handle_valid_verification_for_authentication_context(auth_method:)
mark_user_session_authenticated(auth_method:, authentication_type: :valid_2fa)
disavowal_event, disavowal_token = create_user_event_with_disavowal(:sign_in_after_2fa)

if IdentityConfig.store.feature_new_device_alert_aggregation_enabled &&
user_session[:new_device] != false
if IdentityConfig.store.feature_new_device_alert_aggregation_enabled && new_device?
if current_user.sign_in_new_device_at.blank?
current_user.update(sign_in_new_device_at: disavowal_event.created_at)
end
Expand Down
7 changes: 5 additions & 2 deletions app/controllers/idv/agreement_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ def update
clear_future_steps!
skip_to_capture if params[:skip_hybrid_handoff]

result = Idv::ConsentForm.new.submit(consent_form_params)
@consent_form = Idv::ConsentForm.new(
idv_consent_given: idv_session.idv_consent_given,
)
result = @consent_form.submit(consent_form_params)

analytics.idv_doc_auth_agreement_submitted(
**analytics_arguments.merge(result.to_h),
Expand All @@ -42,7 +45,7 @@ def update
redirect_to idv_hybrid_handoff_url
end
else
redirect_to idv_agreement_url
render :show
end
end

Expand Down
3 changes: 2 additions & 1 deletion app/controllers/idv/document_capture_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def extra_view_variables
sp_name: decorated_sp_session.sp_name,
failure_to_proof_url: return_to_sp_failure_to_proof_url(step: 'document_capture'),
skip_doc_auth: idv_session.skip_doc_auth,
skip_doc_auth_from_how_to_verify: false,
skip_doc_auth_from_how_to_verify: idv_session.skip_doc_auth_from_how_to_verify,
skip_doc_auth_from_handoff: idv_session.skip_doc_auth_from_handoff,
opted_in_to_in_person_proofing: idv_session.opted_in_to_in_person_proofing,
doc_auth_selfie_capture:,
Expand All @@ -73,6 +73,7 @@ def self.step_info
idv_session.skip_doc_auth_from_handoff ||
idv_session.skip_hybrid_handoff ||
idv_session.skip_doc_auth ||
idv_session.skip_doc_auth_from_how_to_verify ||
!idv_session.selfie_check_required || # desktop but selfie not required
idv_session.desktop_selfie_test_mode_enabled?
)
Expand Down
3 changes: 3 additions & 0 deletions app/controllers/idv/how_to_verify_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ def update
if how_to_verify_form_params['selection'] == Idv::HowToVerifyForm::REMOTE
idv_session.opted_in_to_in_person_proofing = false
idv_session.skip_doc_auth = false
idv_session.skip_doc_auth_from_how_to_verify = false
redirect_to idv_hybrid_handoff_url
else
idv_session.opted_in_to_in_person_proofing = true
idv_session.flow_path = 'standard'
idv_session.skip_doc_auth = true
idv_session.skip_doc_auth_from_how_to_verify = true
redirect_to idv_document_capture_url
end

Expand All @@ -70,6 +72,7 @@ def self.step_info
end,
undo_step: ->(idv_session:, user:) {
idv_session.skip_doc_auth = nil
idv_session.skip_doc_auth_from_how_to_verify = nil
idv_session.opted_in_to_in_person_proofing = nil
},
)
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/idv/hybrid_handoff_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ def self.selected_remote(idv_session:)
idv_session.service_provider&.in_person_proofing_enabled
idv_session.skip_doc_auth == false
else
idv_session.skip_doc_auth.nil? || idv_session.skip_doc_auth == false
idv_session.skip_doc_auth.nil? ||
idv_session.skip_doc_auth == false
end
end

Expand Down
20 changes: 17 additions & 3 deletions app/controllers/openid_connect/authorization_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def block_biometric_requests_in_production
end

def biometric_comparison_requested?
@authorize_form.parsed_vector_of_trust&.biometric_comparison?
@authorize_form.biometric_comparison_requested?
end

def check_sp_active
Expand Down Expand Up @@ -93,17 +93,31 @@ def set_devise_failure_redirect_for_concurrent_session_logout
end

def link_identity_to_service_provider
@authorize_form.link_identity_to_service_provider(current_user, session.id)
@authorize_form.link_identity_to_service_provider(
current_user: current_user,
ial: resolved_authn_context_int_ial,
rails_session_id: session.id,
)
end

def ial_context
IalContext.new(
ial: @authorize_form.ial,
ial: resolved_authn_context_int_ial,
service_provider: @authorize_form.service_provider,
user: current_user,
)
end

def resolved_authn_context_int_ial
if resolved_authn_context_result.ialmax?
0
elsif resolved_authn_context_result.identity_proofing?
2
else
1
end
end

def handle_successful_handoff
track_events
SpHandoffBounce::AddHandoffTimeToSession.call(sp_session)
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/saml_idp_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def log_external_saml_auth_request
requested_ial: requested_ial,
authn_context: saml_request&.requested_authn_contexts,
requested_aal_authn_context: saml_request&.requested_aal_authn_context,
requested_vtr_authn_context: saml_request&.requested_vtr_authn_context,
requested_vtr_authn_contexts: saml_request&.requested_vtr_authn_contexts.presence,
force_authn: saml_request&.force_authn?,
final_auth_request: sp_session[:final_auth_request],
service_provider: saml_request&.issuer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
module TwoFactorAuthentication
class BackupCodeVerificationController < ApplicationController
include TwoFactorAuthenticatable
include NewDeviceConcern

prepend_before_action :authenticate_user
before_action :check_sp_required_mfa
Expand All @@ -22,7 +23,7 @@ def create
@backup_code_form = BackupCodeVerificationForm.new(current_user)
result = @backup_code_form.submit(backup_code_params)
analytics.track_mfa_submit_event(
result.to_h.merge(new_device: user_session[:new_device]),
result.to_h.merge(new_device: new_device?),
)
irs_attempts_api_tracker.mfa_login_backup_code(success: result.success?)
handle_result(result)
Expand All @@ -36,9 +37,7 @@ def all_codes_used?

def handle_last_code
generator = BackupCodeGenerator.new(current_user)
generator.delete_existing_codes
user_session[:backup_codes] = generator.generate
generator.save(user_session[:backup_codes])
user_session[:backup_codes] = generator.delete_and_regenerate
flash[:info] = t('forms.backup_code.last_code')
redirect_to backup_code_refreshed_url
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module TwoFactorAuthentication
class OtpVerificationController < ApplicationController
include TwoFactorAuthenticatable
include MfaSetupConcern
include NewDeviceConcern

before_action :check_sp_required_mfa
before_action :confirm_multiple_factors_enabled
Expand Down Expand Up @@ -132,7 +133,7 @@ def form_params
end

def post_analytics(result)
properties = result.to_h.merge(analytics_properties, new_device: user_session[:new_device])
properties = result.to_h.merge(analytics_properties, new_device: new_device?)
analytics.multi_factor_auth_setup(**properties) if context == 'confirmation'

analytics.track_mfa_submit_event(properties)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
module TwoFactorAuthentication
class PersonalKeyVerificationController < ApplicationController
include TwoFactorAuthenticatable
include NewDeviceConcern

prepend_before_action :authenticate_user
before_action :check_personal_key_enabled
Expand All @@ -28,7 +29,7 @@ def track_analytics(result)
analytics_hash = result.to_h.merge(
multi_factor_auth_method: 'personal-key',
multi_factor_auth_method_created_at: mfa_created_at&.strftime('%s%L'),
new_device: user_session[:new_device],
new_device: new_device?,
)

analytics.track_mfa_submit_event(analytics_hash)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module TwoFactorAuthentication
class PivCacVerificationController < ApplicationController
include TwoFactorAuthenticatable
include PivCacConcern
include NewDeviceConcern

before_action :confirm_piv_cac_enabled, only: :show
before_action :reset_attempt_count_if_user_no_longer_locked_out, only: :show
Expand Down Expand Up @@ -105,7 +106,7 @@ def analytics_properties
context: context,
multi_factor_auth_method: 'piv_cac',
piv_cac_configuration_id: piv_cac_verification_form&.piv_cac_configuration&.id,
new_device: user_session[:new_device],
new_device: new_device?,
}
end
end
Expand Down
Loading