Skip to content

Commit

Permalink
Support URL presign and COPY operation
Browse files Browse the repository at this point in the history
  • Loading branch information
renchap committed Feb 20, 2017
1 parent db1ec13 commit 404430c
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 7 deletions.
58 changes: 51 additions & 7 deletions lib/shrine/storage/google_cloud_storage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,25 @@ def initialize(bucket:, prefix: nil, host: nil)
@host = host
end

def upload(io, id, shrine_metadata: {}, **options)
def upload(io, id, shrine_metadata: {}, **_options)
# uploads `io` to the location `id`
storage_api.insert_object(@bucket, { name: object_name(id) }, content_type: shrine_metadata["mime_type"],
upload_source: io,
options: { uploadType: 'multipart' })

if copyable?(io)
storage_api.copy_object(io.storage.bucket, io.storage.object_name(io.id), @bucket, object_name(id))
else
storage_api.insert_object(
@bucket,
{ name: object_name(id) },
content_type: shrine_metadata["mime_type"],
upload_source: io.to_io,
options: { uploadType: 'multipart' },
)
end
end

def url(id, **options)
def url(id, **_options)
# URL to the remote file, accepts options for customizing the URL
host = @host || "#{@bucket}.storage.googleapis.com"
host = @host || "storage.googleapis.com/#{@bucket}"

"https://#{host}/#{object_name(id)}"
end
Expand Down Expand Up @@ -58,6 +67,12 @@ def exists?(id)
def delete(id)
# deletes the file from the storage
storage_api.delete_object(@bucket, object_name(id))

rescue Google::Apis::ClientError => e
# The object does not exist, Shrine expects us to be ok
return true if e.status_code == 404

raise e
end

def multi_delete(ids)
Expand Down Expand Up @@ -89,12 +104,41 @@ def clear!
batch_delete(ids) unless ids.empty?
end

private
def presign(id, **options)
method = options[:method] || "GET"
content_md5 = options[:content_md5] || ""
content_type = options[:content_type] || ""
expires = (Time.now.utc + (options[:expires] || 300)).to_i
headers = nil
path = "/#{@bucket}/" + object_name(id)

to_sign = [method, content_md5, content_type, expires, headers, path].compact.join("\n")

signing_key = options[:signing_key]
signing_key = OpenSSL::PKey::RSA.new(signing_key) unless signing_key.respond_to?(:sign)
signature = Base64.strict_encode64(signing_key.sign(OpenSSL::Digest::SHA256.new, to_sign)).delete("\n")

signed_url = "https://storage.googleapis.com#{path}?GoogleAccessId=#{options[:issuer]}" \
"&Expires=#{expires}&Signature=#{CGI.escape(signature)}"

OpenStruct.new(
url: signed_url,
fields: {},
)
end

def object_name(id)
@prefix ? "#{@prefix}/#{id}" : id
end

private

def copyable?(io)
io.is_a?(UploadedFile) &&
io.storage.is_a?(Storage::GoogleCloudStorage)
# TODO: add a check for the credentials
end

def batch_delete(object_names)
storage_api.batch do |storage|
object_names.each do |name|
Expand Down
44 changes: 44 additions & 0 deletions test/gcs_test.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require_relative "test_helper"
require "shrine/storage/linter"
require "date"

describe Shrine::Storage::GoogleCloudStorage do
def gcs(options = {})
Expand All @@ -8,6 +9,28 @@ def gcs(options = {})
Shrine::Storage::GoogleCloudStorage.new(options)
end

def service_account
{
private_key: "-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANW9uQf69ivd+txc
v5iMYkTVkGcQIereNz/lYeuZv2s7OZ4I9pdebleUmpxPn/CopRxS3O7JrXBPzAMK
i0tFs/dnn5Ny6AIzZvo1eMSptoKmcHswoYTP9ftTG7cDa8/12woEFu+fX9ob4isF
IaKvbD8kEhcyynWPUH3pP1g0ssUPAgMBAAECgYAtQhgM5Yn8resxf/4d2hPwyVvj
Rto3tkfyoqqCTbLnjMndeb5lPNyWdOPsFzwhpEQZ5D3d3hx4fJ0RQ8lM7fx2EiwD
+gjiOzLu9Fy+9XiVPbqIR20R63sHlA2jmzTuno9TLRdi+YyBS3XUVjckSE9mqTNO
RDmDgRbwQURjsgFH2QJBAP9oNQrNQI2Q+b7ufA8pnf2ZWBX0ASFz99Y1WVR+ip7o
3pByI7EMK3g9h3Ua3yEU+g5WVb/1Zaj6Bx7p4lRL540CQQDWPMC2yXO9wnsw1Nyy
1gtD18hcPBwDbIoQIK+J/AOLtSryKrCAEvOnLsxU1krYAj6ZP5MwrruLmnTLUXCE
+ZoLAkEA2kJuGZX/VTsQAbcBc1+oMOCLIu+Ky9CzeW3LseYVhekQ0TWJBLKWr0E9
cbiN91JawkfLLaiCwJ0x2pwaGtlmvQJAK8PFapHEvyMXn2Ycn7vyGS3flFgDMP/f
RGQo9/svjj64QzhNThyRAbohq8MLDw2GVDAUlYFcdqxa553/amrC+QJBAOYsfZTF
0ICBD2rt+ukhgSmZq/4oWxlM505kDh4z+x2oT3nFSi+fce5IWuNswR2qTRhjIAj8
wXh0ExlzwgD2xJ0=
-----END PRIVATE KEY-----",
client_email: 'test-shrine@test.google',
}
end

before do
@gcs = gcs
end
Expand All @@ -34,4 +57,25 @@ def gcs(options = {})
assert @gcs.exists?('pre')
end
end

describe "presign" do
it "works on a GET url" do
sa = service_account

storage = Shrine::Storage::GoogleCloudStorage.new(bucket: "shrine-test")

Time.stub :now, Time.at(1486649900) do
presign = storage.presign(
'test_presign.txt',
method: "GET",
expires: 60 * 5,
signing_key: sa[:private_key],
issuer: sa[:client_email],
)

assert_equal "https://storage.googleapis.com/shrine-test/test_presign.txt?GoogleAccessId=test-shrine@test.google&Expires=1486650200&Signature=O8tYOAJCtk0fXpO9MDhRO7ikVsIRqjALK01naly2rEJBCM2uRHl7meqEaXzu%2Bl9V17zw9P2%2FGb0K2miF%2FyEuZ6mnRIiuH6aI2tg24CV%2FKtuNU5jSqwqMU83iPzuptwakgFNxMGJj73i0SGbB%2F4wodNR7DNQ6KFhSlpJJ1mgD4hI%3D", presign.url
assert_equal({}, presign.fields)
end
end
end
end

0 comments on commit 404430c

Please sign in to comment.