gitrob 0.0.6 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +30 -7
- data/.rspec +0 -1
- data/.rubocop.yml +55 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +42 -0
- data/CONTRIBUTING.md +137 -9
- data/Gemfile +11 -1
- data/Guardfile +42 -0
- data/LICENSE.txt +17 -18
- data/README.md +79 -29
- data/Rakefile +6 -0
- data/bin/console +34 -0
- data/bin/setup +7 -0
- data/db/migrations/001_create_assessments.rb +19 -0
- data/db/migrations/002_create_github_access_tokens.rb +11 -0
- data/db/migrations/003_create_owners.rb +24 -0
- data/db/migrations/004_create_repositories.rb +23 -0
- data/db/migrations/005_create_blobs.rb +16 -0
- data/db/migrations/006_create_flags.rb +13 -0
- data/db/migrations/007_create_comparisons.rb +17 -0
- data/db/migrations/008_create_blobs_comparisons.rb +8 -0
- data/db/migrations/009_create_comparisons_repositories.rb +8 -0
- data/db/migrations/010_create_comparisons_owners.rb +8 -0
- data/exe/gitrob +6 -0
- data/gitrob.gemspec +25 -18
- data/lib/gitrob/blob_observer.rb +103 -0
- data/lib/gitrob/cli/command.rb +58 -0
- data/lib/gitrob/cli/commands/accept_terms_of_use.rb +61 -0
- data/lib/gitrob/cli/commands/analyze/analysis.rb +75 -0
- data/lib/gitrob/cli/commands/analyze/gathering.rb +101 -0
- data/lib/gitrob/cli/commands/analyze.rb +63 -0
- data/lib/gitrob/cli/commands/banner.rb +25 -0
- data/lib/gitrob/cli/commands/configure.rb +123 -0
- data/lib/gitrob/cli/commands/server.rb +21 -0
- data/lib/gitrob/cli/progress_bar.rb +47 -0
- data/lib/gitrob/cli.rb +213 -0
- data/lib/gitrob/github/client_manager.rb +46 -0
- data/lib/gitrob/github/data_manager.rb +121 -0
- data/lib/gitrob/jobs/assessment.rb +12 -0
- data/lib/gitrob/jobs/comparison.rb +55 -0
- data/lib/gitrob/models/assessment.rb +96 -0
- data/lib/gitrob/models/blob.rb +50 -0
- data/lib/gitrob/models/comparison.rb +15 -0
- data/lib/gitrob/models/flag.rb +15 -0
- data/lib/gitrob/models/github_access_token.rb +17 -0
- data/lib/gitrob/models/owner.rb +23 -0
- data/lib/gitrob/models/repository.rb +20 -0
- data/lib/gitrob/utils.rb +19 -0
- data/lib/gitrob/version.rb +1 -1
- data/lib/gitrob/web_app.rb +292 -0
- data/lib/gitrob.rb +30 -113
- data/public/css/bootstrap.min.css +11 -0
- data/public/css/main.css +130 -0
- data/public/css/tomorrow-night.css +75 -0
- data/public/fonts/glyphicons-halflings-regular.eot +0 -0
- data/public/fonts/glyphicons-halflings-regular.svg +273 -214
- data/public/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/public/fonts/glyphicons-halflings-regular.woff +0 -0
- data/public/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/public/images/blob_spinner.gif +0 -0
- data/public/images/gear_spinner.gif +0 -0
- data/public/js/bootstrap.min.js +7 -0
- data/public/js/highlight.pack.js +2 -0
- data/public/js/highlight.worker.js +13 -0
- data/public/js/jquery-2.1.4.min.js +4 -0
- data/public/js/main.js +239 -0
- data/public/robots.txt +2 -0
- data/signatures.json +541 -0
- data/views/assessments/_assessments.erb +57 -0
- data/views/assessments/_comparable_assessments.erb +38 -0
- data/views/assessments/_comparisons.erb +111 -0
- data/views/assessments/compare.erb +22 -0
- data/views/assessments/findings.erb +55 -0
- data/views/assessments/repositories.erb +35 -0
- data/views/assessments/show.erb +1 -0
- data/views/assessments/users.erb +46 -0
- data/views/blobs/show.erb +37 -0
- data/views/comparisons/show.erb +125 -0
- data/views/errors/internal_server_error.erb +9 -0
- data/views/errors/not_found.erb +5 -0
- data/views/index.erb +43 -28
- data/views/layout.erb +38 -12
- data/views/repositories/show.erb +49 -0
- data/views/users/show.erb +54 -0
- metadata +217 -106
- data/bin/gitrob +0 -260
- data/lib/gitrob/github/blob.rb +0 -41
- data/lib/gitrob/github/http_client.rb +0 -127
- data/lib/gitrob/github/organization.rb +0 -99
- data/lib/gitrob/github/repository.rb +0 -72
- data/lib/gitrob/github/user.rb +0 -84
- data/lib/gitrob/observers/sensitive_files.rb +0 -83
- data/lib/gitrob/progressbar.rb +0 -52
- data/lib/gitrob/util.rb +0 -11
- data/lib/gitrob/webapp.rb +0 -76
- data/models/blob.rb +0 -35
- data/models/finding.rb +0 -14
- data/models/organization.rb +0 -32
- data/models/repo.rb +0 -22
- data/models/user.rb +0 -28
- data/patterns.json +0 -394
- data/public/javascripts/bootstrap.min.js +0 -7
- data/public/javascripts/gitrob.js +0 -75
- data/public/javascripts/jquery-2.1.1.min.js +0 -4
- data/public/javascripts/lang-apollo.js +0 -2
- data/public/javascripts/lang-basic.js +0 -3
- data/public/javascripts/lang-clj.js +0 -18
- data/public/javascripts/lang-css.js +0 -2
- data/public/javascripts/lang-dart.js +0 -3
- data/public/javascripts/lang-erlang.js +0 -2
- data/public/javascripts/lang-go.js +0 -1
- data/public/javascripts/lang-hs.js +0 -2
- data/public/javascripts/lang-lisp.js +0 -3
- data/public/javascripts/lang-llvm.js +0 -1
- data/public/javascripts/lang-lua.js +0 -2
- data/public/javascripts/lang-matlab.js +0 -6
- data/public/javascripts/lang-ml.js +0 -2
- data/public/javascripts/lang-mumps.js +0 -2
- data/public/javascripts/lang-n.js +0 -4
- data/public/javascripts/lang-pascal.js +0 -3
- data/public/javascripts/lang-proto.js +0 -1
- data/public/javascripts/lang-r.js +0 -2
- data/public/javascripts/lang-rd.js +0 -1
- data/public/javascripts/lang-scala.js +0 -2
- data/public/javascripts/lang-sql.js +0 -2
- data/public/javascripts/lang-tcl.js +0 -3
- data/public/javascripts/lang-tex.js +0 -1
- data/public/javascripts/lang-vb.js +0 -2
- data/public/javascripts/lang-vhdl.js +0 -3
- data/public/javascripts/lang-wiki.js +0 -2
- data/public/javascripts/lang-xq.js +0 -3
- data/public/javascripts/lang-yaml.js +0 -2
- data/public/javascripts/prettify.js +0 -30
- data/public/javascripts/run_prettify.js +0 -34
- data/public/stylesheets/bootstrap.min.css +0 -7
- data/public/stylesheets/bootstrap.min.css.vanilla +0 -5
- data/public/stylesheets/gitrob.css +0 -88
- data/public/stylesheets/prettify.css +0 -51
- data/spec/lib/gitrob/observers/sensitive_files_spec.rb +0 -691
- data/spec/spec_helper.rb +0 -127
- data/views/blob.erb +0 -22
- data/views/organization.erb +0 -126
- data/views/repository.erb +0 -51
- data/views/user.erb +0 -51
data/bin/gitrob
DELETED
@@ -1,260 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# coding: utf-8
|
3
|
-
|
4
|
-
require 'gitrob'
|
5
|
-
|
6
|
-
class App
|
7
|
-
include Methadone::Main
|
8
|
-
|
9
|
-
leak_exceptions true
|
10
|
-
|
11
|
-
main do
|
12
|
-
Thread.abort_on_exception = true
|
13
|
-
Paint.mode = 0 if options.include?('no-color')
|
14
|
-
|
15
|
-
puts Paint[Gitrob::banner, :bright, :blue] unless options.include?('no-banner')
|
16
|
-
Gitrob::status("Starting Gitrob version #{Gitrob::VERSION} at #{Time.now.strftime("%Y-%m-%d %I:%S %Z")}")
|
17
|
-
|
18
|
-
if !Gitrob::agreement_accepted?
|
19
|
-
puts Gitrob::agreement
|
20
|
-
print Paint["\n\nDo you agree to the terms of use? [y/n]: ", :bright, :green]
|
21
|
-
choice = $stdin.gets.chomp
|
22
|
-
if %w{y yes}.include?(choice)
|
23
|
-
Gitrob::agreement_accepted
|
24
|
-
else
|
25
|
-
Gitrob::fatal("Exiting Gitrob.")
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
if !Gitrob::configured? || options.include?('configure')
|
30
|
-
Gitrob::status("Starting Gitrob configuration wizard\n\n")
|
31
|
-
|
32
|
-
hostname = ask("#{Paint[" Enter PostgreSQL hostname:", :bright, :white]} ") { |q| q.default = "localhost" }
|
33
|
-
port = ask("#{Paint[" Enter PostgreSQL port:", :bright, :white]} ", Integer) { |q| q.default = 5432; q.in = 1..65535 }
|
34
|
-
username = ask("#{Paint[" Enter PostgreSQL username:", :bright, :white]} ")
|
35
|
-
password = ask("#{Paint[" Enter PostgreSQL password for #{username} (masked):", :bright, :white]} ") { |q| q.echo = 'x' }
|
36
|
-
database = ask("#{Paint[" Enter PostgreSQL database name:", :bright, :white]} ") { |q| q.default = 'gitrob' }
|
37
|
-
|
38
|
-
access_tokens = Array.new
|
39
|
-
while access_tokens.uniq.empty?
|
40
|
-
access_tokens = ask(Paint[" Enter GitHub access tokens (blank line to stop):", :bright, :white],
|
41
|
-
lambda { |ans| ans =~ /[a-f0-9]{40}/ ? ans : nil } ) do |q|
|
42
|
-
q.gather = ""
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
config = {
|
47
|
-
'sql_connection_uri' => "postgres://#{username}:#{password}@#{hostname}:#{port}/#{database}",
|
48
|
-
'github_access_tokens' => access_tokens.uniq
|
49
|
-
}
|
50
|
-
|
51
|
-
Gitrob::task("Saving configuration to file...") do
|
52
|
-
Gitrob::save_configuration!(config)
|
53
|
-
end
|
54
|
-
|
55
|
-
exit unless options['organization']
|
56
|
-
end
|
57
|
-
|
58
|
-
Gitrob::task("Loading configuration...") do
|
59
|
-
Gitrob::load_configuration!
|
60
|
-
end
|
61
|
-
|
62
|
-
Gitrob::task("Preparing SQL database...") do
|
63
|
-
Gitrob::prepare_database!
|
64
|
-
end
|
65
|
-
|
66
|
-
if options['reset-db']
|
67
|
-
Gitrob::task("Resetting database tables...") do
|
68
|
-
DataMapper::auto_migrate!
|
69
|
-
end
|
70
|
-
exit
|
71
|
-
end
|
72
|
-
|
73
|
-
if options['delete']
|
74
|
-
Gitrob::delete_organization(options['delete'])
|
75
|
-
exit
|
76
|
-
end
|
77
|
-
|
78
|
-
if options['start-server']
|
79
|
-
Gitrob::status("Starting web application on http://#{options['bind-address']}:#{options['port']}/...")
|
80
|
-
puts "\n\n"
|
81
|
-
Gitrob::WebApp.run!(:port => options['port'].to_i, :bind => options['bind-address'])
|
82
|
-
exit
|
83
|
-
end
|
84
|
-
|
85
|
-
org_name = options['organization']
|
86
|
-
repo_count = 0
|
87
|
-
members = Array.new
|
88
|
-
http_client = Gitrob::Github::HttpClient.new({:access_tokens => Gitrob::configuration['github_access_tokens']})
|
89
|
-
observers = Gitrob::Observers.constants.collect { |c| Gitrob::Observers::const_get(c) }
|
90
|
-
|
91
|
-
Gitrob::task("Loading file patterns...") do
|
92
|
-
Gitrob::Observers::SensitiveFiles::load_patterns!
|
93
|
-
end
|
94
|
-
|
95
|
-
begin
|
96
|
-
org = Gitrob::Github::Organization.new(org_name, http_client)
|
97
|
-
|
98
|
-
Gitrob::task("Collecting organization repositories...") do
|
99
|
-
repo_count = org.repositories.count
|
100
|
-
end
|
101
|
-
rescue Gitrob::Github::HttpClient::ClientError => e
|
102
|
-
if e.status == 404
|
103
|
-
Gitrob::fatal("Cannot find GitHub organization with that name; exiting.")
|
104
|
-
else
|
105
|
-
raise e
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
Gitrob::task("Collecting organization members...") do
|
110
|
-
members = org.members
|
111
|
-
end
|
112
|
-
|
113
|
-
progress = Gitrob::ProgressBar.new("Collecting member repositories...",
|
114
|
-
:total => members.count
|
115
|
-
)
|
116
|
-
thread_pool = Thread.pool(options['threads'].to_i)
|
117
|
-
|
118
|
-
members.each do |member|
|
119
|
-
thread_pool.process do
|
120
|
-
if member.repositories.count > 0
|
121
|
-
repo_count += member.repositories.count
|
122
|
-
progress.log("Collected #{Gitrob::Util::pluralize(member.repositories.count, 'repository', 'repositories')} from #{Paint[member.username, :bright, :white]}")
|
123
|
-
end
|
124
|
-
progress.increment
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
thread_pool.shutdown
|
129
|
-
|
130
|
-
if repo_count.zero?
|
131
|
-
Gitrob::fatal("Organization has no repositories to check; exiting.")
|
132
|
-
end
|
133
|
-
|
134
|
-
progress = Gitrob::ProgressBar.new("Processing repositories...",
|
135
|
-
:total => repo_count
|
136
|
-
)
|
137
|
-
|
138
|
-
db_org = org.save_to_database!
|
139
|
-
thread_pool = Thread.pool(options['threads'].to_i)
|
140
|
-
|
141
|
-
org.repositories.each do |repo|
|
142
|
-
thread_pool.process do
|
143
|
-
begin
|
144
|
-
if repo.contents.count > 0
|
145
|
-
db_repo = repo.save_to_database!(db_org)
|
146
|
-
findings = 0
|
147
|
-
|
148
|
-
repo.contents.each do |blob|
|
149
|
-
db_blob = blob.to_model(db_org, db_repo)
|
150
|
-
|
151
|
-
observers.each do |observer|
|
152
|
-
observer.observe(db_blob)
|
153
|
-
end
|
154
|
-
|
155
|
-
db_blob.findings.each do |f|
|
156
|
-
db_blob.findings_count += 1
|
157
|
-
findings += 1
|
158
|
-
f.organization = db_org
|
159
|
-
f.repo = db_repo
|
160
|
-
end
|
161
|
-
|
162
|
-
db_blob.save
|
163
|
-
end
|
164
|
-
progress.log("Processed #{Gitrob::Util::pluralize(repo.contents.count, 'file', 'files')} from #{Paint[repo.full_name, :bright, :white]} with #{findings.zero? ? 'no findings' : Paint[Gitrob::Util.pluralize(findings, 'finding', 'findings'), :yellow]}")
|
165
|
-
end
|
166
|
-
progress.increment
|
167
|
-
rescue Exception => e
|
168
|
-
progress.log_error("Encountered error when processing #{Paint[repo.full_name, :bright, :white]} (#{e.class.name})")
|
169
|
-
progress.increment
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
org.members.each do |member|
|
175
|
-
thread_pool.process do
|
176
|
-
begin
|
177
|
-
db_user = member.save_to_database!(db_org)
|
178
|
-
|
179
|
-
member.repositories.each do |repo|
|
180
|
-
if repo.contents.count > 0
|
181
|
-
db_repo = repo.save_to_database!(db_org, db_user)
|
182
|
-
findings = 0
|
183
|
-
|
184
|
-
repo.contents.each do |blob|
|
185
|
-
db_blob = blob.to_model(db_org, db_repo)
|
186
|
-
|
187
|
-
observers.each do |observer|
|
188
|
-
observer.observe(db_blob)
|
189
|
-
end
|
190
|
-
|
191
|
-
db_blob.findings.each do |f|
|
192
|
-
db_blob.findings_count += 1
|
193
|
-
findings += 1
|
194
|
-
f.organization = db_org
|
195
|
-
f.repo = db_repo
|
196
|
-
f.user = db_user
|
197
|
-
end
|
198
|
-
|
199
|
-
db_blob.save
|
200
|
-
end
|
201
|
-
progress.log("Processed #{Gitrob::Util::pluralize(repo.contents.count, 'file', 'files')} from #{Paint[repo.full_name, :bright, :white]} with #{findings.zero? ? 'no findings' : Paint[Gitrob::Util.pluralize(findings, 'finding', 'findings'), :yellow]}")
|
202
|
-
end
|
203
|
-
progress.increment
|
204
|
-
end
|
205
|
-
rescue Exception => e
|
206
|
-
progress.log_error("Encountered error when processing #{Paint[repo.full_name, :bright, :white]} (#{e.class.name})")
|
207
|
-
progress.increment
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
thread_pool.shutdown
|
213
|
-
|
214
|
-
if !options.include?('no-server')
|
215
|
-
Gitrob::status("Starting web application on port #{options['port']}...")
|
216
|
-
Gitrob::status("Browse to http://#{options['bind-address']}:#{options['port']}/ to see results!")
|
217
|
-
puts "\n\n"
|
218
|
-
Gitrob::WebApp.run!(:port => options[:port].to_i, :bind => options['bind-address'])
|
219
|
-
exit
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
version Gitrob::VERSION
|
224
|
-
description "Reconnaissance tool for GitHub organizations."
|
225
|
-
|
226
|
-
options['bind-address'] = '127.0.0.1'
|
227
|
-
options['port'] = 9393
|
228
|
-
options['threads'] = 3
|
229
|
-
|
230
|
-
on('-o', '--organization NAME', "Name of GitHub organization")
|
231
|
-
on('-s', '--start-server', "Start web server, don't run any checks")
|
232
|
-
on('-p', '--port PORT', "Port to bind web server to")
|
233
|
-
on('-b', '--bind-address ADDRESS', "Address to bind web server to")
|
234
|
-
on('-t', '--threads THREADS', "Number of threads to use")
|
235
|
-
on('--delete NAME', "Delete an organization in the database")
|
236
|
-
on('--reset-db', "Resets the database")
|
237
|
-
on('--configure', "Start configuration wizard")
|
238
|
-
on('--no-server', "Don't start the server when finished")
|
239
|
-
on('--no-color', "Don't colorize output")
|
240
|
-
on('--no-banner', "Don't print Gitrob banner")
|
241
|
-
|
242
|
-
begin
|
243
|
-
if ARGV.empty?
|
244
|
-
Gitrob::fatal("No options given; see gitrob --help for options.")
|
245
|
-
end
|
246
|
-
|
247
|
-
go!
|
248
|
-
rescue Gitrob::Github::HttpClient::MissingAccessTokensError
|
249
|
-
Gitrob::fatal("Configuration file does not contain any GitHub access tokens. Run Gitrob with --configure flag to set it up.")
|
250
|
-
rescue Gitrob::Github::HttpClient::AccessTokensDepletedError
|
251
|
-
Gitrob::fatal("All GitHub access tokens under rate limiting. Go have a cup of coffee and try again.")
|
252
|
-
rescue Gitrob::Github::HttpClient::RequestError => e
|
253
|
-
Gitrob::fatal("A request to the GitHub API failed: #{e.message}")
|
254
|
-
rescue Interrupt
|
255
|
-
print "\b\b\n"; # Remove ^C from screen
|
256
|
-
Gitrob::fatal("Caught interrupt; exiting.")
|
257
|
-
rescue => e
|
258
|
-
Gitrob::fatal("An error occurred: #{e.class.name}: #{e.message}")
|
259
|
-
end
|
260
|
-
end
|
data/lib/gitrob/github/blob.rb
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
module Gitrob
|
2
|
-
module Github
|
3
|
-
class Blob
|
4
|
-
attr_reader :path, :size, :repository
|
5
|
-
|
6
|
-
def initialize(path, size, repository)
|
7
|
-
@path, @size, @repository = path, size, repository
|
8
|
-
end
|
9
|
-
|
10
|
-
def extension
|
11
|
-
File.extname(path)[1..-1]
|
12
|
-
end
|
13
|
-
|
14
|
-
def filename
|
15
|
-
File.basename(path)
|
16
|
-
end
|
17
|
-
|
18
|
-
def dirname
|
19
|
-
File.dirname(path)
|
20
|
-
end
|
21
|
-
|
22
|
-
def url
|
23
|
-
"https://github.com/#{URI.escape(repository.owner)}/#{URI.escape(repository.name)}/blob/master/#{URI.escape(path)}"
|
24
|
-
end
|
25
|
-
|
26
|
-
def to_model(organization, repository)
|
27
|
-
repository.blobs.new(
|
28
|
-
:path => self.path,
|
29
|
-
:filename => self.filename,
|
30
|
-
:extension => self.extension,
|
31
|
-
:size => self.size,
|
32
|
-
:organization => organization
|
33
|
-
)
|
34
|
-
end
|
35
|
-
|
36
|
-
def save_to_database!(organization, repository)
|
37
|
-
self.to_model(organization, repository).tap { |m| m.save }
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
@@ -1,127 +0,0 @@
|
|
1
|
-
module Gitrob
|
2
|
-
module Github
|
3
|
-
class HttpClient
|
4
|
-
include HTTParty
|
5
|
-
base_uri 'https://api.github.com'
|
6
|
-
|
7
|
-
class HttpError < StandardError; end
|
8
|
-
class ConnectionError < HttpError; end
|
9
|
-
|
10
|
-
class RequestError < HttpError
|
11
|
-
attr_reader :status, :body
|
12
|
-
def initialize(method, path, status, body, options)
|
13
|
-
@status = status
|
14
|
-
@body = body
|
15
|
-
super("#{method} to #{path} returned status #{status} - options: #{options.inspect}")
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
class ClientError < RequestError; end
|
20
|
-
class ServerError < RequestError; end
|
21
|
-
|
22
|
-
class UnhandledError < StandardError; end
|
23
|
-
|
24
|
-
class AccessTokenError < StandardError; end
|
25
|
-
class MissingAccessTokensError < AccessTokenError; end
|
26
|
-
class AccessTokensDepletedError < AccessTokenError; end
|
27
|
-
|
28
|
-
DEFAULT_TIMEOUT = 0.5 #seconds
|
29
|
-
DEFAULT_RETRIES = 3
|
30
|
-
|
31
|
-
Response = Struct.new(:status, :headers, :body)
|
32
|
-
|
33
|
-
RETRIABLE_EXCEPTIONS = [
|
34
|
-
ServerError,
|
35
|
-
AccessTokenError,
|
36
|
-
Timeout::Error,
|
37
|
-
Errno::ETIMEDOUT,
|
38
|
-
Errno::ECONNRESET,
|
39
|
-
Errno::ECONNREFUSED,
|
40
|
-
Errno::ENETUNREACH,
|
41
|
-
Errno::EHOSTUNREACH,
|
42
|
-
EOFError
|
43
|
-
]
|
44
|
-
|
45
|
-
def initialize(options)
|
46
|
-
@config = {
|
47
|
-
:timeout => DEFAULT_TIMEOUT,
|
48
|
-
:retries => DEFAULT_RETRIES
|
49
|
-
}.merge(options)
|
50
|
-
raise MissingAccessTokensErrors.new("No access tokens given") unless @config[:access_tokens]
|
51
|
-
default_timeout = @config[:timeout]
|
52
|
-
end
|
53
|
-
|
54
|
-
def do_get(path, params=nil, options={})
|
55
|
-
do_request(:get, path, {:query => params}.merge(options))
|
56
|
-
end
|
57
|
-
|
58
|
-
def do_post(path, params=nil, options={})
|
59
|
-
do_request(:post, path, {:query => params}.merge(options))
|
60
|
-
end
|
61
|
-
|
62
|
-
def do_put(path, params=nil, options={})
|
63
|
-
do_request(:put, path, {:query => params}.merge(options))
|
64
|
-
end
|
65
|
-
|
66
|
-
def do_delete(path, params=nil, options={})
|
67
|
-
do_request(:delete, path, {:query => params}.merge(options))
|
68
|
-
end
|
69
|
-
|
70
|
-
private
|
71
|
-
|
72
|
-
def do_request(method, path, options)
|
73
|
-
with_retries do
|
74
|
-
access_token = get_access_token!
|
75
|
-
response = self.class.send(method, path, {
|
76
|
-
:headers => {
|
77
|
-
'Authorization' => "token #{access_token}",
|
78
|
-
'User-Agent' => "Gitrob v#{Gitrob::VERSION}"
|
79
|
-
}
|
80
|
-
}.merge(options))
|
81
|
-
handle_possible_error!(method, path, response, options, access_token)
|
82
|
-
Response.new(response.code, response.headers, response.body)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def with_retries(&block)
|
87
|
-
tries = @config[:retries]
|
88
|
-
yield
|
89
|
-
rescue *RETRIABLE_EXCEPTIONS => ex
|
90
|
-
if (tries -= 1) > 0
|
91
|
-
sleep 0.2
|
92
|
-
retry
|
93
|
-
else
|
94
|
-
raise ex
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def handle_possible_error!(method, path, response, options, access_token)
|
99
|
-
if access_token_rate_limited?(response) || access_token_unauthorized?(response)
|
100
|
-
access_tokens.delete(access_token)
|
101
|
-
raise AccessTokenError
|
102
|
-
elsif response.code >= 500
|
103
|
-
raise ServerError.new(method, path, response.code, response.body, options)
|
104
|
-
elsif response.code >= 400
|
105
|
-
raise ClientError.new(method, path, response.code, response.body, options)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def access_token_rate_limited?(response)
|
110
|
-
response.code == 403 && response.headers['X-RateLimit-Remaining'].to_i.zero?
|
111
|
-
end
|
112
|
-
|
113
|
-
def access_token_unauthorized?(response)
|
114
|
-
response.code == 401
|
115
|
-
end
|
116
|
-
|
117
|
-
def get_access_token!
|
118
|
-
raise AccessTokensDepletedError.new("Rate limit on all access tokens has been used up") if access_tokens.count.zero?
|
119
|
-
access_tokens.sample
|
120
|
-
end
|
121
|
-
|
122
|
-
def access_tokens
|
123
|
-
@config[:access_tokens]
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
@@ -1,99 +0,0 @@
|
|
1
|
-
module Gitrob
|
2
|
-
module Github
|
3
|
-
class Organization
|
4
|
-
attr_reader :name, :http_client
|
5
|
-
|
6
|
-
def initialize(name, http_client)
|
7
|
-
@name, @http_client = name, http_client
|
8
|
-
end
|
9
|
-
|
10
|
-
def display_name
|
11
|
-
info['name'].to_s.empty? ? info['login'] : info['name']
|
12
|
-
end
|
13
|
-
|
14
|
-
def login
|
15
|
-
info['login']
|
16
|
-
end
|
17
|
-
|
18
|
-
def website
|
19
|
-
info['blog']
|
20
|
-
end
|
21
|
-
|
22
|
-
def location
|
23
|
-
info['location']
|
24
|
-
end
|
25
|
-
|
26
|
-
def email
|
27
|
-
info['email']
|
28
|
-
end
|
29
|
-
|
30
|
-
def url
|
31
|
-
"https://github.com/#{name}"
|
32
|
-
end
|
33
|
-
|
34
|
-
def avatar_url
|
35
|
-
info['avatar_url']
|
36
|
-
end
|
37
|
-
|
38
|
-
def repositories
|
39
|
-
@repositories ||= recursive_repositories
|
40
|
-
end
|
41
|
-
|
42
|
-
def members
|
43
|
-
@members ||= recursive_members
|
44
|
-
end
|
45
|
-
|
46
|
-
def to_model
|
47
|
-
Gitrob::Organization.new(
|
48
|
-
:name => self.display_name,
|
49
|
-
:login => self.login,
|
50
|
-
:website => self.website,
|
51
|
-
:location => self.location,
|
52
|
-
:email => self.email,
|
53
|
-
:avatar_url => self.avatar_url,
|
54
|
-
:url => self.url
|
55
|
-
)
|
56
|
-
end
|
57
|
-
|
58
|
-
def save_to_database!
|
59
|
-
self.to_model.tap { |m| m.save }
|
60
|
-
end
|
61
|
-
|
62
|
-
private
|
63
|
-
|
64
|
-
def recursive_members(page = 1)
|
65
|
-
members = Array.new
|
66
|
-
response = http_client.do_get("/orgs/#{name}/members?page=#{page.to_i}")
|
67
|
-
JSON.parse(response.body).each do |member|
|
68
|
-
members << User.new(member['login'], http_client)
|
69
|
-
end
|
70
|
-
|
71
|
-
if response.headers.include?('link') && response.headers['link'].include?('rel="next"')
|
72
|
-
members += recursive_members(page + 1)
|
73
|
-
end
|
74
|
-
members
|
75
|
-
end
|
76
|
-
|
77
|
-
def recursive_repositories(page = 1)
|
78
|
-
repositories = Array.new
|
79
|
-
response = http_client.do_get("/orgs/#{name}/repos?page=#{page.to_i}")
|
80
|
-
JSON.parse(response.body).each do |repo|
|
81
|
-
next if repo['fork']
|
82
|
-
repositories << Repository.new(name, repo['name'], http_client)
|
83
|
-
end
|
84
|
-
|
85
|
-
if response.headers.include?('link') && response.headers['link'].include?('rel="next"')
|
86
|
-
repositories += recursive_repositories(page + 1)
|
87
|
-
end
|
88
|
-
repositories
|
89
|
-
end
|
90
|
-
|
91
|
-
def info
|
92
|
-
if !@info
|
93
|
-
@info = JSON.parse(http_client.do_get("/orgs/#{name}").body)
|
94
|
-
end
|
95
|
-
@info
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
@@ -1,72 +0,0 @@
|
|
1
|
-
module Gitrob
|
2
|
-
module Github
|
3
|
-
class Repository
|
4
|
-
|
5
|
-
attr_reader :owner, :name, :http_client
|
6
|
-
def initialize(owner, name, http_client)
|
7
|
-
@owner, @name, @http_client = owner, name, http_client
|
8
|
-
end
|
9
|
-
|
10
|
-
def contents
|
11
|
-
if !@contents
|
12
|
-
@contents = []
|
13
|
-
response = JSON.parse(http_client.do_get("/repos/#{owner}/#{name}/git/trees/master?recursive=1").body)
|
14
|
-
response['tree'].each do |object|
|
15
|
-
next unless object['type'] == 'blob'
|
16
|
-
@contents << Blob.new(object['path'], object['size'], self)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
@contents
|
20
|
-
rescue HttpClient::ClientError => ex
|
21
|
-
if ex.status == 409 || ex.status == 404
|
22
|
-
@contents = []
|
23
|
-
else
|
24
|
-
raise ex
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def full_name
|
29
|
-
[owner, name].join('/')
|
30
|
-
end
|
31
|
-
|
32
|
-
def url
|
33
|
-
info['html_url']
|
34
|
-
end
|
35
|
-
|
36
|
-
def description
|
37
|
-
info['description']
|
38
|
-
end
|
39
|
-
|
40
|
-
def website
|
41
|
-
info['homepage']
|
42
|
-
end
|
43
|
-
|
44
|
-
def to_model(organization, user = nil)
|
45
|
-
Gitrob::Repo.new(
|
46
|
-
:name => self.name,
|
47
|
-
:owner_name => self.owner,
|
48
|
-
:description => self.description,
|
49
|
-
:website => self.website,
|
50
|
-
:url => self.url,
|
51
|
-
:organization => organization,
|
52
|
-
:user => user
|
53
|
-
)
|
54
|
-
end
|
55
|
-
|
56
|
-
def save_to_database!(organization, user = nil)
|
57
|
-
self.to_model(organization, user).tap { |m| m.save }
|
58
|
-
rescue DataMapper::SaveFailureError => e
|
59
|
-
puts e.resource.errors.inspect
|
60
|
-
end
|
61
|
-
|
62
|
-
private
|
63
|
-
|
64
|
-
def info
|
65
|
-
if !@info
|
66
|
-
@info = JSON.parse(http_client.do_get("/repos/#{owner}/#{name}").body)
|
67
|
-
end
|
68
|
-
@info
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
data/lib/gitrob/github/user.rb
DELETED
@@ -1,84 +0,0 @@
|
|
1
|
-
module Gitrob
|
2
|
-
module Github
|
3
|
-
class User
|
4
|
-
|
5
|
-
attr_reader :username, :http_client
|
6
|
-
|
7
|
-
def initialize(username, http_client)
|
8
|
-
@username, @http_client = username, http_client
|
9
|
-
end
|
10
|
-
|
11
|
-
def name
|
12
|
-
info['name'] || username
|
13
|
-
end
|
14
|
-
|
15
|
-
def email
|
16
|
-
info['email']
|
17
|
-
end
|
18
|
-
|
19
|
-
def website
|
20
|
-
info['blog']
|
21
|
-
end
|
22
|
-
|
23
|
-
def location
|
24
|
-
info['location']
|
25
|
-
end
|
26
|
-
|
27
|
-
def bio
|
28
|
-
info['bio']
|
29
|
-
end
|
30
|
-
|
31
|
-
def url
|
32
|
-
info['html_url']
|
33
|
-
end
|
34
|
-
|
35
|
-
def avatar_url
|
36
|
-
info['avatar_url']
|
37
|
-
end
|
38
|
-
|
39
|
-
def repositories
|
40
|
-
@repositories ||= recursive_repositories
|
41
|
-
end
|
42
|
-
|
43
|
-
def to_model(organization)
|
44
|
-
organization.users.new(
|
45
|
-
:username => self.username,
|
46
|
-
:name => self.name,
|
47
|
-
:website => self.website,
|
48
|
-
:location => self.location,
|
49
|
-
:email => self.email,
|
50
|
-
:bio => self.bio,
|
51
|
-
:url => self.url,
|
52
|
-
:avatar_url => self.avatar_url
|
53
|
-
)
|
54
|
-
end
|
55
|
-
|
56
|
-
def save_to_database!(organization)
|
57
|
-
self.to_model(organization).tap { |m| m.save }
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
def recursive_repositories(page = 1)
|
63
|
-
repositories = Array.new
|
64
|
-
response = http_client.do_get("/users/#{username}/repos?page=#{page.to_i}")
|
65
|
-
JSON.parse(response.body).each do |repo|
|
66
|
-
next if repo['fork']
|
67
|
-
repositories << Repository.new(username, repo['name'], http_client)
|
68
|
-
end
|
69
|
-
|
70
|
-
if response.headers.include?('link') && response.headers['link'].include?('rel="next"')
|
71
|
-
repositories += recursive_repositories(page + 1)
|
72
|
-
end
|
73
|
-
repositories
|
74
|
-
end
|
75
|
-
|
76
|
-
def info
|
77
|
-
if !@info
|
78
|
-
@info = JSON.parse(http_client.do_get("/users/#{username}").body)
|
79
|
-
end
|
80
|
-
@info
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|