diff --git a/modules/ivc_champva/app/controllers/ivc_champva/v1/uploads_controller.rb b/modules/ivc_champva/app/controllers/ivc_champva/v1/uploads_controller.rb index 5905c4ab18b..1b2b90bca66 100644 --- a/modules/ivc_champva/app/controllers/ivc_champva/v1/uploads_controller.rb +++ b/modules/ivc_champva/app/controllers/ivc_champva/v1/uploads_controller.rb @@ -5,6 +5,8 @@ module IvcChampva module V1 class UploadsController < ApplicationController + skip_before_action :authenticate + before_action :authenticate, if: :should_authenticate skip_after_action :set_csrf_header FORM_NUMBER_MAP = { @@ -13,18 +15,21 @@ class UploadsController < ApplicationController '10-7959F-2' => 'vha_10_7959f_2' }.freeze + UNAUTHENTICATED_FORMS = %w[].freeze + def submit Datadog::Tracing.active_trace&.set_tag('form_id', params[:form_number]) form_id = get_form_id parsed_form_data = JSON.parse(params.to_json) - file_path, file_paths, metadata, form = get_file_paths_and_metadata(parsed_form_data) - + file_paths, metadata = get_file_paths_and_metadata(parsed_form_data) status, error_message = handle_uploads(form_id, metadata, file_paths) - render json: get_json(form_id, error_message || nil) - - rescue => e - raise Exceptions::ScrubbedUploadsSubmitError.new(params), e + render json: { + error_message:, + status: + } + rescue + puts 'A default error occurred while uploading a document.' end def submit_supporting_documents @@ -38,8 +43,34 @@ def submit_supporting_documents end end + def authenticate + super + rescue Common::Exceptions::Unauthorized + Rails.logger.info( + 'IVC Champva - unauthenticated user submitting form', + { form_number: params[:form_number] } + ) + end + private + def get_file_paths_and_metadata(parsed_form_data) + form_id = get_form_id + form = "IvcChampva::#{form_id.titleize.gsub(' ', '')}".constantize.new(parsed_form_data) + filler = IvcChampva::PdfFiller.new(form_number: form_id, form:) + + file_path = if @current_user + filler.generate(@current_user.loa[:current]) + else + filler.generate + end + + metadata = IvcChampva::MetadataValidator.validate(form.metadata) + file_paths = form.handle_attachments(file_path) + + [file_paths, metadata] + end + def handle_uploads(form_id, metadata, pdf_file_paths) meta_file_name = "#{form_id}_metadata.json" meta_file_path = "tmp/#{meta_file_name}" @@ -67,29 +98,6 @@ def handle_uploads(form_id, metadata, pdf_file_paths) end end - def get_file_paths_and_metadata(parsed_form_data) - form_id = get_form_id - form = "IvcChampva::#{form_id.titleize.gsub(' ', '')}".constantize.new(parsed_form_data) - filler = IvcChampva::PdfFiller.new(form_number: form_id, form:) - - file_path = if @current_user - filler.generate(@current_user.loa[:current]) - else - filler.generate - end - metadata = IvcChampva::MetadataValidator.validate(form.metadata) - - maybe_add_file_paths = - case form_id - when 'vba_40_0247', 'vba_20_10207', 'vha_10_10d', 'vba_40_10007' - form.handle_attachments(file_path) - else - [file_path] - end - - [file_path, maybe_add_file_paths, metadata, form] - end - def upload_to_ivc_s3(file_name, file_path, metadata = {}) case ivc_s3_client.put_object(file_name, file_path, metadata) in { success: true } @@ -108,12 +116,8 @@ def get_form_id FORM_NUMBER_MAP[form_number] end - def get_json(confirmation_number, form_id, error_message) - json = { confirmation_number: } - json[:expiration_date] = 1.year.from_now if form_id == 'vba_21_0966' - json[:error_message] = error_message - - json + def should_authenticate + true unless UNAUTHENTICATED_FORMS.include? params[:form_number] end def ivc_s3_client diff --git a/modules/ivc_champva/app/models/ivc_champva/vha_10_10d.rb b/modules/ivc_champva/app/models/ivc_champva/vha_10_10d.rb index add25a5c763..4df2629fcc4 100644 --- a/modules/ivc_champva/app/models/ivc_champva/vha_10_10d.rb +++ b/modules/ivc_champva/app/models/ivc_champva/vha_10_10d.rb @@ -54,7 +54,13 @@ def submission_date_config { should_stamp_date?: false } end - def track_user_identity(confirmation_number); end + def method_missing(_, *args, _) + args + end + + def respond_to_missing?(method) + method == :handle_attachments || super + end private diff --git a/modules/ivc_champva/app/models/ivc_champva/vha_10_7959c.rb b/modules/ivc_champva/app/models/ivc_champva/vha_10_7959c.rb index 53e80096ddb..b1f871e2487 100644 --- a/modules/ivc_champva/app/models/ivc_champva/vha_10_7959c.rb +++ b/modules/ivc_champva/app/models/ivc_champva/vha_10_7959c.rb @@ -30,5 +30,13 @@ def submission_date_config text_coords: [440, 670] } end + + def method_missing(_, *args, _) + args + end + + def respond_to_missing?(method) + method == :handle_attachments || super + end end end diff --git a/modules/ivc_champva/app/models/ivc_champva/vha_10_7959f_1.rb b/modules/ivc_champva/app/models/ivc_champva/vha_10_7959f_1.rb index 054f691273d..815171b0cd6 100644 --- a/modules/ivc_champva/app/models/ivc_champva/vha_10_7959f_1.rb +++ b/modules/ivc_champva/app/models/ivc_champva/vha_10_7959f_1.rb @@ -26,6 +26,12 @@ def submission_date_config { should_stamp_date?: false } end - def track_user_identity(confirmation_number); end + def method_missing(_, *args, _) + args + end + + def respond_to_missing?(method) + method == :handle_attachments || super + end end end diff --git a/modules/ivc_champva/app/models/ivc_champva/vha_10_7959f_2.rb b/modules/ivc_champva/app/models/ivc_champva/vha_10_7959f_2.rb index dd80d578662..d4577711855 100644 --- a/modules/ivc_champva/app/models/ivc_champva/vha_10_7959f_2.rb +++ b/modules/ivc_champva/app/models/ivc_champva/vha_10_7959f_2.rb @@ -26,6 +26,12 @@ def submission_date_config { should_stamp_date?: false } end - def track_user_identity(confirmation_number); end + def method_missing(_, *args, _) + args + end + + def respond_to_missing?(method) + method == :handle_attachments || super + end end end diff --git a/modules/ivc_champva/app/services/ivc_champva/pdf_stamper.rb b/modules/ivc_champva/app/services/ivc_champva/pdf_stamper.rb index 8c634e7b4ef..3b370d68793 100644 --- a/modules/ivc_champva/app/services/ivc_champva/pdf_stamper.rb +++ b/modules/ivc_champva/app/services/ivc_champva/pdf_stamper.rb @@ -4,7 +4,7 @@ module IvcChampva class PdfStamper - FORM_REQUIRES_STAMP = %w[26-4555 21-4142 21-10210 21-0845 21P-0847 21-0966 21-0972 20-10207 10-7959F-1].freeze + FORM_REQUIRES_STAMP = %w[10-7959F-1].freeze SUBMISSION_TEXT = 'Signed electronically and submitted via VA.gov at ' SUBMISSION_DATE_TITLE = 'Application Submitted:' @@ -37,145 +37,6 @@ def self.stamp107959f1(stamped_template_path, form) stamp(desired_stamps, stamped_template_path, append_to_stamp) end - def self.stamp264555(stamped_template_path, form) - desired_stamps = [] - desired_stamps.append([73, 390, 'X']) unless form.data['previous_sah_application']['has_previous_sah_application'] - desired_stamps.append([73, 355, 'X']) unless form.data['previous_hi_application']['has_previous_hi_application'] - desired_stamps.append([73, 320, 'X']) unless form.data['living_situation']['is_in_care_facility'] - append_to_stamp = false - stamp(desired_stamps, stamped_template_path, append_to_stamp) - end - - def self.stamp214142(stamped_template_path, form) - desired_stamps = [[50, 560]] - signature_text = form.data['statement_of_truth_signature'] - page_configuration = [ - { type: :new_page }, - { type: :text, position: desired_stamps[0] }, - { type: :new_page } - ] - - multistamp(stamped_template_path, signature_text, page_configuration) - - # This is a one-off case where we need to stamp a date on the first page of 21-4142 when resubmitting - if form.data['in_progress_form_created_at'] - date_title = 'Application Submitted:' - date_text = form.data['in_progress_form_created_at'] - stamp214142_date_stamp_for_resubmission(stamped_template_path, date_title, date_text) - end - end - - def self.stamp214142_date_stamp_for_resubmission(stamped_template_path, date_title, date_text) - date_title_stamp_position = [440, 710] - date_text_stamp_position = [440, 690] - page_configuration = [ - { type: :text, position: date_title_stamp_position }, - { type: :new_page }, - { type: :new_page } - ] - - multistamp(stamped_template_path, date_title, page_configuration, 12) - - page_configuration = [ - { type: :text, position: date_text_stamp_position }, - { type: :new_page }, - { type: :new_page } - ] - - multistamp(stamped_template_path, date_text, page_configuration, 12) - end - - def self.stamp2110210(stamped_template_path, form) - desired_stamps = [[50, 160]] - signature_text = form.data['statement_of_truth_signature'] - page_configuration = [ - { type: :new_page }, - { type: :new_page }, - { type: :text, position: desired_stamps[0] } - ] - - multistamp(stamped_template_path, signature_text, page_configuration) - end - - def self.stamp210845(stamped_template_path, form) - desired_stamps = [[50, 240]] - signature_text = form.data['statement_of_truth_signature'] - page_configuration = [ - { type: :new_page }, - { type: :new_page }, - { type: :text, position: desired_stamps[0] } - ] - - multistamp(stamped_template_path, signature_text, page_configuration) - end - - def self.stamp21p0847(stamped_template_path, form) - desired_stamps = [[50, 190]] - signature_text = form.data['statement_of_truth_signature'] - page_configuration = [ - { type: :new_page }, - { type: :text, position: desired_stamps[0] } - ] - - multistamp(stamped_template_path, signature_text, page_configuration) - end - - def self.stamp210972(stamped_template_path, form) - desired_stamps = [[50, 465]] - signature_text = form.data['statement_of_truth_signature'] - page_configuration = [ - { type: :new_page }, - { type: :new_page }, - { type: :text, position: desired_stamps[0] } - ] - - multistamp(stamped_template_path, signature_text, page_configuration) - end - - def self.stamp210966(stamped_template_path, form) - desired_stamps = [[50, 415]] - signature_text = form.data['statement_of_truth_signature'] - page_configuration = [ - { type: :new_page }, - { type: :text, position: desired_stamps[0] } - ] - - multistamp(stamped_template_path, signature_text, page_configuration) - end - - def self.stamp2010207(stamped_template_path, form) - desired_stamps = if form.data['preparer_type'] == 'veteran' - [[50, 690]] - elsif form.data['third_party_type'] == 'power-of-attorney' - [[50, 445]] - elsif form.data['preparer_type'] == 'third-party-veteran' || - form.data['preparer_type'] == 'third-party-non-veteran' || - form.data['preparer_type'] == 'non-veteran' - [[50, 570]] - end - signature_text = form.data['statement_of_truth_signature'] - page_configuration = [ - { type: :new_page }, - { type: :new_page }, - { type: :new_page }, - { type: :new_page }, - { type: :text, position: desired_stamps[0] } - ] - - multistamp(stamped_template_path, signature_text, page_configuration) - end - - def self.stamp4010007_uuid(uuid) - uuid = "UUID: #{uuid}" - stamped_template_path = 'tmp/vba_40_10007-tmp.pdf' - desired_stamps = [[410, 20]] - page_configuration = [ - { type: :text, position: desired_stamps[0] } - ] - - multistamp(stamped_template_path, uuid, page_configuration, 7) - end - def self.multistamp(stamped_template_path, signature_text, page_configuration, font_size = 16) stamp_path = Common::FileHelpers.random_file_path Prawn::Document.generate(stamp_path, margin: [0, 0]) do |pdf| diff --git a/modules/ivc_champva/app/services/ivc_champva/s3.rb b/modules/ivc_champva/app/services/ivc_champva/s3.rb index 5a95f1dc0ed..4d59c660f7a 100644 --- a/modules/ivc_champva/app/services/ivc_champva/s3.rb +++ b/modules/ivc_champva/app/services/ivc_champva/s3.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# S3 Module for simple form submission +# S3 Module for ivc form submission # Return # { success: Boolean, [error_message: String] } module IvcChampva @@ -14,6 +14,24 @@ def initialize(region:, access_key_id:, secret_access_key:, bucket_name:) @bucket_name = bucket_name end + def put_object(key, file, metadata = {}) + Datadog::Tracing.trace('S3 Put File(s)') do + # Convert nil values to empty strings in the metadata + metadata&.transform_values! { |value| value || '' } + + client.put_object({ + bucket: Settings.ivc_forms.s3.bucket, + key:, + body: File.read(file), + metadata:, + acl: 'public-read' + }) + { success: true } + rescue => e + { success: false, error_message: "S3 PutObject failure for #{file}: #{e.message}" } + end + end + def upload_file(key, file) obj = resource.bucket(bucket_name).object(key) obj.upload_file(file) diff --git a/modules/ivc_champva/config/routes.rb b/modules/ivc_champva/config/routes.rb index f558080e870..6bdddf4ae08 100644 --- a/modules/ivc_champva/config/routes.rb +++ b/modules/ivc_champva/config/routes.rb @@ -5,4 +5,4 @@ post '/forms', to: 'uploads#submit' post '/forms/submit_supporting_documents', to: 'uploads#submit_supporting_documents' end -end \ No newline at end of file +end diff --git a/modules/ivc_champva/lib/tasks/forms.rake b/modules/ivc_champva/lib/tasks/forms.rake index 77cb8e28973..cdf0a7293df 100644 --- a/modules/ivc_champva/lib/tasks/forms.rake +++ b/modules/ivc_champva/lib/tasks/forms.rake @@ -5,6 +5,7 @@ PDFTK_LOCAL_PATH = '/usr/local/bin/pdftk' MODELS_PATH = 'modules/ivc_champva/app/models/ivc_champva' MAPPINGS_PATH = 'modules/ivc_champva/app/form_mappings' +# rubocop:disable Metrics/BlockLength namespace :ivc_champva do task :generate, [:form_path] => :environment do |_, args| file_path = args[:form_path] @@ -37,6 +38,24 @@ namespace :ivc_champva do end METADATA + submission_date_config_method = <<-SUB_DATE_CONFIG + def submission_date_config + { should_stamp_date?: false } + end + SUB_DATE_CONFIG + + method_missing_method = <<-METHOD_MISSING + def method_missing(_, *args, _) + args + end + METHOD_MISSING + + respond_to_missing_method = <<-RESPOND_METHOD_MISSING + def respond_to_missing?(method) + method == :handle_attachments || super + end + RESPOND_METHOD_MISSING + File.open(new_model_file, 'w') do |f| f.puts '# frozen_string_literal: true' f.puts '' @@ -61,6 +80,12 @@ namespace :ivc_champva do f.puts metadata_method + f.puts submission_date_config_method + + f.puts method_missing_method + + f.puts respond_to_missing_method + f.puts ' end' f.puts 'end' end @@ -82,3 +107,4 @@ namespace :ivc_champva do puts "Created #{mapping_file}" end end +# rubocop:enable Metrics/BlockLength diff --git a/modules/ivc_champva/spec/fixtures/form_json/vha_10_7959c-min.json b/modules/ivc_champva/spec/fixtures/form_json/vha_10_7959c-min.json deleted file mode 100644 index 6fcc0c5343b..00000000000 --- a/modules/ivc_champva/spec/fixtures/form_json/vha_10_7959c-min.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "form_number": "10-7959C", - "applicants": { - "address": { - "country": "USA", - "street": "2 Second St", - "city": "Town", - "state": "LA", - "postal_code": "16542" - }, - "full_name": { - "first": "Applicant", - "middle": "C", - "last": "Onceler" - }, - "ssn_or_tin": "123456644", - "home_phone": "6543219877", - "is_new_address": false, - "male_or_female": "female" - }, - - "part_a": { - "has_part_a": true, - "part_a_effective_date": "2010-05-05", - "part_a_carrier": "United Health" - }, - "other_health_insurance1": { - "name_of_health_insurance": "Blue Cross", - "date_health_insurance": "2010-02-02", - "terminate_date_health_insurance": "2012-09-09" - } -} diff --git a/modules/ivc_champva/spec/models/vha_1010d_spec.rb b/modules/ivc_champva/spec/models/vha_1010d_spec.rb index b26554e2b71..8dd14c1488d 100644 --- a/modules/ivc_champva/spec/models/vha_1010d_spec.rb +++ b/modules/ivc_champva/spec/models/vha_1010d_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -# spec/models/simple_forms_api/vha1010d_spec.rb - require 'rails_helper' folder_path = 'modules/ivc_champva/spec/fixtures/test_file/' diff --git a/modules/ivc_champva/spec/services/pdf_stamper_spec.rb b/modules/ivc_champva/spec/services/pdf_stamper_spec.rb index 1450cb3e484..cc2dc2732d9 100644 --- a/modules/ivc_champva/spec/services/pdf_stamper_spec.rb +++ b/modules/ivc_champva/spec/services/pdf_stamper_spec.rb @@ -22,5 +22,5 @@ def self.test_pdf_stamp_error(stamp_method, test_payload) end end - #test_pdf_stamp_error 'stamp214142', 'vba_21_4142' + test_pdf_stamp_error 'stamp107959f1', 'vha_10_7959f_1' end diff --git a/modules/ivc_champva/spec/services/s3_spec.rb b/modules/ivc_champva/spec/services/s3_spec.rb index fe7255e1c65..ace56c10427 100644 --- a/modules/ivc_champva/spec/services/s3_spec.rb +++ b/modules/ivc_champva/spec/services/s3_spec.rb @@ -2,7 +2,6 @@ require 'rails_helper' require 'common/file_helpers' -#require 'simple_forms_api_submission/s3' describe IvcChampva::S3 do let(:region) { 'test-region' } @@ -14,7 +13,7 @@ # rubocop:disable Style/HashSyntax let(:s3_instance) do - SimpleFormsApiSubmission::S3.new( + IvcChampva::S3.new( region: region, access_key_id: access_key_id, secret_access_key: secret_access_key, @@ -23,6 +22,33 @@ end # rubocop:enable Style/HashSyntax + describe '#put_object' do + let(:key) { 'test_file.pdf' } + let(:file_path) { 'spec/fixtures/files/doctors-note.pdf' } + + context 'when upload is successful' do + before do + allow_any_instance_of(Aws::S3::Client).to receive(:put_object).and_return(true) + end + + it 'returns success response' do + expect(s3_instance.put_object(key, file_path)).to eq({ success: true }) + end + end + + context 'when upload fails' do + before do + allow_any_instance_of(Aws::S3::Client).to receive(:put_object) + .and_raise(Aws::S3::Errors::ServiceError.new(nil, 'upload failed')) + end + + it 'returns error response' do + expect(s3_instance.put_object(key, file_path)) + .to eq({ success: false, error_message: "S3 PutObject failure for #{file_path}: upload failed" }) + end + end + end + describe '#upload_file' do let(:key) { 'test_form.pdf' } let(:file_path) { 'test_form.pdf' }