11const Step = require ( '../../actions' ) . Step ;
22const config = require ( '../../../config' ) ;
3+ const parseDiff = require ( 'parse-diff' )
34
45const commitConfig = config . getCommitConfig ( ) ;
56const 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+
71134const 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