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 +4 -4
- data/README.md +11 -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 +9 -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 +8 -2
- data/lib/active_storage_validations/version.rb +1 -1
- data/lib/active_storage_validations.rb +2 -5
- 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: 77abbd375626f37f0ecae9dc8710249bde59202cb71ea2e925b5c2f026afd025
|
4
|
+
data.tar.gz: 5d694245be38c6f3ea7f3e1d157974e9bd5e63a67be276e25fccde072e48a52e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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`
|
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
|
-
|
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
|
-
|
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
|
-
|
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 (
|
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
|
|
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,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
|
-
|
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'
|
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
|
@@ -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
|
-
|
7
|
-
|
12
|
+
ActiveSupport.on_load(:active_storage_blob) do
|
13
|
+
include(ActiveStorageValidations::ASVBlobMetadatable)
|
8
14
|
end
|
9
15
|
end
|
10
16
|
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
|
-
|
30
|
-
|
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.
|
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
|
11
|
+
date: 2025-02-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|