Class: Zxcvbn::Matchers::Spatial Private

Inherits:
Object
  • Object
show all
Defined in:
lib/zxcvbn/matchers/spatial.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Matches keyboard spatial patterns (e.g. “qwerty”, “asdf”) across all configured adjacency graphs.

Constant Summary collapse

SHIFTED_RX =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Matches characters that require the Shift key on a standard keyboard.

/[~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?]/

Instance Method Summary collapse

Constructor Details

#initialize(graphs) ⇒ Spatial

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of Spatial.

Parameters:

  • graphs (Hash{String => Hash})

    adjacency graph data keyed by graph name



15
16
17
# File 'lib/zxcvbn/matchers/spatial.rb', line 15

def initialize(graphs)
  @graphs = graphs
end

Instance Method Details

#matches(password) ⇒ Array<MatchBuilder>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns matches with pattern “spatial” across all graphs.

Parameters:

  • password (String)

Returns:

  • (Array<MatchBuilder>)

    matches with pattern “spatial” across all graphs



21
22
23
24
25
26
27
# File 'lib/zxcvbn/matchers/spatial.rb', line 21

def matches(password)
  results = []
  @graphs.each do |graph_name, graph|
    results += matches_for_graph(graph, graph_name, password)
  end
  results
end

#matches_for_graph(graph, graph_name, password) ⇒ Array<MatchBuilder>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns spatial matches found in password using a single adjacency graph.

Parameters:

  • graph (Hash)

    adjacency map for each key character

  • graph_name (String)

    name of the graph (e.g. “qwerty”)

  • password (String)

Returns:

  • (Array<MatchBuilder>)

    matches with pattern “spatial”



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/zxcvbn/matchers/spatial.rb', line 35

def matches_for_graph(graph, graph_name, password)
  result = []
  keyboard_graph = %w[qwerty dvorak].include?(graph_name)
  i = 0
  while i < password.length - 1
    j = i + 1
    last_direction = nil
    turns = 0
    shifted_count = keyboard_graph && SHIFTED_RX.match?(password[i]) ? 1 : 0
    loop do
      prev_char = password[j - 1]
      found = false
      found_direction = -1
      cur_direction = -1
      adjacents = graph[prev_char] || []
      # consider growing pattern by one character if j hasn't gone over the edge.
      if j < password.length
        cur_char = password[j]
        adjacents.each do |adj|
          cur_direction += 1
          next unless adj&.index(cur_char)

          found = true
          found_direction = cur_direction
          if adj.index(cur_char) == 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.
            shifted_count += 1
          end
          if last_direction != found_direction
            # adding a turn is correct even in the initial case when last_direction is null:
            # every spatial pattern starts with a turn.
            turns += 1
            last_direction = found_direction
          end
          break
        end
      end
      # if the current pattern continued, extend j and try to grow again
      if found
        j += 1
      else
        # otherwise push the pattern discovered so far, if any...
        if j - i > 2 # don't consider length 1 or 2 chains.
          result << MatchBuilder.new(
            pattern: 'spatial',
            i:,
            j: j - 1,
            token: password.slice(i, j - i),
            graph: graph_name,
            turns:,
            shifted_count:
          )
        end
        # ...and then start a new search for the rest of the password.
        i = j
        break
      end
    end
  end
  result
end