Commit 5e86a99b by Larry Sprock Committed by Prem Sichanugrist

Ability to set permissions on a per style bases.

Example:
  has_attached_file :image,
    :bucket => "testing",
    :path => ":attachment/:style/:basename.:extension"
    :s3_permissions => {
      :original => :private
    },
    :styles => {
      :thumb => '80x80>'
    }

The above will allow public read access on :thumb but require credentials for :original.

Example of setting default to private:
  has_attached_file :image,
    :bucket => "testing",
    :path => ":attachment/:style/:basename.:extension"
    :s3_permissions => :private
    :styles => {
      :thumb => '80x80>'
    }

The above will set all to private

Closes #398
parent 036265ca
...@@ -27,6 +27,14 @@ module Paperclip ...@@ -27,6 +27,14 @@ module Paperclip
# policies that S3 provides (more information can be found here: # policies that S3 provides (more information can be found here:
# http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAccessPolicy.html) # http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAccessPolicy.html)
# The default for Paperclip is :public_read. # The default for Paperclip is :public_read.
#
# You can set permission on a per style bases by doing the following:
# :s3_permissions => {
# :original => :private
# }
# Or globaly:
# :s3_permissions => :private
#
# * +s3_protocol+: The protocol for the URLs generated to your S3 assets. Can be either # * +s3_protocol+: The protocol for the URLs generated to your S3 assets. Can be either
# 'http' or 'https'. Defaults to 'http' when your :s3_permissions are :public_read (the # 'http' or 'https'. Defaults to 'http' when your :s3_permissions are :public_read (the
# default), and 'https' when your :s3_permissions are anything else. # default), and 'https' when your :s3_permissions are anything else.
...@@ -72,14 +80,17 @@ module Paperclip ...@@ -72,14 +80,17 @@ module Paperclip
@bucket = @options[:bucket] || @s3_credentials[:bucket] @bucket = @options[:bucket] || @s3_credentials[:bucket]
@bucket = @bucket.call(self) if @bucket.is_a?(Proc) @bucket = @bucket.call(self) if @bucket.is_a?(Proc)
@s3_options = @options[:s3_options] || {} @s3_options = @options[:s3_options] || {}
@s3_permissions = @options[:s3_permissions] || :public_read @s3_permissions = set_permissions(@options[:s3_permissions])
@s3_protocol = @options[:s3_protocol] || (@s3_permissions == :public_read ? 'http' : 'https') @s3_protocol = @options[:s3_protocol] ||
Proc.new do |style|
(@s3_permissions[style.to_sym] || @s3_permissions[:default]) == :public_read ? 'http' : 'https'
end
@s3_headers = @options[:s3_headers] || {} @s3_headers = @options[:s3_headers] || {}
@s3_host_alias = @options[:s3_host_alias] @s3_host_alias = @options[:s3_host_alias]
@s3_host_alias = @s3_host_alias.call(self) if @s3_host_alias.is_a?(Proc) @s3_host_alias = @s3_host_alias.call(self) if @s3_host_alias.is_a?(Proc)
unless @url.to_s.match(/^:s3.*url$/) unless @url.to_s.match(/^:s3.*url$/)
@path = @path.gsub(/:url/, @url) @path = @path.gsub(/:url/, @url)
@url = ":s3_path_url" @url = ":s3_path_url"
end end
@url = ":asset_host" if @options[:url].to_s == ":asset_host" @url = ":asset_host" if @options[:url].to_s == ":asset_host"
AWS::S3::Base.establish_connection!( @s3_options.merge( AWS::S3::Base.establish_connection!( @s3_options.merge(
...@@ -88,13 +99,13 @@ module Paperclip ...@@ -88,13 +99,13 @@ module Paperclip
)) ))
end end
Paperclip.interpolates(:s3_alias_url) do |attachment, style| Paperclip.interpolates(:s3_alias_url) do |attachment, style|
"#{attachment.s3_protocol}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}" "#{attachment.s3_protocol(style)}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
end unless Paperclip::Interpolations.respond_to? :s3_alias_url end unless Paperclip::Interpolations.respond_to? :s3_alias_url
Paperclip.interpolates(:s3_path_url) do |attachment, style| Paperclip.interpolates(:s3_path_url) do |attachment, style|
"#{attachment.s3_protocol}://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}" "#{attachment.s3_protocol(style)}://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
end unless Paperclip::Interpolations.respond_to? :s3_path_url end unless Paperclip::Interpolations.respond_to? :s3_path_url
Paperclip.interpolates(:s3_domain_url) do |attachment, style| Paperclip.interpolates(:s3_domain_url) do |attachment, style|
"#{attachment.s3_protocol}://#{attachment.bucket_name}.s3.amazonaws.com/#{attachment.path(style).gsub(%r{^/}, "")}" "#{attachment.s3_protocol(style)}://#{attachment.bucket_name}.s3.amazonaws.com/#{attachment.path(style).gsub(%r{^/}, "")}"
end unless Paperclip::Interpolations.respond_to? :s3_domain_url end unless Paperclip::Interpolations.respond_to? :s3_domain_url
Paperclip.interpolates(:asset_host) do |attachment, style| Paperclip.interpolates(:asset_host) do |attachment, style|
"#{attachment.path(style).gsub(%r{^/}, "")}" "#{attachment.path(style).gsub(%r{^/}, "")}"
...@@ -102,13 +113,22 @@ module Paperclip ...@@ -102,13 +113,22 @@ module Paperclip
end end
def expiring_url(time = 3600, style_name = default_style) def expiring_url(time = 3600, style_name = default_style)
AWS::S3::S3Object.url_for(path(style_name), bucket_name, :expires_in => time, :use_ssl => (s3_protocol == 'https')) AWS::S3::S3Object.url_for(path(style_name), bucket_name, :expires_in => time, :use_ssl => (s3_protocol(style_name) == 'https'))
end end
def bucket_name def bucket_name
@bucket @bucket
end end
def set_permissions permissions
if permissions.is_a?(Hash)
permissions[:default] = permissions[:default] || :public_read
else
permissions = { :default => permissions || :public_read }
end
permissions
end
def s3_host_alias def s3_host_alias
@s3_host_alias @s3_host_alias
end end
...@@ -126,8 +146,12 @@ module Paperclip ...@@ -126,8 +146,12 @@ module Paperclip
end end
end end
def s3_protocol def s3_protocol(style)
@s3_protocol if @s3_protocol.is_a?(Proc)
@s3_protocol.call(style)
else
@s3_protocol
end
end end
# Returns representation of the data of the file assigned to the given # Returns representation of the data of the file assigned to the given
...@@ -156,7 +180,7 @@ module Paperclip ...@@ -156,7 +180,7 @@ module Paperclip
file, file,
bucket_name, bucket_name,
{:content_type => file.content_type.to_s.strip, {:content_type => file.content_type.to_s.strip,
:access => @s3_permissions, :access => (@s3_permissions[style] || @s3_permissions[:default]),
}.merge(@s3_headers)) }.merge(@s3_headers))
rescue AWS::S3::NoSuchBucket => e rescue AWS::S3::NoSuchBucket => e
create_bucket create_bucket
......
...@@ -216,6 +216,7 @@ class StorageTest < Test::Unit::TestCase ...@@ -216,6 +216,7 @@ class StorageTest < Test::Unit::TestCase
:production => { :bucket => "prod_bucket" }, :production => { :bucket => "prod_bucket" },
:development => { :bucket => "dev_bucket" } :development => { :bucket => "dev_bucket" }
}, },
:s3_permissions => :private,
:s3_host_alias => "something.something.com", :s3_host_alias => "something.something.com",
:path => ":attachment/:style/:basename.:extension", :path => ":attachment/:style/:basename.:extension",
:url => ":s3_alias_url" :url => ":s3_alias_url"
...@@ -225,10 +226,10 @@ class StorageTest < Test::Unit::TestCase ...@@ -225,10 +226,10 @@ class StorageTest < Test::Unit::TestCase
@dummy = Dummy.new @dummy = Dummy.new
@dummy.avatar = StringIO.new(".") @dummy.avatar = StringIO.new(".")
AWS::S3::S3Object.expects(:url_for).with("avatars/original/stringio.txt", "prod_bucket", { :expires_in => 3600, :use_ssl => false }) AWS::S3::S3Object.expects(:url_for).with("avatars/original/stringio.txt", "prod_bucket", { :expires_in => 3600, :use_ssl => true })
@dummy.avatar.expiring_url @dummy.avatar.expiring_url
AWS::S3::S3Object.expects(:url_for).with("avatars/thumb/stringio.txt", "prod_bucket", { :expires_in => 1800, :use_ssl => false }) AWS::S3::S3Object.expects(:url_for).with("avatars/thumb/stringio.txt", "prod_bucket", { :expires_in => 1800, :use_ssl => true })
@dummy.avatar.expiring_url(1800, :thumb) @dummy.avatar.expiring_url(1800, :thumb)
end end
...@@ -432,6 +433,132 @@ class StorageTest < Test::Unit::TestCase ...@@ -432,6 +433,132 @@ class StorageTest < Test::Unit::TestCase
assert_equal 'env_secret', AWS::S3::Base.connection.options[:secret_access_key] assert_equal 'env_secret', AWS::S3::Base.connection.options[:secret_access_key]
end end
end end
context "S3 Permissions" do
context "defaults to public-read" do
setup do
rebuild_model :storage => :s3,
:bucket => "testing",
:path => ":attachment/:style/:basename.:extension",
:s3_credentials => {
'access_key_id' => "12345",
'secret_access_key' => "54321"
}
end
context "when assigned" do
setup do
@file = File.new(File.join(File.dirname(__FILE__), 'fixtures', '5k.png'), 'rb')
@dummy = Dummy.new
@dummy.avatar = @file
end
teardown { @file.close }
context "and saved" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
AWS::S3::S3Object.expects(:store).with(@dummy.avatar.path,
anything,
'testing',
:content_type => 'image/png',
:access => :public_read)
@dummy.save
end
should "succeed" do
assert true
end
end
end
end
context "string permissions set" do
setup do
rebuild_model :storage => :s3,
:bucket => "testing",
:path => ":attachment/:style/:basename.:extension",
:s3_credentials => {
'access_key_id' => "12345",
'secret_access_key' => "54321"
},
:s3_permissions => 'private'
end
context "when assigned" do
setup do
@file = File.new(File.join(File.dirname(__FILE__), 'fixtures', '5k.png'), 'rb')
@dummy = Dummy.new
@dummy.avatar = @file
end
teardown { @file.close }
context "and saved" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
AWS::S3::S3Object.expects(:store).with(@dummy.avatar.path,
anything,
'testing',
:content_type => 'image/png',
:access => 'private')
@dummy.save
end
should "succeed" do
assert true
end
end
end
end
context "hash permissions set" do
setup do
rebuild_model :storage => :s3,
:bucket => "testing",
:path => ":attachment/:style/:basename.:extension",
:styles => {
:thumb => "80x80>"
},
:s3_credentials => {
'access_key_id' => "12345",
'secret_access_key' => "54321"
},
:s3_permissions => {
:original => 'private',
:thumb => 'public-read'
}
end
context "when assigned" do
setup do
@file = File.new(File.join(File.dirname(__FILE__), 'fixtures', '5k.png'), 'rb')
@dummy = Dummy.new
@dummy.avatar = @file
end
teardown { @file.close }
context "and saved" do
setup do
AWS::S3::Base.stubs(:establish_connection!)
[:thumb, :original].each do |style|
AWS::S3::S3Object.expects(:store).with("avatars/#{style}/5k.png",
anything,
'testing',
:content_type => 'image/png',
:access => style == :thumb ? 'public-read' : 'private')
end
@dummy.save
end
should "succeed" do
assert true
end
end
end
end
end
unless ENV["S3_TEST_BUCKET"].blank? unless ENV["S3_TEST_BUCKET"].blank?
context "Using S3 for real, an attachment with S3 storage" do context "Using S3 for real, an attachment with S3 storage" 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