gitrob 0.0.6 → 1.0.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 +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
|