carrierwave_direct 0.0.16 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +14 -9
  3. data/Changelog.md +45 -1
  4. data/README.md +37 -23
  5. data/carrierwave_direct.gemspec +4 -3
  6. data/gemfiles/{3.2.gemfile → 5.1.gemfile} +3 -3
  7. data/gemfiles/{4.0.gemfile → 5.2.gemfile} +3 -3
  8. data/gemfiles/{4.1.gemfile → 6.0.gemfile} +3 -3
  9. data/gemfiles/6.1.gemfile +13 -0
  10. data/lib/carrierwave_direct/action_view_extensions/form_helper.rb +1 -1
  11. data/lib/carrierwave_direct/form_builder.rb +30 -12
  12. data/lib/carrierwave_direct/mount.rb +1 -11
  13. data/lib/carrierwave_direct/policies/aws4_hmac_sha256.rb +93 -0
  14. data/lib/carrierwave_direct/policies/aws_base64_sha1.rb +57 -0
  15. data/lib/carrierwave_direct/policies/base.rb +21 -0
  16. data/lib/carrierwave_direct/test/capybara_helpers.rb +3 -3
  17. data/lib/carrierwave_direct/test/helpers.rb +1 -1
  18. data/lib/carrierwave_direct/uploader.rb +55 -56
  19. data/lib/carrierwave_direct/validations/active_model.rb +2 -2
  20. data/lib/carrierwave_direct/version.rb +1 -1
  21. data/spec/form_builder_spec.rb +24 -15
  22. data/spec/mount_spec.rb +2 -2
  23. data/spec/orm/activerecord_spec.rb +11 -7
  24. data/spec/orm/indirect_activerecord_spec.rb +7 -1
  25. data/spec/policies/aws4_hmac_sha256_spec.rb +243 -0
  26. data/spec/policies/aws_base64_sha1_spec.rb +229 -0
  27. data/spec/spec_helper.rb +5 -0
  28. data/spec/support/carrier_wave_config.rb +1 -0
  29. data/spec/test/capybara_helpers_spec.rb +4 -4
  30. data/spec/test/helpers_spec.rb +3 -3
  31. data/spec/uploader_spec.rb +20 -26
  32. metadata +36 -18
  33. data/lib/carrierwave_direct/uploader/direct_url.rb +0 -15
  34. data/spec/uploader/direct_url_spec.rb +0 -26
@@ -0,0 +1,93 @@
1
+ require "carrierwave_direct/policies/base"
2
+
3
+ module CarrierWaveDirect
4
+ module Policies
5
+ class Aws4HmacSha256 < Base
6
+
7
+ def direct_fog_hash(policy_options = {})
8
+ {
9
+ key: uploader.key,
10
+ acl: uploader.acl,
11
+ policy: policy(policy_options),
12
+ 'X-Amz-Signature': signature,
13
+ 'X-Amz-Credential': credential,
14
+ 'X-Amz-Algorithm': algorithm,
15
+ 'X-Amz-Date': date,
16
+ uri: uploader.direct_fog_url,
17
+ }
18
+ end
19
+
20
+ def date
21
+ timestamp.strftime("%Y%m%dT%H%M%SZ")
22
+ end
23
+
24
+ def generate(options, &block)
25
+
26
+ return @policy if @policy.present?
27
+ conditions = []
28
+
29
+ conditions << ["starts-with", "$utf8", ""] if options[:enforce_utf8]
30
+ conditions << ["starts-with", "$key", uploader.key.sub(/#{Regexp.escape(CarrierWaveDirect::Uploader::FILENAME_WILDCARD)}\z/, "")]
31
+ conditions << {'X-Amz-Algorithm' => algorithm}
32
+ conditions << {'X-Amz-Credential' => credential}
33
+ conditions << {'X-Amz-Date' => date }
34
+ conditions << ["starts-with", "$Content-Type", ""] if uploader.will_include_content_type
35
+ conditions << {"bucket" => uploader.fog_directory}
36
+ conditions << {"acl" => uploader.acl}
37
+
38
+ if uploader.use_action_status
39
+ conditions << {"success_action_status" => uploader.success_action_status}
40
+ else
41
+ conditions << {"success_action_redirect" => uploader.success_action_redirect}
42
+ end
43
+
44
+ conditions << ["content-length-range", options[:min_file_size], options[:max_file_size]]
45
+
46
+ yield conditions if block_given?
47
+
48
+ @policy = Base64.encode64(
49
+ {
50
+ 'expiration' => (Time.now + options[:expiration]).utc.iso8601,
51
+ 'conditions' => conditions
52
+ }.to_json
53
+ ).gsub("\n","")
54
+ end
55
+
56
+ def credential
57
+ "#{uploader.aws_access_key_id}/#{timestamp.strftime("%Y%m%d")}/#{uploader.region}/s3/aws4_request"
58
+ end
59
+
60
+ def algorithm
61
+ 'AWS4-HMAC-SHA256'
62
+ end
63
+
64
+ def clear!
65
+ super
66
+ @timestamp = nil
67
+ end
68
+
69
+ def signature
70
+ OpenSSL::HMAC.hexdigest(
71
+ 'sha256',
72
+ signing_key,
73
+ policy
74
+ )
75
+ end
76
+
77
+ def signing_key(options = {})
78
+ #AWS Signature Version 4
79
+ kDate = OpenSSL::HMAC.digest('sha256', "AWS4" + uploader.aws_secret_access_key, timestamp.strftime("%Y%m%d"))
80
+ kRegion = OpenSSL::HMAC.digest('sha256', kDate, uploader.region)
81
+ kService = OpenSSL::HMAC.digest('sha256', kRegion, 's3')
82
+ kSigning = OpenSSL::HMAC.digest('sha256', kService, "aws4_request")
83
+
84
+ kSigning
85
+ end
86
+
87
+ def timestamp
88
+ @timestamp ||= Time.now.utc
89
+ end
90
+
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,57 @@
1
+ require "carrierwave_direct/policies/base"
2
+
3
+ module CarrierWaveDirect
4
+ module Policies
5
+ class AwsBase64Sha1 < Base
6
+ def signature
7
+ Base64.encode64(
8
+ OpenSSL::HMAC.digest(
9
+ OpenSSL::Digest.new('sha1'),
10
+ uploader.aws_secret_access_key, policy
11
+ )
12
+ ).gsub("\n", "")
13
+ end
14
+
15
+ def direct_fog_hash(policy_options = {})
16
+ {
17
+ key: uploader.key,
18
+ AWSAccessKeyId: uploader.aws_access_key_id,
19
+ acl: uploader.acl,
20
+ policy: policy(policy_options),
21
+ signature: signature,
22
+ uri: uploader.direct_fog_url
23
+ }
24
+ end
25
+
26
+ def generate(options, &block)
27
+
28
+ return @policy if @policy.present?
29
+ conditions = []
30
+
31
+ conditions << ["starts-with", "$utf8", ""] if options[:enforce_utf8]
32
+ conditions << ["starts-with", "$key", uploader.key.sub(/#{Regexp.escape(CarrierWaveDirect::Uploader::FILENAME_WILDCARD)}\z/, "")]
33
+ conditions << ["starts-with", "$Content-Type", ""] if uploader.will_include_content_type
34
+ conditions << {"bucket" => uploader.fog_directory}
35
+ conditions << {"acl" => uploader.acl}
36
+
37
+ if uploader.use_action_status
38
+ conditions << {"success_action_status" => uploader.success_action_status}
39
+ else
40
+ conditions << {"success_action_redirect" => uploader.success_action_redirect}
41
+ end
42
+
43
+ conditions << ["content-length-range", options[:min_file_size], options[:max_file_size]]
44
+
45
+ yield conditions if block_given?
46
+
47
+ @policy = Base64.encode64(
48
+ {
49
+ 'expiration' => (Time.now + options[:expiration]).utc.iso8601,
50
+ 'conditions' => conditions
51
+ }.to_json
52
+ ).gsub("\n","")
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,21 @@
1
+ module CarrierWaveDirect
2
+ module Policies
3
+ class Base
4
+ attr_reader :uploader
5
+ def initialize(uploader)
6
+ @uploader = uploader
7
+ end
8
+
9
+ def policy(options = {}, &block)
10
+ options[:expiration] ||= uploader.upload_expiration
11
+ options[:min_file_size] ||= uploader.min_file_size
12
+ options[:max_file_size] ||= uploader.max_file_size
13
+ @policy ||= generate(options, &block)
14
+ end
15
+
16
+ def clear!
17
+ @policy = nil
18
+ end
19
+ end
20
+ end
21
+ end
@@ -18,7 +18,7 @@ module CarrierWaveDirect
18
18
  page.find("input[name='file']", visible: false).value
19
19
  end
20
20
 
21
- def upload_directly(uploader, button_locator, options = {})
21
+ def upload_directly(uploader, button_locator, **options)
22
22
  options[:success] = true unless options[:success] == false
23
23
  options[:success] &&= !options[:fail]
24
24
 
@@ -33,9 +33,9 @@ module CarrierWaveDirect
33
33
  sample_key_args.unshift(uploader) if method(:sample_key).arity == -2
34
34
  options[:redirect_key] = sample_key(*sample_key_args)
35
35
  end
36
-
36
+
37
37
  redirect_url_params = Rack::Utils.parse_nested_query(redirect_url.query)
38
-
38
+
39
39
  redirect_url.query = Rack::Utils.build_nested_query({
40
40
  :bucket => uploader.fog_directory,
41
41
  :key => options[:redirect_key],
@@ -18,7 +18,7 @@ module CarrierWaveDirect
18
18
  options[:filename] = filename_parts.join(".")
19
19
  end
20
20
  options[:filename] ||= "filename"
21
- valid_extension = uploader.extension_white_list.first if uploader.extension_white_list
21
+ valid_extension = uploader.extension_allowlist.first if uploader.extension_allowlist
22
22
  options[:extension] = options[:extension] ? options[:extension].gsub(".", "") : (valid_extension || "extension")
23
23
  key = options[:base].split("/")
24
24
  key.pop
@@ -2,7 +2,8 @@
2
2
 
3
3
  require "securerandom"
4
4
  require "carrierwave_direct/uploader/content_type"
5
- require "carrierwave_direct/uploader/direct_url"
5
+ require "carrierwave_direct/policies/aws_base64_sha1"
6
+ require "carrierwave_direct/policies/aws4_hmac_sha256"
6
7
 
7
8
  module CarrierWaveDirect
8
9
  module Uploader
@@ -24,31 +25,38 @@ module CarrierWaveDirect
24
25
  end
25
26
 
26
27
  include CarrierWaveDirect::Uploader::ContentType
27
- include CarrierWaveDirect::Uploader::DirectUrl
28
+
29
+ #ensure that region returns something. Since sig v4 it is required in the signing key & credentials
30
+ def region
31
+ defined?(super) ? super : "us-east-1"
32
+ end
28
33
 
29
34
  def acl
30
35
  fog_public ? 'public-read' : 'private'
31
36
  end
32
37
 
33
38
  def policy(options = {}, &block)
34
- options[:expiration] ||= upload_expiration
35
- options[:min_file_size] ||= min_file_size
36
- options[:max_file_size] ||= max_file_size
39
+ signing_policy.policy(options, &block)
40
+ end
41
+
42
+ def date
43
+ signing_policy.date
44
+ end
45
+
46
+ def algorithm
47
+ signing_policy.algorithm
48
+ end
37
49
 
38
- @policy ||= generate_policy(options, &block)
50
+ def credential
51
+ signing_policy.credential
39
52
  end
40
53
 
41
54
  def clear_policy!
42
- @policy = nil
55
+ signing_policy.clear!
43
56
  end
44
57
 
45
58
  def signature
46
- Base64.encode64(
47
- OpenSSL::HMAC.digest(
48
- OpenSSL::Digest.new('sha1'),
49
- aws_secret_access_key, policy
50
- )
51
- ).gsub("\n","")
59
+ signing_policy.signature
52
60
  end
53
61
 
54
62
  def url_scheme_white_list
@@ -59,13 +67,22 @@ module CarrierWaveDirect
59
67
  false
60
68
  end
61
69
 
70
+ def signing_policy_class
71
+ @signing_policy_class ||= Policies::Aws4HmacSha256
72
+ end
73
+
74
+ def signing_policy_class=(signing_policy_class)
75
+ @signing_policy_class = signing_policy_class
76
+ end
77
+
62
78
  def key
63
79
  return @key if @key.present?
64
80
  if present?
65
81
  identifier = model.send("#{mounted_as}_identifier")
66
- self.key = "#{store_dir}/#{identifier}"
82
+ self.key = [store_dir, identifier].join("/")
67
83
  else
68
- @key = "#{store_dir}/#{guid}/#{FILENAME_WILDCARD}"
84
+ guid = SecureRandom.uuid
85
+ @key = [store_dir, guid, FILENAME_WILDCARD].join("/")
69
86
  end
70
87
  @key
71
88
  end
@@ -75,10 +92,6 @@ module CarrierWaveDirect
75
92
  update_version_keys(:with => @key)
76
93
  end
77
94
 
78
- def guid
79
- SecureRandom.uuid
80
- end
81
-
82
95
  def has_key?
83
96
  key !~ /#{Regexp.escape(FILENAME_WILDCARD)}\z/
84
97
  end
@@ -88,7 +101,7 @@ module CarrierWaveDirect
88
101
  end
89
102
 
90
103
  def extension_regexp
91
- allowed_file_types = extension_white_list
104
+ allowed_file_types = extension_allowlist
92
105
  extension_regexp = allowed_file_types.present? && allowed_file_types.any? ? "(#{allowed_file_types.join("|")})" : "\\w+"
93
106
  end
94
107
 
@@ -96,27 +109,37 @@ module CarrierWaveDirect
96
109
  unless has_key?
97
110
  # Use the attached models remote url to generate a new key otherwise return nil
98
111
  remote_url = model.send("remote_#{mounted_as}_url")
99
- remote_url ? key_from_file(CarrierWave::SanitizedFile.new(remote_url).filename) : return
112
+ if remote_url
113
+ key_from_file(CarrierWave::SanitizedFile.new(remote_url).filename)
114
+ else
115
+ return
116
+ end
100
117
  end
101
118
 
102
- key_path = key.split("/")
119
+ key_parts = key.split("/")
120
+ filename = key_parts.pop
121
+ guid = key_parts.pop
122
+
103
123
  filename_parts = []
104
- filename_parts.unshift(key_path.pop)
105
- unique_key = key_path.pop
106
- filename_parts.unshift(unique_key) if unique_key
124
+ filename_parts << guid if guid
125
+ filename_parts << filename
107
126
  filename_parts.join("/")
108
127
  end
109
128
 
110
- private
129
+ def direct_fog_url
130
+ CarrierWave::Storage::Fog::File.new(self, CarrierWave::Storage::Fog.new(self), nil).public_url
131
+ end
111
132
 
112
- def decoded_key
113
- URI.decode(URI.parse(url).path[1 .. -1])
133
+ def direct_fog_hash(policy_options = {})
134
+ signing_policy.direct_fog_hash(policy_options)
114
135
  end
115
136
 
116
- def key_from_file(fname)
137
+ private
138
+
139
+ def key_from_file(filename)
117
140
  new_key_parts = key.split("/")
118
141
  new_key_parts.pop
119
- new_key_parts << fname
142
+ new_key_parts << filename
120
143
  self.key = new_key_parts.join("/")
121
144
  end
122
145
 
@@ -134,32 +157,8 @@ module CarrierWaveDirect
134
157
  [for_file.chomp(extname), version_name].compact.join('_') << extname
135
158
  end
136
159
 
137
- def generate_policy(options)
138
- conditions = []
139
-
140
- conditions << ["starts-with", "$utf8", ""] if options[:enforce_utf8]
141
- conditions << ["starts-with", "$key", key.sub(/#{Regexp.escape(FILENAME_WILDCARD)}\z/, "")]
142
-
143
- conditions << ["starts-with", "$Content-Type", ""] if will_include_content_type
144
- conditions << {"bucket" => fog_directory}
145
- conditions << {"acl" => acl}
146
-
147
- if use_action_status
148
- conditions << {"success_action_status" => success_action_status}
149
- else
150
- conditions << {"success_action_redirect" => success_action_redirect}
151
- end
152
-
153
- conditions << ["content-length-range", options[:min_file_size], options[:max_file_size]]
154
-
155
- yield conditions if block_given?
156
-
157
- Base64.encode64(
158
- {
159
- 'expiration' => (Time.now + options[:expiration]).utc.iso8601,
160
- 'conditions' => conditions
161
- }.to_json
162
- ).gsub("\n","")
160
+ def signing_policy
161
+ @signing_policy ||= signing_policy_class.new(self)
163
162
  end
164
163
  end
165
164
  end
@@ -23,7 +23,7 @@ module CarrierWaveDirect
23
23
  class FilenameFormatValidator < ::ActiveModel::EachValidator
24
24
  def validate_each(record, attribute, value)
25
25
  if record.send("has_#{attribute}_upload?") && record.send("#{attribute}_key") !~ record.send(attribute).key_regexp
26
- extensions = record.send(attribute).extension_white_list
26
+ extensions = record.send(attribute).extension_allowlist
27
27
  message = I18n.t("errors.messages.carrierwave_direct_filename_invalid")
28
28
 
29
29
  if extensions.present?
@@ -43,7 +43,7 @@ module CarrierWaveDirect
43
43
  url_scheme_white_list = uploader.url_scheme_white_list
44
44
 
45
45
  if (remote_net_url !~ URI.regexp(url_scheme_white_list) || remote_net_url !~ /#{uploader.extension_regexp}\z/)
46
- extensions = uploader.extension_white_list
46
+ extensions = uploader.extension_allowlist
47
47
 
48
48
  message = I18n.t("errors.messages.carrierwave_direct_filename_invalid")
49
49
 
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module CarrierwaveDirect
4
- VERSION = "0.0.16"
4
+ VERSION = "3.0.0"
5
5
  end
6
6
 
@@ -12,11 +12,13 @@ end
12
12
  shared_examples_for 'hidden values form' do
13
13
  hidden_fields = [
14
14
  :key,
15
- {:aws_access_key_id => "AWSAccessKeyId"},
15
+ {:credential => "X-Amz-Credential"},
16
+ {:algorithm => "X-Amz-Algorithm"},
17
+ {:date => "X-Amz-Date"},
18
+ {:signature => "X-Amz-Signature"},
16
19
  :acl,
17
20
  :success_action_redirect,
18
- :policy,
19
- :signature
21
+ :policy
20
22
  ]
21
23
 
22
24
  hidden_fields.each do |input|
@@ -28,13 +30,14 @@ shared_examples_for 'hidden values form' do
28
30
  end
29
31
 
30
32
  it "should have a hidden field for '#{name}'" do
33
+ allow(direct_uploader.send(:signing_policy)).to receive(key).and_return(key.to_s)
31
34
  allow(direct_uploader).to receive(key).and_return(key.to_s)
32
35
  expect(subject).to have_input(
33
36
  :direct_uploader,
34
37
  key,
35
38
  :type => :hidden,
36
39
  :name => name,
37
- :value => key,
40
+ :value => direct_uploader.send(key),
38
41
  :required => false
39
42
  )
40
43
  end
@@ -60,24 +63,28 @@ describe CarrierWaveDirect::FormBuilder do
60
63
 
61
64
  default_hidden_fields = [
62
65
  :key,
63
- {:aws_access_key_id => "AWSAccessKeyId"},
66
+ {:credential => "X-Amz-Credential"},
67
+ {:algorithm => "X-Amz-Algorithm"},
68
+ {:date => "X-Amz-Date"},
69
+ {:signature => "X-Amz-Signature"},
64
70
  :acl,
65
71
  :success_action_redirect,
66
72
  :policy,
67
- :signature
68
73
  ]
69
74
  status_hidden_fields = [
70
75
  :key,
71
- {:aws_access_key_id => "AWSAccessKeyId"},
76
+ {:credential => "X-Amz-Credential"},
77
+ {:algorithm => "X-Amz-Algorithm"},
78
+ {:date => "X-Amz-Date"},
79
+ {:signature => "X-Amz-Signature"},
72
80
  :acl,
73
81
  :success_action_status,
74
82
  :policy,
75
- :signature
76
83
  ]
77
84
 
78
85
  # http://aws.amazon.com/articles/1434?_encoding=UTF8
79
86
  context "form" do
80
- let(:subject) {form_with_default_file_field}
87
+ subject { form_with_default_file_field }
81
88
  it_should_behave_like 'hidden values form'
82
89
 
83
90
  default_hidden_fields.each do |input|
@@ -89,13 +96,14 @@ describe CarrierWaveDirect::FormBuilder do
89
96
  end
90
97
 
91
98
  it "should have a hidden field for '#{name}'" do
99
+ allow(direct_uploader.send(:signing_policy)).to receive(key).and_return(key.to_s)
92
100
  allow(direct_uploader).to receive(key).and_return(key.to_s)
93
- expect(form_with_default_file_field).to have_input(
101
+ expect(subject).to have_input(
94
102
  :direct_uploader,
95
103
  key,
96
104
  :type => :hidden,
97
105
  :name => name,
98
- :value => key,
106
+ :value => direct_uploader.send(key),
99
107
  :required => false
100
108
  )
101
109
  end
@@ -110,20 +118,21 @@ describe CarrierWaveDirect::FormBuilder do
110
118
  end
111
119
 
112
120
  it "should have a hidden field for '#{name}'" do
121
+ allow(direct_uploader.send(:signing_policy)).to receive(key).and_return(key.to_s)
113
122
  allow(direct_uploader).to receive(key).and_return(key.to_s)
114
123
  expect(form_with_file_field_and_no_redirect).to have_input(
115
124
  :direct_uploader,
116
125
  key,
117
126
  :type => :hidden,
118
127
  :name => name,
119
- :value => key,
128
+ :value => direct_uploader.send(key),
120
129
  :required => false
121
130
  )
122
131
  end
123
132
  end
124
133
 
125
134
  it "should have an input for a file to upload" do
126
- expect(form_with_default_file_field).to have_input(
135
+ expect(subject).to have_input(
127
136
  :direct_uploader,
128
137
  :video,
129
138
  :type => :file,
@@ -136,7 +145,7 @@ describe CarrierWaveDirect::FormBuilder do
136
145
 
137
146
  describe "#content_type_select" do
138
147
  context "form" do
139
- let(:subject) do
148
+ subject do
140
149
  form do |f|
141
150
  f.content_type_select
142
151
  end
@@ -172,7 +181,7 @@ describe CarrierWaveDirect::FormBuilder do
172
181
 
173
182
  describe "#content_type_label" do
174
183
  context "form" do
175
- let(:subject) do
184
+ subject do
176
185
  form do |f|
177
186
  f.content_type_label
178
187
  end