gitrob 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +47 -0
  7. data/Rakefile +2 -0
  8. data/bin/gitrob +258 -0
  9. data/gitrob.gemspec +36 -0
  10. data/lib/gitrob.rb +116 -0
  11. data/lib/gitrob/github/blob.rb +41 -0
  12. data/lib/gitrob/github/http_client.rb +127 -0
  13. data/lib/gitrob/github/organization.rb +93 -0
  14. data/lib/gitrob/github/repository.rb +72 -0
  15. data/lib/gitrob/github/user.rb +78 -0
  16. data/lib/gitrob/observers/sensitive_files.rb +82 -0
  17. data/lib/gitrob/progressbar.rb +52 -0
  18. data/lib/gitrob/util.rb +11 -0
  19. data/lib/gitrob/version.rb +3 -0
  20. data/lib/gitrob/webapp.rb +76 -0
  21. data/models/blob.rb +35 -0
  22. data/models/finding.rb +14 -0
  23. data/models/organization.rb +32 -0
  24. data/models/repo.rb +22 -0
  25. data/models/user.rb +28 -0
  26. data/patterns.json +303 -0
  27. data/public/fonts/glyphicons-halflings-regular.eot +0 -0
  28. data/public/fonts/glyphicons-halflings-regular.svg +229 -0
  29. data/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  30. data/public/fonts/glyphicons-halflings-regular.woff +0 -0
  31. data/public/javascripts/bootstrap.min.js +7 -0
  32. data/public/javascripts/gitrob.js +75 -0
  33. data/public/javascripts/jquery-2.1.1.min.js +4 -0
  34. data/public/javascripts/lang-apollo.js +2 -0
  35. data/public/javascripts/lang-basic.js +3 -0
  36. data/public/javascripts/lang-clj.js +18 -0
  37. data/public/javascripts/lang-css.js +2 -0
  38. data/public/javascripts/lang-dart.js +3 -0
  39. data/public/javascripts/lang-erlang.js +2 -0
  40. data/public/javascripts/lang-go.js +1 -0
  41. data/public/javascripts/lang-hs.js +2 -0
  42. data/public/javascripts/lang-lisp.js +3 -0
  43. data/public/javascripts/lang-llvm.js +1 -0
  44. data/public/javascripts/lang-lua.js +2 -0
  45. data/public/javascripts/lang-matlab.js +6 -0
  46. data/public/javascripts/lang-ml.js +2 -0
  47. data/public/javascripts/lang-mumps.js +2 -0
  48. data/public/javascripts/lang-n.js +4 -0
  49. data/public/javascripts/lang-pascal.js +3 -0
  50. data/public/javascripts/lang-proto.js +1 -0
  51. data/public/javascripts/lang-r.js +2 -0
  52. data/public/javascripts/lang-rd.js +1 -0
  53. data/public/javascripts/lang-scala.js +2 -0
  54. data/public/javascripts/lang-sql.js +2 -0
  55. data/public/javascripts/lang-tcl.js +3 -0
  56. data/public/javascripts/lang-tex.js +1 -0
  57. data/public/javascripts/lang-vb.js +2 -0
  58. data/public/javascripts/lang-vhdl.js +3 -0
  59. data/public/javascripts/lang-wiki.js +2 -0
  60. data/public/javascripts/lang-xq.js +3 -0
  61. data/public/javascripts/lang-yaml.js +2 -0
  62. data/public/javascripts/prettify.js +30 -0
  63. data/public/javascripts/run_prettify.js +34 -0
  64. data/public/stylesheets/bootstrap.min.css +7 -0
  65. data/public/stylesheets/bootstrap.min.css.vanilla +5 -0
  66. data/public/stylesheets/gitrob.css +88 -0
  67. data/public/stylesheets/prettify.css +51 -0
  68. data/spec/lib/gitrob/observers/sensitive_files_spec.rb +558 -0
  69. data/spec/spec_helper.rb +127 -0
  70. data/views/blob.erb +22 -0
  71. data/views/index.erb +32 -0
  72. data/views/layout.erb +30 -0
  73. data/views/organization.erb +126 -0
  74. data/views/repository.erb +51 -0
  75. data/views/user.erb +51 -0
  76. metadata +317 -0
@@ -0,0 +1,41 @@
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
@@ -0,0 +1,127 @@
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
@@ -0,0 +1,93 @@
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
+ if !@repositories
40
+ @repositories = []
41
+ response = JSON.parse(http_client.do_get("/orgs/#{name}/repos").body)
42
+ response.each do |repo|
43
+ next if repo['fork']
44
+ @repositories << Repository.new(name, repo['name'], http_client)
45
+ end
46
+ end
47
+ @repositories
48
+ end
49
+
50
+ def members()
51
+ @members ||= recursive_members
52
+ end
53
+
54
+ def to_model
55
+ Gitrob::Organization.new(
56
+ :name => self.display_name,
57
+ :login => self.login,
58
+ :website => self.website,
59
+ :location => self.location,
60
+ :email => self.email,
61
+ :avatar_url => self.avatar_url,
62
+ :url => self.url
63
+ )
64
+ end
65
+
66
+ def save_to_database!
67
+ self.to_model.tap { |m| m.save }
68
+ end
69
+
70
+ private
71
+
72
+ def recursive_members(page = 1)
73
+ members = Array.new
74
+ response = http_client.do_get("/orgs/#{name}/members?page=#{page.to_i}")
75
+ JSON.parse(response.body).each do |member|
76
+ members << User.new(member['login'], http_client)
77
+ end
78
+
79
+ if response.headers.include?('link') && response.headers['link'].include?('rel="next"')
80
+ members += recursive_members(page + 1)
81
+ end
82
+ members
83
+ end
84
+
85
+ def info
86
+ if !@info
87
+ @info = JSON.parse(http_client.do_get("/orgs/#{name}").body)
88
+ end
89
+ @info
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,72 @@
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
@@ -0,0 +1,78 @@
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
+ if !@repositories
41
+ @repositories = []
42
+ response = JSON.parse(http_client.do_get("/users/#{username}/repos").body)
43
+ response.each do |repo|
44
+ next if repo['fork']
45
+ @repositories << Repository.new(username, repo['name'], http_client)
46
+ end
47
+ end
48
+ @repositories
49
+ end
50
+
51
+ def to_model(organization)
52
+ organization.users.new(
53
+ :username => self.username,
54
+ :name => self.name,
55
+ :website => self.website,
56
+ :location => self.location,
57
+ :email => self.email,
58
+ :bio => self.bio,
59
+ :url => self.url,
60
+ :avatar_url => self.avatar_url
61
+ )
62
+ end
63
+
64
+ def save_to_database!(organization)
65
+ self.to_model(organization).tap { |m| m.save }
66
+ end
67
+
68
+ private
69
+
70
+ def info
71
+ if !@info
72
+ @info = JSON.parse(http_client.do_get("/users/#{username}").body)
73
+ end
74
+ @info
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,82 @@
1
+ module Gitrob
2
+ module Observers
3
+ class SensitiveFiles
4
+
5
+ class InvalidPatternFileError < StandardError; end
6
+ class InvalidPatternError < StandardError; end
7
+
8
+ VALID_KEYS = %w(part type pattern caption description)
9
+ VALID_PARTS = %w(path filename extension)
10
+ VALID_TYPES = %w(match regex)
11
+
12
+ def self.observe(blob)
13
+ patterns.each do |pattern|
14
+ check_blob(blob, pattern)
15
+ end
16
+ end
17
+
18
+ def self.load_patterns!
19
+ patterns = read_pattern_file!
20
+ validate_patterns!(patterns)
21
+ @patterns = patterns
22
+ end
23
+
24
+ def self.patterns
25
+ @patterns
26
+ end
27
+
28
+ private
29
+
30
+ def self.read_pattern_file!
31
+ JSON.parse(File.read("#{File.dirname(__FILE__)}/../../../patterns.json"))
32
+ rescue JSON::ParserError => e
33
+ raise InvalidPatternFileError.new("Cannot parse pattern file: #{e.message}")
34
+ end
35
+
36
+ def self.validate_patterns!(patterns)
37
+ if !patterns.is_a?(Array) || patterns.empty?
38
+ raise InvalidPatternFileError.new("Pattern file contains no patterns")
39
+ end
40
+ patterns.each do |pattern|
41
+ validate_pattern!(pattern)
42
+ end
43
+ end
44
+
45
+ def self.validate_pattern!(pattern)
46
+ pattern.keys.each do |key|
47
+ if !VALID_KEYS.include?(key)
48
+ raise InvalidPatternError.new("Pattern contains unknown key: #{key}")
49
+ end
50
+ end
51
+
52
+ if !VALID_PARTS.include?(pattern['part'])
53
+ raise InvalidPatternError.new("Pattern has unknown part: #{pattern['part']}")
54
+ end
55
+
56
+ if !VALID_TYPES.include?(pattern['type'])
57
+ raise InvalidPatternError.new("Pattern has unknown type: #{pattern['type']}")
58
+ end
59
+ end
60
+
61
+ def self.check_blob(blob, pattern)
62
+ haystack = blob.send(pattern['part'].to_sym)
63
+ if pattern['type'] == 'match'
64
+ if haystack == pattern['pattern']
65
+ blob.findings.new(
66
+ :caption => pattern['caption'],
67
+ :description => pattern['description']
68
+ )
69
+ end
70
+ else
71
+ regex = Regexp.new(pattern['pattern'], Regexp::IGNORECASE)
72
+ if !regex.match(haystack).nil?
73
+ blob.findings.new(
74
+ :caption => pattern['caption'],
75
+ :description => pattern['description']
76
+ )
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end