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

GuessableData::getGuessableData()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 5
nc 3
nop 1
dl 0
loc 10
ccs 6
cts 6
cp 1
crap 4
rs 10
c 0
b 0
f 0
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