gems/aws-sdk-s3/spec/presigned_post_spec.rb (273 lines of code) (raw):
# frozen_string_literal: true
require_relative 'spec_helper'
require 'base64'
module Aws
module S3
describe PresignedPost do
let(:creds) { Credentials.new('akid', 'secret') }
let(:region) { 'us-east-1' }
let(:bucket) { 'bucket-name' }
let(:options) { {} }
let(:post) { PresignedPost.new(creds, region, bucket, options) }
def decode(policy)
Json.load(Base64.decode64(policy))
end
def policy(post)
decode(post.fields['policy'])['conditions']
end
describe '#initialize' do
it 'rejects unknown options' do
expect do
PresignedPost.new(creds, region, bucket, foo: 'bar')
end.to raise_error(/Unsupported option: foo/)
end
it 'provides options for setting fields or fields starts with' do
post = PresignedPost.new(creds, region, bucket, key: 'key',
acl: 'public-read',
content_type_starts_with: 'image/')
expect(post.fields['acl']).to eq('public-read')
expect(policy(post)).to include(['starts-with', '$Content-Type', 'image/'])
end
end
describe '#url' do
it 'puts dns compatible bucket names in the host' do
post = PresignedPost.new(creds, 'us-east-1', 'bucket-name')
expect(post.url).to eq('https://bucket-name.s3.amazonaws.com')
end
it 'puts dns compatible bucket name that contain dots in the path' do
post = PresignedPost.new(creds, 'us-east-1', 'bucket.name')
expect(post.url).to eq('https://s3.amazonaws.com/bucket.name')
end
it 'puts non-dns compatible bucket name in the path' do
post = PresignedPost.new(creds, 'us-east-1', 'BucketName')
expect(post.url).to eq('https://s3.amazonaws.com/BucketName')
end
it 'creates regionalized urls for other regions' do
post = PresignedPost.new(creds, 'eu-central-1', 'bucket-name')
expect(post.url).to eq(
'https://bucket-name.s3.eu-central-1.amazonaws.com'
)
end
it 'can use an accelerated endpoint' do
post = PresignedPost.new(
creds, 'us-east-1', 'bucket-name', use_accelerate_endpoint: true
)
expect(post.url).to eq(
'https://bucket-name.s3-accelerate.amazonaws.com'
)
end
end
describe 'key' do
it 'is required' do
expect { post.fields }.to raise_error(/you must provide a key/)
end
it 'can be set via :key' do
options[:key] = 'obj-key'
expect(post.fields['key']).to eq('obj-key')
expect(policy(post)).to include('key' => 'obj-key')
end
it 'can be set via :key_starts_with' do
options[:key_starts_with] = 'prefix/'
expect(post.fields['key']).to be(nil)
expect(policy(post)).to include(['starts-with', '$key', 'prefix/'])
end
it 'can be set via #key' do
post.key('obj-key')
expect(post.fields['key']).to eq('obj-key')
expect(policy(post)).to include('key' => 'obj-key')
end
it 'can be set via #key_starts_with' do
post.key_starts_with('prefix/')
expect(post.fields['key']).to be(nil)
expect(policy(post)).to include(['starts-with', '$key', 'prefix/'])
end
it 'can be set via :allow_any' do
options[:allow_any] = 'key'
expect(post.fields['key']).to be(nil)
expect(policy(post)).to include(['starts-with', '$key', ''])
end
it 'can be set via #allow_any' do
post.allow_any('key')
expect(post.fields['key']).to be(nil)
expect(policy(post)).to include(['starts-with', '$key', ''])
end
end
describe ':signature_expiration' do
it 'defaults to one hour from now' do
now = Time.now
allow(Time).to receive(:now).and_return(now)
policy = decode(post.key('key').fields['policy'])
expect(policy['expiration']).to eq((now + 3600).utc.iso8601)
end
it 'can be set in the constructor' do
now = Time.now
options[:signature_expiration] = now
policy = decode(post.key('key').fields['policy'])
expect(policy['expiration']).to eq(now.utc.iso8601)
end
end
describe 'fields' do
before(:each) do
post.key('key')
end
it 'provides methods for setting fields or fields starts with' do
post = PresignedPost.new(creds, region, bucket).key('key')
post.acl('public-read')
expect(post.fields['acl']).to eq('public-read')
expect(policy(post)).to include('acl' => 'public-read')
post = PresignedPost.new(creds, region, bucket).key('key')
post.acl_starts_with('')
expect(post.fields['acl']).to be(nil)
expect(policy(post)).to include(['starts-with', '$acl', ''])
end
it 'formats the Expires time value as httpdate' do
now = Time.now
post.expires(now)
expect(post.fields['Expires']).to eq(now.httpdate)
end
it 'allows prefixed Expires' do
post.expires_starts_with('')
expect(post.fields['Expires']).to be(nil)
expect(policy(post)).to include(['starts-with', '$Expires', ''])
end
it 'accepts a range for content_length_range' do
post.content_length_range(10..20)
expect(policy(post)).to include(['content-length-range', 10, 20])
end
it 'respects non-inclusive ranges' do
post.content_length_range(10...20)
expect(policy(post)).to include(['content-length-range', 10, 19])
end
it 'accepts a hash to :metadata' do
post.metadata(foo: 'bar', 'mno' => 'xyz')
expect(post.fields['x-amz-meta-foo']).to eq('bar')
expect(post.fields['x-amz-meta-mno']).to eq('xyz')
expect(policy(post)).to include('x-amz-meta-foo' => 'bar')
expect(policy(post)).to include('x-amz-meta-mno' => 'xyz')
end
it 'accepts a hash to :metadata_starts_with' do
post.metadata_starts_with(foo: 'foo/', 'mno' => 'mno/')
expect(post.fields['x-amz-meta-foo']).to be(nil)
expect(post.fields['x-amz-meta-mno']).to be(nil)
expect(policy(post)).to include(
['starts-with', '$x-amz-meta-foo', 'foo/']
)
expect(policy(post)).to include(
['starts-with', '$x-amz-meta-mno', 'mno/']
)
end
it 'computes a MD5 of the customer provided encryption key' do
key = 'abcmnoxyz12345'
encoded_key = Base64.strict_encode64(key)
md5 = Base64.strict_encode64(OpenSSL::Digest::MD5.digest(key))
post.server_side_encryption_customer_key(key)
expect(
post.fields['x-amz-server-side-encryption-customer-key']
).to eq(encoded_key)
expect(
post.fields['x-amz-server-side-encryption-customer-key-MD5']
).to eq(md5)
expect(policy(post)).to include(
'x-amz-server-side-encryption-customer-key' => encoded_key
)
expect(policy(post)).to include(
'x-amz-server-side-encryption-customer-key-MD5' => md5
)
end
it 'does not computes MD5 for starts with' do
post.server_side_encryption_customer_key_starts_with('prefix')
expect(
post.fields['x-amz-server-side-encryption-customer-key']
).to be(nil)
expect(
post.fields['x-amz-server-side-encryption-customer-key-MD5']
).to be(nil)
expect(policy(post)).to include(
[
'starts-with', '$x-amz-server-side-encryption-customer-key',
'prefix'
]
)
end
it 'accepts ${filename} to eq' do
post.metadata('orig-filename' => '${filename}')
expect(post.fields['x-amz-meta-orig-filename']).to eq('${filename}')
expect(policy(post)).to include(
['starts-with', '$x-amz-meta-orig-filename', '']
)
end
it 'accepts ${filename} and removes it from starts-with' do
post = PresignedPost.new(creds, region, bucket)
post.key('prefix/${filename}')
expect(post.fields['key']).to eq('prefix/${filename}')
expect(policy(post)).to include(['starts-with', '$key', 'prefix/'])
end
it 'requires ${filename} to be at the end of the value' do
post = PresignedPost.new(creds, region, bucket)
expect do
post.key('${filename}/foo')
end.to raise_error(ArgumentError)
end
it 'accepts a list of fields to white-list' do
post.allow_any(['foo']).allow_any('Filename')
expect(post.fields['foo']).to be(nil)
expect(post.fields['Filename']).to be(nil)
expect(policy(post)).to include(['starts-with', '$foo', ''])
expect(policy(post)).to include(['starts-with', '$Filename', ''])
end
end
describe '#fields' do
it 'returns a hash with a policy document and signature' do
now = Time.now
allow(Time).to receive(:now).and_return(now)
post.key('key')
expect(post.fields.keys).to eq(
[
'key', 'policy', 'x-amz-credential', 'x-amz-algorithm',
'x-amz-date', 'x-amz-signature'
]
)
expect(post.fields['x-amz-algorithm']).to eq('AWS4-HMAC-SHA256')
expect(post.fields['x-amz-credential']).to eq(
"akid/#{now.strftime('%Y%m%d')}/#{region}/s3/aws4_request"
)
expect(post.fields['x-amz-date']).to eq(
now.strftime('%Y%m%dT%H%M%SZ')
)
end
it 'generates a valid signature' do
# test example from http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html
creds = Credentials.new(
'AKIAIOSFODNN7EXAMPLE',
'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'
)
region = 'us-east-1'
bucket = 'examplebucket'
now = Time.parse('20130806T000000Z')
allow(Time).to receive(:now).and_return(now)
post = PresignedPost.new(creds, region, bucket, key: 'key')
policy = <<-POLICY.strip
eyAiZXhwaXJhdGlvbiI6ICIyMDEzLTA4LTA3VDEyOjAwOjAwLjAwMFoiLA0KICAiY29uZGl0aW9ucyI6IFsNCiAgICB7ImJ1Y2tldCI6ICJleGFtcGxlYnVja2V0In0sDQogICAgWyJzdGFydHMtd2l0aCIsICIka2V5IiwgInVzZXIvdXNlcjEvIl0sDQogICAgeyJhY2wiOiAicHVibGljLXJlYWQifSwNCiAgICB7InN1Y2Nlc3NfYWN0aW9uX3JlZGlyZWN0IjogImh0dHA6Ly9leGFtcGxlYnVja2V0LnMzLmFtYXpvbmF3cy5jb20vc3VjY2Vzc2Z1bF91cGxvYWQuaHRtbCJ9LA0KICAgIFsic3RhcnRzLXdpdGgiLCAiJENvbnRlbnQtVHlwZSIsICJpbWFnZS8iXSwNCiAgICB7IngtYW16LW1ldGEtdXVpZCI6ICIxNDM2NTEyMzY1MTI3NCJ9LA0KICAgIFsic3RhcnRzLXdpdGgiLCAiJHgtYW16LW1ldGEtdGFnIiwgIiJdLA0KDQogICAgeyJ4LWFtei1jcmVkZW50aWFsIjogIkFLSUFJT1NGT0ROTjdFWEFNUExFLzIwMTMwODA2L3VzLWVhc3QtMS9zMy9hd3M0X3JlcXVlc3QifSwNCiAgICB7IngtYW16LWFsZ29yaXRobSI6ICJBV1M0LUhNQUMtU0hBMjU2In0sDQogICAgeyJ4LWFtei1kYXRlIjogIjIwMTMwODA2VDAwMDAwMFoiIH0NCiAgXQ0KfQ==
POLICY
allow(post).to receive(:policy).and_return(policy)
expect(post.fields['x-amz-signature']).to eq(
'21496b44de44ccb73d545f1a995c68214c9cb0d41c45a17a5daeec0b1a6db047'
)
end
it 'adds x-amz-security-token as appropriate' do
creds = Credentials.new('key', 'secret', 'token')
post = PresignedPost.new(creds, region, bucket).key('key')
expect(post.fields['x-amz-security-token']).to eq('token')
expect(policy(post)).to include('x-amz-security-token' => 'token')
end
it 'sets the storage class if requested' do
creds = Credentials.new('key', 'secret', 'token')
post = PresignedPost.new(
creds,
region,
bucket
).key('key').storage_class('REDUCED_REDUNDANCY')
expect(post.fields['x-amz-storage-class']).to eq('REDUCED_REDUNDANCY')
end
end
end
end
end