sonar-client 0.1.1.pre → 0.1.6

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
- SHA1:
3
- metadata.gz: a0a22718af920b0a25379d0e6a1de7f8c5a21de2
4
- data.tar.gz: c662403673f22f221939e87cb40e623e4b0289bb
2
+ SHA256:
3
+ metadata.gz: e013916a1cf6c42fbd99d568ee88529cc7ac581b83aad80a4b62e4c2175aec7e
4
+ data.tar.gz: fb2e18d3b290f1dffb370448c603f3853fd380635ca79c5e8936e124f24fdcd5
5
5
  SHA512:
6
- metadata.gz: ec58eac6918e22b85c462b7f2da638c58e640d10bca319130422f626f796517e67fe17f3355197cee0eb616126438f6ffd17502b5e80c66981eedb4de210f655
7
- data.tar.gz: 6e59096a6f87ace1a081a2fd268727d306cb7838ac9f4ebb1219f2c7ff8dc736e5ba5c11f14297fb3047ff2d034d27615df863b0aa27c19fb76ebcb95ede9c2b
6
+ metadata.gz: 61c7694ca76d748ad6354f0010ab0891f28d4ed28999e4d57b32d15d5e816e0bf5fd57d311fac06937a19be937a1295016bdf44b9823faa748e51eeaf5b8f71f
7
+ data.tar.gz: 6dc07ee303279815e6b88f662be748292875d198f72759a828b4032ce012c27b9915869e89ba197b057261c5e7208db59075959fbef4b0db830cb891b6ad8142
@@ -2,14 +2,22 @@ language: ruby
2
2
  sudo: false
3
3
  cache: bundler
4
4
  rvm:
5
- - 2.1.5
6
- - jruby
5
+ - '2.4.5'
6
+ - '2.5.5'
7
+ - '2.6.5'
8
+ - '2.7.1'
9
+ - 'jruby-head'
7
10
  before_install:
11
+ - gem install bundler
12
+ - gem update bundler
8
13
  - "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
9
14
  - rake --version
10
15
  before_script:
11
16
  - bundle exec rake --version
12
17
  script: bundle exec rspec
18
+ notifications:
19
+ email:
20
+ - r7_labs@rapid7.com
13
21
  #
14
22
  # Environment variables should be set in Travis repository settings per
15
23
  # https://docs.travis-ci.com/user/environment-variables/#Defining-Variables-in-Repository-Settings
data/bin/sonar CHANGED
@@ -6,6 +6,8 @@ require 'sonar'
6
6
 
7
7
  begin
8
8
  Sonar::CLI.start(ARGV)
9
+ rescue Sonar::Search::SearchError => e
10
+ exit 1
9
11
  rescue Interrupt
10
12
  puts 'Quitting...'
11
13
  end
@@ -2,11 +2,14 @@
2
2
 
3
3
  require 'thor'
4
4
  require 'sonar/cli/rcfile'
5
+ require 'sonar/search'
6
+ require 'sonar/request'
5
7
  require 'awesome_print'
8
+ require 'table_print'
6
9
 
7
10
  module Sonar
8
11
  class CLI < Thor
9
- class_option 'format', type: :string, desc: 'Flat JSON, JSON lines, or pretty printed [flat/lines/pretty]'
12
+ class_option 'format', type: :string, desc: 'Flat JSON (include empty collections), JSON lines of collection data (default), or pretty printed [flat/lines/pretty]'
10
13
 
11
14
  def initialize(*)
12
15
  @config = Sonar::RCFile.instance.load_file
@@ -24,29 +27,34 @@ module Sonar
24
27
  ap @client.usage
25
28
  end
26
29
 
27
- desc 'search [QUERY TYPE] [QUERY TERM]', 'Search anything from Sonars'
30
+ desc 'search [QUERY TYPE] [QUERY TERM]', 'Search any query type from Sonar or specify \'all\' as QUERY TYPE to search them all.'
28
31
  method_option 'record_limit', type: :numeric, aliases: '-n', desc: 'Maximum number of records to fetch'
29
32
  method_option 'exact', type: :boolean, aliases: '-e', desc: 'Search for the query string exactly, do not include partial string matches'
30
-
31
33
  def search(type, term)
32
- @query = {}
33
- @query[type.to_sym] = term
34
- @query[:limit] = options['record_limit']
35
- @query[:exact] = options['exact']
36
- resp = @client.search(@query)
34
+ types = [type]
37
35
 
38
- if resp.is_a?(Sonar::Request::RequestIterator)
39
- resp.each do |data|
40
- print_json(cleanup_data(data), options['format'])
36
+ if type == 'all'
37
+ if term =~ Search::IS_IP
38
+ types = @client.ip_search_type_names
39
+ else
40
+ types = @client.domain_search_type_names
41
41
  end
42
- else
43
- print_json(cleanup_data(resp), options['format'])
42
+ end
43
+
44
+ types.each do |type|
45
+ @query = {}
46
+ @query[type.to_sym] = term
47
+ @query[:limit] = options['record_limit']
48
+ @query[:exact] = options['exact']
49
+ resp = @client.search(@query)
50
+ handle_search_response(resp)
44
51
  end
45
52
  end
46
53
 
47
54
  desc 'types', 'List all Sonar query types'
48
55
  def types
49
- ap Search::QUERY_TYPES_MAP
56
+ tp.set :io, $stdout
57
+ tp Search::QUERY_TYPES, :name, { description: { width: 100 } }, :input
50
58
  end
51
59
 
52
60
  desc 'config', 'Sonar config file location'
@@ -74,6 +82,21 @@ module Sonar
74
82
  end
75
83
  end
76
84
 
85
+ def handle_search_response(resp)
86
+ errors = 0
87
+ if resp.is_a?(Sonar::Request::RequestIterator)
88
+ resp.each do |data|
89
+ errors += 1 if data.key?('errors') || data.key?('error')
90
+ print_json(cleanup_data(data), options['format'])
91
+ end
92
+ else
93
+ errors += 1 if resp.key?('errors') || resp.key?('error')
94
+ print_json(cleanup_data(resp), options['format'])
95
+ end
96
+
97
+ raise Search::SearchError.new("Encountered #{errors} errors while searching") if errors > 0
98
+ end
99
+
77
100
  # Clean up whitespace and parse JSON values in responses
78
101
  def cleanup_data(data)
79
102
  return data unless data.is_a?(Hash) && data.has_key?('collection')
@@ -2,21 +2,51 @@
2
2
 
3
3
  module Sonar
4
4
  module Search
5
+
6
+ # Allow IP queries to be in the form of "1.", "1.2.", "1.2.3.", and "1.2.3.4"
7
+ IS_IP = /^(\d{1,3}\.|\d{1,3}\.\d{1,3}\.|\d{1,3}\.\d{1,3}\.\d{1,3}\.|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/
8
+
5
9
  # Implemented search query types
6
- QUERY_TYPES_MAP = {
7
- 'certificate' => 'Certificate lookup',
8
- 'certips' => 'Certificate to IPs',
9
- 'rdns' => 'IP to Reverse DNS Lookup or DNS Lookup to IP',
10
- 'fdns' => 'Domains to IP or IPs to Domain',
11
- 'ipcerts' => 'IP to Certificates',
12
- 'namecerts' => 'Domain to Certificates',
13
- 'links_to' => 'HTTP References to Domain',
14
- 'ports' => 'Open Ports',
15
- 'processed' => 'Open Ports (Processed)',
16
- 'raw' => 'Open Ports (Raw)',
17
- 'sslcert' => 'Certificate Details',
18
- 'whois_ip' => 'Whois (IP)'
19
- }
10
+ QUERY_TYPES = [
11
+ { name: 'certificate', description: 'Certificate lookup', input: 'sha' },
12
+ { name: 'certips', description: 'Certificate to IPs', input: 'sha' },
13
+ { name: 'rdns', description: 'IP to Reverse DNS Lookup or DNS Lookup to IP', input: 'ip' },
14
+ { name: 'fdns', description: 'Domains to IP or IPs to Domain', input: 'domain' },
15
+ { name: 'ipcerts', description: 'IP to Certificates', input: 'ip' },
16
+ { name: 'namecerts', description: 'Domain to Certificates', input: 'domain' },
17
+ { name: 'links_to', description: 'HTTP References to Domain', input: 'domain' },
18
+ { name: 'ports', description: 'Open Ports', input: 'ip' },
19
+ { name: 'processed', description: 'Open Ports (Processed)', input: 'ip' },
20
+ { name: 'raw', description: 'Open Ports (Raw)', input: 'ip' },
21
+ { name: 'sslcert', description: 'Certificate Details', input: 'sha' },
22
+ { name: 'all', description: 'Search all appropriate search types for an IP or domain', input: 'all' }
23
+ ]
24
+
25
+ ##
26
+ # Generic exception for errors encountered while searching
27
+ ##
28
+ class SearchError < StandardError
29
+ end
30
+
31
+ def ip_search_type_names
32
+ ip_search_types.map { |type| type[:name] }
33
+ end
34
+
35
+ def domain_search_type_names
36
+ domain_search_types.map { |type| type[:name] }
37
+ end
38
+
39
+ def ip_search_types
40
+ QUERY_TYPES.select { |type| type[:input] == 'ip' }
41
+ end
42
+
43
+ def domain_search_types
44
+ QUERY_TYPES.select { |type| type[:input] == 'domain' }
45
+ end
46
+
47
+ def query_type_names
48
+ QUERY_TYPES.map { |type| type[:name] }
49
+ end
20
50
 
21
51
  ##
22
52
  # Get search
@@ -26,7 +56,7 @@ module Sonar
26
56
  #
27
57
  # @return [Hashie::Mash] with response of search
28
58
  def search(params = {})
29
- type_query = params.select { |k, _v| QUERY_TYPES_MAP.keys.include?(k.to_s) }.first
59
+ type_query = params.select { |k, _v| query_type_names.include?(k.to_s) }.first
30
60
  fail ArgumentError, "The query type provided is invalid or not yet implemented." unless type_query
31
61
  type = type_query[0].to_sym
32
62
  params[:q] = type_query[1]
@@ -1,3 +1,3 @@
1
1
  module Sonar
2
- VERSION = "0.1.1.pre"
2
+ VERSION = "0.1.6"
3
3
  end
@@ -18,11 +18,12 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency 'faraday_middleware', '~> 0.9.0'
22
- spec.add_dependency 'hashie', '~> 2.0.3'
21
+ spec.add_dependency 'faraday_middleware'
22
+ spec.add_dependency 'hashie'
23
23
  spec.add_dependency 'multi_json'
24
24
  spec.add_dependency 'thor'
25
25
  spec.add_dependency 'awesome_print'
26
+ spec.add_dependency 'table_print'
26
27
 
27
28
  spec.add_development_dependency "bundler"
28
29
  spec.add_development_dependency "rake"
@@ -30,8 +31,8 @@ Gem::Specification.new do |spec|
30
31
  spec.add_development_dependency "simplecov"
31
32
  spec.add_development_dependency "simplecov-rcov"
32
33
  spec.add_development_dependency "yard"
33
- spec.add_development_dependency "vcr", '~> 2.8.0'
34
+ spec.add_development_dependency "vcr", '~> 4.0.0'
34
35
  spec.add_development_dependency "shoulda"
35
- spec.add_development_dependency "webmock", '~> 1.8.0'
36
+ spec.add_development_dependency "webmock", '~> 3.5.1'
36
37
  spec.add_development_dependency "api_matchers"
37
38
  end
@@ -6,9 +6,8 @@ describe Sonar::CLI do
6
6
  before do
7
7
  Sonar::RCFile.instance.path = "#{fixtures_path}/sonar-stock.rc"
8
8
  end
9
- it 'warns user of missing config values when trying to search' do
10
- output = run_command('search rdns 8.8.8.8')
11
- expect(output).to match(/Could not authenticate/)
9
+ it 'throws an exception because of errors' do
10
+ expect { run_command('search rdns 8.8.8.8') }.to raise_error(Sonar::Search::SearchError)
12
11
  end
13
12
  end
14
13
 
@@ -17,7 +16,7 @@ describe Sonar::CLI do
17
16
  Sonar::RCFile.instance.path = "#{fixtures_path}/sonar.rc"
18
17
  end
19
18
  it "should return the profile" do
20
- output = run_command('profile')
19
+ output = run_command('profile')
21
20
  expect(output).to match(/email@asdfasdfasfd.com/)
22
21
  end
23
22
 
@@ -28,11 +27,11 @@ describe Sonar::CLI do
28
27
  )
29
28
  end
30
29
  it 'strips whitespace from values' do
31
- output = run_command('search rdns 8.8.8.8')
30
+ output = run_command('search rdns 8.8.8.8')
32
31
  expect(output).to eq('{"collection":[{"address":"192.168.1.1"}],"more":"false"}')
33
32
  end
34
33
  it 'can return lines format' do
35
- output = run_command('search --format lines rdns 8.8.8.8')
34
+ output = run_command('search --format lines rdns 8.8.8.8')
36
35
  expect(output).to eq('{"address":"192.168.1.1"}')
37
36
  end
38
37
  end
@@ -43,7 +42,7 @@ describe Sonar::CLI do
43
42
  )
44
43
  end
45
44
  it 'parses the nested values in an array' do
46
- output = run_command('search sslcert 152a0a633aaf13f02c428ac1a3e672e895512bfd')
45
+ output = run_command('search sslcert 152a0a633aaf13f02c428ac1a3e672e895512bfd')
47
46
  expect(JSON.parse(output)['collection'].first['details'].first['subject']['ST']).to eq('California')
48
47
  end
49
48
  end
@@ -54,38 +53,61 @@ describe Sonar::CLI do
54
53
  )
55
54
  end
56
55
  xit 'parses the nested value as a string' do
57
- output = run_command('search processed 8.8.8.')
56
+ output = run_command('search processed 8.8.8.')
58
57
  expect(JSON.parse(output)['collection'].first['value']['ip']).to eq('8.8.8.8')
59
58
  end
60
59
  end
61
60
  describe 'searching with #exact --exact option' do
62
- context 'client that returns fdns for rapid7 IP exact' do
61
+ context 'client that returns fdns for rapid7 exact' do
63
62
  before do
64
63
  allow_any_instance_of(Sonar::Client).to receive(:search).and_return(
65
- Sonar::Client.new.search(fdns: '208.118.227.20', exact: true)
64
+ Sonar::Client.new.search(fdns: 'rapid7.com', exact: true)
66
65
  )
67
66
  end
68
67
  it 'matches exactly with --exact' do
69
- output = run_command('search fdns 208.118.227.20 --exact')
68
+ output = run_command('search fdns rapid7.com --exact')
70
69
  expect(JSON.parse(output)['collection'].size).to be >= 1
71
- expect(JSON.parse(output)['collection'].any? { |c| c['name'] == '208.118.227.20' }).to be(true)
70
+ expect(JSON.parse(output)['collection'].map{ |x| x['name'] }.uniq).to eq ['rapid7.com']
72
71
  end
73
72
  end
74
73
  context 'client that returns fdns for rapid7 IP' do
75
74
  before do
76
75
  allow_any_instance_of(Sonar::Client).to receive(:search).and_return(
77
- Sonar::Client.new.search(fdns: '208.118.227.20')
76
+ Sonar::Client.new.search(fdns: 'rapid7.com')
78
77
  )
79
78
  end
80
- it 'matches exactly without --exact' do
81
- output = run_command('search fdns 208.118.227.20')
82
- expect(JSON.parse(output)['collection'].size).to be > 1
79
+ it 'matches many domains without --exact' do
80
+ output = run_command('search fdns rapid7.com')
81
+ expect(JSON.parse(output)['collection'].map{ |x| x['name'] }.uniq.size).to be > 1
83
82
  end
84
83
  end
85
84
  end
85
+
86
+ describe 'sonar types command' do
87
+ it 'returns all sonar search types' do
88
+ output = run_command('types')
89
+ expect(output).to match(/Certificate to IPs/)
90
+ end
91
+ end
92
+
93
+ describe 'search all command' do
94
+ before do
95
+ allow_any_instance_of(Sonar::Client).to receive(:search).and_return(
96
+ Sonar::Client.new.search(fdns: '208.118.227.20', exact: true)
97
+ )
98
+ end
99
+ it 'returns results when searching for an IP' do
100
+ output = run_command('search all 208.118.227.20')
101
+ expect(output).to match(/rapid7\.com/)
102
+ end
103
+ it 'returns results when searching for a domain' do
104
+ output = run_command('search all rapid7.com')
105
+ expect(output).to match(/208\.118\.227\.20/)
106
+ end
107
+ end
86
108
  end
87
109
 
88
110
  def run_command(args)
89
- capture(:stdout) { Sonar::CLI.start(args.split) }.strip
111
+ capture(:stdout) { ret = Sonar::CLI.start(args.split) }.strip
90
112
  end
91
113
  end
@@ -2,8 +2,29 @@
2
2
  require 'spec_helper'
3
3
 
4
4
  describe Sonar::Search do
5
+ let(:dummy_class) {
6
+ Class.new { extend Sonar::Search }
7
+ }
5
8
  let(:client) { Sonar::Client.new }
6
9
 
10
+ describe "#ip_search_type_names" do
11
+ it 'includes rdns' do
12
+ expect(dummy_class.ip_search_type_names).to include('rdns')
13
+ end
14
+ it 'does not include fdns' do
15
+ expect(dummy_class.ip_search_type_names).to_not include('fdns')
16
+ end
17
+ end
18
+
19
+ describe "#domain_search_type_names" do
20
+ it 'includes fdns' do
21
+ expect(dummy_class.domain_search_type_names).to include('fdns')
22
+ end
23
+ it 'does not include rdns' do
24
+ expect(dummy_class.domain_search_type_names).to_not include('rdns')
25
+ end
26
+ end
27
+
7
28
  describe "parameters" do
8
29
  describe "query type" do
9
30
  context "with an invalid query type" do
@@ -15,12 +36,12 @@ describe Sonar::Search do
15
36
 
16
37
  describe "exact" do
17
38
  it "shouldn't match anything when #exact is true" do
18
- resp = client.search(rdns: "1.1.", exact: true)
39
+ resp = client.search(fdns: ".rapid7.com", exact: true)
19
40
  expect(resp["collection"].size).to eq(0)
20
41
  end
21
42
  it "should match when #exact is false" do
22
- resp = client.search(rdns: "1.1.", exact: false)
23
- expect(resp["collection"].first["address"]).to match(/^1.1./)
43
+ resp = client.search(fdns: ".rapid7.com", exact: false)
44
+ expect(resp["collection"].size).to be > 0
24
45
  end
25
46
  end
26
47
 
@@ -89,16 +110,16 @@ describe Sonar::Search do
89
110
  it "returns hashie response of search" do
90
111
  expect(resp.class).to eq(Hashie::Mash)
91
112
  end
92
- it "finds fdnsname 208.118.227.10 for rapid7.com" do
93
- expect(resp['collection'].any? { |x| x['address'] == '208.118.227.10' }).to be(true)
113
+ it "finds fdnsname multiple IP addresses for rapid7.com" do
114
+ expect(resp['collection'].select { |x| x['address'] }.size).to be >= 2
94
115
  end
95
116
  end
96
117
 
97
118
  context "fdnsip" do
98
119
  let(:resp) { client.search(fdns: '208.118.227.10') }
99
120
 
100
- it "finds fdnsip rapid7.com at 208.118.227.10" do
101
- expect(resp['collection'].any? { |x| x['address'].match('rapid7') }).to be(true)
121
+ it "finds fdnsip rapid7 domains at 208.118.227.10" do
122
+ expect(resp['collection'].any? { |x| x['address'].match('rapidseven') }).to be(true)
102
123
  end
103
124
  end
104
125
 
@@ -152,14 +173,6 @@ describe Sonar::Search do
152
173
  end
153
174
  end
154
175
 
155
- context "whois_ip" do
156
- let(:resp) { client.search(whois_ip: '208.118.227.10') }
157
-
158
- xit "should find rapid7.com" do
159
- expect(resp['name']).to eq('TWDX-208-118-227-0-1')
160
- end
161
- end
162
-
163
176
  # TODO: actually check response
164
177
  context "raw" do
165
178
  let(:resp) { client.search(raw: '208.118.227.10') }
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sonar-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1.pre
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Deardorff & HD Moore
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-12 00:00:00.000000000 Z
11
+ date: 2020-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday_middleware
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.9.0
19
+ version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.9.0
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: hashie
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 2.0.3
33
+ version: '0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 2.0.3
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: multi_json
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: table_print
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: bundler
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -170,14 +184,14 @@ dependencies:
170
184
  requirements:
171
185
  - - "~>"
172
186
  - !ruby/object:Gem::Version
173
- version: 2.8.0
187
+ version: 4.0.0
174
188
  type: :development
175
189
  prerelease: false
176
190
  version_requirements: !ruby/object:Gem::Requirement
177
191
  requirements:
178
192
  - - "~>"
179
193
  - !ruby/object:Gem::Version
180
- version: 2.8.0
194
+ version: 4.0.0
181
195
  - !ruby/object:Gem::Dependency
182
196
  name: shoulda
183
197
  requirement: !ruby/object:Gem::Requirement
@@ -198,14 +212,14 @@ dependencies:
198
212
  requirements:
199
213
  - - "~>"
200
214
  - !ruby/object:Gem::Version
201
- version: 1.8.0
215
+ version: 3.5.1
202
216
  type: :development
203
217
  prerelease: false
204
218
  version_requirements: !ruby/object:Gem::Requirement
205
219
  requirements:
206
220
  - - "~>"
207
221
  - !ruby/object:Gem::Version
208
- version: 1.8.0
222
+ version: 3.5.1
209
223
  - !ruby/object:Gem::Dependency
210
224
  name: api_matchers
211
225
  requirement: !ruby/object:Gem::Requirement
@@ -275,12 +289,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
275
289
  version: '0'
276
290
  required_rubygems_version: !ruby/object:Gem::Requirement
277
291
  requirements:
278
- - - ">"
292
+ - - ">="
279
293
  - !ruby/object:Gem::Version
280
- version: 1.3.1
294
+ version: '0'
281
295
  requirements: []
282
296
  rubyforge_project:
283
- rubygems_version: 2.4.3
297
+ rubygems_version: 2.7.9
284
298
  signing_key:
285
299
  specification_version: 4
286
300
  summary: API Wrapper for Sonar
@@ -296,4 +310,3 @@ test_files:
296
310
  - spec/sonar/user_spec.rb
297
311
  - spec/sonar_spec.rb
298
312
  - spec/spec_helper.rb
299
- has_rdoc: