Skip to content

Commit 5a42ec8

Browse files
Merge branch 'finos:main' into Search
2 parents 7886540 + 5ac891c commit 5a42ec8

File tree

12 files changed

+463
-85
lines changed

12 files changed

+463
-85
lines changed

package-lock.json

Lines changed: 36 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@finos/git-proxy",
3-
"version": "1.5.1",
3+
"version": "1.6.0",
44
"description": "Deploy custom push protections and policies on top of Git.",
55
"scripts": {
66
"cli": "node ./packages/git-proxy-cli/index.js",
@@ -23,11 +23,6 @@
2323
"git-proxy": "./index.js",
2424
"git-proxy-all": "concurrently 'npm run server' 'npm run client'"
2525
},
26-
"exports": {
27-
"./plugin": "./src/plugin.js",
28-
"./proxy/actions": "./src/proxy/actions/index.js",
29-
"./src/config/env": "./src/config/env.js"
30-
},
3126
"workspaces": [
3227
"./packages/git-proxy-cli"
3328
],
@@ -63,6 +58,7 @@
6358
"moment": "^2.29.4",
6459
"mongodb": "^5.0.0",
6560
"nodemailer": "^6.6.1",
61+
"parse-diff": "^0.11.1",
6662
"passport": "^0.7.0",
6763
"passport-activedirectory": "^1.0.4",
6864
"passport-local": "^1.0.0",
@@ -72,6 +68,7 @@
7268
"react-dom": "^16.13.1",
7369
"react-html-parser": "^2.0.2",
7470
"react-router-dom": "6.26.2",
71+
"simple-git": "^3.25.0",
7572
"uuid": "^10.0.0",
7673
"yargs": "^17.7.2"
7774
},

plugins/git-proxy-plugin-samples/example.cjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
*/
55

66
// Peer dependencies; its expected that these deps exist on Node module path if you've installed @finos/git-proxy
7-
const { PushActionPlugin } = require('@finos/git-proxy/plugin');
8-
const { Step } = require('@finos/git-proxy/proxy/actions');
7+
const { PushActionPlugin } = require('@finos/git-proxy/src/plugin');
8+
const { Step } = require('@finos/git-proxy/src/proxy/actions');
99
'use strict';
1010

1111
/**
@@ -42,4 +42,4 @@ module.exports = {
4242
// Sub-classing is fine too if you require more control over the plugin
4343
logRequest: new LogRequestPlugin(),
4444
someOtherValue: 'foo', // This key will be ignored by the plugin loader
45-
};
45+
};

plugins/git-proxy-plugin-samples/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
*/
55

66
// Peer dependencies; its expected that these deps exist on Node module path if you've installed @finos/git-proxy
7-
import { PullActionPlugin } from "@finos/git-proxy/plugin";
8-
import { Step } from "@finos/git-proxy/proxy/actions";
7+
import { PullActionPlugin } from "@finos/git-proxy/src/plugin";
8+
import { Step } from "@finos/git-proxy/src/proxy/actions";
99

1010
class RunOnPullPlugin extends PullActionPlugin {
1111
constructor() {

src/proxy/processors/push-action/getDiff.js

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
const child = require('child_process');
21
const Step = require('../../actions').Step;
2+
const simpleGit = require('simple-git')
3+
34

45
const exec = async (req, action) => {
56
const step = new Step('diff');
67

78
try {
89
const path = `${action.proxyGitPath}/${action.repoName}`;
9-
10+
const git = simpleGit(path);
1011
// https://stackoverflow.com/questions/40883798/how-to-get-git-diff-of-the-first-commit
1112
let commitFrom = `4b825dc642cb6eb9a060e54bf8d69288fbee4904`;
1213

@@ -19,16 +20,10 @@ const exec = async (req, action) => {
1920
}
2021

2122
step.log(`Executing "git diff ${commitFrom} ${action.commitTo}" in ${path}`);
22-
23-
// Get the diff
24-
const content = child.spawnSync('git', ['diff', commitFrom, action.commitTo], {
25-
cwd: path,
26-
encoding: 'utf-8',
27-
maxBuffer: 50 * 1024 * 1024,
28-
}).stdout;
29-
30-
step.log(content);
31-
step.setContent(content);
23+
const revisionRange = `${commitFrom}..${action.commitTo}`;
24+
const diff = await git.diff([revisionRange]);
25+
step.log(diff);
26+
step.setContent(diff);
3227
} catch (e) {
3328
step.setError(e.toString('utf-8'));
3429
} finally {

src/proxy/processors/push-action/scanDiff.js

Lines changed: 121 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,136 @@
11
const Step = require('../../actions').Step;
22
const config = require('../../../config');
3+
const parseDiff = require('parse-diff')
34

45
const commitConfig = config.getCommitConfig();
56
const privateOrganizations = config.getPrivateOrganizations();
67

7-
const isDiffLegal = (diff, organization) => {
8+
const BLOCK_TYPE = {
9+
LITERAL: 'Offending Literal',
10+
PATTERN: 'Offending Pattern',
11+
PROVIDER: 'PROVIDER'
12+
}
13+
14+
15+
const getDiffViolations = (diff, organization) => {
816
// Commit diff is empty, i.e. '', null or undefined
917
if (!diff) {
1018
console.log('No commit diff...');
11-
return false;
19+
return 'No commit diff...';
1220
}
1321

1422
// Validation for configured block pattern(s) check...
1523
if (typeof diff !== 'string') {
1624
console.log('A non-string value has been captured for the commit diff...');
17-
return false;
25+
return 'A non-string value has been captured for the commit diff...';
1826
}
1927

20-
// Configured blocked literals
21-
const blockedLiterals = commitConfig.diff.block.literals;
22-
23-
// Configured blocked patterns
24-
const blockedPatterns = commitConfig.diff.block.patterns;
25-
26-
// Configured blocked providers
27-
const blockedProviders = Object.values(commitConfig.diff.block.providers);
28-
29-
// Find all instances of blocked literals in diff...
30-
const positiveLiterals = blockedLiterals.map((literal) =>
31-
diff.toLowerCase().includes(literal.toLowerCase()),
32-
);
33-
34-
// Find all instances of blocked patterns in diff...
35-
const positivePatterns = blockedPatterns.map((pattern) => diff.match(new RegExp(pattern, 'gi')));
36-
37-
// Find all instances of blocked providers in diff...
38-
const positiveProviders = blockedProviders.map((pattern) =>
39-
diff.match(new RegExp(pattern, 'gi')),
40-
);
41-
42-
console.log({ positiveLiterals });
43-
console.log({ positivePatterns });
44-
console.log({ positiveProviders });
45-
46-
// Flatten any positive literal results into a 1D array...
47-
const literalMatches = positiveLiterals.flat().filter((result) => !!result);
28+
const parsedDiff = parseDiff(diff);
29+
const combinedMatches = combineMatches(organization);
4830

49-
// Flatten any positive pattern results into a 1D array...
50-
const patternMatches = positivePatterns.flat().filter((result) => !!result);
51-
52-
// Flatten any positive pattern results into a 1D array...
53-
const providerMatches =
54-
organization && privateOrganizations.includes(organization) // Return empty results for private organizations
55-
? []
56-
: positiveProviders.flat().filter((result) => !!result);
57-
58-
console.log({ literalMatches });
59-
console.log({ patternMatches });
60-
console.log({ providerMatches });
6131

32+
const res = collectMatches(parsedDiff, combinedMatches);
6233
// Diff matches configured block pattern(s)
63-
if (literalMatches.length || patternMatches.length || providerMatches.length) {
34+
if (res.length > 0) {
6435
console.log('Diff is blocked via configured literals/patterns/providers...');
65-
return false;
36+
// combining matches with file and line number
37+
return res
6638
}
6739

68-
return true;
40+
return null;
6941
};
7042

43+
const combineMatches = (organization) => {
44+
45+
// Configured blocked literals
46+
const blockedLiterals = commitConfig.diff.block.literals;
47+
48+
// Configured blocked patterns
49+
const blockedPatterns = commitConfig.diff.block.patterns;
50+
51+
// Configured blocked providers
52+
const blockedProviders = organization && privateOrganizations.includes(organization) ? [] :
53+
Object.entries(commitConfig.diff.block.providers);
54+
55+
// Combine all matches (literals, paterns)
56+
const combinedMatches = [
57+
...blockedLiterals.map(literal => ({
58+
type: BLOCK_TYPE.LITERAL,
59+
match: new RegExp(literal, 'gi')
60+
})),
61+
...blockedPatterns.map(pattern => ({
62+
type: BLOCK_TYPE.PATTERN,
63+
match: new RegExp(pattern, 'gi')
64+
})),
65+
...blockedProviders.map(([key, value]) => ({
66+
type: key,
67+
match: new RegExp(value, 'gi')
68+
})),
69+
];
70+
return combinedMatches;
71+
}
72+
73+
const collectMatches = (parsedDiff, combinedMatches) => {
74+
const allMatches = {};
75+
parsedDiff.forEach(file => {
76+
const fileName = file.to || file.from;
77+
console.log("CHANGE", file.chunks)
78+
79+
file.chunks.forEach(chunk => {
80+
chunk.changes.forEach(change => {
81+
if (change.add) {
82+
// store line number
83+
const lineNumber = change.ln;
84+
// Iterate through each match types - literal, patterns, providers
85+
combinedMatches.forEach(({ type, match }) => {
86+
// using Match all to find all occurences of the pattern in the line
87+
const matches = [...change.content.matchAll(match)]
88+
89+
matches.forEach(matchInstance => {
90+
const matchLiteral = matchInstance[0];
91+
const matchKey = `${type}_${matchLiteral}_${fileName}`; // unique key
92+
93+
94+
if (!allMatches[matchKey]) {
95+
// match entry
96+
allMatches[matchKey] = {
97+
type,
98+
literal: matchLiteral,
99+
file: fileName,
100+
lines: [],
101+
content: change.content.trim()
102+
};
103+
}
104+
105+
// apend line numbers to the list of lines
106+
allMatches[matchKey].lines.push(lineNumber)
107+
})
108+
});
109+
}
110+
});
111+
});
112+
});
113+
114+
// convert matches into a final result array, joining line numbers
115+
const result = Object.values(allMatches).map(match => ({
116+
...match,
117+
lines: match.lines.join(',') // join the line numbers into a comma-separated string
118+
}))
119+
120+
console.log("RESULT", result)
121+
return result;
122+
}
123+
124+
const formatMatches = (matches) => {
125+
return matches.map((match, index) => {
126+
return `---------------------------------- #${index + 1} ${match.type} ------------------------------
127+
Policy Exception Type: ${match.type}
128+
DETECTED: ${match.literal}
129+
FILE(S) LOCATED: ${match.file}
130+
Line(s) of code: ${match.lines}`
131+
});
132+
}
133+
71134
const exec = async (req, action) => {
72135
const step = new Step('scanDiff');
73136

@@ -76,17 +139,26 @@ const exec = async (req, action) => {
76139

77140
const diff = steps.find((s) => s.stepName === 'diff')?.content;
78141

79-
const legalDiff = isDiffLegal(diff, action.project);
142+
console.log(diff)
143+
const diffViolations = getDiffViolations(diff, action.project);
144+
145+
if (diffViolations) {
146+
const formattedMatches = Array.isArray(diffViolations) ? formatMatches(diffViolations).join('\n\n') : diffViolations;
147+
const errorMsg = [];
148+
errorMsg.push(`\n\n\n\nYour push has been blocked.\n`);
149+
errorMsg.push(`Please ensure your code does not contain sensitive information or URLs.\n\n`);
150+
errorMsg.push(formattedMatches)
151+
errorMsg.push('\n')
80152

81-
if (!legalDiff) {
82153
console.log(`The following diff is illegal: ${commitFrom}:${commitTo}`);
83154

84155
step.error = true;
85156
step.log(`The following diff is illegal: ${commitFrom}:${commitTo}`);
86157
step.setError(
87-
'\n\n\n\nYour push has been blocked.\nPlease ensure your code does not contain sensitive information or URLs.\n\n\n',
158+
errorMsg.join('\n')
88159
);
89160

161+
90162
action.addStep(step);
91163
return action;
92164
}

0 commit comments

Comments
 (0)