active_storage_validations 2.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98a3c208496f942fcf1348e94240110af0c004e097e666b40e2aa1eed10927f5
4
- data.tar.gz: 32961f1c72066621fe248837954673bc1182c9a0caac0960b9f666c0cdeae1e6
3
+ metadata.gz: bcd9bfe3dafea8c00d688ab4816cf69c9848455a8d94fa5a4727fcd2f8e707e1
4
+ data.tar.gz: 623d46eb1b8eeb8e835496f174ce4dda01dee9df2e21b2d2033c6db8f387dd17
5
5
  SHA512:
6
- metadata.gz: 7cf99126987df855833f01535c25fc9c7d3e2585d562f6a3bd063a3bb37563a8bd0d78e8e0119930e2c9370ba2425128c169f50a11b8d11d3649959a72092fdb
7
- data.tar.gz: c852f751c20ce7a9e38e1fe906faf47b9cf8fcf0ce8ce839717c245aca94a4a2c52c7440eb7ee797853d8e9a11c28214d95a956cc025460728501d9524b307c3
6
+ metadata.gz: f22a2f92007a66c55d5a72d2c8158b95e88d1237650dd5e094a7362d36eabf35e0201d6e5ee4a5f3c896fdff21b601e5a9d5fbedc0d328b375d45fe81cf01d81
7
+ data.tar.gz: 85568d6d041a7a367f14f841e1617c5ac11b97de7ad3df72a8f1c90b3364077a2dcdbfd5e501ba77fe132bc4810a276307e3b6f5413427ffced46fad963d3d0f
data/README.md CHANGED
@@ -220,7 +220,7 @@ end
220
220
 
221
221
  #### Best practices
222
222
 
223
- When using the `content_type` validator, it is recommended to reflect the allowed content types in the html [`accept` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept) in the corresponding file field in your views. This will prevent users from trying to upload files with not allowed content types (however it is only an UX improvement, a malicious user can still try to upload files with not allowed content types therefore the backend validation).
223
+ When using the `content_type` validator, it is recommended to reflect the allowed content types in the html [`accept`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept) attribute in the corresponding file field in your views. This will prevent users from trying to upload files with not allowed content types (however it is only an UX improvement, a malicious user can still try to upload files with not allowed content types therefore the backend validation).
224
224
 
225
225
  For example, if you want to allow PNG and JPEG images only, you can do this:
226
226
  ```ruby
@@ -253,15 +253,17 @@ By default, the gem does not prevent content type spoofing. You can enable it by
253
253
 
254
254
  <details>
255
255
  <summary>
256
- ##### What is content type spoofing?
256
+ What is content type spoofing?
257
257
  </summary>
258
+
258
259
  File content type spoofing happens when an ill-intentioned user uploads a file which hides its true content type by faking its extension and its declared content type value. For example, a user may try to upload a `.exe` file (application/x-msdownload content type) dissimulated as a `.jpg` file (image/jpeg content type).
259
260
  </details>
260
261
 
261
262
  <details>
262
263
  <summary>
263
- ##### How do we prevent it?
264
+ How do we prevent it?
264
265
  </summary>
266
+
265
267
  The spoofing protection relies on both the UNIX `file` command and `Marcel` gem. Be careful, since it needs to load the whole file io to perform the analysis, it will use a lot of RAM for very large files. Therefore it could be a wise decision not to enable it in this case.
266
268
 
267
269
  Take note that the `file` analyzer will not find the exactly same content type as the ActiveStorage blob (ActiveStorage content type detection relies on a different logic using first 4kb of content + filename + extension). To handle this issue, we consider a close parent content type to be a match. For example, for an ActiveStorage blob which content type is `video/x-ms-wmv`, the `file` analyzer will probably detect a `video/x-ms-asf` content type, this will be considered as a valid match because these 2 content types are closely related. The correlation mapping is based on `Marcel::TYPE_PARENTS` table.
@@ -269,8 +271,9 @@ Take note that the `file` analyzer will not find the exactly same content type a
269
271
 
270
272
  <details>
271
273
  <summary>
272
- ##### Edge cases
274
+ Edge cases
273
275
  </summary>
276
+
274
277
  The difficulty to accurately predict a mime type may generate false positives, if so there are two solutions available:
275
278
  - If the ActiveStorage blob content type is closely related to the detected content type using the `file` analyzer, you can enhance `Marcel::TYPE_PARENTS` mapping using `Marcel::MimeType.extend "application/x-rar-compressed", parents: %(application/x-rar)` in the `config/initializers/mime_types.rb` file. (Please drop an issue so we can add it to the gem for everyone!)
276
279
  - If the ActiveStorage blob content type is not closely related, you still can disable the content type spoofing protection in the validator, if so, please drop us an issue so we can fix it for everyone!
@@ -708,7 +711,7 @@ en:
708
711
  file_not_processable: "is not identified as a valid media file"
709
712
  ```
710
713
 
711
- Other translation files are available (here)[https://github.com/igorkasyanchuk/active_storage_validations/tree/master/config/locales].
714
+ Other translation files are available [here](https://github.com/igorkasyanchuk/active_storage_validations/tree/master/config/locales).
712
715
 
713
716
  ## Test matchers
714
717
 
@@ -31,7 +31,7 @@ ja:
31
31
  one: "1つのファイルのみが添付されています(少なくとも%{min}ファイルが必要です)"
32
32
  other: "%{count}添付ファイル(少なくとも%{min}ファイルが必要です)"
33
33
  limit_max_exceeded:
34
- zero: "添付されたファイルはありません(最大は%{最大}ファイル)"
34
+ zero: "添付されたファイルはありません(最大は%{max}ファイル)"
35
35
  one: "添付されているファイルが多すぎます(最大は%{max}ファイル、%{count})"
36
36
  other: "添付されているファイルが多すぎます(最大は%{max}ファイル、%{count})"
37
37
  media_metadata_missing: "有効なメディアファイルではありません"
@@ -50,4 +50,4 @@ ja:
50
50
  aspect_ratio_not_landscape: "は横長である必要があります(現在のファイルは%{width}x%{height})"
51
51
  aspect_ratio_not_x_y: "は%{authorized_aspect_ratios}である必要があります(現在のファイルは%{width}x%{height})"
52
52
  aspect_ratio_invalid: "は無効なアスペクト比です(有効なアスペクト比は%{authorized_aspect_ratios}です)"
53
- file_not_processable: "有効なメディアファイルとして識別されない"
53
+ file_not_processable: "は無効なメディアファイルです"
@@ -19,21 +19,21 @@ pt-BR:
19
19
  total_file_size_not_between: "o tamanho total do arquivo deve estar entre %{min} e %{max} (o tamanho atual é %{total_file_size})"
20
20
  duration_not_less_than: "a duração deve ser inferior a %{max} (a duração atual é %{duration})"
21
21
  duration_not_less_than_or_equal_to: "a duração deve ser inferior ou igual a %{max} (a duração atual é %{duration})"
22
- duration_not_greater_than: "a duração tem de ser superior a %{min} (a duração actual é %{duration})"
22
+ duration_not_greater_than: "a duração tem de ser superior a %{min} (a duração atual é %{duration})"
23
23
  duration_not_greater_than_or_equal_to: "a duração deve ser maior ou igual a %{min} (a duração atual é %{duration})"
24
24
  duration_not_between: "a duração deve estar entre %{min} e %{max} (a duração atual é %{duration})"
25
25
  limit_out_of_range:
26
- zero: "nenhum ficheiro anexado (deve ter entre %{min} e %{max})"
27
- one: "apenas 1 ficheiro anexado (deve ter entre %{min} e %{max})"
28
- other: "o número total de ficheiros deve estar entre os ficheiros %{min} e %{max} (há ficheiros %{count} anexados)"
26
+ zero: "nenhum arquivo anexado (deve ter entre %{min} e %{max})"
27
+ one: "apenas 1 arquivo anexado (deve ter entre %{min} e %{max})"
28
+ other: "o número total de arquivos deve estar entre os arquivos %{min} e %{max} (há arquivos %{count} anexados)"
29
29
  limit_min_not_reached:
30
- zero: "sem ficheiros anexados (deve ter pelo menos %{min})"
31
- one: "apenas 1 ficheiro anexado (deve ter pelo menos %{min} ficheiros)"
32
- other: "%{count} ficheiros anexados (deve ter pelo menos %{min} ficheiros)"
30
+ zero: "sem arquivos anexados (deve ter pelo menos %{min})"
31
+ one: "apenas 1 arquivo anexado (deve ter pelo menos %{min} arquivos)"
32
+ other: "%{count} arquivos anexados (deve ter pelo menos %{min} arquivos)"
33
33
  limit_max_exceeded:
34
- zero: "sem ficheiros anexados (o máximo é %{max})"
35
- one: "muitos ficheiros anexados (o máximo é %{max}, obteve %{count})"
36
- other: "muitos ficheiros anexados (o máximo é %{max}, obteve %{count})"
34
+ zero: "sem arquivos anexados (o máximo é %{max})"
35
+ one: "muitos arquivos anexados (o máximo é %{max}, obteve %{count})"
36
+ other: "muitos arquivos anexados (o máximo é %{max}, obteve %{count})"
37
37
  media_metadata_missing: "não é um arquivo de mídia válido"
38
38
  dimension_min_not_included_in: "deve ser maior ou igual a %{width} x %{height} pixels"
39
39
  dimension_max_not_included_in: "deve ser menor ou igual a %{width} x %{height} pixels"
@@ -45,9 +45,9 @@ pt-BR:
45
45
  dimension_height_not_less_than_or_equal_to: "deve ter altura menor ou igual a %{length} pixels"
46
46
  dimension_width_not_equal_to: "deve ter largura igual a %{length} pixels"
47
47
  dimension_height_not_equal_to: "deve ter altura igual a %{length} pixels"
48
- aspect_ratio_not_square: "deve ser quadrado (o ficheiro actual é %{width}x%{height}px)"
49
- aspect_ratio_not_portrait: "deve ser portrait (o ficheiro actual é %{width}x%{height}px)"
50
- aspect_ratio_not_landscape: "deve ser paisagem (o ficheiro actual é %{width}x%{height}px)"
51
- aspect_ratio_not_x_y: "deve ser %{authorized_aspect_ratios} (o ficheiro actual é %{width}x%{height}px)"
48
+ aspect_ratio_not_square: "deve ser quadrado (o arquivo atual é %{width}x%{height}px)"
49
+ aspect_ratio_not_portrait: "deve ser portrait (o arquivo atual é %{width}x%{height}px)"
50
+ aspect_ratio_not_landscape: "deve ser paisagem (o arquivo atual é %{width}x%{height}px)"
51
+ aspect_ratio_not_x_y: "deve ser %{authorized_aspect_ratios} (o arquivo atual é %{width}x%{height}px)"
52
52
  aspect_ratio_invalid: "tem uma proporção inválida (as proporções válidas são %{authorized_aspect_ratios})"
53
53
  file_not_processable: "não é identificado como um arquivo de mídia válido"
@@ -191,7 +191,7 @@ module ActiveStorageValidations
191
191
  if content_type.to_s.match?(/\//)
192
192
  <<~ERROR_MESSAGE
193
193
  You must pass valid content types to the validator
194
- '#{content_type}' is not found in Marcel::TYPE_EXTS
194
+ '#{content_type}' is not found in Marcel content types (Marcel::TYPE_EXTS + Marcel::MAGIC)
195
195
  ERROR_MESSAGE
196
196
  else
197
197
  <<~ERROR_MESSAGE
@@ -215,7 +215,12 @@ module ActiveStorageValidations
215
215
  raise ArgumentError, "'image/jpg' is not a valid content type, you should use 'image/jpeg' instead"
216
216
  end
217
217
 
218
- Marcel::TYPE_EXTS[content_type.to_s] == nil
218
+ all_available_marcel_content_types.keys.exclude?(content_type.to_s)
219
+ end
220
+
221
+ def all_available_marcel_content_types
222
+ @all_available_marcel_content_types ||= Marcel::MAGIC.map {|dd| dd.first }
223
+ .each_with_object(Marcel::TYPE_EXTS) { |(k,v), h| h[k] = v unless h.key?(k) }
219
224
  end
220
225
 
221
226
  def invalid_extension?(content_type)
@@ -5,19 +5,44 @@ module ActiveStorageValidations
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
+ # This method returns the metadata that has been set by our gem.
9
+ # The metadata is stored in the blob's custom metadata. All keys are prefixed with 'asv_'
10
+ # to avoid conflicts with other metadata.
11
+ # It is not to set a active_storage_validation key equal to a a hash of our gem's metadata,
12
+ # because this would result in errors down the road with services such as S3.
13
+ #
14
+ # Because of how the metadata is stored, we need to convert the values from String
15
+ # to Integer or Boolean.
8
16
  def active_storage_validations_metadata
9
- metadata.dig('custom', 'active_storage_validations') || {}
17
+ metadata.dig('custom')
18
+ &.select { |key, _| key.to_s.start_with?('asv_') }
19
+ &.transform_keys { |key| key.to_s.delete_prefix('asv_') }
20
+ &.transform_values do |value|
21
+ case value
22
+ when /\A\d+\z/ then value.to_i
23
+ when /\A\d+\.\d+\z/ then value.to_f
24
+ when 'true' then true
25
+ when 'false' then false
26
+ else value
27
+ end
28
+ end || {}
10
29
  end
11
30
 
12
- def active_storage_validations_metadata=(value)
31
+ # This method sets the metadata that has been detected by our gem.
32
+ # The metadata is stored in the blob's custom metadata. All keys are prefixed with 'asv_'.
33
+ # We need to store values as String, because services such as S3 will not accept other types.
34
+ def merge_into_active_storage_validations_metadata(hash)
35
+ aws_compatible_metadata = normalize_active_storage_validations_metadata_for_aws(hash)
36
+
13
37
  metadata['custom'] ||= {}
14
- metadata['custom']['active_storage_validations'] = value
38
+ metadata['custom'].merge!(aws_compatible_metadata)
39
+
40
+ active_storage_validations_metadata
15
41
  end
16
42
 
17
- def merge_into_active_storage_validations_metadata(new_data)
18
- metadata['custom'] ||= {}
19
- metadata['custom']['active_storage_validations'] ||= {}
20
- metadata['custom']['active_storage_validations'].merge!(new_data)
43
+ def normalize_active_storage_validations_metadata_for_aws(hash)
44
+ hash.transform_keys { |key, _| key.to_s.start_with?('asv_') ? key : "asv_#{key}" }
45
+ .transform_values(&:to_s)
21
46
  end
22
47
  end
23
48
  end
@@ -27,7 +27,8 @@ module ActiveStorageValidations
27
27
  end
28
28
 
29
29
  def self.mock_metadata(attachment, metadata = {})
30
- mock = Struct.new(:metadata).new(metadata)
30
+ asv_metadata_available_keys = { width: nil, height: nil, duration: nil, content_type: nil }
31
+ mock = Struct.new(:metadata).new(asv_metadata_available_keys.merge(metadata)) # ensure all keys are present, and it does not raise while trying to access them
31
32
 
32
33
  stub_method(ActiveStorageValidations::Analyzer, :new, mock) do
33
34
  yield
@@ -3,8 +3,8 @@
3
3
  module ActiveStorageValidations
4
4
  class Railtie < ::Rails::Railtie
5
5
  initializer 'active_storage_validations.extend_active_storage_blob' do
6
- Rails.application.config.to_prepare do
7
- ActiveStorage::Blob.include(ActiveStorageValidations::ASVBlobMetadatable)
6
+ ActiveSupport.on_load(:active_storage_blob) do
7
+ include(ActiveStorageValidations::ASVBlobMetadatable)
8
8
  end
9
9
  end
10
10
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorageValidations
4
- VERSION = '2.0.0'
4
+ VERSION = '2.0.1'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_storage_validations
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Kasyanchuk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-24 00:00:00.000000000 Z
11
+ date: 2025-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob