Always use OpenSSL constants for Digest operations

As also previously discussed in
https://github.com/rails/rails/pull/40770#issuecomment-748347066, this
moves the usage of Digest constants to always use the OpenSSL version of
those Digest implementations.
This commit is contained in:
Dirkjan Bussink 2021-03-22 10:25:49 +01:00
parent bead3221c7
commit 0523532a3c
No known key found for this signature in database
GPG Key ID: F1573D8E835753FD
37 changed files with 110 additions and 73 deletions

View File

@ -1,3 +1,7 @@
* OpenSSL constants are now used for Digest computations.
*Dirkjan Bussink*
* The Action Cable client now includes safeguards to prevent a "thundering * The Action Cable client now includes safeguards to prevent a "thundering
herd" of client reconnects after server connectivity loss: herd" of client reconnects after server connectivity loss:

View File

@ -3,7 +3,7 @@
gem "pg", "~> 1.1" gem "pg", "~> 1.1"
require "pg" require "pg"
require "thread" require "thread"
require "digest/sha1" require "openssl"
module ActionCable module ActionCable
module SubscriptionAdapter module SubscriptionAdapter
@ -58,7 +58,7 @@ module ActionCable
private private
def channel_identifier(channel) def channel_identifier(channel)
channel.size > 63 ? Digest::SHA1.hexdigest(channel) : channel channel.size > 63 ? OpenSSL::Digest::SHA1.hexdigest(channel) : channel
end end
def listener def listener

View File

@ -2,5 +2,8 @@
*Santiago Bartesaghi* *Santiago Bartesaghi*
* OpenSSL constants are now used for Digest computations.
*Dirkjan Bussink*
Please check [6-1-stable](https://github.com/rails/rails/blob/6-1-stable/actionmailbox/CHANGELOG.md) for previous changes. Please check [6-1-stable](https://github.com/rails/rails/blob/6-1-stable/actionmailbox/CHANGELOG.md) for previous changes.

View File

@ -14,7 +14,7 @@ module ActionMailbox::InboundEmail::MessageId
# attachment called +raw_email+. Before the upload, extract the Message-ID from the +source+ and set # attachment called +raw_email+. Before the upload, extract the Message-ID from the +source+ and set
# it as an attribute on the new +InboundEmail+. # it as an attribute on the new +InboundEmail+.
def create_and_extract_message_id!(source, **options) def create_and_extract_message_id!(source, **options)
message_checksum = Digest::SHA1.hexdigest(source) message_checksum = OpenSSL::Digest::SHA1.hexdigest(source)
message_id = extract_message_id(source) || generate_missing_message_id(message_checksum) message_id = extract_message_id(source) || generate_missing_message_id(message_checksum)
create! raw_email: create_and_upload_raw_email!(source), create! raw_email: create_and_upload_raw_email!(source),

View File

@ -1,3 +1,7 @@
* OpenSSL constants are now used for Digest computations.
*Dirkjan Bussink*
* Remove IE6-7-8 file download related hack/fix from ActionController::DataStreaming module * Remove IE6-7-8 file download related hack/fix from ActionController::DataStreaming module
Due to the age of those versions of IE this fix is no longer relevant, more importantly it creates an edge-case for unexpected Cache-Control headers. Due to the age of those versions of IE this fix is no longer relevant, more importantly it creates an edge-case for unexpected Cache-Control headers.

View File

@ -138,11 +138,11 @@ module ActionController
# #
# === Simple \Digest example # === Simple \Digest example
# #
# require "digest/md5" # require "openssl"
# class PostsController < ApplicationController # class PostsController < ApplicationController
# REALM = "SuperSecret" # REALM = "SuperSecret"
# USERS = {"dhh" => "secret", #plain text password # USERS = {"dhh" => "secret", #plain text password
# "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password # "dap" => OpenSSL::Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
# #
# before_action :authenticate, except: [:index] # before_action :authenticate, except: [:index]
# #
@ -230,12 +230,12 @@ module ActionController
# of a plain-text password. # of a plain-text password.
def expected_response(http_method, uri, credentials, password, password_is_ha1 = true) def expected_response(http_method, uri, credentials, password, password_is_ha1 = true)
ha1 = password_is_ha1 ? password : ha1(credentials, password) ha1 = password_is_ha1 ? password : ha1(credentials, password)
ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":")) ha2 = OpenSSL::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":")) OpenSSL::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":"))
end end
def ha1(credentials, password) def ha1(credentials, password)
::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":")) OpenSSL::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":"))
end end
def encode_credentials(http_method, credentials, password, password_is_ha1) def encode_credentials(http_method, credentials, password, password_is_ha1)
@ -309,7 +309,7 @@ module ActionController
def nonce(secret_key, time = Time.now) def nonce(secret_key, time = Time.now)
t = time.to_i t = time.to_i
hashed = [t, secret_key] hashed = [t, secret_key]
digest = ::Digest::MD5.hexdigest(hashed.join(":")) digest = OpenSSL::Digest::MD5.hexdigest(hashed.join(":"))
::Base64.strict_encode64("#{t}:#{digest}") ::Base64.strict_encode64("#{t}:#{digest}")
end end
@ -326,7 +326,7 @@ module ActionController
# Opaque based on digest of secret key # Opaque based on digest of secret key
def opaque(secret_key) def opaque(secret_key)
::Digest::MD5.hexdigest(secret_key) OpenSSL::Digest::MD5.hexdigest(secret_key)
end end
end end

View File

@ -9,7 +9,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
before_action :authenticate_with_request, only: :display before_action :authenticate_with_request, only: :display
USERS = { "lifo" => "world", "pretty" => "please", USERS = { "lifo" => "world", "pretty" => "please",
"dhh" => ::Digest::MD5.hexdigest(["dhh", "SuperSecret", "secret"].join(":")) } "dhh" => OpenSSL::Digest::MD5.hexdigest(["dhh", "SuperSecret", "secret"].join(":")) }
def index def index
render plain: "Hello Secret" render plain: "Hello Secret"
@ -185,7 +185,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
test "authentication request with password stored as ha1 digest hash" do test "authentication request with password stored as ha1 digest hash" do
@request.env["HTTP_AUTHORIZATION"] = encode_credentials( @request.env["HTTP_AUTHORIZATION"] = encode_credentials(
username: "dhh", username: "dhh",
password: ::Digest::MD5.hexdigest(["dhh", "SuperSecret", "secret"].join(":")), password: OpenSSL::Digest::MD5.hexdigest(["dhh", "SuperSecret", "secret"].join(":")),
password_is_ha1: true) password_is_ha1: true)
get :display get :display

View File

@ -1,3 +1,7 @@
* OpenSSL constants are now used for Digest computations.
*Dirkjan Bussink*
* Add support for passing `form:` option to `rich_text_area_tag` and * Add support for passing `form:` option to `rich_text_area_tag` and
`rich_text_area` helpers to specify the `<input type="hidden" form="...">` `rich_text_area` helpers to specify the `<input type="hidden" form="...">`
value. value.

View File

@ -9,7 +9,7 @@ module ActionText
private private
def cache_digest def cache_digest
Digest::SHA256.hexdigest(node.to_s) OpenSSL::Digest::SHA256.hexdigest(node.to_s)
end end
end end
end end

View File

@ -1,3 +1,7 @@
* OpenSSL constants are now used for Digest computations.
*Dirkjan Bussink*
* The `translate` helper now passes `default` values that aren't * The `translate` helper now passes `default` values that aren't
translation keys through `I18n.translate` for interpolation. translation keys through `I18n.translate` for interpolation.

View File

@ -65,7 +65,7 @@ module ActionView
# +asset_host+ to a proc like this: # +asset_host+ to a proc like this:
# #
# ActionController::Base.asset_host = Proc.new { |source| # ActionController::Base.asset_host = Proc.new { |source|
# "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com" # "http://assets#{OpenSSL::Digest::SHA256.hexdigest(source).to_i(16) % 2 + 1}.example.com"
# } # }
# image_tag("rails.png") # image_tag("rails.png")
# # => <img src="http://assets1.example.com/assets/rails.png" /> # # => <img src="http://assets1.example.com/assets/rails.png" />

View File

@ -1,3 +1,7 @@
* OpenSSL constants are now used for Digest computations.
*Dirkjan Bussink*
* Add a Serializer for the Range class * Add a Serializer for the Range class
This should allow things like `MyJob.perform_later(range: 1..100)` This should allow things like `MyJob.perform_later(range: 1..100)`

View File

@ -31,7 +31,7 @@ module SneakersJobsManager
@pid = fork do @pid = fork do
queues = %w(integration_tests) queues = %w(integration_tests)
workers = queues.map do |q| workers = queues.map do |q|
worker_klass = "ActiveJobWorker" + Digest::MD5.hexdigest(q) worker_klass = "ActiveJobWorker" + OpenSSL::Digest::MD5.hexdigest(q)
Sneakers.const_set(worker_klass, Class.new(ActiveJob::QueueAdapters::SneakersAdapter::JobWrapper) do Sneakers.const_set(worker_klass, Class.new(ActiveJob::QueueAdapters::SneakersAdapter::JobWrapper) do
from_queue q from_queue q
end) end)

View File

@ -1,5 +1,11 @@
* OpenSSL constants are now used for Digest computations.
*Dirkjan Bussink*
* Relation#destroy_all perform its work in batches * Relation#destroy_all perform its work in batches
* Prevent double saves in autosave of cyclic associations
Since destroy_all actually loads the entire relation and then iteratively destroys the records one by one, Since destroy_all actually loads the entire relation and then iteratively destroys the records one by one,
you can blow your memory gasket very easily. So let's do the right thing by default you can blow your memory gasket very easily. So let's do the right thing by default
and do this work in batches of 100 by default and allow you to specify and do this work in batches of 100 by default and allow you to specify

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require "active_support/core_ext/string/access" require "active_support/core_ext/string/access"
require "digest/sha2" require "openssl"
module ActiveRecord module ActiveRecord
module ConnectionAdapters # :nodoc: module ConnectionAdapters # :nodoc:
@ -1532,7 +1532,7 @@ module ActiveRecord
def foreign_key_name(table_name, options) def foreign_key_name(table_name, options)
options.fetch(:name) do options.fetch(:name) do
identifier = "#{table_name}_#{options.fetch(:column)}_fk" identifier = "#{table_name}_#{options.fetch(:column)}_fk"
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10)
"fk_rails_#{hashed_identifier}" "fk_rails_#{hashed_identifier}"
end end
@ -1560,7 +1560,7 @@ module ActiveRecord
options.fetch(:name) do options.fetch(:name) do
expression = options.fetch(:expression) expression = options.fetch(:expression)
identifier = "#{table_name}_#{expression}_chk" identifier = "#{table_name}_#{expression}_chk"
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10)
"chk_rails_#{hashed_identifier}" "chk_rails_#{hashed_identifier}"
end end

View File

@ -592,7 +592,7 @@ module ActiveRecord
end end
def schema_sha1(file) def schema_sha1(file)
Digest::SHA1.hexdigest(File.read(file)) OpenSSL::Digest::SHA1.hexdigest(File.read(file))
end end
def structure_dump_flags_for(adapter) def structure_dump_flags_for(adapter)

View File

@ -1,3 +1,7 @@
* OpenSSL constants are now used for Digest computations.
*Dirkjan Bussink*
* Deprecate `config.active_storage.replace_on_assign_to_many`. Future versions of Rails * Deprecate `config.active_storage.replace_on_assign_to_many`. Future versions of Rails
will behave the same way as when the config is set to `true`. will behave the same way as when the config is set to `true`.

View File

@ -305,7 +305,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
private private
def compute_checksum_in_chunks(io) def compute_checksum_in_chunks(io)
Digest::MD5.new.tap do |checksum| OpenSSL::Digest::MD5.new.tap do |checksum|
while chunk = io.read(5.megabytes) while chunk = io.read(5.megabytes)
checksum << chunk checksum << chunk
end end

View File

@ -67,7 +67,7 @@ class ActiveStorage::Variant
# Returns a combination key of the blob and the variation that together identifies a specific variant. # Returns a combination key of the blob and the variation that together identifies a specific variant.
def key def key
"variants/#{blob.key}/#{Digest::SHA256.hexdigest(variation.key)}" "variants/#{blob.key}/#{OpenSSL::Digest::SHA256.hexdigest(variation.key)}"
end end
# Returns the URL of the blob variant on the service. See {ActiveStorage::Blob#url} for details. # Returns the URL of the blob variant on the service. See {ActiveStorage::Blob#url} for details.

View File

@ -75,7 +75,7 @@ class ActiveStorage::Variation
end end
def digest def digest
Digest::SHA1.base64digest Marshal.dump(transformations) OpenSSL::Digest::SHA1.base64digest Marshal.dump(transformations)
end end
private private

View File

@ -35,7 +35,7 @@ module ActiveStorage
end end
def verify_integrity_of(file, checksum:) def verify_integrity_of(file, checksum:)
unless Digest::MD5.file(file).base64digest == checksum unless OpenSSL::Digest::MD5.file(file).base64digest == checksum
raise ActiveStorage::IntegrityError raise ActiveStorage::IntegrityError
end end
end end

View File

@ -2,7 +2,7 @@
require "fileutils" require "fileutils"
require "pathname" require "pathname"
require "digest/md5" require "openssl"
require "active_support/core_ext/numeric/bytes" require "active_support/core_ext/numeric/bytes"
module ActiveStorage module ActiveStorage
@ -158,7 +158,7 @@ module ActiveStorage
end end
def ensure_integrity_of(key, checksum) def ensure_integrity_of(key, checksum)
unless Digest::MD5.file(path_for(key)).base64digest == checksum unless OpenSSL::Digest::MD5.file(path_for(key)).base64digest == checksum
delete key delete key
raise ActiveStorage::IntegrityError raise ActiveStorage::IntegrityError
end end

View File

@ -15,7 +15,7 @@ if SERVICE_CONFIGURATIONS[:s3] && SERVICE_CONFIGURATIONS[:s3][:access_key_id].pr
end end
test "creating new direct upload" do test "creating new direct upload" do
checksum = Digest::MD5.base64digest("Hello") checksum = OpenSSL::Digest::MD5.base64digest("Hello")
metadata = { metadata = {
"foo": "bar", "foo": "bar",
"my_key_1": "my_value_1", "my_key_1": "my_value_1",
@ -58,7 +58,7 @@ if SERVICE_CONFIGURATIONS[:gcs]
end end
test "creating new direct upload" do test "creating new direct upload" do
checksum = Digest::MD5.base64digest("Hello") checksum = OpenSSL::Digest::MD5.base64digest("Hello")
metadata = { metadata = {
"foo": "bar", "foo": "bar",
"my_key_1": "my_value_1", "my_key_1": "my_value_1",
@ -100,7 +100,7 @@ if SERVICE_CONFIGURATIONS[:azure]
end end
test "creating new direct upload" do test "creating new direct upload" do
checksum = Digest::MD5.base64digest("Hello") checksum = OpenSSL::Digest::MD5.base64digest("Hello")
metadata = { metadata = {
"foo": "bar", "foo": "bar",
"my_key_1": "my_value_1", "my_key_1": "my_value_1",
@ -130,7 +130,7 @@ end
class ActiveStorage::DiskDirectUploadsControllerTest < ActionDispatch::IntegrationTest class ActiveStorage::DiskDirectUploadsControllerTest < ActionDispatch::IntegrationTest
test "creating new direct upload" do test "creating new direct upload" do
checksum = Digest::MD5.base64digest("Hello") checksum = OpenSSL::Digest::MD5.base64digest("Hello")
metadata = { metadata = {
"foo": "bar", "foo": "bar",
"my_key_1": "my_value_1", "my_key_1": "my_value_1",
@ -155,7 +155,7 @@ class ActiveStorage::DiskDirectUploadsControllerTest < ActionDispatch::Integrati
end end
test "creating new direct upload does not include root in json" do test "creating new direct upload does not include root in json" do
checksum = Digest::MD5.base64digest("Hello") checksum = OpenSSL::Digest::MD5.base64digest("Hello")
metadata = { metadata = {
"foo": "bar", "foo": "bar",
"my_key_1": "my_value_1", "my_key_1": "my_value_1",

View File

@ -67,7 +67,7 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest
test "directly uploading blob with integrity" do test "directly uploading blob with integrity" do
data = "Something else entirely!" data = "Something else entirely!"
blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest(data) blob = create_blob_before_direct_upload byte_size: data.size, checksum: OpenSSL::Digest::MD5.base64digest(data)
put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "text/plain" } put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "text/plain" }
assert_response :no_content assert_response :no_content
@ -76,7 +76,7 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest
test "directly uploading blob without integrity" do test "directly uploading blob without integrity" do
data = "Something else entirely!" data = "Something else entirely!"
blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest("bad data") blob = create_blob_before_direct_upload byte_size: data.size, checksum: OpenSSL::Digest::MD5.base64digest("bad data")
put blob.service_url_for_direct_upload, params: data put blob.service_url_for_direct_upload, params: data
assert_response :unprocessable_entity assert_response :unprocessable_entity
@ -85,7 +85,7 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest
test "directly uploading blob with mismatched content type" do test "directly uploading blob with mismatched content type" do
data = "Something else entirely!" data = "Something else entirely!"
blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest(data) blob = create_blob_before_direct_upload byte_size: data.size, checksum: OpenSSL::Digest::MD5.base64digest(data)
put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "application/octet-stream" } put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "application/octet-stream" }
assert_response :unprocessable_entity assert_response :unprocessable_entity
@ -95,7 +95,7 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest
test "directly uploading blob with different but equivalent content type" do test "directly uploading blob with different but equivalent content type" do
data = "Something else entirely!" data = "Something else entirely!"
blob = create_blob_before_direct_upload( blob = create_blob_before_direct_upload(
byte_size: data.size, checksum: Digest::MD5.base64digest(data), content_type: "application/x-gzip") byte_size: data.size, checksum: OpenSSL::Digest::MD5.base64digest(data), content_type: "application/x-gzip")
put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "application/x-gzip" } put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "application/x-gzip" }
assert_response :no_content assert_response :no_content
@ -104,7 +104,7 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest
test "directly uploading blob with mismatched content length" do test "directly uploading blob with mismatched content length" do
data = "Something else entirely!" data = "Something else entirely!"
blob = create_blob_before_direct_upload byte_size: data.size - 1, checksum: Digest::MD5.base64digest(data) blob = create_blob_before_direct_upload byte_size: data.size - 1, checksum: OpenSSL::Digest::MD5.base64digest(data)
put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "text/plain" } put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "text/plain" }
assert_response :unprocessable_entity assert_response :unprocessable_entity

View File

@ -38,7 +38,7 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase
assert_equal data, blob.download assert_equal data, blob.download
assert_equal data.length, blob.byte_size assert_equal data.length, blob.byte_size
assert_equal Digest::MD5.base64digest(data), blob.checksum assert_equal OpenSSL::Digest::MD5.base64digest(data), blob.checksum
end end
test "create_and_upload extracts content type from data" do test "create_and_upload extracts content type from data" do
@ -129,7 +129,7 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase
test "open without integrity" do test "open without integrity" do
create_blob(data: "Hello, world!").tap do |blob| create_blob(data: "Hello, world!").tap do |blob|
blob.update! checksum: Digest::MD5.base64digest("Goodbye, world!") blob.update! checksum: OpenSSL::Digest::MD5.base64digest("Goodbye, world!")
assert_raises ActiveStorage::IntegrityError do assert_raises ActiveStorage::IntegrityError do
blob.open { |file| flunk "Expected integrity check to fail" } blob.open { |file| flunk "Expected integrity check to fail" }

View File

@ -21,7 +21,7 @@ if SERVICE_CONFIGURATIONS[:azure_public]
test "direct upload" do test "direct upload" do
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
checksum = Digest::MD5.base64digest(data) checksum = OpenSSL::Digest::MD5.base64digest(data)
content_type = "text/xml" content_type = "text/xml"
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: content_type, content_length: data.size, checksum: checksum) url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: content_type, content_length: data.size, checksum: checksum)

View File

@ -12,7 +12,7 @@ if SERVICE_CONFIGURATIONS[:azure]
test "direct upload with content type" do test "direct upload with content type" do
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
checksum = Digest::MD5.base64digest(data) checksum = OpenSSL::Digest::MD5.base64digest(data)
content_type = "text/xml" content_type = "text/xml"
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: content_type, content_length: data.size, checksum: checksum) url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: content_type, content_length: data.size, checksum: checksum)
@ -34,7 +34,7 @@ if SERVICE_CONFIGURATIONS[:azure]
test "direct upload with content disposition" do test "direct upload with content disposition" do
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
checksum = Digest::MD5.base64digest(data) checksum = OpenSSL::Digest::MD5.base64digest(data)
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum) url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum)
uri = URI.parse url uri = URI.parse url
@ -56,7 +56,7 @@ if SERVICE_CONFIGURATIONS[:azure]
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Foobar" data = "Foobar"
@service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest(data), filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain") @service.upload(key, StringIO.new(data), checksum: OpenSSL::Digest::MD5.base64digest(data), filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain")
url = @service.url(key, expires_in: 2.minutes, disposition: :attachment, content_type: nil, filename: ActiveStorage::Filename.new("test.html")) url = @service.url(key, expires_in: 2.minutes, disposition: :attachment, content_type: nil, filename: ActiveStorage::Filename.new("test.html"))
response = Net::HTTP.get_response(URI(url)) response = Net::HTTP.get_response(URI(url))
@ -70,7 +70,7 @@ if SERVICE_CONFIGURATIONS[:azure]
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Foobar" data = "Foobar"
@service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest(data), filename: ActiveStorage::Filename.new("test.txt"), disposition: :inline) @service.upload(key, StringIO.new(data), checksum: OpenSSL::Digest::MD5.base64digest(data), filename: ActiveStorage::Filename.new("test.txt"), disposition: :inline)
assert_equal("inline; filename=\"test.txt\"; filename*=UTF-8''test.txt", @service.client.get_blob_properties(@service.container, key).properties[:content_disposition]) assert_equal("inline; filename=\"test.txt\"; filename*=UTF-8''test.txt", @service.client.get_blob_properties(@service.container, key).properties[:content_disposition])

View File

@ -21,7 +21,7 @@ if SERVICE_CONFIGURATIONS[:gcs_public]
test "direct upload" do test "direct upload" do
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
checksum = Digest::MD5.base64digest(data) checksum = OpenSSL::Digest::MD5.base64digest(data)
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum) url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum)
uri = URI.parse url uri = URI.parse url

View File

@ -16,7 +16,7 @@ if SERVICE_CONFIGURATIONS[:gcs]
test "direct upload" do test "direct upload" do
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
checksum = Digest::MD5.base64digest(data) checksum = OpenSSL::Digest::MD5.base64digest(data)
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum) url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum)
uri = URI.parse url uri = URI.parse url
@ -36,7 +36,7 @@ if SERVICE_CONFIGURATIONS[:gcs]
test "direct upload with content disposition" do test "direct upload with content disposition" do
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
checksum = Digest::MD5.base64digest(data) checksum = OpenSSL::Digest::MD5.base64digest(data)
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum) url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum)
uri = URI.parse url uri = URI.parse url
@ -91,7 +91,7 @@ if SERVICE_CONFIGURATIONS[:gcs]
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
@service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest(data), disposition: :attachment, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain") @service.upload(key, StringIO.new(data), checksum: OpenSSL::Digest::MD5.base64digest(data), disposition: :attachment, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain")
url = @service.url(key, expires_in: 2.minutes, disposition: :inline, content_type: "text/html", filename: ActiveStorage::Filename.new("test.html")) url = @service.url(key, expires_in: 2.minutes, disposition: :inline, content_type: "text/html", filename: ActiveStorage::Filename.new("test.html"))
response = Net::HTTP.get_response(URI(url)) response = Net::HTTP.get_response(URI(url))
@ -105,7 +105,7 @@ if SERVICE_CONFIGURATIONS[:gcs]
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
@service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest(data), content_type: "text/plain") @service.upload(key, StringIO.new(data), checksum: OpenSSL::Digest::MD5.base64digest(data), content_type: "text/plain")
url = @service.url(key, expires_in: 2.minutes, disposition: :inline, content_type: "text/html", filename: ActiveStorage::Filename.new("test.html")) url = @service.url(key, expires_in: 2.minutes, disposition: :inline, content_type: "text/html", filename: ActiveStorage::Filename.new("test.html"))
response = Net::HTTP.get_response(URI(url)) response = Net::HTTP.get_response(URI(url))
@ -135,7 +135,7 @@ if SERVICE_CONFIGURATIONS[:gcs]
test "update metadata" do test "update metadata" do
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
@service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest(data), disposition: :attachment, filename: ActiveStorage::Filename.new("test.html"), content_type: "text/html") @service.upload(key, StringIO.new(data), checksum: OpenSSL::Digest::MD5.base64digest(data), disposition: :attachment, filename: ActiveStorage::Filename.new("test.html"), content_type: "text/html")
@service.update_metadata(key, disposition: :inline, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain") @service.update_metadata(key, disposition: :inline, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain")
url = @service.url(key, expires_in: 2.minutes, disposition: :attachment, content_type: "text/html", filename: ActiveStorage::Filename.new("test.html")) url = @service.url(key, expires_in: 2.minutes, disposition: :attachment, content_type: "text/html", filename: ActiveStorage::Filename.new("test.html"))

View File

@ -25,7 +25,7 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
io = StringIO.new(data) io = StringIO.new(data)
checksum = Digest::MD5.base64digest(data) checksum = OpenSSL::Digest::MD5.base64digest(data)
@service.upload key, io.tap(&:read), checksum: checksum @service.upload key, io.tap(&:read), checksum: checksum
assert_predicate io, :eof? assert_predicate io, :eof?
@ -41,7 +41,7 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase
test "downloading from primary service" do test "downloading from primary service" do
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
checksum = Digest::MD5.base64digest(data) checksum = OpenSSL::Digest::MD5.base64digest(data)
@service.primary.upload key, StringIO.new(data), checksum: checksum @service.primary.upload key, StringIO.new(data), checksum: checksum
@ -60,7 +60,7 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase
test "mirroring a file from the primary service to secondary services where it doesn't exist" do test "mirroring a file from the primary service to secondary services where it doesn't exist" do
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
checksum = Digest::MD5.base64digest(data) checksum = OpenSSL::Digest::MD5.base64digest(data)
@service.primary.upload key, StringIO.new(data), checksum: checksum @service.primary.upload key, StringIO.new(data), checksum: checksum
@service.mirrors.third.upload key, StringIO.new("Surprise!") @service.mirrors.third.upload key, StringIO.new("Surprise!")

View File

@ -35,7 +35,7 @@ if SERVICE_CONFIGURATIONS[:s3_public]
test "direct upload" do test "direct upload" do
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
checksum = Digest::MD5.base64digest(data) checksum = OpenSSL::Digest::MD5.base64digest(data)
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum) url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum)
uri = URI.parse url uri = URI.parse url

View File

@ -17,7 +17,7 @@ if SERVICE_CONFIGURATIONS[:s3]
test "direct upload" do test "direct upload" do
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
checksum = Digest::MD5.base64digest(data) checksum = OpenSSL::Digest::MD5.base64digest(data)
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum) url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum)
uri = URI.parse url uri = URI.parse url
@ -37,7 +37,7 @@ if SERVICE_CONFIGURATIONS[:s3]
test "direct upload with content disposition" do test "direct upload with content disposition" do
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
checksum = Digest::MD5.base64digest(data) checksum = OpenSSL::Digest::MD5.base64digest(data)
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum) url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum)
uri = URI.parse url uri = URI.parse url
@ -58,7 +58,7 @@ if SERVICE_CONFIGURATIONS[:s3]
test "directly uploading file larger than the provided content-length does not work" do test "directly uploading file larger than the provided content-length does not work" do
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Some text that is longer than the specified content length" data = "Some text that is longer than the specified content length"
checksum = Digest::MD5.base64digest(data) checksum = OpenSSL::Digest::MD5.base64digest(data)
url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size - 1, checksum: checksum) url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size - 1, checksum: checksum)
uri = URI.parse url uri = URI.parse url
@ -99,7 +99,7 @@ if SERVICE_CONFIGURATIONS[:s3]
begin begin
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
service.upload key, StringIO.new(data), checksum: Digest::MD5.base64digest(data) service.upload key, StringIO.new(data), checksum: OpenSSL::Digest::MD5.base64digest(data)
assert_equal "AES256", service.bucket.object(key).server_side_encryption assert_equal "AES256", service.bucket.object(key).server_side_encryption
ensure ensure
@ -115,7 +115,7 @@ if SERVICE_CONFIGURATIONS[:s3]
@service.upload( @service.upload(
key, key,
StringIO.new(data), StringIO.new(data),
checksum: Digest::MD5.base64digest(data), checksum: OpenSSL::Digest::MD5.base64digest(data),
filename: "cool_data.txt", filename: "cool_data.txt",
content_type: content_type content_type: content_type
) )
@ -132,7 +132,7 @@ if SERVICE_CONFIGURATIONS[:s3]
@service.upload( @service.upload(
key, key,
StringIO.new(data), StringIO.new(data),
checksum: Digest::MD5.base64digest(data), checksum: OpenSSL::Digest::MD5.base64digest(data),
filename: ActiveStorage::Filename.new("cool_data.txt"), filename: ActiveStorage::Filename.new("cool_data.txt"),
disposition: :attachment disposition: :attachment
) )
@ -149,7 +149,7 @@ if SERVICE_CONFIGURATIONS[:s3]
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = SecureRandom.bytes(8.megabytes) data = SecureRandom.bytes(8.megabytes)
service.upload key, StringIO.new(data), checksum: Digest::MD5.base64digest(data) service.upload key, StringIO.new(data), checksum: OpenSSL::Digest::MD5.base64digest(data)
assert data == service.download(key) assert data == service.download(key)
ensure ensure
service.delete key service.delete key
@ -163,7 +163,7 @@ if SERVICE_CONFIGURATIONS[:s3]
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = SecureRandom.bytes(3.megabytes) data = SecureRandom.bytes(3.megabytes)
service.upload key, StringIO.new(data), checksum: Digest::MD5.base64digest(data) service.upload key, StringIO.new(data), checksum: OpenSSL::Digest::MD5.base64digest(data)
assert data == service.download(key) assert data == service.download(key)
ensure ensure
service.delete key service.delete key

View File

@ -22,7 +22,7 @@ module ActiveStorage::Service::SharedServiceTests
test "uploading with integrity" do test "uploading with integrity" do
key = SecureRandom.base58(24) key = SecureRandom.base58(24)
data = "Something else entirely!" data = "Something else entirely!"
@service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest(data)) @service.upload(key, StringIO.new(data), checksum: OpenSSL::Digest::MD5.base64digest(data))
assert_equal data, @service.download(key) assert_equal data, @service.download(key)
ensure ensure
@ -34,7 +34,7 @@ module ActiveStorage::Service::SharedServiceTests
data = "Something else entirely!" data = "Something else entirely!"
assert_raises(ActiveStorage::IntegrityError) do assert_raises(ActiveStorage::IntegrityError) do
@service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest("bad data")) @service.upload(key, StringIO.new(data), checksum: OpenSSL::Digest::MD5.base64digest("bad data"))
end end
assert_not @service.exist?(key) assert_not @service.exist?(key)
@ -48,7 +48,7 @@ module ActiveStorage::Service::SharedServiceTests
@service.upload( @service.upload(
key, key,
StringIO.new(data), StringIO.new(data),
checksum: Digest::MD5.base64digest(data), checksum: OpenSSL::Digest::MD5.base64digest(data),
filename: "racecar.jpg", filename: "racecar.jpg",
content_type: "image/jpeg" content_type: "image/jpeg"
) )

View File

@ -97,7 +97,7 @@ class ActiveSupport::TestCase
def directly_upload_file_blob(filename: "racecar.jpg", content_type: "image/jpeg", record: nil) def directly_upload_file_blob(filename: "racecar.jpg", content_type: "image/jpeg", record: nil)
file = file_fixture(filename) file = file_fixture(filename)
byte_size = file.size byte_size = file.size
checksum = Digest::MD5.file(file).base64digest checksum = OpenSSL::Digest::MD5.file(file).base64digest
create_blob_before_direct_upload(filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type, record: record).tap do |blob| create_blob_before_direct_upload(filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type, record: record).tap do |blob|
service = ActiveStorage::Blob.service.try(:primary) || ActiveStorage::Blob.service service = ActiveStorage::Blob.service.try(:primary) || ActiveStorage::Blob.service

View File

@ -1,3 +1,7 @@
* OpenSSL constants are now used for Digest computations.
*Dirkjan Bussink*
* `TimeZone.iso8601` now accepts valid ordinal values similar to Ruby's `Date._iso8601` method. * `TimeZone.iso8601` now accepts valid ordinal values similar to Ruby's `Date._iso8601` method.
A valid ordinal value will be converted to an instance of `TimeWithZone` using the `:year` A valid ordinal value will be converted to an instance of `TimeWithZone` using the `:year`
and `:yday` fragments returned from `Date._iso8601`. and `:yday` fragments returned from `Date._iso8601`.

View File

@ -11,17 +11,17 @@ module Digest
# Generates a v5 non-random UUID (Universally Unique IDentifier). # Generates a v5 non-random UUID (Universally Unique IDentifier).
# #
# Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs. # Using OpenSSL::Digest::MD5 generates version 3 UUIDs; OpenSSL::Digest::SHA1 generates version 5 UUIDs.
# uuid_from_hash always generates the same UUID for a given name and namespace combination. # uuid_from_hash always generates the same UUID for a given name and namespace combination.
# #
# See RFC 4122 for details of UUID at: https://www.ietf.org/rfc/rfc4122.txt # See RFC 4122 for details of UUID at: https://www.ietf.org/rfc/rfc4122.txt
def self.uuid_from_hash(hash_class, uuid_namespace, name) def self.uuid_from_hash(hash_class, uuid_namespace, name)
if hash_class == Digest::MD5 if hash_class == Digest::MD5 || hash_class == OpenSSL::Digest::MD5
version = 3 version = 3
elsif hash_class == Digest::SHA1 elsif hash_class == Digest::SHA1 || hash_class == OpenSSL::Digest::SHA1
version = 5 version = 5
else else
raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}." raise ArgumentError, "Expected OpenSSL::Digest::SHA1 or OpenSSL::Digest::MD5, got #{hash_class.name}."
end end
hash = hash_class.new hash = hash_class.new
@ -35,14 +35,14 @@ module Digest
"%08x-%04x-%04x-%04x-%04x%08x" % ary "%08x-%04x-%04x-%04x-%04x%08x" % ary
end end
# Convenience method for uuid_from_hash using Digest::MD5. # Convenience method for uuid_from_hash using OpenSSL::Digest::MD5.
def self.uuid_v3(uuid_namespace, name) def self.uuid_v3(uuid_namespace, name)
uuid_from_hash(Digest::MD5, uuid_namespace, name) uuid_from_hash(OpenSSL::Digest::MD5, uuid_namespace, name)
end end
# Convenience method for uuid_from_hash using Digest::SHA1. # Convenience method for uuid_from_hash using OpenSSL::Digest::SHA1.
def self.uuid_v5(uuid_namespace, name) def self.uuid_v5(uuid_namespace, name)
uuid_from_hash(Digest::SHA1, uuid_namespace, name) uuid_from_hash(OpenSSL::Digest::SHA1, uuid_namespace, name)
end end
# Convenience method for SecureRandom.uuid. # Convenience method for SecureRandom.uuid.

View File

@ -20,7 +20,7 @@ class DigestUUIDExt < ActiveSupport::TestCase
def test_invalid_hash_class def test_invalid_hash_class
assert_raise ArgumentError do assert_raise ArgumentError do
Digest::UUID.uuid_from_hash(Digest::SHA2, Digest::UUID::OID_NAMESPACE, "1.2.3") Digest::UUID.uuid_from_hash(OpenSSL::Digest::SHA256, Digest::UUID::OID_NAMESPACE, "1.2.3")
end end
end end
end end