carrierwave_direct 0.0.16 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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