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
@@ -0,0 +1,25 @@
|
|
1
|
+
module Gitrob
|
2
|
+
class CLI
|
3
|
+
module Commands
|
4
|
+
class Banner < Gitrob::CLI::Command
|
5
|
+
def initialize(options)
|
6
|
+
@options = options
|
7
|
+
output banner if options[:banner]
|
8
|
+
info "Starting Gitrob version #{Gitrob::VERSION} " \
|
9
|
+
"at #{Time.now.strftime('%Y-%m-%d %H:%M %Z')}"
|
10
|
+
debug "Debugging mode enabled"
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def banner
|
16
|
+
" _ _ _\n" \
|
17
|
+
" ___|_| |_ ___ ___| |_\n" \
|
18
|
+
"| . | | _| _| . | . |\n" \
|
19
|
+
"|_ |_|_| |_| |___|___|\n" \
|
20
|
+
"|___|".light_blue + " By @michenriksen\n\n".light_white
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Gitrob
|
2
|
+
class CLI
|
3
|
+
module Commands
|
4
|
+
class Configure < Gitrob::CLI::Command
|
5
|
+
CONFIGURATION_FILE_PATH = File.join(Dir.home, ".gitrobrc")
|
6
|
+
|
7
|
+
class ConfigurationError < StandardError; end
|
8
|
+
class ConfigurationFileNotFound < ConfigurationError; end
|
9
|
+
class ConfigurationFileNotReadable < ConfigurationError; end
|
10
|
+
class ConfigurationFileCorrupt < ConfigurationError; end
|
11
|
+
|
12
|
+
def initialize(options)
|
13
|
+
@options = options
|
14
|
+
info("Starting Gitrob configuration wizard")
|
15
|
+
return unless agree_to_overwrite?
|
16
|
+
config = gather_configuration
|
17
|
+
task("Saving configuration to #{CONFIGURATION_FILE_PATH}") do
|
18
|
+
save_configuration(config)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.configured?
|
23
|
+
File.exist?(CONFIGURATION_FILE_PATH)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.load_configuration!
|
27
|
+
fail ConfigurationFileNotFound \
|
28
|
+
unless File.exist?(CONFIGURATION_FILE_PATH)
|
29
|
+
fail ConfigurationFileNotReadable \
|
30
|
+
unless File.readable?(CONFIGURATION_FILE_PATH)
|
31
|
+
YAML.load(File.read(CONFIGURATION_FILE_PATH))
|
32
|
+
rescue Psych::SyntaxError
|
33
|
+
raise ConfigurationFileCorrupt
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def agree_to_overwrite?
|
39
|
+
return true unless self.class.configured?
|
40
|
+
warn("Configuration file already exists\n")
|
41
|
+
agree(
|
42
|
+
"Proceed and overwrite existing configuration file? (y/n): ")
|
43
|
+
end
|
44
|
+
|
45
|
+
def gather_configuration
|
46
|
+
{
|
47
|
+
:hostname => gather_hostname,
|
48
|
+
:port => gather_port,
|
49
|
+
:username => gather_username,
|
50
|
+
:password => gather_password,
|
51
|
+
:database => gather_database,
|
52
|
+
:access_tokens => gather_access_tokens
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def gather_hostname
|
57
|
+
ask("Enter PostgreSQL hostname: ") do |q|
|
58
|
+
q.default = "localhost"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def gather_port
|
63
|
+
ask("Enter PostgreSQL port: |5432| ", Integer) do |q|
|
64
|
+
q.default = 5432
|
65
|
+
q.in = 1..65_535
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def gather_username
|
70
|
+
ask("Enter PostgreSQL username: ") do |q|
|
71
|
+
q.default = "gitrob"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def gather_password
|
76
|
+
ask("Enter PostgreSQL password (masked): ") do |q|
|
77
|
+
q.echo = "x"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def gather_database
|
82
|
+
ask("Enter PostgreSQL database name: ") do |q|
|
83
|
+
q.default = "gitrob"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def gather_access_tokens
|
88
|
+
tokens = []
|
89
|
+
while tokens.uniq.empty?
|
90
|
+
tokens = ask("Enter GitHub access tokens (blank line to stop):",
|
91
|
+
->(ans) { ans =~ /[a-f0-9]{40}/ ? ans : nil }) do |q|
|
92
|
+
q.gather = ""
|
93
|
+
end
|
94
|
+
end
|
95
|
+
tokens
|
96
|
+
end
|
97
|
+
|
98
|
+
def save_configuration(config)
|
99
|
+
File.open(CONFIGURATION_FILE_PATH, "w") do |file|
|
100
|
+
file.write(build_yaml(config))
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def make_connection_uri(username, password, hostname, port, database)
|
105
|
+
"postgres://#{username}:#{password}@#{hostname}:#{port}/#{database}"
|
106
|
+
end
|
107
|
+
|
108
|
+
def build_yaml(config)
|
109
|
+
YAML.dump(
|
110
|
+
"sql_connection_uri" => make_connection_uri(
|
111
|
+
config[:username],
|
112
|
+
config[:password],
|
113
|
+
config[:hostname],
|
114
|
+
config[:port],
|
115
|
+
config[:database]
|
116
|
+
),
|
117
|
+
"github_access_tokens" => config[:access_tokens]
|
118
|
+
)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Gitrob
|
2
|
+
class CLI
|
3
|
+
module Commands
|
4
|
+
class Server < Gitrob::CLI::Command
|
5
|
+
def initialize(options)
|
6
|
+
@options = options
|
7
|
+
info "Starting web application on port #{options[:port]}..."
|
8
|
+
|
9
|
+
if debugging_enabled?
|
10
|
+
Sequel::Model.db.logger = QueryLogger.new(STDOUT)
|
11
|
+
end
|
12
|
+
|
13
|
+
Gitrob::WebApp.run!(
|
14
|
+
:port => options[:port].to_i,
|
15
|
+
:bind => options[:bind_address]
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Gitrob
|
2
|
+
class CLI
|
3
|
+
class ProgressBar
|
4
|
+
def initialize(message, options={})
|
5
|
+
@options = {
|
6
|
+
:format =>
|
7
|
+
"#{'[*]'.light_blue} %t %c/%C %B %j% %e",
|
8
|
+
:progress_mark => "|".light_blue,
|
9
|
+
:remainder_mark => "|"
|
10
|
+
}.merge(options)
|
11
|
+
@mutex = Mutex.new
|
12
|
+
Gitrob::CLI.info(message)
|
13
|
+
@progress_bar = ::ProgressBar.create(@options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def finish
|
17
|
+
progress_bar.finish
|
18
|
+
end
|
19
|
+
|
20
|
+
def info(message)
|
21
|
+
progress_bar.log("#{'[+]'.light_blue} #{message}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def error(message)
|
25
|
+
progress_bar.log("#{'[!]'.light_red} #{message}")
|
26
|
+
end
|
27
|
+
|
28
|
+
def warn(message)
|
29
|
+
progress_bar.log("#{'[!]'.light_yellow} #{message}")
|
30
|
+
end
|
31
|
+
|
32
|
+
def method_missing(method, *args, &block)
|
33
|
+
if progress_bar.respond_to?(method)
|
34
|
+
progress_bar.send(method, *args, &block)
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def progress_bar
|
43
|
+
@mutex.synchronize { @progress_bar }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/gitrob/cli.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
module Gitrob
|
2
|
+
class QueryLogger < Logger
|
3
|
+
def format_message(_severity, _timestamp, _progname, msg)
|
4
|
+
"#{msg}\n".cyan
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class CLI < Thor
|
9
|
+
HELP_COMMANDS = %w(help h --help -h)
|
10
|
+
package_name "Gitrob"
|
11
|
+
|
12
|
+
attr_reader :configuration
|
13
|
+
|
14
|
+
DB_MIGRATIONS_PATH = File.expand_path(
|
15
|
+
"../../../db/migrations", __FILE__
|
16
|
+
)
|
17
|
+
|
18
|
+
class_option :bind_address,
|
19
|
+
:type => :string,
|
20
|
+
:banner => "ADDRESS",
|
21
|
+
:default => "127.0.0.1",
|
22
|
+
:desc => "Address to bind web server to"
|
23
|
+
class_option :port,
|
24
|
+
:type => :numeric,
|
25
|
+
:default => 9393,
|
26
|
+
:desc => "Port to run web server on"
|
27
|
+
class_option :access_tokens,
|
28
|
+
:type => :array,
|
29
|
+
:banner => "TOKENS",
|
30
|
+
:desc => "GitHub API tokens to use " \
|
31
|
+
"instead of what has been configured"
|
32
|
+
class_option :color,
|
33
|
+
:type => :boolean,
|
34
|
+
:default => true,
|
35
|
+
:desc => "Colorize or don't colorize output"
|
36
|
+
class_option :banner,
|
37
|
+
:type => :boolean,
|
38
|
+
:default => true,
|
39
|
+
:desc => "Show or don't show Gitrob banner"
|
40
|
+
class_option :debug,
|
41
|
+
:type => :boolean,
|
42
|
+
:default => false,
|
43
|
+
:desc => "Show or don't show debugging information"
|
44
|
+
|
45
|
+
def initialize(*args)
|
46
|
+
super
|
47
|
+
self.class.enable_debugging if options[:debug]
|
48
|
+
String.disable_colorization(!options[:color])
|
49
|
+
return if help_command?
|
50
|
+
banner
|
51
|
+
configure unless configured?
|
52
|
+
load_configuration
|
53
|
+
prepare_database
|
54
|
+
end
|
55
|
+
|
56
|
+
desc "analyze TARGETS", "Analyze one or more organizations or users"
|
57
|
+
option :title,
|
58
|
+
:type => :string,
|
59
|
+
:desc => "Give assessment a custom title"
|
60
|
+
option :threads,
|
61
|
+
:type => :numeric,
|
62
|
+
:default => 5,
|
63
|
+
:desc => "Number of threads to use"
|
64
|
+
option :server,
|
65
|
+
:type => :boolean,
|
66
|
+
:default => true,
|
67
|
+
:desc => "Start or don't start web server after assessment"
|
68
|
+
option :endpoint,
|
69
|
+
:type => :string,
|
70
|
+
:banner => "URL",
|
71
|
+
:default => "https://api.github.com",
|
72
|
+
:desc => "Specify a URL for a custom GitHub Enterprise API"
|
73
|
+
option :site,
|
74
|
+
:type => :string,
|
75
|
+
:banner => "URL",
|
76
|
+
:default => "https://github.com",
|
77
|
+
:desc => "Specify a URL for a custom GitHub Enterprise site"
|
78
|
+
option :verify_ssl,
|
79
|
+
:type => :boolean,
|
80
|
+
:default => true,
|
81
|
+
:desc => "Verify or don't verify SSL connection (careful here)"
|
82
|
+
def analyze(targets)
|
83
|
+
accept_tos
|
84
|
+
Gitrob::CLI::Commands::Analyze.start(targets, options)
|
85
|
+
end
|
86
|
+
|
87
|
+
desc "server", "Start web server"
|
88
|
+
def server
|
89
|
+
accept_tos
|
90
|
+
Gitrob::CLI::Commands::Server.start(options)
|
91
|
+
end
|
92
|
+
|
93
|
+
desc "configure", "Start configuration wizard"
|
94
|
+
def configure
|
95
|
+
Gitrob::CLI::Commands::Configure.start(options)
|
96
|
+
end
|
97
|
+
|
98
|
+
desc "banner", "Print Gitrob banner", :hide => true
|
99
|
+
def banner
|
100
|
+
Gitrob::CLI::Commands::Banner.start(options)
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "accept-tos", "Accept Terms Of Use", :hide => true
|
104
|
+
def accept_tos
|
105
|
+
Gitrob::CLI::Commands::AcceptTermsOfUse.start(options)
|
106
|
+
end
|
107
|
+
|
108
|
+
no_commands do
|
109
|
+
def help_command?
|
110
|
+
!ENV["GITROB_ENV"] == "TEST" &&
|
111
|
+
(ARGV.empty? || HELP_COMMANDS.include?(ARGV.first.downcase))
|
112
|
+
end
|
113
|
+
|
114
|
+
def configured?
|
115
|
+
Gitrob::CLI::Commands::Configure.configured?
|
116
|
+
end
|
117
|
+
|
118
|
+
def load_configuration
|
119
|
+
self.class.task("Loading configuration...", true) do
|
120
|
+
@configuration = Gitrob::CLI::Commands::Configure.load_configuration!
|
121
|
+
self.class.configuration = @configuration
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def prepare_database
|
126
|
+
self.class.task("Preparing database...", true) do
|
127
|
+
Sequel.extension :migration, :core_extensions
|
128
|
+
db = Sequel.connect(configuration["sql_connection_uri"])
|
129
|
+
Sequel::Migrator.run(db, DB_MIGRATIONS_PATH)
|
130
|
+
Sequel::Model.raise_on_save_failure = true
|
131
|
+
Sequel::Model.db = db
|
132
|
+
Sequel::Model.plugin :validation_helpers
|
133
|
+
Sequel::Model.plugin :timestamps
|
134
|
+
load_models
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def load_models
|
139
|
+
require "gitrob/models/assessment"
|
140
|
+
require "gitrob/models/github_access_token"
|
141
|
+
require "gitrob/models/owner"
|
142
|
+
require "gitrob/models/repository"
|
143
|
+
require "gitrob/models/blob"
|
144
|
+
require "gitrob/models/flag"
|
145
|
+
require "gitrob/models/comparison"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.info(message)
|
150
|
+
output "[*]".light_blue + " #{message}\n"
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.task(message, fatal_error=false, &block)
|
154
|
+
output "[*]".light_blue + " #{message}"
|
155
|
+
yield block
|
156
|
+
output " done\n".light_green
|
157
|
+
rescue => e
|
158
|
+
output " failed\n".light_red
|
159
|
+
output_failed_task(e, fatal_error)
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.output_failed_task(exception, fatal_error)
|
163
|
+
message = "#{exception.class}: #{exception.message}"
|
164
|
+
debug exception.backtrace.join("\n")
|
165
|
+
if fatal_error
|
166
|
+
fatal message
|
167
|
+
else
|
168
|
+
error message
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.warn(message)
|
173
|
+
output "[!]".light_yellow + " #{message}\n"
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.error(message)
|
177
|
+
output "[!]".light_red + " #{message}\n"
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.fatal(message)
|
181
|
+
output "[!]".light_white.on_red + " #{message}\n"
|
182
|
+
exit(1)
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.debug(message)
|
186
|
+
output "[#]".light_cyan + " #{message}\n" if debugging_enabled?
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.output(string)
|
190
|
+
print string
|
191
|
+
end
|
192
|
+
|
193
|
+
def self.enable_debugging
|
194
|
+
@debugging_enabled = true
|
195
|
+
end
|
196
|
+
|
197
|
+
def self.disable_debugging
|
198
|
+
@debugging_enabled = false
|
199
|
+
end
|
200
|
+
|
201
|
+
def self.debugging_enabled? # rubocop:disable Style/TrivialAccessors
|
202
|
+
@debugging_enabled
|
203
|
+
end
|
204
|
+
|
205
|
+
def self.configuration=(config)
|
206
|
+
@config = config
|
207
|
+
end
|
208
|
+
|
209
|
+
def self.configuration
|
210
|
+
@config
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Gitrob
|
2
|
+
module Github
|
3
|
+
class ClientManager
|
4
|
+
USER_AGENT = "Gitrob v#{Gitrob::VERSION}"
|
5
|
+
|
6
|
+
attr_reader :clients
|
7
|
+
|
8
|
+
class NoClientsError < StandardError; end
|
9
|
+
|
10
|
+
def initialize(config)
|
11
|
+
@config = config
|
12
|
+
@mutex = Mutex.new
|
13
|
+
@clients = []
|
14
|
+
config[:access_tokens].each do |token|
|
15
|
+
clients << create_client(token)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def sample
|
20
|
+
@mutex.synchronize do
|
21
|
+
fail NoClientsError if clients.count.zero?
|
22
|
+
clients.sample
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def remove(client)
|
27
|
+
@mutex.synchronize do
|
28
|
+
clients.delete(client)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def create_client(access_token)
|
35
|
+
::Github.new(
|
36
|
+
:oauth_token => access_token,
|
37
|
+
:endpoint => @config[:endpoint],
|
38
|
+
:site => @config[:site],
|
39
|
+
:ssl => @config[:verify_ssl],
|
40
|
+
:user_agent => USER_AGENT,
|
41
|
+
:auto_pagination => true
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module Gitrob
|
2
|
+
module Github
|
3
|
+
class DataManager
|
4
|
+
attr_reader :client_manager,
|
5
|
+
:unknown_logins,
|
6
|
+
:owners,
|
7
|
+
:repositories
|
8
|
+
|
9
|
+
def initialize(logins, client_manager)
|
10
|
+
@logins = logins
|
11
|
+
@client_manager = client_manager
|
12
|
+
@unknown_logins = []
|
13
|
+
@owners = []
|
14
|
+
@repositories = []
|
15
|
+
@repositories_for_owners = {}
|
16
|
+
@mutex = Mutex.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def gather_owners(thread_pool)
|
20
|
+
@logins.each do |login|
|
21
|
+
next unless owner = get_owner(login)
|
22
|
+
@owners << owner
|
23
|
+
@repositories_for_owners[owner["login"]] = []
|
24
|
+
next unless owner["type"] == "Organization"
|
25
|
+
get_members(owner, thread_pool) if owner["type"] == "Organization"
|
26
|
+
end
|
27
|
+
@owners = @owners.uniq { |o| o["login"] }
|
28
|
+
end
|
29
|
+
|
30
|
+
def gather_repositories(thread_pool)
|
31
|
+
owners.each do |owner|
|
32
|
+
thread_pool.process do
|
33
|
+
repositories = get_repositories(owner)
|
34
|
+
with_mutex do
|
35
|
+
save_repositories(owner, repositories)
|
36
|
+
end
|
37
|
+
yield owner, repositories if block_given?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def repositories_for_owner(owner)
|
43
|
+
@repositories_for_owners[owner["login"]]
|
44
|
+
end
|
45
|
+
|
46
|
+
def blobs_for_repository(repository)
|
47
|
+
get_blobs(repository)
|
48
|
+
rescue ::Github::Error::Forbidden => e
|
49
|
+
# Hidden GitHub feature?
|
50
|
+
raise e unless e.message.include?("403 Repository access blocked")
|
51
|
+
[]
|
52
|
+
rescue ::Github::Error::NotFound
|
53
|
+
[]
|
54
|
+
rescue ::Github::Error::ServiceError => e
|
55
|
+
raise e unless e.message.include?("409 Git Repository is empty")
|
56
|
+
[]
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def get_owner(login)
|
62
|
+
github_client do |client|
|
63
|
+
client.users.get(:user => login)
|
64
|
+
end
|
65
|
+
rescue ::Github::Error::NotFound
|
66
|
+
@unknown_logins << login
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_members(org, thread_pool)
|
71
|
+
github_client do |client|
|
72
|
+
client.orgs.members.list(:org_name => org["login"]) do |owner|
|
73
|
+
thread_pool.process do
|
74
|
+
owner = get_owner(owner["login"])
|
75
|
+
with_mutex do
|
76
|
+
@owners << owner
|
77
|
+
@repositories_for_owners[owner["login"]] = []
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_repositories(owner)
|
85
|
+
github_client do |client|
|
86
|
+
client.repos.list(
|
87
|
+
:user => owner["login"]).reject { |r| r["fork"] }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def get_blobs(repository)
|
92
|
+
github_client do |client|
|
93
|
+
client.get_request(
|
94
|
+
"repos/#{repository[:full_name]}/git/trees/" \
|
95
|
+
"#{repository[:default_branch]}",
|
96
|
+
::Github::ParamsHash.new(:recursive => 1))["tree"]
|
97
|
+
.reject { |b| b["type"] != "blob" }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def github_client
|
102
|
+
client = @client_manager.sample
|
103
|
+
yield client
|
104
|
+
rescue ::Github::Error::Forbidden => e
|
105
|
+
raise e unless e.message.include?("API rate limit exceeded")
|
106
|
+
@client_manager.remove(client)
|
107
|
+
rescue ::Github::Error::Unauthorized
|
108
|
+
@client_manager.remove(client)
|
109
|
+
end
|
110
|
+
|
111
|
+
def save_repositories(owner, repositories)
|
112
|
+
@repositories += repositories
|
113
|
+
@repositories_for_owners[owner["login"]] = repositories
|
114
|
+
end
|
115
|
+
|
116
|
+
def with_mutex
|
117
|
+
@mutex.synchronize { yield }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Gitrob
|
2
|
+
module Jobs
|
3
|
+
class Comparison
|
4
|
+
include SuckerPunch::Job
|
5
|
+
|
6
|
+
def perform(primary_assessment, secondary_assessment)
|
7
|
+
@comparison = Gitrob::Models::Comparison.new
|
8
|
+
@primary_assessment = primary_assessment
|
9
|
+
@secondary_assessment = secondary_assessment
|
10
|
+
@comparison.primary_assessment = primary_assessment
|
11
|
+
@comparison.secondary_assessment = secondary_assessment
|
12
|
+
@comparison.save
|
13
|
+
compare_blobs
|
14
|
+
compare_repositories
|
15
|
+
compare_owners
|
16
|
+
@comparison.finished = true
|
17
|
+
@comparison.save
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def compare_blobs
|
23
|
+
old_blob_shas = @secondary_assessment.blobs_dataset.select_map(:sha)
|
24
|
+
@primary_assessment.blobs_dataset.eager(:flags).all.each do |blob|
|
25
|
+
next if old_blob_shas.include?(blob.sha)
|
26
|
+
@comparison.add_blob(blob)
|
27
|
+
@comparison.blobs_count += 1
|
28
|
+
@comparison.findings_count += 1 unless blob.flags_count.zero?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def compare_repositories
|
33
|
+
old_repository_github_ids = @secondary_assessment
|
34
|
+
.repositories_dataset
|
35
|
+
.select_map(:github_id)
|
36
|
+
@primary_assessment.repositories.each do |repository|
|
37
|
+
next if old_repository_github_ids.include?(repository.github_id)
|
38
|
+
@comparison.add_repository(repository)
|
39
|
+
@comparison.repositories_count += 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def compare_owners
|
44
|
+
old_owner_github_ids = @secondary_assessment
|
45
|
+
.owners_dataset
|
46
|
+
.select_map(:github_id)
|
47
|
+
@primary_assessment.owners.each do |owner|
|
48
|
+
next if old_owner_github_ids.include?(owner.github_id)
|
49
|
+
@comparison.add_owner(owner)
|
50
|
+
@comparison.owners_count += 1
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|