Tuesday, 18 June 2013

String Masking using RegEx

Problem

We are often asked to stop users from entering credit card numbers or filter inappropriate language from data entered by users. With the use of regular expressions and the code snippet below, this task becomes simple and quick to complete. Coupled with a before trigger, this code becomes an automated pattern driven masking tool that keeps watch over your data.

Solution

For this solution, let's assume that we have the need to mask Visa credit card numbers that are entered in Case Comments. Before we address the scenario, let's first look at the code used to mask the string.
01public class StringUtils {
02    static public string MaskString(string inString, List<string> inPatterns, string inMask, integer inVisibleCharacters) {
03       // validate the passed in variables
04       if (inString == null || instring.length() < 1 ||
05           inPatterns == null || inPatterns.size() < 1 ||
06           inMask == nullreturn inString;
07       if (inVisibleCharacters < 0) inVisibleCharacters = 0;
08        
09       // prime the internal variables to be used during processing
10       string stringToMask = inString;
11       string maskedString = inString;
12        
13       // iterate through each pattern and mask any matches leaving the last visible characters
14       for(string regEx : inPatterns) {
15           Pattern p = Pattern.compile(regEx);
16           Matcher m = p.matcher(stringToMask);
17           while(m.find()) {
18               // find the start and end indexes of the match
19               integer startIdx = m.start();
20               integer endIdx = m.end();
21                
22               // extract the matched string
23               string patternMatch = stringToMask.substring(startIdx, endIdx);                   
24                
25               // mask the string leaving any visible characters
26               string partToBeMasked = patternMatch.substring(0, patternMatch.length() - inVisibleCharacters);                                                            
27               string mask = '';                                         
28               for(integer i = 0; i < partToBeMasked.length(); i++) {
29                   mask += inMask;
30               }   
31                
32               // concatenate mask string with the last visible characters              
33               string maskedNumber = mask + patternMatch.substring(patternMatch.length() - inVisibleCharacters);                  
34                
35               // replace the the card number with masked number
36               maskedString = maskedString.replace(patternMatch, maskedNumber);
37           }              
38       }      
39       return maskedString;    
40   }
41}
Now, we create the trigger needed to scrub the Case Comments body. For the code below, assume that we have placed the code snippet above in a class named StringUtils.
1trigger MaskCaseComments on CaseComment (before insert, before update) {
2    for(CaseComment cc : trigger.new) {
3    if (cc.CommentBody != null)
4        cc.CommentBody = StringUtils.MaskString(cc.CommentBody, new list<string>{'4\\d{3}[- ]*\\d{4}[- ]*\\d{4}[- ]*\\d{4}'}, '*'4);
5    }
6}
Now, each time a Case Comment is entered, the CommentBody value, if provided, will be scrubbed for Visa credit card numbers and masked with a star (*), leaving the last 4 characters. The test class below illustrates the masking results...


1static private testMethod void testMaskStirng()
2{
3 // create a pattern for matching, in this case we are looking for visa numbers
4 list<string> listPatterns = new list<string>{'4\\d{3}[- ]*\\d{4}[- ]*\\d{4}[- ]*\\d{4}'};
5 Test.startTest();
6 // test to make sure the credit card number is masked leaving the last 4 digits
7 System.Assert(StringUtils.MaskString('my visa number is 4111-1111-1111-1111', listPatterns, '*'4) == 'my visa number is ***************1111');
8 Test.stopTest();
9}

No comments:

Post a Comment