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

GuessableData   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 138
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 47
dl 0
loc 138
ccs 34
cts 34
cp 1
rs 10
c 0
b 0
f 0
wmc 17

8 Methods

Rating   Name   Duplication   Size   Complexity  
A contains() 0 7 2
A containsString() 0 3 1
A getDateFormats() 0 5 3
A getMessage() 0 6 1
A containsDate() 0 9 3
A enforce() 0 6 2
A test() 0 5 1
A getGuessableData() 0 10 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Stadly\PasswordPolice\Rule;
6
7
use DateTimeInterface;
8
use InvalidArgumentException;
9
use RuntimeException;
10
use Stadly\PasswordPolice\Password;
11
use Stadly\PasswordPolice\Policy;
12
use Stadly\PasswordPolice\WordList\WordListInterface;
13
14
final class GuessableData implements RuleInterface
15
{
16
    private const DATE_FORMATS = [
17
        // Year
18
        ['y'], // 18
19
        ['Y'], // 2018
20
21
        // Day month
22
        ['j', 'n'], // 4 8
23
        ['j', 'm'], // 4 08
24
        ['j', 'M'], // 4 Aug
25
        ['j', 'F'], // 4 August
26
27
        // Month day
28
        ['n', 'j'], // 8 4
29
        ['n', 'd'], // 8 04
30
        ['M', 'j'], // Aug 4
31
        ['M', 'd'], // Aug 04
32
        ['F', 'j'], // August 4
33
        ['F', 'd'], // August 04
34
    ];
35
36
    private const DATE_SEPARATORS = [
37
        '',
38
        '-',
39
        ' ',
40
        '/',
41
        '.',
42
        ',',
43
        '. ',
44
        ', ',
45
    ];
46
47
    /**
48
     * Check whether a password is in compliance with the rule.
49
     *
50
     * @param Password|string $password Password to check.
51
     * @return bool Whether the password is in compliance with the rule.
52
     */
53 5
    public function test($password): bool
54
    {
55 5
        $data = $this->getGuessableData($password);
56
57 5
        return $data === null;
58
    }
59
60
    /**
61
     * Enforce that a password is in compliance with the rule.
62
     *
63
     * @param Password|string $password Password that must adhere to the rule.
64
     * @throws RuleException If the password does not adhrere to the rule.
65
     */
66 2
    public function enforce($password): void
67
    {
68 2
        $data = $this->getGuessableData($password);
69
70 2
        if ($data !== null) {
71 1
            throw new RuleException($this, $this->getMessage());
72
        }
73 1
    }
74
75
    /**
76
     * @param Password|string $password Password to find guessable data in.
77
     * @return string|DateTimeInterface|null Guessable data in the password.
78
     */
79 7
    private function getGuessableData($password)
80
    {
81 7
        if ($password instanceof Password) {
82 6
            foreach ($password->getGuessableData() as $data) {
83 6
                if ($this->contains((string)$password, $data)) {
84 6
                    return $data;
85
                }
86
            }
87
        }
88 4
        return null;
89
    }
90
91
    /**
92
     * @param string $password Password to check.
93
     * @param string|DateTimeInterface $data Data to check.
94
     * @return bool Whether the password contains the data.
95
     */
96 6
    private function contains(string $password, $data): bool
97
    {
98 6
        if ($data instanceof DateTimeInterface) {
99 3
            return $this->containsDate($password, $data);
100
        }
101
102 4
        return $this->containsString($password, $data);
103
    }
104
105
    /**
106
     * @param string $password Password to check.
107
     * @param string $string String to check.
108
     * @return bool Whether the password contains the string.
109
     */
110 6
    private function containsString(string $password, string $string): bool
111
    {
112 6
        return false !== mb_stripos($password, $string);
113
    }
114
115
    /**
116
     * @param string $password Password to check.
117
     * @param DateTimeInterface $date Date to check.
118
     * @return bool Whether the password contains the date.
119
     */
120 3
    private function containsDate(string $password, DateTimeInterface $date): bool
121
    {
122 3
        foreach ($this->getDateFormats() as $format) {
123 3
            if ($this->containsString($password, $date->format($format))) {
124 3
                return true;
125
            }
126
        }
127
128 2
        return false;
129
    }
130
131
    /**
132
     * @return iterable<string> Date formats.
133
     */
134 3
    private function getDateFormats(): iterable
135
    {
136 3
        foreach (self::DATE_FORMATS as $format) {
137 3
            foreach (self::DATE_SEPARATORS as $separator) {
138 3
                yield implode($separator, $format);
139
            }
140
        }
141 2
    }
142
143
    /**
144
     * {@inheritDoc}
145
     */
146 1
    public function getMessage(): string
147
    {
148 1
        $translator = Policy::getTranslator();
149
150 1
        return $translator->trans(
151 1
            'Must not contain guessable data.'
152
        );
153
    }
154
}
155