active_storage_validations 2.0.0 → 2.0.2

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: 77abbd375626f37f0ecae9dc8710249bde59202cb71ea2e925b5c2f026afd025
4
+ data.tar.gz: 5d694245be38c6f3ea7f3e1d157974e9bd5e63a67be276e25fccde072e48a52e
5
5
  SHA512:
6
- metadata.gz: 7cf99126987df855833f01535c25fc9c7d3e2585d562f6a3bd063a3bb37563a8bd0d78e8e0119930e2c9370ba2425128c169f50a11b8d11d3649959a72092fdb
7
- data.tar.gz: c852f751c20ce7a9e38e1fe906faf47b9cf8fcf0ce8ce839717c245aca94a4a2c52c7440eb7ee797853d8e9a11c28214d95a956cc025460728501d9524b307c3
6
+ metadata.gz: c7e1a56bf9eb906d7bb5c05c2d89d901bbe93ca63ed86423276edb1eb22078f35dd70b40c997c9b0ed7e53df7364c8ed264ca627cf415bf74b8054e1a9d19f00
7
+ data.tar.gz: 024c5f428d64ed7462e395cea6add2c121b393ccd7d89992a0304fe44e97cdba6eb99b9b5b6ace0a4a199e759d31e08dd2eb1427c3aee4acfa6f624fe8238027
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
@@ -243,25 +243,30 @@ end
243
243
  #### Content type shorthands
244
244
 
245
245
  If you choose to use a content_type 'shorthand' (like `png`), note that it will be converted to a full content type using `Marcel::MimeType.for` under the hood. Therefore, you should check if the content_type is registered by [`Marcel::EXTENSIONS`](https://github.com/rails/marcel/blob/main/lib/marcel/tables.rb). If it's not, you can register it by adding the following code to your `config/initializers/mime_types.rb` file:
246
+
246
247
  ```ruby
247
248
  Marcel::MimeType.extend "application/ino", extensions: %w(ino), parents: "text/plain" # Registering arduino INO files
248
249
  ```
249
250
 
251
+ Be sure to at least include one the the `extensions`, `parents` or `magic` option, otherwise the content type will not be registered.
252
+
250
253
  #### Content type spoofing protection
251
254
 
252
255
  By default, the gem does not prevent content type spoofing. You can enable it by setting the `spoofing_protection` option to `true` in your validator options.
253
256
 
254
257
  <details>
255
258
  <summary>
256
- ##### What is content type spoofing?
259
+ What is content type spoofing?
257
260
  </summary>
261
+
258
262
  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
263
  </details>
260
264
 
261
265
  <details>
262
266
  <summary>
263
- ##### How do we prevent it?
267
+ How do we prevent it?
264
268
  </summary>
269
+
265
270
  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
271
 
267
272
  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 +274,9 @@ Take note that the `file` analyzer will not find the exactly same content type a
269
274
 
270
275
  <details>
271
276
  <summary>
272
- ##### Edge cases
277
+ Edge cases
273
278
  </summary>
279
+
274
280
  The difficulty to accurately predict a mime type may generate false positives, if so there are two solutions available:
275
281
  - 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
282
  - 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 +714,7 @@ en:
708
714
  file_not_processable: "is not identified as a valid media file"
709
715
  ```
710
716
 
711
- Other translation files are available (here)[https://github.com/igorkasyanchuk/active_storage_validations/tree/master/config/locales].
717
+ Other translation files are available [here](https://github.com/igorkasyanchuk/active_storage_validations/tree/master/config/locales).
712
718
 
713
719
  ## Test matchers
714
720
 
@@ -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,14 @@ 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.exclude?(content_type.to_s)
219
+ end
220
+
221
+ def all_available_marcel_content_types
222
+ @all_available_marcel_content_types ||= Marcel::TYPE_EXTS
223
+ .keys
224
+ .push(*Marcel::MAGIC.map(&:first))
225
+ .tap(&:uniq!)
219
226
  end
220
227
 
221
228
  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
@@ -2,9 +2,15 @@
2
2
 
3
3
  module ActiveStorageValidations
4
4
  class Railtie < ::Rails::Railtie
5
+ initializer 'active_storage_validations.configure', after: :load_config_initializers do
6
+ ActiveSupport.on_load(:active_record) do
7
+ send :include, ActiveStorageValidations
8
+ end
9
+ end
10
+
5
11
  initializer 'active_storage_validations.extend_active_storage_blob' do
6
- Rails.application.config.to_prepare do
7
- ActiveStorage::Blob.include(ActiveStorageValidations::ASVBlobMetadatable)
12
+ ActiveSupport.on_load(:active_storage_blob) do
13
+ include(ActiveStorageValidations::ASVBlobMetadatable)
8
14
  end
9
15
  end
10
16
  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.2'
5
5
  end
@@ -14,8 +14,6 @@ require 'active_storage_validations/analyzer/audio_analyzer'
14
14
  require 'active_storage_validations/extensors/asv_blob_metadatable'
15
15
  require 'active_storage_validations/extensors/asv_marcelable'
16
16
 
17
- require 'active_storage_validations/railtie'
18
- require 'active_storage_validations/engine'
19
17
  require 'active_storage_validations/attached_validator'
20
18
  require 'active_storage_validations/content_type_validator'
21
19
  require 'active_storage_validations/limit_validator'
@@ -26,6 +24,5 @@ require 'active_storage_validations/processable_file_validator'
26
24
  require 'active_storage_validations/size_validator'
27
25
  require 'active_storage_validations/total_size_validator'
28
26
 
29
- ActiveSupport.on_load(:active_record) do
30
- send :include, ActiveStorageValidations
31
- end
27
+ require 'active_storage_validations/engine'
28
+ require 'active_storage_validations/railtie'
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.2
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-02-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob