Commit 13c86cf1 by John Mileham

Adds secure :hash interpolation and fixes time zone brittleness in :timestamp interpolation

parent fc792c89
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
source "http://rubygems.org" source "http://rubygems.org"
gem "ruby-debug" gem "ruby-debug"
gem "rails", ">=3.0.3" gem "rails", "~>3.0.0"
gem "rake" gem "rake"
gem "sqlite3-ruby", "~>1.3.0" gem "sqlite3-ruby", "~>1.3.0"
gem "shoulda" gem "shoulda"
......
...@@ -90,7 +90,7 @@ DEPENDENCIES ...@@ -90,7 +90,7 @@ DEPENDENCIES
appraisal appraisal
aws-s3 aws-s3
mocha mocha
rails (>= 3.0.3) rails (~> 3.0.0)
rake rake
ruby-debug ruby-debug
shoulda shoulda
......
...@@ -17,7 +17,10 @@ module Paperclip ...@@ -17,7 +17,10 @@ module Paperclip
:default_style => :original, :default_style => :original,
:storage => :filesystem, :storage => :filesystem,
:use_timestamp => true, :use_timestamp => true,
:whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails] :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails],
:use_default_time_zone => true,
:hash_digest => "SHA1",
:hash_data => ":class/:attachment/:id/:style/:updated_at"
} }
end end
...@@ -43,6 +46,10 @@ module Paperclip ...@@ -43,6 +46,10 @@ module Paperclip
@storage = options[:storage] @storage = options[:storage]
@use_timestamp = options[:use_timestamp] @use_timestamp = options[:use_timestamp]
@whiny = options[:whiny_thumbnails] || options[:whiny] @whiny = options[:whiny_thumbnails] || options[:whiny]
@use_default_time_zone = options[:use_default_time_zone]
@hash_digest = options[:hash_digest]
@hash_data = options[:hash_data]
@hash_secret = options[:hash_secret]
@convert_options = options[:convert_options] @convert_options = options[:convert_options]
@processors = options[:processors] @processors = options[:processors]
@options = options @options = options
...@@ -197,6 +204,21 @@ module Paperclip ...@@ -197,6 +204,21 @@ module Paperclip
time && time.to_f.to_i time && time.to_f.to_i
end end
# The time zone to use for timestamp interpolation. Using the default
# time zone ensures that results are consistent across all threads.
def time_zone
@use_default_time_zone ? Time.zone_default : Time.zone
end
# Returns a unique hash suitable for obfuscating the URL of an otherwise
# publicly viewable attachment.
def hash
raise ArgumentError, "Unable to generate hash without :hash_secret" unless @hash_secret
require 'openssl' unless defined?(OpenSSL)
data = interpolate(@hash_data)
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@hash_digest).new, @hash_secret, data)
end
def generate_fingerprint(source) def generate_fingerprint(source)
data = source.read data = source.read
source.rewind if source.respond_to?(:rewind) source.rewind if source.respond_to?(:rewind)
......
...@@ -48,8 +48,18 @@ module Paperclip ...@@ -48,8 +48,18 @@ module Paperclip
end end
# Returns the timestamp as defined by the <attachment>_updated_at field # Returns the timestamp as defined by the <attachment>_updated_at field
# in the server default time zone unless :use_global_time_zone is set
# to false. Note that a Rails.config.time_zone change will still
# invalidate any path or URL that uses :timestamp. For a
# time_zone-agnostic timestamp, use #updated_at.
def timestamp attachment, style_name def timestamp attachment, style_name
attachment.instance_read(:updated_at).to_s attachment.instance_read(:updated_at).in_time_zone(attachment.time_zone).to_s
end
# Returns an integer timestamp that is time zone-neutral, so that paths
# remain valid even if a server's time zone changes.
def updated_at attachment, style_name
attachment.updated_at
end end
# Returns the Rails.root constant. # Returns the Rails.root constant.
...@@ -94,6 +104,12 @@ module Paperclip ...@@ -94,6 +104,12 @@ module Paperclip
attachment.fingerprint attachment.fingerprint
end end
# Returns a the attachment hash. See Paperclip::Attachment#hash for
# more details.
def hash attachment, style_name
attachment.hash
end
# Returns the id of the instance in a split path form. e.g. returns # Returns the id of the instance in a split path form. e.g. returns
# 000/001/234 for an id of 1234. # 000/001/234 for an id of 1234.
def id_partition attachment, style_name def id_partition attachment, style_name
......
...@@ -97,6 +97,78 @@ class AttachmentTest < Test::Unit::TestCase ...@@ -97,6 +97,78 @@ class AttachmentTest < Test::Unit::TestCase
end end
end end
context "An attachment with :timestamp interpolations" do
setup do
@file = StringIO.new("...")
@zone = 'UTC'
Time.stubs(:zone).returns(@zone)
@zone_default = 'Eastern Time (US & Canada)'
Time.stubs(:zone_default).returns(@zone_default)
end
context "using default time zone" do
setup do
rebuild_model :path => ":timestamp", :use_default_time_zone => true
@dummy = Dummy.new
@dummy.avatar = @file
end
should "return a time in the default zone" do
assert_equal @dummy.avatar_updated_at.in_time_zone(@zone_default).to_s, @dummy.avatar.path
end
end
context "using per-thread time zone" do
setup do
rebuild_model :path => ":timestamp", :use_default_time_zone => false
@dummy = Dummy.new
@dummy.avatar = @file
end
should "return a time in the per-thread zone" do
assert_equal @dummy.avatar_updated_at.in_time_zone(@zone).to_s, @dummy.avatar.path
end
end
end
context "An attachment with :hash interpolations" do
setup do
@file = StringIO.new("...")
end
should "raise if no secret is provided" do
@attachment = attachment :path => ":hash"
@attachment.assign @file
assert_raise ArgumentError do
@attachment.path
end
end
context "when secret is set" do
setup do
@attachment = attachment :path => ":hash", :hash_secret => "w00t"
@attachment.stubs(:instance_read).with(:updated_at).returns(Time.at(1234567890))
@attachment.stubs(:instance_read).with(:file_name).returns("bla.txt")
@attachment.instance.id = 1234
@attachment.assign @file
end
should "interpolate the hash data" do
@attachment.expects(:interpolate).with(@attachment.options[:hash_data]).returns("interpolated_stuff")
@attachment.hash
end
should "result in the correct interpolation" do
assert_equal "fake_models/avatars/1234/original/1234567890", @attachment.send(:interpolate,@attachment.options[:hash_data])
end
should "result in a correct hash" do
assert_equal "d22b617d1bf10016aa7d046d16427ae203f39fce", @attachment.path
end
end
end
context "An attachment with a :rails_env interpolation" do context "An attachment with a :rails_env interpolation" do
setup do setup do
@rails_env = "blah" @rails_env = "blah"
......
...@@ -90,7 +90,7 @@ end ...@@ -90,7 +90,7 @@ end
class FakeModel class FakeModel
attr_accessor :avatar_file_name, attr_accessor :avatar_file_name,
:avatar_file_size, :avatar_file_size,
:avatar_last_updated, :avatar_updated_at,
:avatar_content_type, :avatar_content_type,
:avatar_fingerprint, :avatar_fingerprint,
:id :id
......
...@@ -112,9 +112,25 @@ class InterpolationsTest < Test::Unit::TestCase ...@@ -112,9 +112,25 @@ class InterpolationsTest < Test::Unit::TestCase
should "return the timestamp" do should "return the timestamp" do
now = Time.now now = Time.now
zone = 'UTC'
attachment = mock attachment = mock
attachment.expects(:instance_read).with(:updated_at).returns(now) attachment.expects(:instance_read).with(:updated_at).returns(now)
assert_equal now.to_s, Paperclip::Interpolations.timestamp(attachment, :style) attachment.expects(:time_zone).returns(zone)
assert_equal now.in_time_zone(zone).to_s, Paperclip::Interpolations.timestamp(attachment, :style)
end
should "return updated_at" do
attachment = mock
seconds_since_epoch = 1234567890
attachment.expects(:updated_at).returns(seconds_since_epoch)
assert_equal seconds_since_epoch, Paperclip::Interpolations.updated_at(attachment, :style)
end
should "return hash" do
attachment = mock
fake_hash = "a_wicked_secure_hash"
attachment.expects(:hash).returns(fake_hash)
assert_equal fake_hash, Paperclip::Interpolations.hash(attachment, :style)
end end
should "call all expected interpolations with the given arguments" do should "call all expected interpolations with the given arguments" do
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment