2019-04-29 20:27:42 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2021-08-25 18:50:49 -04:00
|
|
|
require "s3_helper"
|
2014-09-24 16:52:09 -04:00
|
|
|
|
2022-07-27 22:27:38 -04:00
|
|
|
RSpec.describe "S3Helper" do
|
2019-06-08 08:40:35 -04:00
|
|
|
let(:client) { Aws::S3::Client.new(stub_responses: true) }
|
|
|
|
|
2020-09-14 07:32:25 -04:00
|
|
|
before do
|
|
|
|
setup_s3
|
2014-09-24 16:52:09 -04:00
|
|
|
|
2018-09-16 20:57:50 -04:00
|
|
|
@lifecycle = <<~XML
|
2017-11-12 23:36:45 -05:00
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<LifecycleConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
|
|
|
<Rule>
|
|
|
|
<ID>old_rule</ID>
|
|
|
|
<Prefix>projectdocs/</Prefix>
|
|
|
|
<Status>Enabled</Status>
|
|
|
|
<Expiration>
|
|
|
|
<Days>3650</Days>
|
|
|
|
</Expiration>
|
|
|
|
</Rule>
|
|
|
|
<Rule>
|
|
|
|
<ID>purge-tombstone</ID>
|
|
|
|
<Prefix>test/</Prefix>
|
|
|
|
<Status>Enabled</Status>
|
|
|
|
<Expiration>
|
|
|
|
<Days>3650</Days>
|
|
|
|
</Expiration>
|
|
|
|
</Rule>
|
|
|
|
</LifecycleConfiguration>
|
|
|
|
XML
|
2018-09-16 20:57:50 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it "can correctly set the purge policy" do
|
|
|
|
SiteSetting.s3_configure_tombstone_policy = true
|
|
|
|
|
|
|
|
stub_request(:get, "http://169.254.169.254/latest/meta-data/iam/security-credentials/").
|
|
|
|
to_return(status: 404, body: "", headers: {})
|
2017-11-12 23:36:45 -05:00
|
|
|
|
2020-09-14 07:32:25 -04:00
|
|
|
stub_request(:get, "https://bob.s3.#{SiteSetting.s3_region}.amazonaws.com/?lifecycle").
|
2018-09-16 20:57:50 -04:00
|
|
|
to_return(status: 200, body: @lifecycle, headers: {})
|
2017-11-12 23:36:45 -05:00
|
|
|
|
2020-09-14 07:32:25 -04:00
|
|
|
stub_request(:put, "https://bob.s3.#{SiteSetting.s3_region}.amazonaws.com/?lifecycle").
|
2017-11-12 23:36:45 -05:00
|
|
|
with do |req|
|
|
|
|
|
|
|
|
hash = Hash.from_xml(req.body.to_s)
|
|
|
|
rules = hash["LifecycleConfiguration"]["Rule"]
|
|
|
|
|
|
|
|
expect(rules.length).to eq(2)
|
2018-09-16 20:57:50 -04:00
|
|
|
expect(rules[1]["Expiration"]["Days"]).to eq("100")
|
2017-11-12 23:36:45 -05:00
|
|
|
# fixes the bad filter
|
|
|
|
expect(rules[0]["Filter"]["Prefix"]).to eq("projectdocs/")
|
|
|
|
end.to_return(status: 200, body: "", headers: {})
|
|
|
|
|
2018-09-16 20:57:50 -04:00
|
|
|
helper = S3Helper.new('bob', 'tomb')
|
|
|
|
helper.update_tombstone_lifecycle(100)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "can skip policy update when s3_configure_tombstone_policy is false" do
|
|
|
|
SiteSetting.s3_configure_tombstone_policy = false
|
2017-11-12 23:36:45 -05:00
|
|
|
|
|
|
|
helper = S3Helper.new('bob', 'tomb')
|
|
|
|
helper.update_tombstone_lifecycle(100)
|
|
|
|
end
|
|
|
|
|
2018-09-10 04:34:40 -04:00
|
|
|
describe '#list' do
|
|
|
|
it 'creates the prefix correctly' do
|
|
|
|
{
|
|
|
|
'some/bucket' => 'bucket/testing',
|
|
|
|
'some' => 'testing'
|
|
|
|
}.each do |bucket_name, prefix|
|
2019-06-08 08:40:35 -04:00
|
|
|
s3_helper = S3Helper.new(bucket_name, "", client: client)
|
2022-11-02 05:47:59 -04:00
|
|
|
Aws::S3::Bucket.any_instance.expects(:objects).with({ prefix: prefix })
|
2018-09-10 04:34:40 -04:00
|
|
|
s3_helper.list('testing')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2019-06-05 07:51:51 -04:00
|
|
|
|
|
|
|
it "should prefix bucket folder path only if not exists" do
|
2021-08-25 18:50:49 -04:00
|
|
|
s3_helper = S3Helper.new("bucket/folder_path", "", client: client)
|
2019-06-05 07:51:51 -04:00
|
|
|
|
2019-06-08 08:40:35 -04:00
|
|
|
object1 = s3_helper.object("original/1X/def.xyz")
|
|
|
|
object2 = s3_helper.object("folder_path/original/1X/def.xyz")
|
2019-06-05 07:51:51 -04:00
|
|
|
|
2019-06-08 08:40:35 -04:00
|
|
|
expect(object1.key).to eq(object2.key)
|
2019-06-05 07:51:51 -04:00
|
|
|
end
|
2021-08-25 18:50:49 -04:00
|
|
|
|
|
|
|
it "should not prefix the bucket folder path if the key begins with the temporary upload prefix" do
|
|
|
|
s3_helper = S3Helper.new("bucket/folder_path", "", client: client)
|
|
|
|
|
|
|
|
object1 = s3_helper.object("original/1X/def.xyz")
|
|
|
|
object2 = s3_helper.object("#{FileStore::BaseStore::TEMPORARY_UPLOAD_PREFIX}folder_path/uploads/default/blah/def.xyz")
|
|
|
|
|
|
|
|
expect(object1.key).to eq("folder_path/original/1X/def.xyz")
|
|
|
|
expect(object2.key).to eq("#{FileStore::BaseStore::TEMPORARY_UPLOAD_PREFIX}folder_path/uploads/default/blah/def.xyz")
|
|
|
|
end
|
2021-09-09 22:59:51 -04:00
|
|
|
|
|
|
|
describe "#copy" do
|
|
|
|
let(:source_key) { "#{FileStore::BaseStore::TEMPORARY_UPLOAD_PREFIX}uploads/default/blah/source.jpg" }
|
|
|
|
let(:destination_key) { "original/1X/destination.jpg" }
|
|
|
|
let(:s3_helper) { S3Helper.new("test-bucket", "", client: client) }
|
|
|
|
|
2022-06-28 15:30:00 -04:00
|
|
|
it "can copy a small object from the source to the destination" do
|
|
|
|
source_stub = Aws::S3::Object.new(bucket_name: "test-bucket", key: source_key, client: client)
|
|
|
|
source_stub.stubs(:size).returns(5 * 1024 * 1024)
|
|
|
|
s3_helper.send(:s3_bucket).expects(:object).with(source_key).returns(source_stub)
|
|
|
|
|
2021-09-09 22:59:51 -04:00
|
|
|
destination_stub = Aws::S3::Object.new(bucket_name: "test-bucket", key: destination_key, client: client)
|
|
|
|
s3_helper.send(:s3_bucket).expects(:object).with(destination_key).returns(destination_stub)
|
2022-06-28 15:30:00 -04:00
|
|
|
|
|
|
|
destination_stub.expects(:copy_from).with(source_stub, {}).returns(
|
|
|
|
stub(copy_object_result: stub(etag: '"etag"'))
|
2021-09-09 22:59:51 -04:00
|
|
|
)
|
2022-06-28 15:30:00 -04:00
|
|
|
|
|
|
|
response = s3_helper.copy(source_key, destination_key)
|
|
|
|
expect(response.first).to eq(destination_key)
|
|
|
|
expect(response.second).to eq("etag")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "can copy a large object from the source to the destination" do
|
|
|
|
source_stub = Aws::S3::Object.new(bucket_name: "test-bucket", key: source_key, client: client)
|
|
|
|
source_stub.stubs(:size).returns(20 * 1024 * 1024)
|
|
|
|
s3_helper.send(:s3_bucket).expects(:object).with(source_key).returns(source_stub)
|
|
|
|
|
|
|
|
destination_stub = Aws::S3::Object.new(bucket_name: "test-bucket", key: destination_key, client: client)
|
|
|
|
s3_helper.send(:s3_bucket).expects(:object).with(destination_key).returns(destination_stub)
|
|
|
|
|
|
|
|
options = { multipart_copy: true, content_length: source_stub.size }
|
|
|
|
destination_stub.expects(:copy_from).with(source_stub, options).returns(
|
|
|
|
stub(data: stub(etag: '"etag"'))
|
|
|
|
)
|
|
|
|
|
2021-09-09 22:59:51 -04:00
|
|
|
response = s3_helper.copy(source_key, destination_key)
|
|
|
|
expect(response.first).to eq(destination_key)
|
|
|
|
expect(response.second).to eq("etag")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "puts the metadata from options onto the destination if apply_metadata_to_destination" do
|
2022-06-28 15:30:00 -04:00
|
|
|
source_stub = Aws::S3::Object.new(bucket_name: "test-bucket", key: source_key, client: client)
|
|
|
|
source_stub.stubs(:size).returns(5 * 1024 * 1024)
|
|
|
|
s3_helper.send(:s3_bucket).expects(:object).with(source_key).returns(source_stub)
|
|
|
|
|
2021-09-09 22:59:51 -04:00
|
|
|
destination_stub = Aws::S3::Object.new(bucket_name: "test-bucket", key: destination_key, client: client)
|
|
|
|
s3_helper.send(:s3_bucket).expects(:object).with(destination_key).returns(destination_stub)
|
2022-06-28 15:30:00 -04:00
|
|
|
|
|
|
|
content_disposition = "attachment; filename=\"source.jpg\"; filename*=UTF-8''source.jpg"
|
|
|
|
options = { content_disposition: content_disposition, metadata_directive: "REPLACE" }
|
|
|
|
destination_stub.expects(:copy_from).with(source_stub, options).returns(
|
|
|
|
stub(data: stub(etag: '"etag"'))
|
2021-09-09 22:59:51 -04:00
|
|
|
)
|
2022-06-28 15:30:00 -04:00
|
|
|
|
2021-09-09 22:59:51 -04:00
|
|
|
response = s3_helper.copy(
|
|
|
|
source_key, destination_key,
|
|
|
|
options: { apply_metadata_to_destination: true, content_disposition: content_disposition }
|
|
|
|
)
|
|
|
|
expect(response.first).to eq(destination_key)
|
|
|
|
expect(response.second).to eq("etag")
|
|
|
|
end
|
|
|
|
end
|
2021-10-31 18:23:13 -04:00
|
|
|
|
|
|
|
describe "#ensure_cors" do
|
|
|
|
let(:s3_helper) { S3Helper.new("test-bucket", "", client: client) }
|
|
|
|
|
|
|
|
it "does nothing if !s3_install_cors_rule" do
|
|
|
|
SiteSetting.s3_install_cors_rule = false
|
|
|
|
s3_helper.expects(:s3_resource).never
|
|
|
|
s3_helper.ensure_cors!
|
|
|
|
end
|
|
|
|
|
|
|
|
it "creates the assets rule if no rule exists" do
|
|
|
|
s3_helper.s3_client.stub_responses(:get_bucket_cors, Aws::S3::Errors::NoSuchCORSConfiguration.new("", {}))
|
|
|
|
s3_helper.s3_client.expects(:put_bucket_cors).with(
|
|
|
|
bucket: s3_helper.s3_bucket_name,
|
|
|
|
cors_configuration: {
|
|
|
|
cors_rules: [S3CorsRulesets::ASSETS]
|
|
|
|
}
|
|
|
|
)
|
2021-11-07 18:16:38 -05:00
|
|
|
s3_helper.ensure_cors!([S3CorsRulesets::ASSETS])
|
2021-10-31 18:23:13 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it "does nothing if a rule already exists" do
|
|
|
|
s3_helper.s3_client.stub_responses(:get_bucket_cors, {
|
|
|
|
cors_rules: [S3CorsRulesets::ASSETS]
|
|
|
|
})
|
|
|
|
s3_helper.s3_client.expects(:put_bucket_cors).never
|
2021-11-07 18:16:38 -05:00
|
|
|
s3_helper.ensure_cors!([S3CorsRulesets::ASSETS])
|
2021-10-31 18:23:13 -04:00
|
|
|
end
|
|
|
|
|
2021-11-07 18:16:38 -05:00
|
|
|
it "applies the passed in rule if a different rule already exists" do
|
2021-10-31 18:23:13 -04:00
|
|
|
s3_helper.s3_client.stub_responses(:get_bucket_cors, {
|
|
|
|
cors_rules: [S3CorsRulesets::ASSETS]
|
|
|
|
})
|
2021-11-07 18:16:38 -05:00
|
|
|
s3_helper.s3_client.expects(:put_bucket_cors).with(
|
|
|
|
bucket: s3_helper.s3_bucket_name,
|
|
|
|
cors_configuration: {
|
|
|
|
cors_rules: [S3CorsRulesets::ASSETS, S3CorsRulesets::BACKUP_DIRECT_UPLOAD]
|
|
|
|
}
|
|
|
|
)
|
2021-10-31 18:23:13 -04:00
|
|
|
s3_helper.ensure_cors!([S3CorsRulesets::BACKUP_DIRECT_UPLOAD])
|
|
|
|
end
|
2021-11-07 18:16:38 -05:00
|
|
|
|
|
|
|
it "returns false if the CORS rules do not get applied from an error" do
|
|
|
|
s3_helper.s3_client.stub_responses(:get_bucket_cors, {
|
|
|
|
cors_rules: [S3CorsRulesets::ASSETS]
|
|
|
|
})
|
|
|
|
s3_helper.s3_client.expects(:put_bucket_cors).with(
|
|
|
|
bucket: s3_helper.s3_bucket_name,
|
|
|
|
cors_configuration: {
|
|
|
|
cors_rules: [S3CorsRulesets::ASSETS, S3CorsRulesets::BACKUP_DIRECT_UPLOAD]
|
|
|
|
}
|
|
|
|
).raises(Aws::S3::Errors::AccessDenied.new("test", "test", {}))
|
|
|
|
expect(s3_helper.ensure_cors!([S3CorsRulesets::BACKUP_DIRECT_UPLOAD])).to eq(false)
|
|
|
|
end
|
2021-10-31 18:23:13 -04:00
|
|
|
end
|
2022-11-07 07:53:14 -05:00
|
|
|
|
|
|
|
describe "#delete_objects" do
|
|
|
|
let(:s3_helper) { S3Helper.new("test-bucket", "", client: client) }
|
|
|
|
|
|
|
|
it "works" do
|
|
|
|
# The S3::Client with `stub_responses: true` includes validation of requests.
|
|
|
|
# If the request were invalid, this spec would raise an error
|
|
|
|
s3_helper.delete_objects(["object/one.txt", "object/two.txt"])
|
|
|
|
end
|
|
|
|
end
|
2014-09-24 16:52:09 -04:00
|
|
|
end
|