Skip to content
Merged
15 changes: 9 additions & 6 deletions app/services/encrypted_redis_struct_storage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ module EncryptedRedisStructStorage
def load(id, type:)
check_for_id_property!(type)

ciphertext = READTHIS_POOL.with { |client| client.read(key(id, type: type)) }
ciphertext = REDIS_POOL.with { |client| client.get(key(id, type: type)) } ||
READTHIS_POOL.with { |client| client.read(key(id, type: type)) }
return nil if ciphertext.blank?

json = Encryption::Encryptors::SessionEncryptor.new.decrypt(ciphertext)
Expand Down Expand Up @@ -51,12 +52,14 @@ def store(struct, expires_in: 60)

payload.transform_values!(&utf_8_encode_strs)

struct_key = key(struct.id, type: struct.class)
ciphertext = Encryption::Encryptors::SessionEncryptor.new.encrypt(payload.to_json)

REDIS_POOL.with do |client|
client.setex(struct_key, expires_in, ciphertext)
end
READTHIS_POOL.with do |client|
client.write(
key(struct.id, type: struct.class),
Encryption::Encryptors::SessionEncryptor.new.encrypt(payload.to_json),
expires_in: expires_in,
)
client.write(struct_key, ciphertext, expires_in: expires_in)
end
end

Expand Down
27 changes: 20 additions & 7 deletions app/services/service_provider_request_proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@ def self.from_uuid(uuid)

def self.delete(request_id)
return unless request_id
READTHIS_POOL.with do |client|
client.delete(key(request_id))
self.redis_last_uuid = nil if Rails.env.test?
end
REDIS_POOL.with { |client| client.del(key(request_id)) }
READTHIS_POOL.with { |client| client.delete(key(request_id)) }
self.redis_last_uuid = nil if Rails.env.test?
end

def self.find_by(uuid:)
return if uuid.blank?
obj = READTHIS_POOL.with { |client| client.read(key(uuid)) }
obj = REDIS_POOL.with do |client|
str = client.get(key(uuid))
JSON.parse(str, symbolize_names: true) if str
end
obj ||= READTHIS_POOL.with { |client| client.read(key(uuid)) }
obj ? hash_to_spr(obj, uuid) : nil
end

Expand Down Expand Up @@ -56,10 +59,17 @@ def self.create(hash)
end

def self.write(obj, uuid)
REDIS_POOL.with do |client|
client.setex(
key(uuid),
IdentityConfig.store.service_provider_request_ttl_hours.hours.to_i,
obj.to_json,
)
end
READTHIS_POOL.with do |client|
client.write(key(uuid), obj)
self.redis_last_uuid = uuid if Rails.env.test?
end
self.redis_last_uuid = uuid if Rails.env.test?
end

def self.create!(hash)
Expand All @@ -76,7 +86,10 @@ def self.key(uuid)
end

def self.flush
READTHIS_POOL.with(&:clear) if Rails.env.test?
if Rails.env.test?
REDIS_POOL.with { |namespaced| namespaced.redis.flushdb }
READTHIS_POOL.with(&:clear)
end
end

def self.hash_to_spr(hash, uuid)
Expand Down
11 changes: 4 additions & 7 deletions config/initializers/rack_attack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,13 @@ def headers
### Configure Cache ###

# Note: The store is only used for throttling and fail2ban filtering;
# not blocklisting & safelisting. It must implement .increment and .write
# like ActiveSupport::Cache::Store

cache = Readthis::Cache.new(
# not blocklisting & safelisting
Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(
namespace: 'rack-attack',
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.

the namespace should separate new data from old data, it means there will essentially be a reset on the rack-attack values after a deploy, but I think the timeouts are short-lived enough things will be okay?

url: IdentityConfig.store.redis_throttle_url,
expires_in: 2.weeks.to_i,
redis: { url: IdentityConfig.store.redis_throttle_url, driver: :hiredis },
)

Rack::Attack.cache.store = cache

### Configure Safelisting ###

# Always allow requests from localhost
Expand Down
2 changes: 1 addition & 1 deletion spec/models/document_capture_session_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

result_id = record.result_id
key = EncryptedRedisStructStorage.key(result_id, type: DocumentCaptureSessionResult)
data = READTHIS_POOL.with { |client| client.read(key) }
data = REDIS_POOL.with { |client| client.get(key) }
expect(data).to be_a(String)
expect(data).to_not include('Testy')
expect(data).to_not include('Testerson')
Expand Down
2 changes: 1 addition & 1 deletion spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
Rails.application.load_seed

begin
READTHIS_POOL.with { |cache| cache.pool.with(&:info) }
REDIS_POOL.with { |namespaced| namespaced.redis.info }
rescue RuntimeError => error
puts error
puts 'It appears Redis is not running, but it is required for (some) specs to run'
Expand Down
33 changes: 27 additions & 6 deletions spec/services/encrypted_redis_struct_storage_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,29 @@ def self.redis_key_prefix
expect(loaded_result.b).to eq('b')
expect(loaded_result.c).to eq('c')
end

context 'with a struct stored by Readthis gem' do
before do
READTHIS_POOL.with do |client|
struct = struct_class.new(id: id, a: 'a', b: 'b', c: 'c')
client.write(
EncryptedRedisStructStorage.key(struct.id, type: struct.class),
Encryption::Encryptors::SessionEncryptor.new.encrypt(
struct.as_json.tap { |s| s.delete('id') }.to_json,
),
expires_in: 5,
)
end
end

it 'loads the value still' do
loaded_result = load_struct

expect(loaded_result.a).to eq('a')
expect(loaded_result.b).to eq('b')
expect(loaded_result.c).to eq('c')
end
end
end

context 'with an ordered initializer struct' do
Expand Down Expand Up @@ -119,8 +142,8 @@ def self.redis_key_prefix
struct_class.new(id: id, a: 'value for a', b: 'value for b', c: 'value for c'),
)

data = READTHIS_POOL.with do |client|
client.read(EncryptedRedisStructStorage.key(id, type: struct_class))
data = REDIS_POOL.with do |client|
client.get(EncryptedRedisStructStorage.key(id, type: struct_class))
end

expect(data).to be_a(String)
Expand All @@ -134,10 +157,8 @@ def self.redis_key_prefix
struct_class.new(id: id, a: 'value for a', b: 'value for b', c: 'value for c'),
)

ttl = READTHIS_POOL.with do |client|
client.pool.with do |redis|
redis.ttl(EncryptedRedisStructStorage.key(id, type: struct_class))
end
ttl = REDIS_POOL.with do |redis|
redis.ttl(EncryptedRedisStructStorage.key(id, type: struct_class))
end

expect(ttl).to be <= 60
Expand Down
14 changes: 14 additions & 0 deletions spec/services/service_provider_request_proxy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@
expect(sp_request.loa).to eq(sp_request.ial)
expect(ServiceProviderRequestProxy.from_uuid('123')).to eq sp_request
end

context 'with a value stored by Readthis gem' do
let(:uuid) { SecureRandom.uuid }

before do
READTHIS_POOL.with do |client|
client.write(ServiceProviderRequestProxy.key(uuid), issuer: 'foo')
end
end

it 'loads the data' do
expect(ServiceProviderRequestProxy.from_uuid(uuid).issuer).to eq('foo')
end
end
end

context 'when the record does not exist' do
Expand Down