vtapi 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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in vtapi.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 masatanish
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,75 @@
1
+ # VtAPI
2
+
3
+ Ruby gem for VirusTotal Public API v2.0
4
+
5
+ https://www.virustotal.com/en/documentation/public-api/
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'vtapi'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install vtapi
20
+
21
+ ## Usage
22
+ ### Prepare
23
+ ```ruby
24
+ # initialize
25
+ api = VtAPI.new('-- Your API KEY--')
26
+ ```
27
+
28
+ ### File Report
29
+ ```ruby
30
+ # retrieve file report by file hash(SHA256, SHA1, MD5)
31
+ resp = api.file_report('00ce460c8b337110912066f746731a916e85bf1d7f4b44f09ca3cc39f9b52a98')
32
+
33
+ puts resp.response_code # 1: OK, 0: result doesn't exist, -2: still queued
34
+
35
+ # resp is a instance of VtAPI::Response class
36
+ puts resp.positives # num of positives
37
+ puts resp.scan_results # {"McAfee"=>nil, "Symantec"=>"Android.ZertSecurity", ... }
38
+ ```
39
+
40
+ ### File Upload
41
+ ```ruby
42
+ # read file
43
+ data = File.open(some_path, 'rb') {|f| f.read }
44
+
45
+ # upload data
46
+ resp = api.file_scan(data)
47
+
48
+ # confirm response_code
49
+ puts resp.response_code # 1: OK, 0: result doesn't exist, -2: still queued
50
+ ```
51
+
52
+ ## Features
53
+ ### Supported API
54
+ * file/scan
55
+ * file/resan
56
+ * file/report
57
+
58
+ ### not implemented yet
59
+ * url/scan
60
+ * url/report
61
+ * ip-address/report
62
+ * domain/report
63
+
64
+ ### unsupported API
65
+ * comments/puts
66
+
67
+ ## Contributing
68
+
69
+ 1. Fork it
70
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
71
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
72
+ 4. Push to the branch (`git push origin my-new-feature`)
73
+ 5. Create new Pull Request
74
+
75
+
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task :default => :spec
data/lib/vtapi/api.rb ADDED
@@ -0,0 +1,50 @@
1
+ require 'rest-client'
2
+ require 'tempfile'
3
+ require 'json'
4
+
5
+ class VtAPI
6
+ # base URL of the VirusTotal Public API v2.0
7
+ BASE_URL = 'https://www.virustotal.com/vtapi/v2/'
8
+
9
+ attr_reader :apikey
10
+
11
+ def initialize(apikey)
12
+ @apikey = apikey
13
+ end
14
+
15
+ def file_scan(data)
16
+ # TODO: set filename or file path
17
+ tmp = Tempfile.open('tmp')
18
+ tmp.write data
19
+ def tmp.content_type
20
+ 'application/octet-stream'
21
+ end
22
+ tmp.pos=0
23
+ http_post('file/scan', file: tmp, multipart: true)
24
+ end
25
+
26
+ def file_rescan(resource)
27
+ http_post('file/rescan', resource: resource)
28
+ end
29
+
30
+ def file_report(resource)
31
+ http_post('file/report', resource: resource)
32
+ end
33
+
34
+
35
+ def http_post(path, params = {})
36
+ uri = BASE_URL + path
37
+ params['apikey'] = @apikey
38
+ resp = RestClient.post(uri, params) do |resp, req, result, &block|
39
+ case resp.code
40
+ when 204
41
+ raise ExceedAPILimit, "you exceed the public API request rate limit: key[#{@apikey}]"
42
+ when 403
43
+ raise AuthError, "you do not have the required priviledges: key[#{@apikey}]"
44
+ else
45
+ resp.return!(req, result, &block)
46
+ end
47
+ end
48
+ Response.new(resp.body)
49
+ end
50
+ end
@@ -0,0 +1,6 @@
1
+ class VtAPI
2
+ # 204 exceed the public API request rate limit
3
+ class ExceedAPILimit < StandardError; end
4
+ # 403 Forbidden
5
+ class AuthError < StandardError; end
6
+ end
@@ -0,0 +1,38 @@
1
+ require 'json'
2
+ require 'time'
3
+
4
+ class VtAPI
5
+ class Response
6
+ def initialize(response_body)
7
+ @json = JSON.parse(response_body)
8
+ @json.keys.each do |key|
9
+ Response.class_eval {
10
+ define_method key.to_s do |*args|
11
+ @json[key]
12
+ end
13
+ }
14
+ end
15
+ @json['scan_date'] = Time.parse(@json['scan_date'] + "UTC") if @json.has_key? 'scan_date'
16
+ end
17
+
18
+ def keys
19
+ @json.keys
20
+ end
21
+
22
+ def positive_threats
23
+ Hash[@json.fetch('scans', {}).select{|k,v| v['detected'] }.map{|k,v| [k, v['result']] }]
24
+ end
25
+
26
+ def positive_brands
27
+ @json.fetch('scans', {}).select{|k,v| v['detected'] }.keys
28
+ end
29
+
30
+ def scan_results
31
+ Hash[@json.fetch('scans', {}).map{|k,v| [k, v['result']] }]
32
+ end
33
+
34
+ def [](key)
35
+ @json.fetch(key.to_s) # raise KeyError when key doesn't exist.
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,3 @@
1
+ module Vtapi
2
+ VERSION = "0.1.0"
3
+ end
data/lib/vtapi.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'vtapi/version'
2
+ require 'vtapi/exception'
3
+ require 'vtapi/response'
4
+ require 'vtapi/api'
5
+
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ describe VtAPI::Response do
4
+ context 'with sample file_report response' do
5
+ let(:sample_response) {
6
+ "{\"scans\":{\"McAfee\":{\"detected\":false,\"version\":\"5.400.0.1158\",\"result\":null,\"update\":\"20130512\"},\"Symantec\":{\"detected\":true,\"version\":\"20121.3.0.76\",\"result\":\"Android.ZertSecurity\",\"update\":\"20130512\"},\"Kaspersky\":{\"detected\":true,\"version\":\"9.0.0.837\",\"result\":\"HEUR:Trojan-Banker.AndroidOS.Zitmo.a\",\"update\":\"20130512\"},\"TrendMicro\":{\"detected\":false,\"version\":\"9.740.0.1012\",\"result\":null,\"update\":\"20130512\"},\"Microsoft\":{\"detected\":false,\"version\":\"1.9402\",\"result\":null,\"update\":\"20130512\"}},\"scan_id\":\"00ce460c8b337110912066f746731a916e85bf1d7f4b44f09ca3cc39f9b52a98-1368320515\",\"sha1\":\"e1b727b3e9336033606df79eeba03dd218b56c20\",\"resource\":\"00ce460c8b337110912066f746731a916e85bf1d7f4b44f09ca3cc39f9b52a98\",\"response_code\":1,\"scan_date\":\"2013-05-12 01:01:55\",\"permalink\":\"https://www.virustotal.com/file/00ce460c8b337110912066f746731a916e85bf1d7f4b44f09ca3cc39f9b52a98/analysis/1368320515/\",\"verbose_msg\":\"Scan finished, scan information embedded in this object\",\"total\":46,\"positives\":22,\"sha256\":\"00ce460c8b337110912066f746731a916e85bf1d7f4b44f09ca3cc39f9b52a98\",\"md5\":\"1cf41bdc0fdd409774eb755031a6f49d\"}"
7
+ }
8
+ let(:response) { VtAPI::Response.new(sample_response) }
9
+
10
+ describe '#md5' do
11
+ it { expect(response.md5).to eq '1cf41bdc0fdd409774eb755031a6f49d' }
12
+ end
13
+
14
+ describe '#sha1' do
15
+ it { expect(response.sha1).to eq 'e1b727b3e9336033606df79eeba03dd218b56c20' }
16
+ end
17
+
18
+ describe '#sha256' do
19
+ it { expect(response.sha256).to eq '00ce460c8b337110912066f746731a916e85bf1d7f4b44f09ca3cc39f9b52a98' }
20
+ end
21
+
22
+ describe '#scan_date' do
23
+ it { expect(response.scan_date).to be_a Time }
24
+ end
25
+
26
+ describe '#total' do
27
+ it { expect(response.total).to eq 46 }
28
+ end
29
+
30
+ describe '#positives' do
31
+ it { expect(response.positives).to eq 22 }
32
+ end
33
+
34
+ describe '#scan_id' do
35
+ it { expect(response.scan_id).to eq '00ce460c8b337110912066f746731a916e85bf1d7f4b44f09ca3cc39f9b52a98-1368320515' }
36
+ end
37
+
38
+ describe '#resource' do
39
+ it { expect(response.resource).to eq '00ce460c8b337110912066f746731a916e85bf1d7f4b44f09ca3cc39f9b52a98' }
40
+ end
41
+
42
+ describe '#keys' do
43
+ it { expect(response.keys).to match_array ['scans', 'scan_id', 'sha1', 'resource', 'response_code', 'scan_date', 'permalink', 'verbose_msg', 'total', 'positives', 'sha256', 'md5'] }
44
+ end
45
+
46
+ describe '#positive_brands' do
47
+ subject { response.positive_brands }
48
+ it { expect(subject).to match_array ["Kaspersky", "Symantec"] }
49
+ end
50
+
51
+ describe '#positive_threats' do
52
+ subject { response.positive_threats }
53
+ it { expect(subject).to eq({"Symantec"=>"Android.ZertSecurity", "Kaspersky"=>"HEUR:Trojan-Banker.AndroidOS.Zitmo.a"}) }
54
+ end
55
+
56
+ describe '#scan_results' do
57
+ subject { response.scan_results }
58
+ it { expect(subject).to eq({"McAfee"=>nil, "Symantec"=>"Android.ZertSecurity", "Kaspersky"=>"HEUR:Trojan-Banker.AndroidOS.Zitmo.a", "TrendMicro"=>nil, "Microsoft"=>nil}) }
59
+ end
60
+ end
61
+
62
+ context 'with no scan result' do
63
+ describe '#positive_brands' do
64
+ pending 'not implemented yet'
65
+ end
66
+
67
+ describe '#positive_threats' do
68
+ pending 'not implemented yet'
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,11 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
4
+ require 'rspec'
5
+ require 'webmock/rspec'
6
+
7
+ require 'vtapi'
8
+
9
+ RSpec.configure do |config|
10
+
11
+ end
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+
3
+ describe VtAPI do
4
+ before do
5
+ #RestClient.log = STDERR
6
+ end
7
+
8
+ let(:apikey) { 'test apikey' }
9
+ let(:api) { VtAPI.new(apikey) }
10
+ subject { api }
11
+
12
+ describe '#apikey' do
13
+ it { expect(subject.apikey).to eq apikey }
14
+ end
15
+
16
+ describe '#file_scan' do
17
+ let(:sample_response) { '{}' }
18
+ let(:api_url) { 'https://www.virustotal.com/vtapi/v2/file/scan' }
19
+ subject { api.file_scan('binary data') }
20
+ it "should connect to virustotal.com with 'multipart/form-data' Content-Type" do
21
+ stub_request(:post, api_url)
22
+ .with(:headers => { "Content-Type" => /^multipart\/form-data;.*/ })
23
+ .to_return(:body => sample_response)
24
+ subject
25
+ end
26
+ it 'should include data part in body' do
27
+ stub_request(:post, api_url)
28
+ .with(:body => /name="file"; filename="tmp.*\r\n/ )
29
+ .to_return(:body => sample_response)
30
+ subject
31
+ end
32
+ it 'should include posted binary data in body' do
33
+ stub_request(:post, api_url)
34
+ .with(:body => /binary data/ )
35
+ .to_return(:body => sample_response)
36
+ subject
37
+ end
38
+ end
39
+
40
+ describe '#file_rescan' do
41
+ let(:sample_response) { '{}' }
42
+ let(:api_url) { 'https://www.virustotal.com/vtapi/v2/file/rescan' }
43
+ let(:resource) { 'ff' * 32 }
44
+ subject { api.file_rescan(resource) }
45
+ it "should connect to 'https://www.virustotal.com/vtapi/v2/file/rescan'" do
46
+ stub_request(:post, api_url)
47
+ .with(:body => {'resource' => resource, 'apikey' => apikey} )
48
+ .to_return(:body => sample_response, :status => 200)
49
+ subject
50
+ end
51
+ end
52
+
53
+ describe '#file_report' do
54
+ let(:sample_response) { '{}' }
55
+ let(:api_url) { 'https://www.virustotal.com/vtapi/v2/file/report' }
56
+ let(:resource) { 'ff' * 32 }
57
+ subject { api.file_report(resource) }
58
+ it "should connect to 'https://www.virustotal.com/vtapi/v2/file/report'" do
59
+ stub_request(:post, api_url)
60
+ .with(:body => {'resource' => resource, 'apikey' => apikey} )
61
+ .to_return(:body => sample_response, :status => 200)
62
+ subject
63
+ end
64
+
65
+ context 'when server returns 204' do
66
+ it do
67
+ stub_request(:post, api_url).to_return(:status => 204)
68
+ expect{ subject }.to raise_error(VtAPI::ExceedAPILimit)
69
+ end
70
+ end
71
+ context 'when server returns 403' do
72
+ it do
73
+ stub_request(:post, api_url).to_return(:status => 403)
74
+ expect{ subject }.to raise_error(VtAPI::AuthError)
75
+ end
76
+ end
77
+ end
78
+ end
data/vtapi.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'vtapi/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "vtapi"
8
+ spec.version = Vtapi::VERSION
9
+ spec.authors = ["masatanish"]
10
+ spec.email = ["masatanish@gmail.com"]
11
+ spec.description = %q{gem for VirusTotal Public API version2.0.}
12
+ spec.summary = %q{gem for VirusTotal Public API version2.0.}
13
+ spec.homepage = "https://github.com/masatanish/vtapi"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec", "~>2.13.0"
24
+ spec.add_development_dependency "webmock", "~>1.11.0"
25
+
26
+ spec.add_dependency "rest-client", "~>1.6.7"
27
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vtapi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - masatanish
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: &70305944392860 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70305944392860
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &70305944392140 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70305944392140
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &70305944391080 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 2.13.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70305944391080
47
+ - !ruby/object:Gem::Dependency
48
+ name: webmock
49
+ requirement: &70305944390140 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.11.0
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70305944390140
58
+ - !ruby/object:Gem::Dependency
59
+ name: rest-client
60
+ requirement: &70305944388880 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 1.6.7
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70305944388880
69
+ description: gem for VirusTotal Public API version2.0.
70
+ email:
71
+ - masatanish@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - Gemfile
78
+ - LICENSE.txt
79
+ - README.md
80
+ - Rakefile
81
+ - lib/vtapi.rb
82
+ - lib/vtapi/api.rb
83
+ - lib/vtapi/exception.rb
84
+ - lib/vtapi/response.rb
85
+ - lib/vtapi/version.rb
86
+ - spec/response_spec.rb
87
+ - spec/spec_helper.rb
88
+ - spec/vtapi_spec.rb
89
+ - vtapi.gemspec
90
+ homepage: https://github.com/masatanish/vtapi
91
+ licenses:
92
+ - MIT
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ segments:
104
+ - 0
105
+ hash: -530785710278489622
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ segments:
113
+ - 0
114
+ hash: -530785710278489622
115
+ requirements: []
116
+ rubyforge_project:
117
+ rubygems_version: 1.8.16
118
+ signing_key:
119
+ specification_version: 3
120
+ summary: gem for VirusTotal Public API version2.0.
121
+ test_files:
122
+ - spec/response_spec.rb
123
+ - spec/spec_helper.rb
124
+ - spec/vtapi_spec.rb