Passed
Push — master ( ae0a03...686af4 )
by Magnar Ovedal
03:17
created

Dictionary::getDictionaryWord()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 10
nc 5
nop 1
dl 0
loc 18
ccs 10
cts 10
cp 1
crap 5
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Stadly\PasswordPolice\Rule;
6
7
use InvalidArgumentException;
8
use RuntimeException;
9
use Stadly\PasswordPolice\Policy;
10
use Stadly\PasswordPolice\WordList\WordListInterface;
11
12
final class Dictionary implements RuleInterface
13
{
14
    /**
15
     * @var WordListInterface Word list for the dictionary.
16
     */
17
    private $wordList;
18
19
    /**
20
     * @var int Minimum word length to consider.
21
     */
22
    private $minWordLength;
23
24
    /**
25
     * @var int|null Maximum word length to consider.
26
     */
27
    private $maxWordLength;
28
29
    /**
30
     * @param WordListInterface $wordList Word list for the dictionary.
31
     * @param int $minWordLength Ignore words shorter than this.
32
     * @param int|null $maxWordLength Ignore words longer than this.
33
     */
34 7
    public function __construct(WordListInterface $wordList, int $minWordLength = 3, ?int $maxWordLength = 25)
35
    {
36 7
        if ($minWordLength < 1) {
37 2
            throw new InvalidArgumentException('Minimum word length must be positive.');
38
        }
39 5
        if ($maxWordLength !== null && $maxWordLength < $minWordLength) {
40 1
            throw new InvalidArgumentException('Maximum word length cannot be smaller than mininum word length.');
41
        }
42
43 4
        $this->wordList = $wordList;
44 4
        $this->minWordLength = $minWordLength;
45 4
        $this->maxWordLength = $maxWordLength;
46 4
    }
47
48
    /**
49
     * @return WordListInterface Word list for the dictionary.
50
     */
51 1
    public function getWordList(): WordListInterface
52
    {
53 1
        return $this->wordList;
54
    }
55
56
    /**
57
     * @return int Minimum word length to consider.
58
     */
59 1
    public function getMinWordLength(): int
60
    {
61 1
        return $this->minWordLength;
62
    }
63
64
    /**
65
     * @return int|null Maximum word length to consider.
66
     */
67 1
    public function getMaxWordLength(): ?int
68
    {
69 1
        return $this->maxWordLength;
70
    }
71
72
    /**
73
     * {@inheritDoc}
74
     */
75 8
    public function test($password): bool
76
    {
77 8
        $word = $this->getDictionaryWord((string)$password);
78
79 7
        return $word === null;
80
    }
81
82
    /**
83
     * {@inheritDoc}
84
     */
85 2
    public function enforce($password): void
86
    {
87 2
        $word = $this->getDictionaryWord((string)$password);
88
89 2
        if ($word !== null) {
90 1
            throw new RuleException($this, $this->getMessage());
91
        }
92 1
    }
93
94
    /**
95
     * @param string $password Password to find dictionary words in.
96
     * @return string|null Dictionary word in the password.
97
     * @throws TestException If an error occurred while using the word list.
98
     */
99 10
    private function getDictionaryWord(string $password): ?string
100
    {
101 10
        for ($start = 0; $start < mb_strlen($password); ++$start) {
102 10
            $word = mb_substr($password, $start, $this->maxWordLength);
103
104 10
            for ($wordLength = mb_strlen($word); $this->minWordLength <= $wordLength; --$wordLength) {
105 9
                $word = mb_substr($word, 0, $wordLength);
106
107
                try {
108 9
                    if ($this->wordList->contains($word)) {
109 8
                        return $word;
110
                    }
111 1
                } catch (RuntimeException $exception) {
112 1
                    throw new TestException($this, 'An error occurred while using the word list.', $exception);
113
                }
114
            }
115
        }
116 3
        return null;
117
    }
118
119
    /**
120
     * {@inheritDoc}
121
     */
122 1
    public function getMessage(): string
123
    {
124 1
        $translator = Policy::getTranslator();
125
126 1
        return $translator->trans(
127 1
            'Must not contain common dictionary words.'
128
        );
129
    }
130
}
131