Skip to content

Commit

Permalink
Merge pull request #11632 from 18F/stages/rc-2024-12-12
Browse files Browse the repository at this point in the history
Deploy RC 438 to Production
  • Loading branch information
eileen-nava authored Dec 12, 2024
2 parents 8d3e4b0 + 3200cf8 commit 276fb50
Show file tree
Hide file tree
Showing 27 changed files with 461 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ def show
Funnel::DocAuth::RegisterStep.new(document_capture_user.id, sp_session[:issuer]).
call('hybrid_mobile_socure_document_capture', :view, true)

if document_capture_session.socure_docv_capture_app_url.present?
@url = document_capture_session.socure_docv_capture_app_url
return
end

# document request
document_request = DocAuth::Socure::Requests::DocumentRequest.new(
redirect_url: idv_hybrid_mobile_socure_document_capture_update_url,
Expand All @@ -40,9 +45,6 @@ def show
return
end

document_capture_session = DocumentCaptureSession.find_by(
uuid: document_capture_session_uuid,
)
document_capture_session.socure_docv_transaction_token = document_response.dig(
:data,
:docvTransactionToken,
Expand Down
9 changes: 5 additions & 4 deletions app/controllers/idv/socure/document_capture_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ def show
Funnel::DocAuth::RegisterStep.new(current_user.id, sp_session[:issuer]).
call('socure_document_capture', :view, true)

if document_capture_session.socure_docv_capture_app_url.present?
@url = document_capture_session.socure_docv_capture_app_url
return
end

# document request
document_request = DocAuth::Socure::Requests::DocumentRequest.new(
redirect_url: idv_socure_document_capture_update_url,
Expand All @@ -50,10 +55,6 @@ def show
return
end

document_capture_session = DocumentCaptureSession.find_by(
uuid: document_capture_session_uuid,
)

document_capture_session.socure_docv_transaction_token = document_response.dig(
:data,
:docvTransactionToken,
Expand Down
15 changes: 15 additions & 0 deletions app/presenters/account_show_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ def formatted_legacy_idv_date
I18n.l(user.active_profile.created_at, format: :event_date)
end

def connect_to_initiating_idv_sp_url
initiating_service_provider = user.active_profile&.initiating_service_provider
return nil if !initiating_service_provider.present?

SpReturnUrlResolver.new(service_provider: initiating_service_provider).post_idv_follow_up_url
end

def connected_to_initiating_idv_sp?
initiating_service_provider = user.active_profile&.initiating_service_provider
return false if !initiating_service_provider.present?

identity = user.identities.find_by(service_provider: initiating_service_provider.issuer)
!!identity&.last_ial2_authenticated_at.present?
end

def show_unphishable_badge?
MfaPolicy.new(user).unphishable?
end
Expand Down
11 changes: 11 additions & 0 deletions app/views/accounts/_identity_verification.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@
</div>
</div>

<% if @presenter.active_profile? && !@presenter.connected_to_initiating_idv_sp? %>
<%= render AlertComponent.new(type: :warning, class: 'margin-bottom-2') do %>
<%= t('account.index.verification.connect_idv_account.intro') %><br />
<% if @presenter.connect_to_initiating_idv_sp_url.present? %>
<%= link_to(t('account.index.verification.connect_idv_account.cta'), @presenter.connect_to_initiating_idv_sp_url) %>
<% else %>
<%= t('account.index.verification.connect_idv_account.cta') %>
<% end %>
<% end %>
<% end %>

<% if @presenter.active_profile? || @presenter.pending_ipp? || @presenter.pending_gpo? %>
<p>
<% if @presenter.active_profile_for_authn_context? %>
Expand Down
7 changes: 7 additions & 0 deletions config/initializers/app_artifacts.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'app_artifacts'
require 'openid_connect_key_validation'

AppArtifacts.setup do |store|
# When adding or removing certs, make sure to update the 'saml_endpoint_configs' config
Expand All @@ -12,3 +13,9 @@
store.add_artifact(:oidc_private_key, '/%<env>s/oidc.key') { |k| OpenSSL::PKey::RSA.new(k) }
store.add_artifact(:oidc_public_key, '/%<env>s/oidc.pub') { |k| OpenSSL::PKey::RSA.new(k) }
end

valid = OpenidConnectKeyValidation.valid?(
public_key: AppArtifacts.store.oidc_public_key,
private_key: AppArtifacts.store.oidc_private_key,
)
raise 'OIDC Public/Private Keys do not match' if !valid
2 changes: 2 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ account.index.reactivation.instructions: Your profile was recently deactivated d
account.index.reactivation.link: Reactivate your profile now.
account.index.sign_in_location_and_ip: From %{ip} (IP address potentially located in %{location})
account.index.unknown_location: unknown location
account.index.verification.connect_idv_account.cta: Sign in to partner agency to access services.
account.index.verification.connect_idv_account.intro: Connect your account to the partner agency.
account.index.verification.continue_idv: Continue identity verification
account.index.verification.finish_verifying_html: Finish verifying your identity to access <strong>%{sp_name}</strong>.
account.index.verification.finish_verifying_no_sp: Finish the identity verification process to gain access to all %{app_name} partners.
Expand Down
2 changes: 2 additions & 0 deletions config/locales/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ account.index.reactivation.instructions: Su perfil fue desactivado recientemente
account.index.reactivation.link: Reactive su perfil ahora.
account.index.sign_in_location_and_ip: Desde %{ip} (la dirección IP se encuentra posiblemente en %{location})
account.index.unknown_location: ubicación desconocida
account.index.verification.connect_idv_account.cta: Inicie sesión en la agencia asociada para acceder a los servicios.
account.index.verification.connect_idv_account.intro: Conecte su cuenta a la agencia asociada.
account.index.verification.continue_idv: Continuar la verificación de identidad
account.index.verification.finish_verifying_html: Termine de verificar su identidad para acceder a la <strong>%{sp_name}</strong>.
account.index.verification.finish_verifying_no_sp: Termine el proceso de verificación de identidad para obtener acceso a todos los asociados de %{app_name}.
Expand Down
2 changes: 2 additions & 0 deletions config/locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ account.index.reactivation.instructions: Votre profil a été récemment désact
account.index.reactivation.link: Réactiver votre profil maintenant.
account.index.sign_in_location_and_ip: De %{ip} (adresse IP éventuellement située dans %{location})
account.index.unknown_location: lieu inconnu
account.index.verification.connect_idv_account.cta: Connectez-vous à l’organisme partenaire pour accéder à ses services.
account.index.verification.connect_idv_account.intro: Associer votre compte à l’organisme partenaire.
account.index.verification.continue_idv: Poursuivre la vérification d’identité
account.index.verification.finish_verifying_html: Terminez la procédure de vérification d’identité pour pouvoir accéder à <strong>%{sp_name}</strong>.
account.index.verification.finish_verifying_no_sp: Terminer la procédure de vérification d’identité pour pouvoir accéder à tous les organismes partenaires de %{app_name}.
Expand Down
2 changes: 2 additions & 0 deletions config/locales/zh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ account.index.reactivation.instructions: 你的用户资料因为重设密码最
account.index.reactivation.link: 现在重新激活你的用户资料。
account.index.sign_in_location_and_ip: 从 %{ip}(IP 地址可能位于 %{location})。
account.index.unknown_location: 未知地点
account.index.verification.connect_idv_account.cta: 登录合作伙伴机构来获得服务。
account.index.verification.connect_idv_account.intro: 把你的账户连接到合作伙伴机构。
account.index.verification.continue_idv: 继续身份验证
account.index.verification.finish_verifying_html: 完成身份验证流程来获得访问 <strong>%{sp_name}</strong> 的权限。
account.index.verification.finish_verifying_no_sp: 完成身份验证流程来获得访问%{app_name} 合作伙伴机构的权限。
Expand Down
63 changes: 56 additions & 7 deletions docs/backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,68 @@ Forms should have a `#submit` method that returns a `FormResponse`.
- `extra:` is, by convention, a method called `extra_analytics_attributes` that
returns a Hash

```ruby
def submit
FormResponse.new(
success: valid?,
errors: errors,
extra: extra_analytics_attributes,
)
By including `ActiveModel::Model`, you can use any of [Rails' built-in model validation helpers](https://guides.rubyonrails.org/active_record_validations.html#validation-helpers)
or define [custom validation logic](https://guides.rubyonrails.org/active_record_validations.html#custom-methods).
Regardless how you validate, you should use human-readable error messages and associate the error to
the specific form parameter field that it affects, if the form is responsible for validating input
from a page.

```rb
class NewEmailForm
include ActiveModel::Model
include ActionView::Helpers::TranslationHelper

validates_presence_of :email, { message: proc { I18n.t('errors.email.blank')} }
validate :validate_banned_email

def submit(email:)
@email = email

FormResponse.new(success: valid?, errors:, extra: extra_analytics_attributes)
end

def validate_banned_email
return if !BannedEmail.find_by(email: @email)
errors.add(:email, :banned, message: t('errors.email.banned'))
end

# ...
end
```

For sensitive properties, or results that are not meant to be logged, add
properties to the Form object that get written during `#submit`

### Form Error Handling

If form validation is unsuccessful, you should inform the user what needs to be done to correct the
issue by one or both of the following:

- Flash message
- Inline field errors

For convenience, a `FormResponse` object includes a `first_error_message` method which can be used
if you want to display a single error message, such as in a flash banner.

```rb
result = @form.submit(**params)
if result.success?
# ...
else
flash.now[:error] = result.first_error_message
render :new
end
```

In the view, a [SimpleForm](https://github.com/heartcombo/simple_form) form can be bound to a form
object. By doing so, each error will automatically be shown with the corresponding page input.

```erb
<%= simple_form_for @form, url: emails_path do |f| %>
<%= render ValidatedFieldComponent.new(form: f, name: :email) %>
<% end >
```

### Analytics

Analytics events are appended to `log/events.log` and contain information both common information as
Expand Down
28 changes: 24 additions & 4 deletions docs/frontend.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,30 @@ how [`Idv::AnalyticsEventEnhancer`][analytics_events_enhancer.rb] is implemented
[data_attributes]: https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
[analytics_events_enhancer.rb]: https://github.com/18F/identity-idp/blob/main/app/services/idv/analytics_events_enhancer.rb

## Image Assets

When possible, use SVG format for images, as these render at higher quality and with a smaller file
size. Most images in the project are either illustrations or icons, which are ideal for vector image
formats (SVG).

There are few exceptions to this, such as [images used in emails][email-images] needing to be in a
raster format (PNG) due to lack of SVG support in popular email clients. Logos for relying parties
may also be rendered in formats other than SVG, since these are provided to us by partners.

Image assets saved in source control should be optimized using a lossless image optimizer before
being committed, to ensure they're served to users at the lowest possible file size. This is
[enforced automatically for SVG images][lint-optimized-assets], but must be done manually for other
image types. Consider using a tool like [Squoosh][squoosh] (web) or [ImageOptim][image-optim]
(macOS) for these other image types.

Since images, GIFs, and videos are artifacts authored in other tools, there is no need to keep
multiple variants of an asset (e.g., SVG and PNG) in the repository if they are not in use.

[email-images]: https://github.com/18F/identity-idp/tree/main/app/assets/images/email
[lint-optimized-assets]: https://github.com/18F/identity-idp/blob/a1b4c5687739c080cb1d8c66db01956c87b63792/Makefile#L250-L251
[squoosh]: https://squoosh.app/
[imageoptim]: https://imageoptim.com/mac

## Components

### Design System
Expand Down Expand Up @@ -237,10 +261,6 @@ For example, consider a **Password Input** component:
- A web component would be named `PasswordInputElement`
- A web components file would be named `app/javascript/packages/password-input/password-input-element.ts`

#### Graphical Assets

Web graphic assets like images, GIFs, and videos are artifacts authored in other tools. As such, there is no need to keep multiple variants of an asset (e.g., SVG and PNG) in the repository if they are not in use.

## Testing

### Stylelint
Expand Down
10 changes: 10 additions & 0 deletions lib/openid_connect_key_validation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

class OpenidConnectKeyValidation
# @param [private_key] OpenSSL::PKey
# @param [public_key] OpenSSL::PKey
def self.valid?(private_key:, public_key:, data: 'abc123')
signature = private_key.sign('SHA256', data)
public_key.verify('SHA256', signature, data)
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@
success: true,
errors: {},
mfa_method_counts: { phone: 1 },
profile_idv_level: 'legacy_in_person',
profile_idv_level: 'in_person',
identity_verified: true,
account_age_in_days: 0,
account_confirmed_at: user.confirmed_at,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,39 @@
expect(response).to redirect_to(idv_hybrid_mobile_socure_document_capture_errors_url)
end
end
context 'reuse of valid capture app urls when appropriate' do
let(:fake_capture_app_url) { 'https://verify.socure.test/fake_capture_app' }
let(:socure_capture_app_url) { 'https://verify.socure.test/' }
let(:docv_transaction_token) { '176dnc45d-2e34-46f3-82217-6f540ae90673' }
let(:response_body) do
{
referenceId: '123ab45d-2e34-46f3-8d17-6f540ae90303',
data: {
eventId: 'zoYgIxEZUbXBoocYAnbb5DrT',
docvTransactionToken: docv_transaction_token,
qrCode: 'data:image/png;base64,iVBO......K5CYII=',
url: socure_capture_app_url,
},
}
end

before do
allow(request_class).to receive(:new).and_call_original
allow(I18n).to receive(:locale).and_return(expected_language)
end

it 'does not create a DocumentRequest when valid capture app exists' do
dcs = create(
:document_capture_session,
uuid: user.id,
socure_docv_capture_app_url: fake_capture_app_url,
)
allow(DocumentCaptureSession).to receive(:find_by).and_return(dcs)
get(:show)
expect(request_class).not_to have_received(:new)
expect(dcs.socure_docv_capture_app_url).to eq(fake_capture_app_url)
end
end
end

describe '#update' do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@

context 'with enrollment' do
let(:user) { create(:user, :with_pending_in_person_enrollment) }
let(:profile) { create(:profile, :with_pii, user: user) }

it 'renders show template' do
expect(response).to render_template :show
Expand Down
9 changes: 2 additions & 7 deletions spec/controllers/idv/personal_key_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ def assert_personal_key_generated_for_profiles(*profile_pii_pairs)

let(:address_verification_mechanism) { 'phone' }

let(:in_person_enrollment) { nil }

let(:idv_session) { subject.idv_session }

let(:threatmetrix_review_status) { nil }
Expand Down Expand Up @@ -524,13 +522,10 @@ def assert_personal_key_generated_for_profiles(*profile_pii_pairs)
end

context 'with in person profile' do
let!(:in_person_enrollment) do
create(:in_person_enrollment, :pending, user: user).tap do
user.reload_pending_in_person_enrollment
end
end
let!(:profile) { create(:profile, :in_person_verification_pending, user: user) }

before do
user.reload_pending_in_person_enrollment
allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true)
end

Expand Down
34 changes: 34 additions & 0 deletions spec/controllers/idv/socure/document_capture_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,40 @@
expect(response).to redirect_to(idv_socure_document_capture_errors_url)
end
end

context 'reuse of valid capture app urls when appropriate' do
let(:fake_capture_app_url) { 'https://verify.socure.test/fake_capture_app' }
let(:socure_capture_app_url) { 'https://verify.socure.test/' }
let(:docv_transaction_token) { '176dnc45d-2e34-46f3-82217-6f540ae90673' }
let(:response_body) do
{
referenceId: '123ab45d-2e34-46f3-8d17-6f540ae90303',
data: {
eventId: 'zoYgIxEZUbXBoocYAnbb5DrT',
docvTransactionToken: docv_transaction_token,
qrCode: 'data:image/png;base64,iVBO......K5CYII=',
url: socure_capture_app_url,
},
}
end

before do
allow(request_class).to receive(:new).and_call_original
allow(I18n).to receive(:locale).and_return(expected_language)
end

it 'does not create a DocumentRequest when valid capture app exists' do
dcs = create(
:document_capture_session,
uuid: user.id,
socure_docv_capture_app_url: fake_capture_app_url,
)
allow(DocumentCaptureSession).to receive(:find_by).and_return(dcs)
get(:show)
expect(request_class).not_to have_received(:new)
expect(dcs.socure_docv_capture_app_url).to eq(fake_capture_app_url)
end
end
end

describe '#update' do
Expand Down
Loading

0 comments on commit 276fb50

Please sign in to comment.