mirror of
https://github.com/michenriksen/gitrob.git
synced 2026-01-01 03:10:06 -06:00
Add ability to define custom signatures
Gitrob will now look for a file at `~/.gitrobsignatures` which it expects to be a JSON document that follows the same structure as the main signatures.json file. If the signatures are valid, it will be loaded into the BlobObserver.
This commit is contained in:
@@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- Ability to define custom signatures in `~/.gitrobsignatures`
|
||||
|
||||
## [1.0.1]
|
||||
### Fixed
|
||||
|
||||
35
README.md
35
README.md
@@ -86,6 +86,41 @@ By default, the server will listen on [localhost:9393](http://localhost:9393). T
|
||||
|
||||
See `gitrob help server` for more options.
|
||||
|
||||
### Adding custom signatures
|
||||
|
||||
If you want to look for files that are specific to your organisation or projects, it is easy to add custom signatures.
|
||||
|
||||
When Gitrob starts it looks for a file at `~/.gitrobsignatures` which it expects to be a JSON document with signatures that follow the same structure as the main [signatures.json](signatures.json) file. Here is an example:
|
||||
|
||||
[
|
||||
{
|
||||
"part": "filename",
|
||||
"type": "match",
|
||||
"pattern": "otr.private_key",
|
||||
"caption": "Pidgin OTR private key",
|
||||
"description": null
|
||||
}
|
||||
]
|
||||
|
||||
This signature instructs Gitrob to flag files where the filename exactly matches `otr.private_key`. The caption and description are used in the web interface when displaying the findings.
|
||||
|
||||
#### Signature keys
|
||||
|
||||
* `part`: Can be one of:
|
||||
* `path`: The complete file path
|
||||
* `filename`: Only the filename
|
||||
* `extension`: Only the file extension
|
||||
* `type`: Can be one of:
|
||||
* `match`: Simple match of part and pattern
|
||||
* `regex`: Regular expression matching of part and pattern
|
||||
* `pattern`: The value or regular expression to match with
|
||||
* `caption`: A short description of the finding
|
||||
* `description`: More detailed description if needed (set to `null` if not).
|
||||
|
||||
Have a look at the main [signatures.json](signatures.json) file for more examples of signatures.
|
||||
|
||||
**If you think other people can benefit from your custom signatures, please consider contributing them back to the Gitrob project by opening a Pull Request or an Issue. Thanks!**
|
||||
|
||||
## Development
|
||||
|
||||
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. Run `bundle exec gitrob` to use the gem in this directory, ignoring other installed copies of this gem.
|
||||
|
||||
@@ -2,6 +2,8 @@ module Gitrob
|
||||
class BlobObserver
|
||||
SIGNATURES_FILE_PATH = File.expand_path(
|
||||
"../../../signatures.json", __FILE__)
|
||||
CUSTOM_SIGNATURES_FILE_PATH = File.join(
|
||||
Dir.home, ".gitrobsignatures")
|
||||
|
||||
REQUIRED_SIGNATURE_KEYS = %w(part type pattern caption description)
|
||||
ALLOWED_TYPES = %w(regex match)
|
||||
@@ -30,23 +32,49 @@ module Gitrob
|
||||
|
||||
def self.load_signatures!
|
||||
@signatures = []
|
||||
JSON.load(File.read(SIGNATURES_FILE_PATH)).each do |signature|
|
||||
signatures = JSON.load(File.read(SIGNATURES_FILE_PATH))
|
||||
validate_signatures!(signatures)
|
||||
signatures.each_with_index do |signature|
|
||||
@signatures << Signature.new(signature)
|
||||
end
|
||||
validate_signatures!
|
||||
rescue CorruptSignaturesError => e
|
||||
raise e
|
||||
rescue
|
||||
raise CorruptSignaturesError, "Could not parse signature file"
|
||||
end
|
||||
|
||||
def self.validate_signatures!
|
||||
def self.unload_signatures
|
||||
@signatures = []
|
||||
end
|
||||
|
||||
def self.custom_signatures?
|
||||
File.exist?(CUSTOM_SIGNATURES_FILE_PATH)
|
||||
end
|
||||
|
||||
def self.load_custom_signatures!
|
||||
signatures = JSON.load(File.read(CUSTOM_SIGNATURES_FILE_PATH))
|
||||
validate_signatures!(signatures)
|
||||
signatures.each do |signature|
|
||||
@signatures << Signature.new(signature)
|
||||
end
|
||||
rescue CorruptSignaturesError => e
|
||||
raise e
|
||||
rescue
|
||||
raise CorruptSignaturesError, "Could not parse signature file"
|
||||
end
|
||||
|
||||
def self.validate_signatures!(signatures)
|
||||
if !signatures.is_a?(Array) || signatures.empty?
|
||||
fail CorruptSignaturesError,
|
||||
"Signature file contains no signatures"
|
||||
end
|
||||
signatures.each do |signature|
|
||||
validate_signature!(signature)
|
||||
signatures.each_with_index do |signature, index|
|
||||
begin
|
||||
validate_signature!(signature)
|
||||
rescue CorruptSignaturesError => e
|
||||
raise CorruptSignaturesError,
|
||||
"Validation failed for Signature ##{index + 1}: #{e.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -58,7 +86,7 @@ module Gitrob
|
||||
|
||||
def self.validate_signature_keys!(signature)
|
||||
REQUIRED_SIGNATURE_KEYS.each do |key|
|
||||
unless signature.respond_to?(key)
|
||||
unless signature.key?(key)
|
||||
fail CorruptSignaturesError,
|
||||
"Missing required signature key: #{key}"
|
||||
end
|
||||
@@ -66,16 +94,16 @@ module Gitrob
|
||||
end
|
||||
|
||||
def self.validate_signature_type!(signature)
|
||||
unless ALLOWED_TYPES.include?(signature.type)
|
||||
unless ALLOWED_TYPES.include?(signature["type"])
|
||||
fail CorruptSignaturesError,
|
||||
"Invalid signature type: #{signature.type}"
|
||||
"Invalid signature type: #{signature['type']}"
|
||||
end
|
||||
end
|
||||
|
||||
def self.validate_signature_part!(signature)
|
||||
unless ALLOWED_PARTS.include?(signature.part)
|
||||
unless ALLOWED_PARTS.include?(signature["part"])
|
||||
fail CorruptSignaturesError,
|
||||
"Invalid signature part: #{signature.part}"
|
||||
"Invalid signature part: #{signature['part']}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -40,6 +40,15 @@ module Gitrob
|
||||
task("Loading signatures...", true) do
|
||||
Gitrob::BlobObserver.load_signatures!
|
||||
end
|
||||
|
||||
if Gitrob::BlobObserver.custom_signatures?
|
||||
task("Loading custom signatures...", true) do
|
||||
Gitrob::BlobObserver.load_custom_signatures!
|
||||
end
|
||||
info("Please consider contributing your custom signatures to the " \
|
||||
"Gitrob project.")
|
||||
end
|
||||
info("Loaded #{Gitrob::BlobObserver.signatures.count} signatures")
|
||||
end
|
||||
|
||||
def start_web_server
|
||||
|
||||
@@ -1248,6 +1248,70 @@ describe Gitrob::BlobObserver do
|
||||
File.expand_path("../../../../signatures.json", __FILE__)
|
||||
end
|
||||
|
||||
let(:custom_signatures_file_path) do
|
||||
File.join(Dir.home, ".gitrobsignatures")
|
||||
end
|
||||
|
||||
context "when custom signatures file is present" do
|
||||
it "loads custom signatures" do
|
||||
described_class.unload_signatures
|
||||
allow(described_class).to receive(:custom_signatures?)
|
||||
.and_return(true)
|
||||
expect(File).to receive(:read)
|
||||
.with(custom_signatures_file_path)
|
||||
.and_return('
|
||||
[
|
||||
{
|
||||
"part": "filename",
|
||||
"type": "match",
|
||||
"pattern": "test",
|
||||
"caption": "Test signature",
|
||||
"description": "This is a test signature"
|
||||
}
|
||||
]
|
||||
')
|
||||
described_class.load_custom_signatures!
|
||||
expect(described_class.signatures.count).to eq(1)
|
||||
signature = described_class.signatures.first
|
||||
expect(signature).to be_a(Gitrob::BlobObserver::Signature)
|
||||
expect(signature.part).to eq("filename")
|
||||
expect(signature.type).to eq("match")
|
||||
expect(signature.pattern).to eq("test")
|
||||
expect(signature.caption).to eq("Test signature")
|
||||
expect(signature.description).to eq("This is a test signature")
|
||||
end
|
||||
|
||||
it "validates custom signatures" do
|
||||
described_class.unload_signatures
|
||||
allow(described_class).to receive(:custom_signatures?)
|
||||
.and_return(true)
|
||||
allow(File).to receive(:read)
|
||||
.with(custom_signatures_file_path)
|
||||
.and_return('
|
||||
[
|
||||
{
|
||||
"part": "filename",
|
||||
"type": "match",
|
||||
"pattern": "test",
|
||||
"caption": "Test signature",
|
||||
"description": "This is a test signature"
|
||||
}
|
||||
]
|
||||
')
|
||||
expect(described_class).to receive(:validate_signatures!)
|
||||
.with([
|
||||
{
|
||||
"part" => "filename",
|
||||
"type" => "match",
|
||||
"pattern" => "test",
|
||||
"caption" => "Test signature",
|
||||
"description" => "This is a test signature"
|
||||
}
|
||||
])
|
||||
described_class.load_custom_signatures!
|
||||
end
|
||||
end
|
||||
|
||||
context "when Signature file is empty" do
|
||||
it "raises CorruptSignaturesError" do
|
||||
expect(File).to receive(:read)
|
||||
@@ -1258,7 +1322,7 @@ describe Gitrob::BlobObserver do
|
||||
end
|
||||
.to raise_error(
|
||||
Gitrob::BlobObserver::CorruptSignaturesError,
|
||||
"Could not parse signature file"
|
||||
"Signature file contains no signatures"
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -1312,7 +1376,7 @@ describe Gitrob::BlobObserver do
|
||||
end
|
||||
.to raise_error(
|
||||
Gitrob::BlobObserver::CorruptSignaturesError,
|
||||
"Missing required signature key: part"
|
||||
"Validation failed for Signature #1: Missing required signature key: part"
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -1336,7 +1400,7 @@ describe Gitrob::BlobObserver do
|
||||
end
|
||||
.to raise_error(
|
||||
Gitrob::BlobObserver::CorruptSignaturesError,
|
||||
"Missing required signature key: type"
|
||||
"Validation failed for Signature #1: Missing required signature key: type"
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -1360,7 +1424,7 @@ describe Gitrob::BlobObserver do
|
||||
end
|
||||
.to raise_error(
|
||||
Gitrob::BlobObserver::CorruptSignaturesError,
|
||||
"Missing required signature key: pattern"
|
||||
"Validation failed for Signature #1: Missing required signature key: pattern"
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -1384,7 +1448,7 @@ describe Gitrob::BlobObserver do
|
||||
end
|
||||
.to raise_error(
|
||||
Gitrob::BlobObserver::CorruptSignaturesError,
|
||||
"Missing required signature key: caption"
|
||||
"Validation failed for Signature #1: Missing required signature key: caption"
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -1408,7 +1472,7 @@ describe Gitrob::BlobObserver do
|
||||
end
|
||||
.to raise_error(
|
||||
Gitrob::BlobObserver::CorruptSignaturesError,
|
||||
"Missing required signature key: description"
|
||||
"Validation failed for Signature #1: Missing required signature key: description"
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -1433,7 +1497,7 @@ describe Gitrob::BlobObserver do
|
||||
end
|
||||
.to raise_error(
|
||||
Gitrob::BlobObserver::CorruptSignaturesError,
|
||||
"Invalid signature part: what"
|
||||
"Validation failed for Signature #1: Invalid signature part: what"
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -1458,7 +1522,7 @@ describe Gitrob::BlobObserver do
|
||||
end
|
||||
.to raise_error(
|
||||
Gitrob::BlobObserver::CorruptSignaturesError,
|
||||
"Invalid signature type: what"
|
||||
"Validation failed for Signature #1: Invalid signature type: what"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,6 +32,10 @@ describe Gitrob::CLI::Commands::Analyze do
|
||||
deadbabedeadbabedeadbabedeadbabedeadbabe
|
||||
)
|
||||
)
|
||||
allow(Gitrob::BlobObserver).to receive(:custom_signatures?)
|
||||
.and_return(false)
|
||||
allow(Gitrob::BlobObserver).to receive(:signatures)
|
||||
.and_return([])
|
||||
|
||||
expect_any_instance_of(described_class)
|
||||
.to receive(:task)
|
||||
@@ -41,6 +45,53 @@ describe Gitrob::CLI::Commands::Analyze do
|
||||
described_class.new(target, options)
|
||||
end
|
||||
|
||||
context "When custom signatures are present" do
|
||||
it "loads custom signtures" do
|
||||
stub_db_assessment = spy
|
||||
allow(stub_db_assessment)
|
||||
.to receive(:save)
|
||||
allow_any_instance_of(described_class)
|
||||
.to receive(:gather_owners)
|
||||
allow_any_instance_of(described_class)
|
||||
.to receive(:gather_repositories)
|
||||
allow(Gitrob::Models::Assessment)
|
||||
.to receive(:create)
|
||||
.and_return(stub_db_assessment)
|
||||
allow_any_instance_of(described_class)
|
||||
.to receive(:analyze_repositories)
|
||||
allow(Gitrob::CLI)
|
||||
.to receive(:configuration)
|
||||
.and_return(
|
||||
"github_access_tokens" => %w(
|
||||
deadbeefdeadbeefdeadbeefdeadbeefdeadbeef
|
||||
deadbabedeadbabedeadbabedeadbabedeadbabe
|
||||
)
|
||||
)
|
||||
allow(Gitrob::BlobObserver)
|
||||
.to receive(:custom_signatures?)
|
||||
.and_return(true)
|
||||
allow(Gitrob::BlobObserver).to receive(:signatures)
|
||||
.and_return([])
|
||||
|
||||
allow_any_instance_of(described_class)
|
||||
.to receive(:task)
|
||||
.with("Loading signatures...", true)
|
||||
.and_yield
|
||||
expect_any_instance_of(described_class)
|
||||
.to receive(:task)
|
||||
.with("Loading custom signatures...", true)
|
||||
.and_yield
|
||||
expect(Gitrob::BlobObserver).to receive(:load_custom_signatures!)
|
||||
expect_any_instance_of(described_class)
|
||||
.to receive(:info)
|
||||
.with("Please consider contributing your custom signatures to the Gitrob project.")
|
||||
expect_any_instance_of(described_class)
|
||||
.to receive(:info)
|
||||
.with("Loaded 0 signatures")
|
||||
described_class.new(target, options)
|
||||
end
|
||||
end
|
||||
|
||||
it "gathers owners" do
|
||||
stub_db_assessment = spy
|
||||
allow(stub_db_assessment)
|
||||
|
||||
Reference in New Issue
Block a user