Skip to content
30 changes: 30 additions & 0 deletions app/jobs/irs_attempts_events_batch_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class IrsAttemptsEventsBatchJob < ApplicationJob
queue_as :default

def perform(timestamp: Time.zone.now - 1.hour, dir_path: './attempts_api_output')
return nil unless IdentityConfig.store.irs_attempt_api_enabled

events = IrsAttemptsApi::RedisClient.new.read_events(timestamp: timestamp)
event_values = events.values.join("\r\n")

decoded_key_der = Base64.strict_decode64(IdentityConfig.store.irs_attempt_api_public_key)
pub_key = OpenSSL::PKey::RSA.new(decoded_key_der)

result = IrsAttemptsApi::EnvelopeEncryptor.encrypt(
data: event_values, timestamp: timestamp, public_key: pub_key,
)

# write to a file and store on the disk until S3 is setup
FileUtils.mkdir_p(dir_path)

Comment on lines 16 to 19
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in hindsight, this mkdir_p was masking the bug with the tmpdir being cleaned up beforehand, maybe we should remove it. that prevents the job from writing files where it's not supposed to

Suggested change
# write to a file and store on the disk until S3 is setup
FileUtils.mkdir_p(dir_path)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need to get rid of this? I can see an argument for doing so, that whatever calls this shouldn't pass in a directory that doesn't exist. Is there some other way we can ensure safety / error handling if we remove this line?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this code will evolve over time. Eventually we'll write a temp file and upload that to S3. So in that case, the code could make it's own tmpdir and clean that up.

However in the meantime, this code hid a bug so I'd vote to remove it.

file_path = "#{dir_path}/#{result.filename}"

File.open(file_path, 'wb') do |file|
file.write(result.encrypted_data)
end

return { encryptor_result: result, file_path: file_path }

# Write the file to S3 instead of whatever dir_path winds up being
end
end
6 changes: 6 additions & 0 deletions config/initializers/job_configurations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@
class: 'ThreatMetrixJsVerificationJob',
cron: cron_1h,
},
# Batch up IRS Attempts API events
irs_attempt_events_aggregator: {
class: 'IrsAttemptsEventsBatchJob',
cron: cron_1h,
args: -> { [timestamp: Time.zone.now - 1.hour] },
},
}
end
# rubocop:enable Metrics/BlockLength
Expand Down
75 changes: 75 additions & 0 deletions spec/jobs/irs_attempts_events_batch_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
require 'rails_helper'

RSpec.describe IrsAttemptsEventsBatchJob, type: :job do
describe '#perform' do
context 'IRS attempts API is enabled' do
let(:start_time) { Time.new(2020, 1, 1, 12, 0, 0, 'UTC') }
let(:private_key) { OpenSSL::PKey::RSA.new(4096) }
let(:events) do
[
{
event_key: 'key1',
jwe: 'some_event_data_encrypted_with_jwe',
timestamp: start_time + 10.minutes,
},
{
event_key: 'key2',
jwe: 'some_other_event_data_encrypted_with_jwe',
timestamp: start_time + 15.minutes,
},
]
end

before do
allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled).and_return(true)
allow(IdentityConfig.store).to receive(:irs_attempt_api_public_key).
and_return(Base64.strict_encode64(private_key.public_key.to_der))

Dir.mktmpdir do |dir|
@dir_path = dir
end

travel_to start_time + 1.hour

redis_client = IrsAttemptsApi::RedisClient.new
events.each do |event|
redis_client.write_event(
event_key: event[:event_key], jwe: event[:jwe],
timestamp: event[:timestamp]
)
end
end

it 'batches and writes attempt events to an encrypted file' do
result = IrsAttemptsEventsBatchJob.perform_now(timestamp: start_time, dir_path: @dir_path)
expect(result[:file_path]).not_to be_nil

file_data = File.open(result[:file_path], 'rb') do |file|
file.read
end

final_key = private_key.private_decrypt(result[:encryptor_result].encrypted_key)

decrypted_result = IrsAttemptsApi::EnvelopeEncryptor.decrypt(
encrypted_data: file_data,
key: final_key, iv: result[:encryptor_result].iv
)

events_jwes = events.pluck(:jwe)

expect(decrypted_result).to eq(events_jwes.join("\r\n"))
end
end

context 'IRS attempts API is not enabled' do
before do
allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled).and_return(false)
end

it 'returns nil' do
file_path = IrsAttemptsEventsBatchJob.perform_now
expect(file_path).to eq(nil)
end
end
end
end