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 +4 -4
- data/README.md +8 -5
- data/config/locales/ja.yml +2 -2
- data/config/locales/pt-BR.yml +14 -14
- data/lib/active_storage_validations/content_type_validator.rb +7 -2
- data/lib/active_storage_validations/extensors/asv_blob_metadatable.rb +32 -7
- data/lib/active_storage_validations/matchers.rb +2 -1
- data/lib/active_storage_validations/railtie.rb +2 -2
- data/lib/active_storage_validations/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bcd9bfe3dafea8c00d688ab4816cf69c9848455a8d94fa5a4727fcd2f8e707e1
|
4
|
+
data.tar.gz: 623d46eb1b8eeb8e835496f174ce4dda01dee9df2e21b2d2033c6db8f387dd17
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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`
|
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
|
-
|
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
|
-
|
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
|
-
|
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 (
|
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
|
|
data/config/locales/ja.yml
CHANGED
@@ -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: "は無効なメディアファイルです"
|
data/config/locales/pt-BR.yml
CHANGED
@@ -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
|
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
|
27
|
-
one: "apenas 1
|
28
|
-
other: "o número total de
|
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
|
31
|
-
one: "apenas 1
|
32
|
-
other: "%{count}
|
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
|
35
|
-
one: "muitos
|
36
|
-
other: "muitos
|
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
|
49
|
-
aspect_ratio_not_portrait: "deve ser portrait (o
|
50
|
-
aspect_ratio_not_landscape: "deve ser paisagem (o
|
51
|
-
aspect_ratio_not_x_y: "deve ser %{authorized_aspect_ratios} (o
|
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
|
-
|
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'
|
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
|
-
|
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']
|
38
|
+
metadata['custom'].merge!(aws_compatible_metadata)
|
39
|
+
|
40
|
+
active_storage_validations_metadata
|
15
41
|
end
|
16
42
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
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
|
-
|
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
|
-
|
7
|
-
|
6
|
+
ActiveSupport.on_load(:active_storage_blob) do
|
7
|
+
include(ActiveStorageValidations::ASVBlobMetadatable)
|
8
8
|
end
|
9
9
|
end
|
10
10
|
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.
|
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-
|
11
|
+
date: 2025-01-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|