Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Support URL signing for multiple methods #7

Merged
merged 1 commit into from

2 participants

@chmhofer

Motivation: CORS file upload (http://www.w3.org/TR/cors/)

Joyent Manta supports CORS file uploads via the PUT request when the directory has the required CORS headers. The browser will send it as a preflighted request, i.e., it will first send an OPTIONS, then a PUT request. In order for this to work, we have to generate a signed URL that is valid for both requests.

node-manta has support for generating such a signature, but ruby-manta so far doesn't. This pull-request adds this functionality to ruby-manta. Now, a list of methods can be passed to the "method" parameter of gen_signed_url, and :options is regarded an allowed method.

I have adapted the test, too. It demonstrates how it is supposed to be used.

@Marsell Marsell merged commit d63d5b4 into from
@Marsell
Owner

Looks good. Thanks for the fix. :)

@Marsell Marsell referenced this pull request from a commit
Marsell Kukuljevic Update README.md for #7. 41f9865
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 26 additions and 10 deletions.
  1. +11 −8 lib/ruby-manta.rb
  2. +15 −2 tests/test_ruby-manta.rb
View
19 lib/ruby-manta.rb
@@ -650,11 +650,11 @@ def list_jobs(state, opts = {})
# Generates a signed URL which can be used by unauthenticated users to
# make a request to Manta at the given path. This is typically used to GET
- # an object.
+ # an object, or to make a CORS preflighted PUT request.
#
# expires is a Time object or integer representing time after epoch; this
- # determines how long the signed URL will be valid for. The method is the HTTP
- # method (:get, :put, :post, :delete) the signed URL is allowed to be used
+ # determines how long the signed URL will be valid for. The method is either a single HTTP
+ # method (:get, :put, :post, :delete, :options) or a list of such methods that the signed URL is allowed to be used
# for. The path must start with /<user>/stor. Lastly, the optional args is an
# array containing pairs of query args that will be appended at the end of
# the URL.
@@ -662,7 +662,8 @@ def list_jobs(state, opts = {})
# The returned URL is signed, and can be used either over HTTP or HTTPS until
# it reaches the expiry date.
def gen_signed_url(expires, method, path, args=[])
- raise ArgumentError unless [:get, :put, :post, :delete].include? method
+ methods = method.is_a?(Array) ? method : [method]
+ raise ArgumentError unless (methods - [:get, :put, :post, :delete, :options]).empty?
raise ArgumentError unless path =~ OBJ_PATH_REGEX
key_id = '/%s/keys/%s' % [@user, @fingerprint]
@@ -671,15 +672,17 @@ def gen_signed_url(expires, method, path, args=[])
args.push([ 'algorithm', @digest_name ])
args.push([ 'keyId', key_id ])
+ method = methods.map {|m| m.to_s.upcase }.sort.join(",")
+ host = URI.encode(@host.split('/').last)
+ path = URI.encode(path)
+
+ args.push(['method', method]) if methods.count > 1
+
encoded_args = args.sort.map do |key, val|
# to comply with RFC 3986
CGI.escape(key.to_s) + '=' + CGI.escape(val.to_s)
end.join('&')
- method = method.to_s.upcase
- host = URI.encode(@host.split('/').last)
- path = URI.encode(path)
-
plaintext = "#{method}\n#{host}\n#{path}\n#{encoded_args}"
signature = @priv_key.sign(@digest, plaintext)
encoded_signature = CGI.escape(Base64.strict_encode64(signature))
View
17 tests/test_ruby-manta.rb
@@ -292,12 +292,25 @@ def test_cors
def test_signed_urls
- @@client.put_object(@@test_dir_path + '/obj1', 'foo-data')
+
+ client = HTTPClient.new
+
+ put_url = @@client.gen_signed_url(Time.now + 500000, [:put, :options],
+ @@test_dir_path + '/obj1')
+
+ result = client.options("https://" + put_url, {
+ 'Access-Control-Request-Headers' => 'access-control-allow-origin, accept, content-type',
+ 'Access-Control-Request-Method' => 'PUT'
+ })
+
+ assert_equal result.status, 200
+
+ result = client.put("https://" + put_url, 'foo-data', { 'Content-Type' => 'text/plain' })
+ assert_equal result.status, 204
url = @@client.gen_signed_url(Time.now + 500000, :get,
@@test_dir_path + '/obj1')
- client = HTTPClient.new
result = client.get('http://' + url)
assert_equal result.body, 'foo-data'
end
Something went wrong with that request. Please try again.