diff --git a/app/jobs/threat_metrix_js_verification_job.rb b/app/jobs/threat_metrix_js_verification_job.rb index 62354e04976..a6086cb36ee 100644 --- a/app/jobs/threat_metrix_js_verification_job.rb +++ b/app/jobs/threat_metrix_js_verification_job.rb @@ -3,43 +3,42 @@ class ThreatMetrixJsVerificationJob < ApplicationJob def perform(session_id: SecureRandom.uuid) org_id = IdentityConfig.store.lexisnexis_threatmetrix_org_id - - return if org_id.blank? - - return if !IdentityConfig.store.proofing_device_profiling_collecting_enabled + js = nil + valid = nil + error = nil + signature = nil # Certificate is stored ASCII-armored in config raw_cert = IdentityConfig.store.lexisnexis_threatmetrix_js_signing_cert - return if raw_cert.blank? - - cert = OpenSSL::X509::Certificate.new raw_cert - - raise 'Certificate is expired' if cert.not_after < Time.zone.now + cert = OpenSSL::X509::Certificate.new(raw_cert) if raw_cert.present? + raise 'JS signing certificate is missing' if !cert + raise 'JS signing certificate is expired' if cert.not_after < Time.zone.now url = "https://h.online-metrix.net/fp/tags.js?org_id=#{org_id}&session_id=#{session_id}" - - resp = build_faraday.get url - - content, signature = parse_js resp.body - - log_payload = { - name: 'ThreatMetrixJsVerification', - org_id: org_id, - session_id: session_id, - http_status: resp.status, - signature: (signature || '').each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join, - } - - if verify_js content, signature, cert - log_payload[:valid] = true - else - # When signature validation fails, we include the JS payload in the - # log message for future analysis - log_payload[:valid] = false - log_payload[:js] = content - end - - logger.info(log_payload.to_json) + resp = build_faraday.get(url) + content, signature = parse_js(resp.body) + + valid = js_verified?(content, signature, cert) + # When signature validation fails, we include the JS payload in the + # log message for future analysis + js = content if !valid + rescue => err + error = err + raise err + ensure + logger.info( + { + name: 'ThreatMetrixJsVerification', + org_id: org_id, + session_id: session_id, + http_status: resp&.status, + signature: (signature || '').each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join, + js: js, + valid: valid, + error_class: error&.class, + error_message: error&.message, + }.compact.to_json, + ) end def build_faraday @@ -67,12 +66,12 @@ def parse_js(raw) [content, signature] end - def verify_js(js, signature, cert) + def js_verified?(js, signature, cert) return false if signature.nil? public_key = cert&.public_key return false if public_key.nil? - public_key.verify 'SHA256', signature, js + public_key.verify('SHA256', signature, js) end end diff --git a/spec/jobs/threat_metrix_js_verification_job_spec.rb b/spec/jobs/threat_metrix_js_verification_job_spec.rb index c8ef5a7e90e..76b96d928d6 100644 --- a/spec/jobs/threat_metrix_js_verification_job_spec.rb +++ b/spec/jobs/threat_metrix_js_verification_job_spec.rb @@ -78,34 +78,31 @@ ) end - context 'when collecting is disabled' do - let(:proofing_device_profiling_collecting_enabled) { false } - it 'does not run' do - expect(instance.logger).not_to receive(:info) - perform - end - end - context 'when certificate is not configured' do let(:threatmetrix_signing_certificate) { '' } - it 'does not run' do - expect(instance.logger).not_to receive(:info) - perform + it 'logs an error_message, and raises' do + expect(instance.logger).to receive(:info) do |message| + expect(JSON.parse(message, symbolize_names: true)).to include( + name: 'ThreatMetrixJsVerification', + error_message: 'JS signing certificate is missing', + ) + end + + expect { perform }.to raise_error(RuntimeError, 'JS signing certificate is missing') end end context 'when certificate is expired' do let(:threatmetrix_signing_cert_expiry) { Time.zone.now - 3600 } - it 'raises an error' do - expect { perform }.to raise_error - end - end + it 'logs an error_message, and raises' do + expect(instance.logger).to receive(:info) do |message| + expect(JSON.parse(message, symbolize_names: true)).to include( + name: 'ThreatMetrixJsVerification', + error_message: 'JS signing certificate is expired', + ) + end - context 'when org id is not configured' do - let(:threatmetrix_org_id) { nil } - it 'does not run' do - expect(instance.logger).not_to receive(:info) - perform + expect { perform }.to raise_error(RuntimeError, 'JS signing certificate is expired') end end