lib/condo/strata/google_cloud_storage.rb in condo-1.0.6 vs lib/condo/strata/google_cloud_storage.rb in condo-2.0.0

- old
+ new

@@ -1,47 +1,44 @@ module Condo; end module Condo::Strata; end class Fog::Storage::Google::Real - def condo_request(*args) - request(*args) - end + def condo_request(*args) + request(*args) + end end class Condo::Strata::GoogleCloudStorage - - def initialize(options) - @options = { - :name => :GoogleCloudStorage, - :location => :na, # US or Europe, set at bucket creation time - :fog => { - :provider => 'Google', - :google_storage_access_key_id => options[:fog_access_id] || options[:access_id], - :google_storage_secret_access_key => options[:fog_secret_key] || options[:secret_key] - }, - :api => 1 - }.merge!(options) - - - raise ArgumentError, 'Google Access ID missing' if @options[:access_id].nil? - raise ArgumentError, 'Google Secret Key missing' if @options[:secret_key].nil? - - if @options[:api] == 2 - @options[:secret_key] = OpenSSL::PKey::RSA.new(@options[:secret_key]) - end - - @options[:location] = @options[:location].to_sym - end - - - # - # Enable CORS on a bucket for a domain - # - def enable_cors(bucket, origin = '*') - data = + def initialize(options) + @options = { + :name => :GoogleCloudStorage, + :location => :na, # US or Europe, set at bucket creation time + :fog => { + :provider => 'Google', + :google_storage_access_key_id => options[:fog_access_id] || options[:access_id], + :google_storage_secret_access_key => options[:fog_secret_key] || options[:secret_key] + }, + :api => 1 + }.merge!(options) + + + raise ArgumentError, 'Google Access ID missing' if @options[:access_id].nil? + raise ArgumentError, 'Google Secret Key missing' if @options[:secret_key].nil? + + if @options[:api] == 2 + @options[:secret_key] = OpenSSL::PKey::RSA.new(@options[:secret_key]) + end + + @options[:location] = @options[:location].to_sym + end + + + # Enable CORS on a bucket for a domain + def enable_cors(bucket, origin = '*') + data = <<-DATA <?xml version="1.0" encoding="UTF-8"?> <CorsConfig> <Cors> <Origins> @@ -68,248 +65,217 @@ </ResponseHeaders> <MaxAgeSec>1800</MaxAgeSec> </Cors> </CorsConfig> DATA - - fog_connection.condo_request( - :expects => 200, - :body => data, - :method => 'PUT', - :headers => {}, - :host => "#{bucket}.storage.googleapis.com", - :idempotent => true, - :path => '?cors' # There is an issue with Fog where this isn't included as a canonical_resource - ) - end - - - def name - @options[:name] - end - - - def location - @options[:location] - end - - - # - # Create a signed URL for accessing a private file - # - def get_object(options) - options = {}.merge!(options) # Need to deep copy here - options[:object_options] = { - :expires => 5.minutes.from_now, - :verb => :get, - :headers => {}, - :parameters => {}, - :protocol => :https - }.merge!(options[:object_options] || {}) - options.merge!(@options) - - # - # provide the signed request - # - sign_request(options)[:url] - end - - - # - # Creates a new upload request (either single shot or multi-part) - # => Passed: bucket_name, object_key, object_options, file_size - # - def new_upload(options) - options = {}.merge!(options) # Need to deep copy here - options[:object_options] = { - :permissions => :private, - :expires => 5.minutes.from_now, - :verb => :put, # put for direct uploads - :headers => {}, - :parameters => {}, - :protocol => :https - }.merge!(options[:object_options] || {}) - options.merge!(@options) + + fog_connection.condo_request( + :expects => 200, + :body => data, + :method => 'PUT', + :headers => {}, + :host => "#{bucket}.storage.googleapis.com", + :idempotent => true, + :path => '?cors' # There is an issue with Fog where this isn't included as a canonical_resource + ) + end - options[:object_options][:headers]['x-goog-api-version'] = @options[:api] - - if options[:object_options][:headers]['x-goog-acl'].nil? - options[:object_options][:headers]['x-goog-acl'] = case options[:object_options][:permissions] - when :public - :'public-read' - else - :private - end - end - - options[:object_options][:headers]['Content-Type'] = 'binary/octet-stream' if options[:object_options][:headers]['Content-Type'].nil? + def name + @options[:name] + end - - # - # Decide what type of request is being sent - # - if options[:file_size] > 1.megabytes - # Resumables may not support the md5 header at this time - have to compare ETag and fail on the client side - options[:object_options][:verb] = :post - options[:object_options][:headers]['x-goog-resumable'] = 'start' - return { - :signature => sign_request(options), - :type => :chunked_upload # triggers resumable - } - else - options[:object_options][:headers]['Content-Md5'] = options[:file_id] if options[:file_id].present? && options[:object_options][:headers]['Content-Md5'].nil? - return { - :signature => sign_request(options), - :type => :direct_upload - } - end - end - - - # - # Creates a request for the byte we were up to - # - def get_parts(options, setting_parts = false) - options[:object_options] = { - :expires => 5.minutes.from_now, - :verb => :put, # put for direct uploads - :headers => {}, - :parameters => {}, - :protocol => :https - }.merge!(options[:object_options] || {}) - options.merge!(@options) - - # - # Set the upload and request the range of bytes we are after - # - if setting_parts - options[:object_options][:headers]['Content-Md5'] = options[:file_id] if options[:file_id].present? && options[:object_options][:headers]['Content-Md5'].nil? - options[:object_options][:headers]['Content-Range'] = "bytes #{options[:part]}-#{options[:file_size] - 1}/#{options[:file_size]}" - else - options[:object_options][:headers]['Content-Range'] = "bytes */#{options[:file_size]}" - end - options[:object_options][:headers]['x-goog-api-version'] = @options[:api] - options[:object_options][:parameters]['upload_id'] = options[:resumable_id] - - # - # provide the signed request - # - { - :expected => 308, - :type => :status, - :signature => sign_request(options) - } - end - - - # - # Returns the requests for uploading parts and completing a resumable upload - # - def set_part(options) - resp = get_parts(options, true) - resp[:type] = :resume_upload - resp[:type] = :resume_upload - return resp - end - - - def fog_connection - @fog = @fog || Fog::Storage.new(@options[:fog]) - return @fog - end - - - def destroy(upload) - connection = fog_connection - directory = connection.directories.get(upload.bucket_name) # it is assumed this exists - if not then the upload wouldn't have taken place - file = directory.files.get(upload.object_key) # NOTE:: I only assume this works with resumables... should look into it - - return true if file.nil? - return file.destroy - end - - - - protected - - - - def sign_request(options) - - # - # Build base URL - # - verb = options[:object_options][:verb].to_s.upcase.to_sym - options[:object_options][:expires] = options[:object_options][:expires].utc.to_i - - url = "/#{options[:object_key]}" - - - # - # Add signed request params - # - other_params = '' - signed_params = '?' - (options[:object_options][:parameters] || {}).each do |key, value| + + def location + @options[:location] + end + + + # Create a signed URL for accessing a private file + def get_object(options) + options = {}.merge!(options) # Need to deep copy here + options[:object_options] = { + :expires => 5.minutes.from_now, + :verb => :get, + :headers => {}, + :parameters => {}, + :protocol => :https + }.merge!(options[:object_options] || {}) + options.merge!(@options) + + # + # provide the signed request + # + sign_request(options)[:url] + end + + + # Creates a new upload request (either single shot or multi-part) + # => Passed: bucket_name, object_key, object_options, file_size + def new_upload(options) + options = {}.merge!(options) # Need to deep copy here + options[:object_options] = { + :permissions => :private, + :expires => 5.minutes.from_now, + :verb => :put, # put for direct uploads + :headers => {}, + :parameters => {}, + :protocol => :https + }.merge!(options[:object_options] || {}) + options.merge!(@options) + + + options[:object_options][:headers]['x-goog-api-version'] = @options[:api] + + if options[:object_options][:headers]['x-goog-acl'].nil? + options[:object_options][:headers]['x-goog-acl'] = case options[:object_options][:permissions] + when :public + :'public-read' + else + :private + end + end + + options[:object_options][:headers]['Content-Type'] = 'binary/octet-stream' if options[:object_options][:headers]['Content-Type'].nil? + + + # Decide what type of request is being sent + if options[:file_size] > 1.megabytes + # Resumables may not support the md5 header at this time - have to compare ETag and fail on the client side + options[:object_options][:verb] = :post + options[:object_options][:headers]['x-goog-resumable'] = 'start' + return { + :signature => sign_request(options), + :type => :chunked_upload # triggers resumable + } + else + options[:object_options][:headers]['Content-Md5'] = options[:file_id] if options[:file_id].present? && options[:object_options][:headers]['Content-Md5'].nil? + return { + :signature => sign_request(options), + :type => :direct_upload + } + end + end + + + # Creates a request for the byte we were up to + def get_parts(options, setting_parts = false) + options[:object_options] = { + :expires => 5.minutes.from_now, + :verb => :put, # put for direct uploads + :headers => {}, + :parameters => {}, + :protocol => :https + }.merge!(options[:object_options] || {}) + options.merge!(@options) + + # Set the upload and request the range of bytes we are after + if setting_parts + options[:object_options][:headers]['Content-Md5'] = options[:file_id] if options[:file_id].present? && options[:object_options][:headers]['Content-Md5'].nil? + options[:object_options][:headers]['Content-Range'] = "bytes #{options[:part]}-#{options[:file_size] - 1}/#{options[:file_size]}" + else + options[:object_options][:headers]['Content-Range'] = "bytes */#{options[:file_size]}" + end + options[:object_options][:headers]['x-goog-api-version'] = @options[:api] + options[:object_options][:parameters]['upload_id'] = options[:resumable_id] + + # provide the signed request + { + :expected => 308, + :type => :status, + :signature => sign_request(options) + } + end + + + # Returns the requests for uploading parts and completing a resumable upload + def set_part(options) + resp = get_parts(options, true) + resp[:type] = :resume_upload + resp[:type] = :resume_upload + return resp + end + + + def fog_connection + @fog = @fog || Fog::Storage.new(@options[:fog]) + return @fog + end + + + def destroy(upload) + connection = fog_connection + directory = connection.directories.get(upload.bucket_name) # it is assumed this exists - if not then the upload wouldn't have taken place + file = directory.files.get(upload.object_key) # NOTE:: I only assume this works with resumables... should look into it + + return true if file.nil? + return file.destroy + end + + + + protected + + + + def sign_request(options) + + # Build base URL + verb = options[:object_options][:verb].to_s.upcase.to_sym + options[:object_options][:expires] = options[:object_options][:expires].utc.to_i + + url = "/#{options[:object_key]}" + + # Add signed request params + other_params = '' + signed_params = '?' + (options[:object_options][:parameters] || {}).each do |key, value| if ['acl', 'cors', 'location', 'logging', 'requestPayment', 'torrent', 'versions', 'versioning'].include?(key) - signed_params << "#{key}&" - else - other_params << (value.empty? ? "#{key}&" : "#{key}=#{value}&") + signed_params << "#{key}&" + else + other_params << (value.blank? ? "#{key}&" : "#{key}=#{value}&") end - end - signed_params.chop! - - url << signed_params - - - # - # Build a request signature - # - signature = "#{verb}\n#{options[:object_options][:headers]['Content-Md5']}\n#{options[:object_options][:headers]['Content-Type']}\n#{options[:object_options][:expires]}\n" - if verb != :GET - options[:object_options][:headers]['x-goog-date'] ||= Time.now.utc.httpdate - - google_headers, canonical_google_headers = {}, '' # Copied from https://github.com/fog/fog/blob/master/lib/fog/google/storage.rb - for key, value in options[:object_options][:headers] - if key[0..6] == 'x-goog-' - google_headers[key] = value - end - end - - google_headers = google_headers.sort {|x, y| x[0] <=> y[0]} - for key, value in google_headers - signature << "#{key}:#{value}\n" - end - end - - signature << "/#{options[:bucket_name]}#{url}" - - - # - # Encode the request signature - # - if @options[:api] == 1 - signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), @options[:secret_key], signature)).gsub("\n","") - options[:object_options][:headers]['Authorization'] = "GOOG1 #{@options[:access_id]}:#{signature}" - else - signature = Base64.encode64(@options[:secret_key].sign(OpenSSL::Digest::SHA256.new, signature)).gsub("\n","") - end - - - url += signed_params.present? ? '&' : '?' - url = "#{options[:object_options][:protocol]}://#{options[:bucket_name]}.storage.googleapis.com#{url}#{other_params}GoogleAccessId=#{@options[:access_id]}&Expires=#{options[:object_options][:expires]}&Signature=#{CGI::escape(signature)}" - - - # - # Finish building the request - # - return { - :verb => options[:object_options][:verb].to_s.upcase, - :url => url, - :headers => options[:object_options][:headers] - } - end - - + end + signed_params.chop! + + url << signed_params + + # Build a request signature + signature = "#{verb}\n#{options[:object_options][:headers]['Content-Md5']}\n#{options[:object_options][:headers]['Content-Type']}\n#{options[:object_options][:expires]}\n" + if verb != :GET + options[:object_options][:headers]['x-goog-date'] ||= Time.now.utc.httpdate + + google_headers, canonical_google_headers = {}, '' # Copied from https://github.com/fog/fog/blob/master/lib/fog/google/storage.rb + for key, value in options[:object_options][:headers] + if key[0..6] == 'x-goog-' + google_headers[key] = value + end + end + + google_headers = google_headers.sort {|x, y| x[0] <=> y[0]} + for key, value in google_headers + signature << "#{key}:#{value}\n" + end + end + + signature << "/#{options[:bucket_name]}#{url}" + + # Encode the request signature + if @options[:api] == 1 + signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), @options[:secret_key], signature)).gsub("\n","") + options[:object_options][:headers]['Authorization'] = "GOOG1 #{@options[:access_id]}:#{signature}" + else + signature = Base64.encode64(@options[:secret_key].sign(OpenSSL::Digest::SHA256.new, signature)).gsub("\n","") + end + + url += signed_params.present? ? '&' : '?' + url = "#{options[:object_options][:protocol]}://#{options[:bucket_name]}.storage.googleapis.com#{url}#{other_params}GoogleAccessId=#{@options[:access_id]}&Expires=#{options[:object_options][:expires]}&Signature=#{CGI::escape(signature)}" + + # Finish building the request + return { + :verb => options[:object_options][:verb].to_s.upcase, + :url => url, + :headers => options[:object_options][:headers] + } + end end