From 849fab1455aecbdb91c499580d8cb54249ea397a Mon Sep 17 00:00:00 2001 From: Zach Margolis Date: Tue, 19 Oct 2021 13:36:03 -0700 Subject: [PATCH 1/5] Add config so we can enable Rails Mailer previews in deployed envs - Follow-up to #5502 --- config/application.yml.default | 2 ++ config/environments/development.rb | 1 + config/environments/production.rb | 5 +++++ config/initializers/secure_headers.rb | 9 +++++++-- lib/identity_config.rb | 1 + 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/config/application.yml.default b/config/application.yml.default index e365f610a2e..b084340079d 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -160,6 +160,7 @@ push_notifications_enabled: 'false' pwned_passwords_file_path: 'pwned_passwords/pwned_passwords.txt' rack_mini_profiler: 'false' rack_timeout_service_timeout_seconds: '15' +rails_mailer_previews_enabled: 'false' reauthn_window: '120' recovery_code_length: '4' redis_throttle_url: redis://localhost:6379/1 @@ -258,6 +259,7 @@ development: otp_delivery_blocklist_findtime: '5' password_pepper: f22d4b2cafac9066fe2f4416f5b7a32c piv_cac_verify_token_secret: ee7f20f44cdc2ba0c6830f70470d1d1d059e1279cdb58134db92b35947b1528ef5525ece5910cf4f2321ab989a618feea12ef95711dbc62b9601e8520a34ee12 + rails_mailer_previews_enabled: 'true' recurring_jobs_disabled_names: "[]" s3_report_bucket_prefix: '' s3_report_public_bucket_prefix: '' diff --git a/config/environments/development.rb b/config/environments/development.rb index aeb53697f7f..68b01640fd7 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -28,6 +28,7 @@ config.action_mailer.asset_host = IdentityConfig.store.mailer_domain_name config.action_mailer.raise_delivery_errors = false config.action_mailer.smtp_settings = { address: ENV['SMTP_HOST'] || 'localhost', port: 1025 } + config.action_mailer.show_previews = IdentityConfig.store.rails_mailer_previews_enabled routes.default_url_options[:protocol] = 'https' if ENV['HTTPS'] == 'on' diff --git a/config/environments/production.rb b/config/environments/production.rb index 36958077f48..73f781eb079 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -33,6 +33,11 @@ :ses end + if IdentityConfig.store.rails_mailer_previews_enabled + config.action_mailer.show_previews = true + config.action_mailer.preview_path ||= Rails.root.join('spec/mailers/previews') + end + routes.default_url_options[:protocol] = :https # turn off IP spoofing protection since the network configuration in the production environment diff --git a/config/initializers/secure_headers.rb b/config/initializers/secure_headers.rb index dbaf005be92..af46f24dc05 100644 --- a/config/initializers/secure_headers.rb +++ b/config/initializers/secure_headers.rb @@ -1,6 +1,6 @@ SecureHeaders::Configuration.default do |config| # rubocop:disable Metrics/BlockLength config.hsts = "max-age=#{365.days.to_i}; includeSubDomains; preload" - config.x_frame_options = Rails.env.development? ? 'ALLOWALL' : 'DENY' + config.x_frame_options = 'DENY' config.x_content_type_options = 'nosniff' config.x_xss_protection = '1; mode=block' config.x_download_options = 'noopen' @@ -12,7 +12,6 @@ default_csp_config = { default_src: ["'self'"], child_src: ["'self'"], # CSP 2.0 only; replaces frame_src - # frame_ancestors: %w('self'), # CSP 2.0 only; overriden by x_frame_options in some browsers form_action: ["'self'"], block_all_mixed_content: true, # CSP 2.0 only; connect_src: connect_src.flatten, @@ -40,6 +39,12 @@ base_uri: ["'self'"], } + if IdentityConfig.store.rails_mailer_previews_enabled + # CSP 2.0 only; overriden by x_frame_options in some browsers + default_csp_config[:frame_ancestors] = %w('self') + end + + config.csp = if !Rails.env.production? default_csp_config.merge( script_src: ["'self'", "'unsafe-eval'", "'unsafe-inline'"], diff --git a/lib/identity_config.rb b/lib/identity_config.rb index a910632b91f..7b58c7d6d4a 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -229,6 +229,7 @@ def self.build_store(config_map) config.add(:pwned_passwords_file_path, type: :string) config.add(:rack_mini_profiler, type: :boolean) config.add(:rack_timeout_service_timeout_seconds, type: :integer) + config.add(:rails_mailer_previews_enabled, type: :boolean) config.add(:reauthn_window, type: :integer) config.add(:recovery_code_length, type: :integer) config.add(:recurring_jobs_disabled_names, type: :json) From dec7b66d28d147463c4ce983c1d674b93c7623a5 Mon Sep 17 00:00:00 2001 From: Zach Margolis Date: Tue, 19 Oct 2021 15:32:27 -0700 Subject: [PATCH 2/5] Update config/initializers/secure_headers.rb Co-authored-by: Andrew Duthie --- config/initializers/secure_headers.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/config/initializers/secure_headers.rb b/config/initializers/secure_headers.rb index af46f24dc05..0da3fe416e7 100644 --- a/config/initializers/secure_headers.rb +++ b/config/initializers/secure_headers.rb @@ -43,8 +43,6 @@ # CSP 2.0 only; overriden by x_frame_options in some browsers default_csp_config[:frame_ancestors] = %w('self') end - - config.csp = if !Rails.env.production? default_csp_config.merge( script_src: ["'self'", "'unsafe-eval'", "'unsafe-inline'"], From 72fe3f4dc635ed800c8fd48c3d8e04f31a99f85d Mon Sep 17 00:00:00 2001 From: Zach Margolis Date: Tue, 19 Oct 2021 15:34:56 -0700 Subject: [PATCH 3/5] Update config/environments/production.rb --- config/environments/production.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index 73f781eb079..51011a789d4 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -35,7 +35,7 @@ if IdentityConfig.store.rails_mailer_previews_enabled config.action_mailer.show_previews = true - config.action_mailer.preview_path ||= Rails.root.join('spec/mailers/previews') + config.action_mailer.preview_path = Rails.root.join('spec/mailers/previews') end routes.default_url_options[:protocol] = :https From d57503f0584077b97f55f90ad5e6f7c334708f4d Mon Sep 17 00:00:00 2001 From: Zach Margolis Date: Tue, 19 Oct 2021 15:56:21 -0700 Subject: [PATCH 4/5] Use made-up User and EmailAddress records for mailer previews --- spec/mailers/previews/user_mailer_preview.rb | 85 +++++++++++++------ .../previews/user_mailer_preview_spec.rb | 16 +++- 2 files changed, 71 insertions(+), 30 deletions(-) diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb index ad189d5d7b4..461a28822b7 100644 --- a/spec/mailers/previews/user_mailer_preview.rb +++ b/spec/mailers/previews/user_mailer_preview.rb @@ -1,8 +1,8 @@ class UserMailerPreview < ActionMailer::Preview def email_confirmation_instructions UserMailer.email_confirmation_instructions( - User.first, - 'foo@bar.gov', + user, + email_address, SecureRandom.hex, request_id: SecureRandom.uuid, instructions: I18n.t( @@ -14,8 +14,8 @@ def email_confirmation_instructions def unconfirmed_email_instructions UserMailer.unconfirmed_email_instructions( - User.first, - 'foo@bar.gov', + user, + email_address, SecureRandom.hex, request_id: SecureRandom.uuid, instructions: I18n.t( @@ -26,33 +26,33 @@ def unconfirmed_email_instructions end def signup_with_your_email - UserMailer.signup_with_your_email(User.first, 'foo@bar.gov') + UserMailer.signup_with_your_email(user, email_address) end def reset_password_instructions - UserMailer.reset_password_instructions(User.first, 'foo@bar.gov', token: SecureRandom.hex) + UserMailer.reset_password_instructions(user, email_address, token: SecureRandom.hex) end def password_changed - UserMailer.password_changed(User.first, EmailAddress.first, disavowal_token: SecureRandom.hex) + UserMailer.password_changed(user, email_address_record, disavowal_token: SecureRandom.hex) end def phone_added - UserMailer.phone_added(User.first, EmailAddress.first, disavowal_token: SecureRandom.hex) + UserMailer.phone_added(user, email_address_record, disavowal_token: SecureRandom.hex) end def account_does_not_exist - UserMailer.account_does_not_exist('foo@bar.gov', SecureRandom.uuid) + UserMailer.account_does_not_exist(email_address, SecureRandom.uuid) end def personal_key_sign_in - UserMailer.personal_key_sign_in(User.first, 'foo@bar.gov', disavowal_token: SecureRandom.hex) + UserMailer.personal_key_sign_in(user, email_address, disavowal_token: SecureRandom.hex) end def new_device_sign_in UserMailer.new_device_sign_in( - user: User.first, - email_address: EmailAddress.first, + user: user, + email_address: email_address_record, date: 'February 25, 2019 15:02', location: 'Washington, DC', disavowal_token: SecureRandom.hex, @@ -60,64 +60,64 @@ def new_device_sign_in end def personal_key_regenerated - UserMailer.personal_key_regenerated(User.first, 'foo@bar.gov') + UserMailer.personal_key_regenerated(user, email_address) end def account_reset_request UserMailer.account_reset_request( - User.first, EmailAddress.first, User.first.build_account_reset_request + user, email_address_record, user.build_account_reset_request ) end def account_reset_granted UserMailer.account_reset_granted( - User.first, EmailAddress.first, User.first.build_account_reset_request + user, email_address_record, user.build_account_reset_request ) end def account_reset_complete - UserMailer.account_reset_complete(User.first, EmailAddress.first) + UserMailer.account_reset_complete(user, email_address_record) end def account_reset_cancel - UserMailer.account_reset_cancel(User.first, EmailAddress.first) + UserMailer.account_reset_cancel(user, email_address_record) end def please_reset_password - UserMailer.please_reset_password(User.first, 'foo@bar.gov') + UserMailer.please_reset_password(user, email_address) end def doc_auth_desktop_link_to_sp - UserMailer.doc_auth_desktop_link_to_sp(User.first, 'foo@bar.gov', 'Example App', '/') + UserMailer.doc_auth_desktop_link_to_sp(user, email_address, 'Example App', '/') end def letter_reminder - UserMailer.letter_reminder(User.first, 'foo@bar.gov') + UserMailer.letter_reminder(user, email_address) end def add_email - UserMailer.add_email(User.first, 'foo@bar.gov', SecureRandom.hex) + UserMailer.add_email(user, email_address, SecureRandom.hex) end def email_added - UserMailer.email_added(User.first, 'foo@bar.gov') + UserMailer.email_added(user, email_address) end def email_deleted - UserMailer.email_deleted(User.first, 'foo@bar.gov') + UserMailer.email_deleted(user, email_address) end def add_email_associated_with_another_account - UserMailer.add_email_associated_with_another_account('foo@bar.gov') + UserMailer.add_email_associated_with_another_account(email_address) end def sps_over_quota_limit - UserMailer.sps_over_quota_limit('foo@bar.gov') + UserMailer.sps_over_quota_limit(email_address) end def deleted_user_accounts_report UserMailer.deleted_user_accounts_report( - email: 'foo@bar.gov', + email: email_address, name: 'my name', issuers: %w[issuer1 issuer2], data: 'data', @@ -126,11 +126,40 @@ def deleted_user_accounts_report def account_verified UserMailer.account_verified( - User.first, - EmailAddress.first, + user, + email_address_record, date_time: DateTime.now, sp_name: 'Example App', disavowal_token: SecureRandom.hex, ) end + + private + + def user + unsaveable(User.new(email_addresses: [email_address_record])) + end + + def email_address + 'email@example.com' + end + + def email_address_record + unsaveable(EmailAddress.new(email: email_address)) + end + + # Remove #save and #save! to make sure we can't write these made-up records + def unsaveable(record) + class << record + def save + raise "don't save me!" + end + + def save! + raise "don't save me!" + end + end + + record + end end diff --git a/spec/mailers/previews/user_mailer_preview_spec.rb b/spec/mailers/previews/user_mailer_preview_spec.rb index 76333b15533..cd7479b8612 100644 --- a/spec/mailers/previews/user_mailer_preview_spec.rb +++ b/spec/mailers/previews/user_mailer_preview_spec.rb @@ -4,8 +4,6 @@ RSpec.describe UserMailerPreview do UserMailerPreview.instance_methods(false).each do |mailer_method| describe "##{mailer_method}" do - before { create(:user) } - it 'generates a preview without blowing up' do expect { UserMailerPreview.new.public_send(mailer_method) }.to_not raise_error end @@ -17,4 +15,18 @@ preview_methods = UserMailerPreview.instance_methods(false) expect(mailer_methods - preview_methods).to be_empty end + + it 'uses user and email records that cannot be saved' do + expect(User.count).to eq(0) + user = UserMailerPreview.new.send(:user) + expect { user.save }.to raise_error + expect { user.save! }.to raise_error + expect(User.count).to eq(0) + + expect(EmailAddress.count).to eq(0) + email_address_record = UserMailerPreview.new.send(:email_address_record) + expect { email_address_record.save }.to raise_error + expect { email_address_record.save! }.to raise_error + expect(EmailAddress.count).to eq(0) + end end From d9c704346151d1933267b695ac13a1a7d906b6a4 Mon Sep 17 00:00:00 2001 From: Zach Margolis Date: Tue, 19 Oct 2021 15:59:25 -0700 Subject: [PATCH 5/5] lints --- config/initializers/secure_headers.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/initializers/secure_headers.rb b/config/initializers/secure_headers.rb index 0da3fe416e7..5cc94bda25a 100644 --- a/config/initializers/secure_headers.rb +++ b/config/initializers/secure_headers.rb @@ -41,8 +41,9 @@ if IdentityConfig.store.rails_mailer_previews_enabled # CSP 2.0 only; overriden by x_frame_options in some browsers - default_csp_config[:frame_ancestors] = %w('self') + default_csp_config[:frame_ancestors] = %w['self'] end + config.csp = if !Rails.env.production? default_csp_config.merge( script_src: ["'self'", "'unsafe-eval'", "'unsafe-inline'"],