-
-
Save julik/4195b74774dc21dcd2cbd694a25102ce to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Fixes for two ActiveStorage bugs which, separately and especially | |
# in combination, can lead to data being overwritten | |
module BlobKeyFixes | |
# How many times we should try to regenerate the key when a collision is encountered | |
TRIES_ON_NON_UNIQUE_KEY = 5 | |
# Overridden to actually save the blob before attempting to upload to it, | |
# which makes the uniqueness constraint fire earlier if it gets triggered. | |
# Is a fix for https://github.com/rails/rails/issues/34805 | |
def build_after_upload(io:, filename:, content_type: nil, metadata: nil) | |
new.tap do |blob| | |
blob.filename = filename | |
blob.content_type = content_type | |
blob.metadata = metadata | |
blob.byte_size = 0 # Default value to get past constraint | |
blob.checksum = "pending" # Default value to get past constraint | |
tries_remaining = TRIES_ON_NON_UNIQUE_KEY | |
begin | |
blob.save! # Will raise a uniqueness error if the constraint is hit | |
rescue ActiveRecord::RecordNotUnique | |
tries_remaining -= 1 | |
raise if tries_remaining.zero? | |
blob.key = blob.class.generate_unique_secure_token | |
retry | |
end | |
blob.upload(io) | |
end | |
end | |
# Overridden to generate a base36 lowercase token instead of | |
# a base58 case-sensitive one. | |
# Is a fix for https://github.com/rails/rails/issues/34804 | |
def generate_unique_secure_token | |
# With base58 used in Rails by default, we get | |
# [6] pry(main)> (58**24) | |
# => 210025412090735248552623050583059191142809 | |
# possible values. The closest power to this with base36 | |
# offering "no less than" possible values is 28: | |
# [14] pry(main)> (36**28) | |
# => 37711171281396032013366321198900157303750656 | |
SecureRandom.base58(28).downcase | |
end | |
end | |
# In the initializer the ActiveStorage::Blob model is not yet available, | |
# so delay our monkeypatch until it becomes available | |
Rails.application.config.after_initialize do | |
Rails.logger.info { "ActiveStorage blob key override installed" } | |
# We need to prepend because `build_after_upload` is defined on | |
# the Blob class itself | |
class << ::ActiveStorage::Blob | |
prepend BlobKeyFixes | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment