ratonvirus 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2858dfe945bf74c96269185692c5737c5469adf2404f144aeb4f04e9ae546687
4
+ data.tar.gz: 07262f10a07c229a92fa8e0b7f7747cff8f33d538804048706b0bd60650d8aeb
5
+ SHA512:
6
+ metadata.gz: 0f039177a939be1eb63a0fc00c626b533dffafae9c906c607e22bf968247e602380304e6075b636d3d08daaa98815adaca5cafa8d3cf87572fd232a15cb3c1c1
7
+ data.tar.gz: 041f921088ba88398efddd3d437a38360435b88b4a0ab424047768c59b3ecabda69ca1597a5e0a6ac555a818946713b18492597858b29c167d755c3811ccafb4
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # v0.1.0
2
+
3
+ Initial public release.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2018 Mainio Tech Ltd.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,247 @@
1
+ # <img alt="Ratonvirus" src="https://raw.githubusercontent.com/mainio/ratonvirus/master/ratonvirus.png" width="600">
2
+
3
+ Rails antivirus made easy.
4
+ Developed by [Mainio Tech](https://www.mainiotech.fi/).
5
+
6
+ [![Build Status](https://travis-ci.org/mainio/ratonvirus.svg?branch=master)](https://travis-ci.org/mainio/ratonvirus)
7
+ [![codecov](https://codecov.io/gh/mainio/ratonvirus/branch/master/graph/badge.svg)](https://codecov.io/gh/mainio/ratonvirus)
8
+
9
+ Ratonvirus allows your Rails application to rat on the viruses that your users
10
+ try to upload to your site. This works through Rails validators that you can
11
+ easily attach to your models.
12
+
13
+ The purpose of Ratonvirus is to act as the glue that binds your Rails
14
+ application to a virus scanner and file storage engine of your choise.
15
+
16
+ Setting up scanning for files attached to models:
17
+
18
+ ```ruby
19
+ class Model < ApplicationRecord
20
+ # ...
21
+ validates :file, antivirus: true
22
+ # ...
23
+ end
24
+ ```
25
+
26
+ Running manual scans:
27
+
28
+ ```ruby
29
+ puts "File contains a virus" if Ratonvirus.scanner.virus?('/path/to/file.pdf')
30
+ ```
31
+
32
+ Manual scanning works e.g. for file uploads, file object and file paths.
33
+
34
+ Ratonvirus works well with Active Storage out of the box. Support for
35
+ CarrierWave is provided through an additional gem (see details well).
36
+
37
+ ## When to use this?
38
+
39
+ This gem can be handy if you want to:
40
+
41
+ - Scan files for viruses in Ruby, especially in Rails
42
+ - Make the scanning logic agnostic of the
43
+ * scanner implementation (e.g. ClamAV)
44
+ * file storage (e.g. Active Storage, CarrierWave, no storage engine)
45
+ - Separate testing code for the scanner implementation or the file storage
46
+ cleanly into its own place.
47
+
48
+ ## Prerequisites
49
+
50
+ There is no fully Ruby-based virus scanner available on the market for a good
51
+ reason: it is a heavy task. Therefore, this gem may not work as automagically
52
+ as you are used to with many other gems.
53
+
54
+ You will need to setup a virus scanner on your machine. If you have done that
55
+ before, configuration should be rather simple. Instructions are provided for
56
+ the open source ClamAV scanner.
57
+
58
+ This gem ships with an example
59
+ [EICAR file](https://en.wikipedia.org/wiki/EICAR_test_file) scanner to test out
60
+ the configuration process. This scanner allows you to test the functionality of
61
+ this gem with no external requirements but it should not be used in production
62
+ environments.
63
+
64
+ ## Installation
65
+
66
+ Add this line to your application's Gemfile:
67
+
68
+ ```ruby
69
+ gem 'ratonvirus'
70
+ ```
71
+
72
+ And then execute:
73
+
74
+ ```bash
75
+ $ bundle
76
+ ```
77
+
78
+ Add this initializer to your application's `config/initializers/ratonvirus.rb`:
79
+
80
+ ```ruby
81
+ # config/initializers/ratonvirus.rb
82
+ Ratonvirus.configure do |config|
83
+ config.scanner = :eicar
84
+ config.storage = :active_storage
85
+ end
86
+ ```
87
+
88
+ After installation, test that the gem is loaded properly in your it is ready to
89
+ do a sample validation:
90
+
91
+ ```bash
92
+ $ bundle exec rails ratonvirus:test
93
+ ```
94
+
95
+ This command should show the following message when correctly installed:
96
+
97
+ ```
98
+ Ratonvirus correctly configured.
99
+ ```
100
+
101
+ **NOTE:**
102
+
103
+ By default Ratonvirus is set to remove all infected files that it detects after
104
+ scanning them. If you want to remove this functionality, please refer to the
105
+ [Scanner addons](doc/index.md#scanner-addons) section of the developer
106
+ documentation.
107
+
108
+ ## Usage
109
+
110
+ ### Applying the antivirus validator to your models
111
+
112
+ In order to apply the antivirus validator, you need to have your file uploads
113
+ handled by a storage backend such as Active Storage. Examples are provided below
114
+ for the most common options.
115
+
116
+ #### Example with Active Storage
117
+
118
+ Your model should look similar to this:
119
+
120
+ ```ruby
121
+ class YourModel < ApplicationRecord
122
+ has_one_attached :file
123
+ validates :file, antivirus: true # Add this for antivirus validation
124
+ end
125
+ ```
126
+
127
+ #### Example with CarrierWave
128
+
129
+ Your model should look similar to this:
130
+
131
+ ```ruby
132
+ class YourModel < ApplicationRecord
133
+ mount_uploader :file, YourUploader
134
+ validates :file, antivirus: true # Add this for antivirus validation
135
+ end
136
+ ```
137
+
138
+ Please note the extra configuration you need for CarrierWave from the
139
+ configuration examples.
140
+
141
+ ### Manually checking for viruses
142
+
143
+ For the manual scans to work you need to configure the correct storage backend
144
+ first that accepts these resources:
145
+
146
+ ```ruby
147
+ # config/initializers/ratonvirus.rb
148
+
149
+ # When scanning files or file paths only
150
+ Ratonvirus.configure do |config|
151
+ config.storage = :filepath
152
+ end
153
+
154
+ # When scanning files, file paths or active storage resources
155
+ Ratonvirus.configure do |config|
156
+ config.storage = :multi, [:filepath, :active_storage]
157
+ end
158
+ ```
159
+
160
+ In case you want to manually scan for viruses, you can use any of the following
161
+ examples:
162
+
163
+ ```ruby
164
+ # It is a good idea to check first that the scanner is available.
165
+ if Ratonvirus.scanner.available?
166
+ # Scanning a file path
167
+ path = '/path/to/file.pdf'
168
+ if Ratonvirus.scanner.virus?(path)
169
+ puts "There is a virus at #{path}"
170
+ end
171
+
172
+ # Scanning a File object
173
+ file = File.new(path)
174
+ if Ratonvirus.scanner.virus?(file)
175
+ puts "There is a virus at #{file.path}"
176
+ end
177
+ end
178
+ ```
179
+
180
+ ### Sample configurations
181
+
182
+ Here are few sample configurations to speed up the configuration process.
183
+
184
+ #### Active Storage and ClamAV
185
+
186
+ Gemfile:
187
+
188
+ ```ruby
189
+ gem 'ratonvirus'
190
+ gem 'ratonvirus-clamby'
191
+ ```
192
+
193
+ Initializer:
194
+
195
+ ```ruby
196
+ # config/initializers/ratonvirus.rb
197
+ Ratonvirus.configure do |config|
198
+ config.scanner = :clamby
199
+ config.storage = :active_storage
200
+ end
201
+ ```
202
+
203
+ Model:
204
+
205
+ ```ruby
206
+ class YourModel < ApplicationRecord
207
+ has_one_attached :file
208
+ validates :file, antivirus: true
209
+ end
210
+ ```
211
+
212
+ #### CarrierWave and ClamAV
213
+
214
+ Gemfile:
215
+
216
+ ```ruby
217
+ gem 'ratonvirus'
218
+ gem 'ratonvirus-clamby'
219
+ ```
220
+
221
+ Initializer:
222
+
223
+ ```ruby
224
+ # config/initializers/ratonvirus.rb
225
+ Ratonvirus.configure do |config|
226
+ config.scanner = :clamby
227
+ config.storage = :carrierwave
228
+ end
229
+ ```
230
+
231
+ Model:
232
+
233
+ ```ruby
234
+ class YourModel < ApplicationRecord
235
+ mount_uploader :file, YourUploader
236
+ validates :file, antivirus: true
237
+ end
238
+ ```
239
+
240
+ ## Further configuration and development
241
+
242
+ For further information about the configurations and how to create custom
243
+ scanners, please refer to the [documentation](doc/index.md).
244
+
245
+ ## License
246
+
247
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AntivirusValidator < ActiveModel::EachValidator
4
+ def validate_each(record, attribute, value)
5
+ # Avoid unnecessary scans in case
6
+ # a) the storage backend does not accept the resource
7
+ # b) the attribute has not changed
8
+ storage = Ratonvirus.storage
9
+ return unless storage.accept?(value)
10
+ return unless storage.changed?(record, attribute)
11
+
12
+ # Only scan if the scanner is available
13
+ scanner = Ratonvirus.scanner
14
+ return unless scanner.available?
15
+
16
+ if scanner.virus?(value)
17
+ if scanner.errors.any?
18
+ scanner.errors.each do |err|
19
+ record.errors.add attribute, err
20
+ end
21
+ else
22
+ record.errors.add attribute, :antivirus_virus_detected
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,6 @@
1
+ en:
2
+ errors:
3
+ messages:
4
+ antivirus_virus_detected: 'contains a virus'
5
+ antivirus_client_error: 'could not be processed for virus scan'
6
+ antivirus_file_not_found: 'could not be found for virus scan'
@@ -0,0 +1,6 @@
1
+ fi:
2
+ errors:
3
+ messages:
4
+ antivirus_virus_detected: 'sisältää viruksen'
5
+ antivirus_client_error: 'ei voitu käsitellä virustarkistusta varten'
6
+ antivirus_file_not_found: 'ei ollut saatavilla virustarkistusta varten'
@@ -0,0 +1,6 @@
1
+ sv:
2
+ errors:
3
+ messages:
4
+ antivirus_virus_detected: 'innehåller ett virus'
5
+ antivirus_client_error: 'kunde inte behandlas för virusskanning'
6
+ antivirus_file_not_found: 'kunde inte hittas för virusskanning'
data/lib/ratonvirus.rb ADDED
@@ -0,0 +1,109 @@
1
+ require "fileutils"
2
+ require "active_support"
3
+ require "digest"
4
+
5
+ require_relative "ratonvirus/version"
6
+ require_relative "ratonvirus/error"
7
+ require_relative "ratonvirus/processable"
8
+ require_relative "ratonvirus/support/backend"
9
+ require_relative "ratonvirus/scanner/support/callbacks"
10
+ require_relative "ratonvirus/scanner/base"
11
+ require_relative "ratonvirus/scanner/eicar"
12
+ require_relative "ratonvirus/scanner/addon/remove_infected"
13
+ require_relative "ratonvirus/storage/base"
14
+ require_relative "ratonvirus/storage/filepath"
15
+ require_relative "ratonvirus/storage/active_storage"
16
+ require_relative "ratonvirus/storage/carrierwave"
17
+ require_relative "ratonvirus/storage/multi"
18
+
19
+ require_relative "ratonvirus/engine" if defined?(Rails)
20
+
21
+ module Ratonvirus
22
+ extend Ratonvirus::Support::Backend
23
+
24
+ # Usage:
25
+ # Ratonvirus.configure do |config|
26
+ # config.scanner = :eicar
27
+ # config.storage = :active_storage
28
+ # config.addons = [:remove_infected]
29
+ # end
30
+ def self.configure
31
+ yield self
32
+ end
33
+
34
+ # Usage (set):
35
+ # Ratonvirus.scanner = :eicar
36
+ # Ratonvirus.scanner = :eicar, {option: 'value'}
37
+ # Ratonvirus.scanner = Ratonvirus::Scanner::Eicar.new
38
+ # Ratonvirus.scanner = Ratonvirus::Scanner::Eicar.new({option: 'value'})
39
+ #
40
+ # Usage (get):
41
+ # Ratonvirus.scanner
42
+ #
43
+ # Usage (destroy):
44
+ # Ratonvirus.destroy_scanner
45
+ define_backend :scanner, 'Scanner'
46
+
47
+ # Usage (set):
48
+ # Ratonvirus.storage = :active_storage
49
+ # Ratonvirus.storage = :active_storage, {option: 'value'}
50
+ # Ratonvirus.storage = Ratonvirus::Storage::ActiveStorage.new
51
+ # Ratonvirus.storage = Ratonvirus::Storage::ActiveStorage.new({option: 'value'})
52
+ #
53
+ # Usage (get):
54
+ # Ratonvirus.storage
55
+ #
56
+ # Usage (destroy):
57
+ # Ratonvirus.destroy_storage
58
+ define_backend :storage, 'Storage'
59
+
60
+ # Resets Ratonvirus to its initial state and configuration
61
+ def self.reset
62
+ # Default addons
63
+ @addons = [
64
+ ActiveSupport::Inflector.constantize(
65
+ "#{self.name}::Scanner::Addon::RemoveInfected"
66
+ ),
67
+ ]
68
+
69
+ self.destroy_scanner
70
+ self.destroy_storage
71
+ end
72
+
73
+ def self.addons
74
+ @addons
75
+ end
76
+
77
+ def self.addons=(addons)
78
+ @addons = []
79
+ addons.each do |addon|
80
+ self.add_addon addon
81
+ end
82
+ end
83
+
84
+ def self.add_addon(addon)
85
+ addon_cls = addon_class(addon)
86
+ unless @addons.include?(addon_cls)
87
+ @addons << addon_cls
88
+ end
89
+ end
90
+
91
+ def self.remove_addon(addon)
92
+ addon_cls = addon_class(addon)
93
+ @addons.delete(addon_cls)
94
+ end
95
+
96
+ # private
97
+ def self.addon_class(type)
98
+ return type if type.is_a?(Class)
99
+
100
+ subclass = ActiveSupport::Inflector.camelize(type.to_s)
101
+ ActiveSupport::Inflector.constantize(
102
+ "#{self.name}::Scanner::Addon::#{subclass}"
103
+ )
104
+ end
105
+ private_class_method :addon_class
106
+
107
+ # Reset the utility to start with
108
+ reset
109
+ end
@@ -0,0 +1,6 @@
1
+ # Binding it to rails to make the app and config folders available in the Rails
2
+ # load paths.
3
+ module Ratonvirus
4
+ class Engine < ::Rails::Engine
5
+ end
6
+ end