CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/tools/modules/file_pull_requests.rb
Views: 11766
1
#!/usr/bin/env ruby
2
3
##
4
# This module requires Metasploit: https://metasploit.com/download
5
# Current source: https://github.com/rapid7/metasploit-framework
6
##
7
8
#
9
# This tool allows you to find all the pull requests for a particular file in the Metasploit
10
# repository. It does not include commit history from SVN.
11
#
12
# Author: sinn3r
13
#
14
15
require 'net/http'
16
require 'optparse'
17
18
begin
19
require 'octokit'
20
require 'nokogiri'
21
rescue LoadError => e
22
gem = e.message.split.last
23
abort "#{gem} not installed: please run `gem install #{gem}'"
24
end
25
26
module FilePullRequestCollector
27
28
class Exception < RuntimeError; end
29
30
class PullRequestFinder
31
32
attr_accessor :git_client
33
attr_accessor :repository
34
attr_accessor :branch
35
attr_accessor :owner
36
attr_accessor :git_access_token
37
38
# Initializes parameters.
39
#
40
# @param api_key [String] Personal access token from Github.
41
# @return [void]
42
def initialize(api_key)
43
self.owner = 'rapid7'
44
self.repository = "#{owner}/metasploit-framework"
45
self.branch = 'master'
46
self.git_access_token = api_key
47
self.git_client = Octokit::Client.new(access_token: git_access_token)
48
end
49
50
# Returns the commit history of a file.
51
#
52
# @param path [String] A file path in the Metasploit repository.
53
# @return [Array<Sawyer::Resource>] An array of commits.
54
# @raise [FilePullRequestCollector::Exception] No commits found. Probably the file path is wrong.
55
def get_commits_from_file(path)
56
commits = git_client.commits(repository, branch, path: path)
57
if commits.empty?
58
# Possibly the path is wrong.
59
raise FilePullRequestCollector::Exception, 'No commits found.'
60
end
61
62
commits
63
end
64
65
# Returns the author of a commit.
66
#
67
# @param commit [Sawyer::Resource] Commit.
68
# @return [String]
69
def get_author(commit)
70
if commit.author
71
return commit.author[:login].to_s
72
end
73
74
''
75
end
76
77
# Checks if a author should be ignored or not.
78
#
79
# @param commit [Sawyer::Resource] Commit.
80
# @return [TrueClass] Author should be ignored
81
# @return [FalseClass] Author should not be ignored.
82
def is_author_blacklisted?(commit)
83
['tabassassin'].include?(get_author(commit))
84
end
85
86
# Returns all found pull requests.
87
#
88
# @param commits [Array<Sawyer::Resource>] Commits
89
# @return [Hash]
90
def get_pull_requests_from_commits(commits)
91
pull_requests = {}
92
93
commits.each do |commit|
94
next if is_author_blacklisted?(commit)
95
96
pr = get_pull_request_from_commit(commit)
97
unless pr.empty?
98
pull_requests[pr[:number]] = pr
99
end
100
end
101
102
pull_requests
103
end
104
105
# Returns the found pull request for a commit.
106
#
107
# @param commit [Sawyer::Resource] Commit
108
# @return [Hash]
109
def get_pull_request_from_commit(commit)
110
sha = commit.sha
111
url = URI.parse("https://github.com/#{repository}/branch_commits/#{sha}")
112
cli = Net::HTTP.new(url.host, url.port)
113
cli.use_ssl = true
114
req = Net::HTTP::Get.new(url.request_uri)
115
res = cli.request(req)
116
n = Nokogiri::HTML(res.body)
117
found_pr_link = n.at('li[@class="pull-request"]//a')
118
119
# If there is no PR associated with this commit, it's probably from the SVN days.
120
return {} unless found_pr_link
121
122
href = found_pr_link.attributes['href'].text
123
title = found_pr_link.attributes['title'].text
124
125
# Filter out all the pull requests that do not belong to rapid7.
126
# If this happens, it's probably because the PR was submitted to somebody's fork.
127
return {} unless /^\/#{owner}\// === href
128
129
{ number: href.scan(/\d+$/).flatten.first, title: title }
130
end
131
end
132
133
class Client
134
135
attr_accessor :finder
136
137
# Initializes parameters.
138
#
139
# @param api_key [String]
140
# @return [void]
141
def initialize(api_key)
142
self.finder = PullRequestFinder.new(api_key)
143
end
144
145
# Prints all the found PRs for a file.
146
#
147
# @param file_name [String] The file to look up.
148
# @return [void]
149
def search(file_name)
150
commits = finder.get_commits_from_file(file_name)
151
pull_requests = finder.get_pull_requests_from_commits(commits)
152
puts "Pull request(s) associated with #{file_name}"
153
pull_requests.each_pair do |number, pr|
154
puts "##{number} - #{pr[:title]}"
155
end
156
end
157
end
158
159
class OptsParser
160
161
def self.banner
162
%Q|
163
This tool collects all the pull requests submitted to rapid7/metasploit-framework for a
164
particular file. It does not include history from SVN (what Metasploit used to use
165
before Git).
166
167
Usage: #{__FILE__} [options]
168
169
Usage Example:
170
#{__FILE__} -k KEY -f modules/exploits/windows/browser/ms13_069_caret.rb
171
or
172
export GITHUB_OAUTH_TOKEN=KEY
173
#{__FILE__} -f modules/exploits/windows/browser/ms13_069_caret.rb
174
175
How to obtain an API key (access token):
176
1. Go to github.com.
177
2. Go to Settings under your profile.
178
3. Click on Personal Access Tokens
179
4. Click on Generate new token
180
5. Follow the steps on the screen to complete the process.
181
182
|
183
end
184
185
def self.parse(args)
186
options = {}
187
188
opts = OptionParser.new do |opts|
189
opts.banner = banner.strip.gsub(/^[[:blank:]]{4}/, '')
190
191
opts.separator ""
192
opts.separator "Specific options:"
193
194
opts.on("-k", "-k <key>", "Github Access Token") do |v|
195
options[:api_key] = v
196
end
197
198
opts.on("-f", "--file <name>", "File name") do |v|
199
options[:file] = v
200
end
201
202
opts.separator ""
203
opts.separator "Common options:"
204
205
opts.on_tail("-h", "--help", "Show this message") do
206
puts opts
207
exit
208
end
209
end
210
211
begin
212
opts.parse!(args)
213
rescue OptionParser::InvalidOption
214
abort "Invalid option, try -h for usage"
215
end
216
217
if options.empty?
218
abort "No options specified, try -h for usage"
219
end
220
221
options
222
end
223
end
224
225
end
226
227
if __FILE__ == $PROGRAM_NAME
228
begin
229
opts = FilePullRequestCollector::OptsParser.parse(ARGV)
230
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
231
abort "#{e.message} (please see -h)"
232
end
233
234
if !opts.has_key?(:api_key)
235
if !ENV.has_key?('GITHUB_OAUTH_TOKEN')
236
abort <<EOF
237
A Github Access Token must be specified to use this tool
238
Please set GITHUB_OAUTH_TOKEN or specify the -k option
239
EOF
240
else
241
opts[:api_key] = ENV['GITHUB_OAUTH_TOKEN']
242
end
243
end
244
245
begin
246
cli = FilePullRequestCollector::Client.new(opts[:api_key])
247
cli.search(opts[:file])
248
rescue FilePullRequestCollector::Exception => e
249
abort e.message
250
rescue Interrupt
251
$stdout.puts
252
$stdout.puts "Good bye"
253
end
254
end
255
256