Skip to content

Commit d2f262a

Browse files
author
Neil Zhao
committed
feat: add files-not-hash rule for files detections.
1 parent 2e48a85 commit d2f262a

File tree

3 files changed

+206
-0
lines changed

3 files changed

+206
-0
lines changed

rules/files-not-hash-config.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema",
3+
"$id": "https://raw.githubusercontent.com/todogroup/repolinter/master/rules/file-not-hash-config.json",
4+
"type": "object",
5+
"properties": {
6+
"globsAll": {
7+
"type": "array",
8+
"items": { "type": "string" }
9+
},
10+
"nocase": {
11+
"type": "boolean",
12+
"default": false
13+
},
14+
"algorithm": {
15+
"type": "string",
16+
"default": "sha256"
17+
},
18+
"hashes": {
19+
"type": "array",
20+
"items": { "type": "string" }
21+
}
22+
},
23+
"required": ["hashes"],
24+
"oneOf": [{ "required": ["globsAny"] }, { "required": ["files"] }]
25+
}

rules/files-not-hash.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
const Result = require('../lib/result')
2+
const crypto = require('crypto')
3+
// eslint-disable-next-line no-unused-vars
4+
const FileSystem = require('../lib/file_system')
5+
6+
/**
7+
* Check files not include a list of certain cryptographic hashes.
8+
*
9+
* @param {FileSystem} fs A filesystem object configured with filter paths and target directories
10+
* @param {object} options The rule configuration
11+
* @returns {Promise<Result>} The lint rule result
12+
*/
13+
async function filesNotHash(fs, options) {
14+
const fileList = options.globsAll || options.files
15+
const files = await fs.findAllFiles(fileList, !!options.nocase)
16+
17+
if (files.length === 0) {
18+
return new Result(
19+
'Did not find file matching the specified patterns',
20+
fileList.map(f => {
21+
return { passed: false, pattern: f }
22+
}),
23+
!options['fail-on-non-existent']
24+
)
25+
}
26+
27+
let algorithm = options.algorithm
28+
if (algorithm === undefined) {
29+
algorithm = 'sha256'
30+
}
31+
32+
const resultsList = await Promise.all(
33+
options.hashes.map(async hash => {
34+
const singleHashResults = (
35+
await Promise.all(
36+
files.map(async file => {
37+
const digester = crypto.createHash(algorithm)
38+
let fileContents = await fs.getFileContents(file)
39+
if (fileContents === undefined) {
40+
fileContents = ''
41+
}
42+
digester.update(fileContents)
43+
const fileHash = digester.digest('hex')
44+
const passed = fileHash !== hash
45+
const message = passed ? "Doesn't Matches hash" : 'Match hash'
46+
47+
return {
48+
passed,
49+
path: file,
50+
message
51+
}
52+
})
53+
)
54+
).filter(result => !result.passed)
55+
return singleHashResults
56+
})
57+
)
58+
59+
const results = []
60+
resultsList.map(singleHashResults => {
61+
for (const result of singleHashResults) {
62+
results.push(result)
63+
}
64+
})
65+
66+
const passed = results.length === 0
67+
68+
if (passed) {
69+
return new Result('No file matching hash found', results, passed)
70+
}
71+
return new Result('File matching has found', results, passed)
72+
}
73+
74+
module.exports = filesNotHash

tests/rules/files_not_hash_tests.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright 2022 TODO Group. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
const chai = require('chai')
5+
const expect = chai.expect
6+
const filesNotHash = require('../../rules/files-not-hash')
7+
8+
describe('rule', () => {
9+
describe('files_not_hash', () => {
10+
it('returns pass if requested files not matches the hashes', async () => {
11+
/** @type {any} */
12+
const mockfs = {
13+
findAllFiles() {
14+
return ['README.md']
15+
},
16+
getFileContents() {
17+
return 'foo'
18+
},
19+
targetDir: '.'
20+
}
21+
22+
const ruleopts = {
23+
globsAll: ['README.md'],
24+
hashes: ['notAValidHash']
25+
}
26+
27+
const actual = await filesNotHash(mockfs, ruleopts)
28+
expect(actual.passed).to.equal(true)
29+
})
30+
31+
it('returns failure if requested files matches the hash', async () => {
32+
/** @type {any} */
33+
const mockfs = {
34+
findAllFiles() {
35+
return ['README.md']
36+
},
37+
getFileContents() {
38+
return 'foo'
39+
},
40+
targetDir: '.'
41+
}
42+
43+
const ruleopts = {
44+
globsAll: ['README.md'],
45+
hashes: [
46+
'2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae'
47+
]
48+
}
49+
50+
const actual = await filesNotHash(mockfs, ruleopts)
51+
expect(actual.passed).to.equal(false)
52+
expect(actual.targets).to.have.length(1)
53+
expect(actual.targets[0]).to.deep.include({
54+
passed: false,
55+
path: 'README.md'
56+
})
57+
})
58+
59+
it('returns failed if requested file contents exists different algorithm', async () => {
60+
/** @type {any} */
61+
const mockfs = {
62+
findAllFiles() {
63+
return ['README.md']
64+
},
65+
getFileContents() {
66+
return 'foo'
67+
},
68+
targetDir: '.'
69+
}
70+
71+
const ruleopts = {
72+
globsAll: ['README.md'],
73+
algorithm: 'sha512',
74+
hashes: [
75+
'f7fbba6e0636f890e56fbbf3283e524c6fa3204ae298382d624741d0dc6638326e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7'
76+
]
77+
}
78+
79+
const actual = await filesNotHash(mockfs, ruleopts)
80+
expect(actual.passed).to.equal(false)
81+
expect(actual.targets).to.have.length(1)
82+
expect(actual.targets[0]).to.deep.include({
83+
passed: false,
84+
path: 'README.md'
85+
})
86+
})
87+
88+
it('returns success if requested file does not exist', async () => {
89+
/** @type {any} */
90+
const mockfs = {
91+
findAllFiles() {
92+
return []
93+
},
94+
getFileContents() {},
95+
targetDir: '.'
96+
}
97+
98+
const ruleopts = {
99+
globsAll: ['README.md'],
100+
content: 'foo'
101+
}
102+
103+
const actual = await filesNotHash(mockfs, ruleopts)
104+
expect(actual.passed).to.equal(true)
105+
})
106+
})
107+
})

0 commit comments

Comments
 (0)