diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2020-07-04 11:12:55 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2020-07-04 15:05:30 +0200 |
| commit | c7d7f10bdff703e4a3c0414e8a33d4e45c91eb35 (patch) | |
| tree | 0dff0ee1f98dbfa3ad8776112053a450d176592b /vendor/github.com/nbutton23/zxcvbn-go/matching | |
| parent | 9573094ce235bd9afe88f5da27a47dd6bcc1e13b (diff) | |
go.mod: vendor golangci-lint
Diffstat (limited to 'vendor/github.com/nbutton23/zxcvbn-go/matching')
7 files changed, 813 insertions, 0 deletions
diff --git a/vendor/github.com/nbutton23/zxcvbn-go/matching/dateMatchers.go b/vendor/github.com/nbutton23/zxcvbn-go/matching/dateMatchers.go new file mode 100644 index 000000000..8dfdf2410 --- /dev/null +++ b/vendor/github.com/nbutton23/zxcvbn-go/matching/dateMatchers.go @@ -0,0 +1,209 @@ +package matching + +import ( + "regexp" + "strconv" + "strings" + + "github.com/nbutton23/zxcvbn-go/entropy" + "github.com/nbutton23/zxcvbn-go/match" +) + +const ( + dateSepMatcherName = "DATESEP" + dateWithOutSepMatcherName = "DATEWITHOUT" +) + +var ( + dateRxYearSuffix = regexp.MustCompile(`((\d{1,2})(\s|-|\/|\\|_|\.)(\d{1,2})(\s|-|\/|\\|_|\.)(19\d{2}|200\d|201\d|\d{2}))`) + dateRxYearPrefix = regexp.MustCompile(`((19\d{2}|200\d|201\d|\d{2})(\s|-|/|\\|_|\.)(\d{1,2})(\s|-|/|\\|_|\.)(\d{1,2}))`) + dateWithOutSepMatch = regexp.MustCompile(`\d{4,8}`) +) + +//FilterDateSepMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher +func FilterDateSepMatcher(m match.Matcher) bool { + return m.ID == dateSepMatcherName +} + +//FilterDateWithoutSepMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher +func FilterDateWithoutSepMatcher(m match.Matcher) bool { + return m.ID == dateWithOutSepMatcherName +} + +func checkDate(day, month, year int64) (bool, int64, int64, int64) { + if (12 <= month && month <= 31) && day <= 12 { + day, month = month, day + } + + if day > 31 || month > 12 { + return false, 0, 0, 0 + } + + if !((1900 <= year && year <= 2019) || (0 <= year && year <= 99)) { + return false, 0, 0, 0 + } + + return true, day, month, year +} + +func dateSepMatcher(password string) []match.Match { + dateMatches := dateSepMatchHelper(password) + + var matches []match.Match + for _, dateMatch := range dateMatches { + match := match.Match{ + I: dateMatch.I, + J: dateMatch.J, + Entropy: entropy.DateEntropy(dateMatch), + DictionaryName: "date_match", + Token: dateMatch.Token, + } + + matches = append(matches, match) + } + + return matches +} +func dateSepMatchHelper(password string) []match.DateMatch { + + var matches []match.DateMatch + + for _, v := range dateRxYearSuffix.FindAllString(password, len(password)) { + splitV := dateRxYearSuffix.FindAllStringSubmatch(v, len(v)) + i := strings.Index(password, v) + j := i + len(v) + day, _ := strconv.ParseInt(splitV[0][4], 10, 16) + month, _ := strconv.ParseInt(splitV[0][2], 10, 16) + year, _ := strconv.ParseInt(splitV[0][6], 10, 16) + match := match.DateMatch{Day: day, Month: month, Year: year, Separator: splitV[0][5], I: i, J: j, Token: password[i:j]} + matches = append(matches, match) + } + + for _, v := range dateRxYearPrefix.FindAllString(password, len(password)) { + splitV := dateRxYearPrefix.FindAllStringSubmatch(v, len(v)) + i := strings.Index(password, v) + j := i + len(v) + day, _ := strconv.ParseInt(splitV[0][4], 10, 16) + month, _ := strconv.ParseInt(splitV[0][6], 10, 16) + year, _ := strconv.ParseInt(splitV[0][2], 10, 16) + match := match.DateMatch{Day: day, Month: month, Year: year, Separator: splitV[0][5], I: i, J: j, Token: password[i:j]} + matches = append(matches, match) + } + + var out []match.DateMatch + for _, match := range matches { + if valid, day, month, year := checkDate(match.Day, match.Month, match.Year); valid { + match.Pattern = "date" + match.Day = day + match.Month = month + match.Year = year + out = append(out, match) + } + } + return out + +} + +type dateMatchCandidate struct { + DayMonth string + Year string + I, J int +} + +type dateMatchCandidateTwo struct { + Day string + Month string + Year string + I, J int +} + +func dateWithoutSepMatch(password string) []match.Match { + dateMatches := dateWithoutSepMatchHelper(password) + + var matches []match.Match + for _, dateMatch := range dateMatches { + match := match.Match{ + I: dateMatch.I, + J: dateMatch.J, + Entropy: entropy.DateEntropy(dateMatch), + DictionaryName: "date_match", + Token: dateMatch.Token, + } + + matches = append(matches, match) + } + + return matches +} + +//TODO Has issues with 6 digit dates +func dateWithoutSepMatchHelper(password string) (matches []match.DateMatch) { + for _, v := range dateWithOutSepMatch.FindAllString(password, len(password)) { + i := strings.Index(password, v) + j := i + len(v) + length := len(v) + lastIndex := length - 1 + var candidatesRoundOne []dateMatchCandidate + + if length <= 6 { + //2-digit year prefix + candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[2:], v[0:2], i, j)) + + //2-digityear suffix + candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[0:lastIndex-2], v[lastIndex-2:], i, j)) + } + if length >= 6 { + //4-digit year prefix + candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[4:], v[0:4], i, j)) + + //4-digit year sufix + candidatesRoundOne = append(candidatesRoundOne, buildDateMatchCandidate(v[0:lastIndex-3], v[lastIndex-3:], i, j)) + } + + var candidatesRoundTwo []dateMatchCandidateTwo + for _, c := range candidatesRoundOne { + if len(c.DayMonth) == 2 { + candidatesRoundTwo = append(candidatesRoundTwo, buildDateMatchCandidateTwo(c.DayMonth[0:0], c.DayMonth[1:1], c.Year, c.I, c.J)) + } else if len(c.DayMonth) == 3 { + candidatesRoundTwo = append(candidatesRoundTwo, buildDateMatchCandidateTwo(c.DayMonth[0:2], c.DayMonth[2:2], c.Year, c.I, c.J)) + candidatesRoundTwo = append(candidatesRoundTwo, buildDateMatchCandidateTwo(c.DayMonth[0:0], c.DayMonth[1:3], c.Year, c.I, c.J)) + } else if len(c.DayMonth) == 4 { + candidatesRoundTwo = append(candidatesRoundTwo, buildDateMatchCandidateTwo(c.DayMonth[0:2], c.DayMonth[2:4], c.Year, c.I, c.J)) + } + } + + for _, candidate := range candidatesRoundTwo { + intDay, err := strconv.ParseInt(candidate.Day, 10, 16) + if err != nil { + continue + } + + intMonth, err := strconv.ParseInt(candidate.Month, 10, 16) + + if err != nil { + continue + } + + intYear, err := strconv.ParseInt(candidate.Year, 10, 16) + if err != nil { + continue + } + + if ok, _, _, _ := checkDate(intDay, intMonth, intYear); ok { + matches = append(matches, match.DateMatch{Token: password, Pattern: "date", Day: intDay, Month: intMonth, Year: intYear, I: i, J: j}) + } + + } + } + + return matches +} + +func buildDateMatchCandidate(dayMonth, year string, i, j int) dateMatchCandidate { + return dateMatchCandidate{DayMonth: dayMonth, Year: year, I: i, J: j} +} + +func buildDateMatchCandidateTwo(day, month string, year string, i, j int) dateMatchCandidateTwo { + + return dateMatchCandidateTwo{Day: day, Month: month, Year: year, I: i, J: j} +} diff --git a/vendor/github.com/nbutton23/zxcvbn-go/matching/dictionaryMatch.go b/vendor/github.com/nbutton23/zxcvbn-go/matching/dictionaryMatch.go new file mode 100644 index 000000000..4ddb2c3b0 --- /dev/null +++ b/vendor/github.com/nbutton23/zxcvbn-go/matching/dictionaryMatch.go @@ -0,0 +1,57 @@ +package matching + +import ( + "strings" + + "github.com/nbutton23/zxcvbn-go/entropy" + "github.com/nbutton23/zxcvbn-go/match" +) + +func buildDictMatcher(dictName string, rankedDict map[string]int) func(password string) []match.Match { + return func(password string) []match.Match { + matches := dictionaryMatch(password, dictName, rankedDict) + for _, v := range matches { + v.DictionaryName = dictName + } + return matches + } + +} + +func dictionaryMatch(password string, dictionaryName string, rankedDict map[string]int) []match.Match { + var results []match.Match + pwLower := strings.ToLower(password) + + pwLowerRunes := []rune(pwLower) + length := len(pwLowerRunes) + + for i := 0; i < length; i++ { + for j := i; j < length; j++ { + word := pwLowerRunes[i : j+1] + if val, ok := rankedDict[string(word)]; ok { + matchDic := match.Match{Pattern: "dictionary", + DictionaryName: dictionaryName, + I: i, + J: j, + Token: string([]rune(password)[i : j+1]), + } + matchDic.Entropy = entropy.DictionaryEntropy(matchDic, float64(val)) + + results = append(results, matchDic) + } + } + } + + return results +} + +func buildRankedDict(unrankedList []string) map[string]int { + + result := make(map[string]int) + + for i, v := range unrankedList { + result[strings.ToLower(v)] = i + 1 + } + + return result +} diff --git a/vendor/github.com/nbutton23/zxcvbn-go/matching/leet.go b/vendor/github.com/nbutton23/zxcvbn-go/matching/leet.go new file mode 100644 index 000000000..610f1973f --- /dev/null +++ b/vendor/github.com/nbutton23/zxcvbn-go/matching/leet.go @@ -0,0 +1,234 @@ +package matching + +import ( + "strings" + + "github.com/nbutton23/zxcvbn-go/entropy" + "github.com/nbutton23/zxcvbn-go/match" +) + +// L33TMatcherName id +const L33TMatcherName = "l33t" + +//FilterL33tMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher +func FilterL33tMatcher(m match.Matcher) bool { + return m.ID == L33TMatcherName +} + +func l33tMatch(password string) []match.Match { + permutations := getPermutations(password) + + var matches []match.Match + + for _, permutation := range permutations { + for _, mather := range dictionaryMatchers { + matches = append(matches, mather.MatchingFunc(permutation)...) + } + } + + for _, match := range matches { + match.Entropy += entropy.ExtraLeetEntropy(match, password) + match.DictionaryName = match.DictionaryName + "_3117" + } + + return matches +} + +// This function creates a list of permutations based on a fixed table stored on data. The table +// will be reduced in order to proceed in the function using only relevant values (see +// relevantL33tSubtable). +func getPermutations(password string) []string { + substitutions := relevantL33tSubtable(password) + permutations := getAllPermutationsOfLeetSubstitutions(password, substitutions) + return permutations +} + +// This function loads the table from data but only keep in memory the values that are present +// inside the provided password. +func relevantL33tSubtable(password string) map[string][]string { + relevantSubs := make(map[string][]string) + for key, values := range l33tTable.Graph { + for _, value := range values { + if strings.Contains(password, value) { + relevantSubs[key] = append(relevantSubs[key], value) + } + } + } + + return relevantSubs +} + +// This function creates the list of permutations of a given password using the provided table as +// reference for its operation. +func getAllPermutationsOfLeetSubstitutions(password string, table map[string][]string) []string { + result := []string{} + + // create a list of tables without conflicting keys/values (this happens for "|", "7" and "1") + noConflictsTables := createListOfMapsWithoutConflicts(table) + for _, noConflictsTable := range noConflictsTables { + substitutionsMaps := createSubstitutionsMapsFromTable(noConflictsTable) + for _, substitutionsMap := range substitutionsMaps { + newValue := createWordForSubstitutionMap(password, substitutionsMap) + if !stringSliceContainsValue(result, newValue) { + result = append(result, newValue) + } + } + } + + return result +} + +// Create the possible list of maps removing the conflicts from it. As an example, the value "|" +// may represent "i" and "l". For each representation of the conflicting value, a new map is +// created. This may grow exponencialy according to the number of conflicts. The number of maps +// returned by this function may be reduced if the relevantL33tSubtable function was called to +// identify only relevant items. +func createListOfMapsWithoutConflicts(table map[string][]string) []map[string][]string { + // the resulting list starts with the provided table + result := []map[string][]string{} + result = append(result, table) + + // iterate over the list of conflicts in order to expand the maps for each one + conflicts := retrieveConflictsListFromTable(table) + for _, value := range conflicts { + newMapList := []map[string][]string{} + + // for each conflict a new list of maps will be created for every already known map + for _, currentMap := range result { + newMaps := createDifferentMapsForLeetChar(currentMap, value) + newMapList = append(newMapList, newMaps...) + } + + result = newMapList + } + + return result +} + +// This function retrieves the list of values that appear for one or more keys. This is usefull to +// know which l33t chars can represent more than one letter. +func retrieveConflictsListFromTable(table map[string][]string) []string { + result := []string{} + foundValues := []string{} + + for _, values := range table { + for _, value := range values { + if stringSliceContainsValue(foundValues, value) { + // only add on results if it was not identified as conflict before + if !stringSliceContainsValue(result, value) { + result = append(result, value) + } + } else { + foundValues = append(foundValues, value) + } + } + } + + return result +} + +// This function aims to create different maps for a given char if this char represents a conflict. +// If the specified char is not a conflit one, the same map will be returned. In scenarios which +// the provided char can not be found on map, an empty list will be returned. This function was +// designed to be used on conflicts situations. +func createDifferentMapsForLeetChar(table map[string][]string, leetChar string) []map[string][]string { + result := []map[string][]string{} + + keysWithSameValue := retrieveListOfKeysWithSpecificValueFromTable(table, leetChar) + for _, key := range keysWithSameValue { + newMap := copyMapRemovingSameValueFromOtherKeys(table, key, leetChar) + result = append(result, newMap) + } + + return result +} + +// This function retrieves the list of keys that can be represented using the given value. +func retrieveListOfKeysWithSpecificValueFromTable(table map[string][]string, valueToFind string) []string { + result := []string{} + + for key, values := range table { + for _, value := range values { + if value == valueToFind && !stringSliceContainsValue(result, key) { + result = append(result, key) + } + } + } + + return result +} + +// This function returns a lsit of substitution map from a given table. Each map in the result will +// provide only one representation for each value. As an example, if the provided map contains the +// values "@" and "4" in the possibilities to represent "a", two maps will be created where one +// will contain "a" mapping to "@" and the other one will provide "a" mapping to "4". +func createSubstitutionsMapsFromTable(table map[string][]string) []map[string]string { + result := []map[string]string{{"": ""}} + + for key, values := range table { + newResult := []map[string]string{} + + for _, mapInCurrentResult := range result { + for _, value := range values { + newMapForValue := copyMap(mapInCurrentResult) + newMapForValue[key] = value + newResult = append(newResult, newMapForValue) + } + } + + result = newResult + } + + // verification to make sure that the slice was filled + if len(result) == 1 && len(result[0]) == 1 && result[0][""] == "" { + return []map[string]string{} + } + + return result +} + +// This function replaces the values provided on substitution map over the provided word. +func createWordForSubstitutionMap(word string, substitutionMap map[string]string) string { + result := word + for key, value := range substitutionMap { + result = strings.Replace(result, value, key, -1) + } + + return result +} + +func stringSliceContainsValue(slice []string, value string) bool { + for _, valueInSlice := range slice { + if valueInSlice == value { + return true + } + } + + return false +} + +func copyMap(table map[string]string) map[string]string { + result := make(map[string]string) + + for key, value := range table { + result[key] = value + } + + return result +} + +// This function creates a new map based on the one provided but excluding possible representations +// of the same value on other keys. +func copyMapRemovingSameValueFromOtherKeys(table map[string][]string, keyToFix string, valueToFix string) map[string][]string { + result := make(map[string][]string) + + for key, values := range table { + for _, value := range values { + if !(value == valueToFix && key != keyToFix) { + result[key] = append(result[key], value) + } + } + } + + return result +} diff --git a/vendor/github.com/nbutton23/zxcvbn-go/matching/matching.go b/vendor/github.com/nbutton23/zxcvbn-go/matching/matching.go new file mode 100644 index 000000000..4577db8a4 --- /dev/null +++ b/vendor/github.com/nbutton23/zxcvbn-go/matching/matching.go @@ -0,0 +1,82 @@ +package matching + +import ( + "sort" + + "github.com/nbutton23/zxcvbn-go/adjacency" + "github.com/nbutton23/zxcvbn-go/frequency" + "github.com/nbutton23/zxcvbn-go/match" +) + +var ( + dictionaryMatchers []match.Matcher + matchers []match.Matcher + adjacencyGraphs []adjacency.Graph + l33tTable adjacency.Graph + + sequences map[string]string +) + +func init() { + loadFrequencyList() +} + +// Omnimatch runs all matchers against the password +func Omnimatch(password string, userInputs []string, filters ...func(match.Matcher) bool) (matches []match.Match) { + + //Can I run into the issue where nil is not equal to nil? + if dictionaryMatchers == nil || adjacencyGraphs == nil { + loadFrequencyList() + } + + if userInputs != nil { + userInputMatcher := buildDictMatcher("user_inputs", buildRankedDict(userInputs)) + matches = userInputMatcher(password) + } + + for _, matcher := range matchers { + shouldBeFiltered := false + for i := range filters { + if filters[i](matcher) { + shouldBeFiltered = true + break + } + } + if !shouldBeFiltered { + matches = append(matches, matcher.MatchingFunc(password)...) + } + } + sort.Sort(match.Matches(matches)) + return matches +} + +func loadFrequencyList() { + + for n, list := range frequency.Lists { + dictionaryMatchers = append(dictionaryMatchers, match.Matcher{MatchingFunc: buildDictMatcher(n, buildRankedDict(list.List)), ID: n}) + } + + l33tTable = adjacency.GraphMap["l33t"] + + adjacencyGraphs = append(adjacencyGraphs, adjacency.GraphMap["qwerty"]) + adjacencyGraphs = append(adjacencyGraphs, adjacency.GraphMap["dvorak"]) + adjacencyGraphs = append(adjacencyGraphs, adjacency.GraphMap["keypad"]) + adjacencyGraphs = append(adjacencyGraphs, adjacency.GraphMap["macKeypad"]) + + //l33tFilePath, _ := filepath.Abs("adjacency/L33t.json") + //L33T_TABLE = adjacency.GetAdjancencyGraphFromFile(l33tFilePath, "l33t") + + sequences = make(map[string]string) + sequences["lower"] = "abcdefghijklmnopqrstuvwxyz" + sequences["upper"] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + sequences["digits"] = "0123456789" + + matchers = append(matchers, dictionaryMatchers...) + matchers = append(matchers, match.Matcher{MatchingFunc: spatialMatch, ID: spatialMatcherName}) + matchers = append(matchers, match.Matcher{MatchingFunc: repeatMatch, ID: repeatMatcherName}) + matchers = append(matchers, match.Matcher{MatchingFunc: sequenceMatch, ID: sequenceMatcherName}) + matchers = append(matchers, match.Matcher{MatchingFunc: l33tMatch, ID: L33TMatcherName}) + matchers = append(matchers, match.Matcher{MatchingFunc: dateSepMatcher, ID: dateSepMatcherName}) + matchers = append(matchers, match.Matcher{MatchingFunc: dateWithoutSepMatch, ID: dateWithOutSepMatcherName}) + +} diff --git a/vendor/github.com/nbutton23/zxcvbn-go/matching/repeatMatch.go b/vendor/github.com/nbutton23/zxcvbn-go/matching/repeatMatch.go new file mode 100644 index 000000000..a93e45935 --- /dev/null +++ b/vendor/github.com/nbutton23/zxcvbn-go/matching/repeatMatch.go @@ -0,0 +1,67 @@ +package matching + +import ( + "strings" + + "github.com/nbutton23/zxcvbn-go/entropy" + "github.com/nbutton23/zxcvbn-go/match" +) + +const repeatMatcherName = "REPEAT" + +//FilterRepeatMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher +func FilterRepeatMatcher(m match.Matcher) bool { + return m.ID == repeatMatcherName +} + +func repeatMatch(password string) []match.Match { + var matches []match.Match + + //Loop through password. if current == prev currentStreak++ else if currentStreak > 2 {buildMatch; currentStreak = 1} prev = current + var current, prev string + currentStreak := 1 + var i int + var char rune + for i, char = range password { + current = string(char) + if i == 0 { + prev = current + continue + } + + if strings.ToLower(current) == strings.ToLower(prev) { + currentStreak++ + + } else if currentStreak > 2 { + iPos := i - currentStreak + jPos := i - 1 + matchRepeat := match.Match{ + Pattern: "repeat", + I: iPos, + J: jPos, + Token: password[iPos : jPos+1], + DictionaryName: prev} + matchRepeat.Entropy = entropy.RepeatEntropy(matchRepeat) + matches = append(matches, matchRepeat) + currentStreak = 1 + } else { + currentStreak = 1 + } + + prev = current + } + + if currentStreak > 2 { + iPos := i - currentStreak + 1 + jPos := i + matchRepeat := match.Match{ + Pattern: "repeat", + I: iPos, + J: jPos, + Token: password[iPos : jPos+1], + DictionaryName: prev} + matchRepeat.Entropy = entropy.RepeatEntropy(matchRepeat) + matches = append(matches, matchRepeat) + } + return matches +} diff --git a/vendor/github.com/nbutton23/zxcvbn-go/matching/sequenceMatch.go b/vendor/github.com/nbutton23/zxcvbn-go/matching/sequenceMatch.go new file mode 100644 index 000000000..e0ed05229 --- /dev/null +++ b/vendor/github.com/nbutton23/zxcvbn-go/matching/sequenceMatch.go @@ -0,0 +1,76 @@ +package matching + +import ( + "strings" + + "github.com/nbutton23/zxcvbn-go/entropy" + "github.com/nbutton23/zxcvbn-go/match" +) + +const sequenceMatcherName = "SEQ" + +//FilterSequenceMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher +func FilterSequenceMatcher(m match.Matcher) bool { + return m.ID == sequenceMatcherName +} + +func sequenceMatch(password string) []match.Match { + var matches []match.Match + for i := 0; i < len(password); { + j := i + 1 + var seq string + var seqName string + seqDirection := 0 + for seqCandidateName, seqCandidate := range sequences { + iN := strings.Index(seqCandidate, string(password[i])) + var jN int + if j < len(password) { + jN = strings.Index(seqCandidate, string(password[j])) + } else { + jN = -1 + } + + if iN > -1 && jN > -1 { + direction := jN - iN + if direction == 1 || direction == -1 { + seq = seqCandidate + seqName = seqCandidateName + seqDirection = direction + break + } + } + + } + + if seq != "" { + for { + var prevN, curN int + if j < len(password) { + prevChar, curChar := password[j-1], password[j] + prevN, curN = strings.Index(seq, string(prevChar)), strings.Index(seq, string(curChar)) + } + + if j == len(password) || curN-prevN != seqDirection { + if j-i > 2 { + matchSequence := match.Match{ + Pattern: "sequence", + I: i, + J: j - 1, + Token: password[i:j], + DictionaryName: seqName, + } + + matchSequence.Entropy = entropy.SequenceEntropy(matchSequence, len(seq), (seqDirection == 1)) + matches = append(matches, matchSequence) + } + break + } else { + j++ + } + + } + } + i = j + } + return matches +} diff --git a/vendor/github.com/nbutton23/zxcvbn-go/matching/spatialMatch.go b/vendor/github.com/nbutton23/zxcvbn-go/matching/spatialMatch.go new file mode 100644 index 000000000..fd858f5d1 --- /dev/null +++ b/vendor/github.com/nbutton23/zxcvbn-go/matching/spatialMatch.go @@ -0,0 +1,88 @@ +package matching + +import ( + "strings" + + "github.com/nbutton23/zxcvbn-go/adjacency" + "github.com/nbutton23/zxcvbn-go/entropy" + "github.com/nbutton23/zxcvbn-go/match" +) + +const spatialMatcherName = "SPATIAL" + +//FilterSpatialMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher +func FilterSpatialMatcher(m match.Matcher) bool { + return m.ID == spatialMatcherName +} + +func spatialMatch(password string) (matches []match.Match) { + for _, graph := range adjacencyGraphs { + if graph.Graph != nil { + matches = append(matches, spatialMatchHelper(password, graph)...) + } + } + return matches +} + +func spatialMatchHelper(password string, graph adjacency.Graph) (matches []match.Match) { + + for i := 0; i < len(password)-1; { + j := i + 1 + lastDirection := -99 //an int that it should never be! + turns := 0 + shiftedCount := 0 + + for { + prevChar := password[j-1] + found := false + foundDirection := -1 + curDirection := -1 + //My graphs seem to be wrong. . . and where the hell is qwerty + adjacents := graph.Graph[string(prevChar)] + //Consider growing pattern by one character if j hasn't gone over the edge + if j < len(password) { + curChar := password[j] + for _, adj := range adjacents { + curDirection++ + + if strings.Index(adj, string(curChar)) != -1 { + found = true + foundDirection = curDirection + + if strings.Index(adj, string(curChar)) == 1 { + //index 1 in the adjacency means the key is shifted, 0 means unshifted: A vs a, % vs 5, etc. + //for example, 'q' is adjacent to the entry '2@'. @ is shifted w/ index 1, 2 is unshifted. + shiftedCount++ + } + + if lastDirection != foundDirection { + //adding a turn is correct even in the initial case when last_direction is null: + //every spatial pattern starts with a turn. + turns++ + lastDirection = foundDirection + } + break + } + } + } + + //if the current pattern continued, extend j and try to grow again + if found { + j++ + } else { + //otherwise push the pattern discovered so far, if any... + //don't consider length 1 or 2 chains. + if j-i > 2 { + matchSpc := match.Match{Pattern: "spatial", I: i, J: j - 1, Token: password[i:j], DictionaryName: graph.Name} + matchSpc.Entropy = entropy.SpatialEntropy(matchSpc, turns, shiftedCount) + matches = append(matches, matchSpc) + } + //. . . and then start a new search from the rest of the password + i = j + break + } + } + + } + return matches +} |
