Passed
Pull Request — master (#7801)
by
unknown
09:25 queued 03:33
created

PasswordValidator::getMinLength()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Security;
4
5
use SilverStripe\Core\Config\Configurable;
6
use SilverStripe\Core\Extensible;
7
use SilverStripe\Core\Injector\Injectable;
8
use SilverStripe\Dev\Deprecation;
9
use SilverStripe\ORM\ValidationResult;
10
11
/**
12
 * This class represents a validator for member passwords.
13
 */
14
class PasswordValidator
15
{
16
    use Injectable;
17
    use Configurable;
18
    use Extensible;
19
20
    /**
21
     * @config
22
     * @var array
23
     */
24
    private static $character_strength_tests = [
25
        'lowercase' => '/[a-z]/',
26
        'uppercase' => '/[A-Z]/',
27
        'digits' => '/[0-9]/',
28
        'punctuation' => '/[^A-Za-z0-9]/',
29
    ];
30
31
    /**
32
     * @config
33
     * @var integer
34
     */
35
    private static $min_length = 3;
36
37
    /**
38
     * @config
39
     * @var integer
40
     */
41
    private static $min_test_score = null;
42
43
    /**
44
     * @config
45
     * @var integer
46
     */
47
    private static $historic_count = 6;
48
49
    /**
50
     * @deprecated 5.0
51
     * Minimum password length
52
     *
53
     * @param int $minLength
54
     * @return $this
55
     */
56
    public function minLength($minLength)
57
    {
58
        Deprecation::notice('5.0', 'Use ->config()->set(\'min_length\', $value) instead.');
59
        $this->config()->set('min_length', $minLength);
60
        return $this;
61
    }
62
63
    /**
64
     * @deprecated 5.0
65
     * Check the character strength of the password.
66
     *
67
     * Eg: $this->characterStrength(3, array("lowercase", "uppercase", "digits", "punctuation"))
68
     *
69
     * @param int $minScore The minimum number of character tests that must pass
70
     * @param array $testNames The names of the tests to perform
71
     * @return $this
72
     */
73
    public function characterStrength($minScore, $testNames = null)
74
    {
75
76
        Deprecation::notice(
77
            '5.0',
78
            'Use ->config()->set(\'min_test_score\', $value) and '.
79
            '->config()->set(\'character_strength_tests\', $value) instead.'
80
        );
81
        $this->config()->set('min_test_score', $minScore);
82
        return $this;
83
    }
84
85
    /**
86
     * @deprecated 5.0
87
     * Check a number of previous passwords that the user has used, and don't let them change to that.
88
     *
89
     * @param int $count
90
     * @return $this
91
     */
92
    public function checkHistoricalPasswords($count)
93
    {
94
        Deprecation::notice('5.0', 'Use ->config()->set(\'historic_count\', $value) instead.');
95
        $this->config()->set('historic_count', $count);
96
        return $this;
97
    }
98
99
    /**
100
     * @return integer
101
     */
102
    public function getMinLength()
103
    {
104
        return $this->config()->get('min_length');
105
    }
106
107
    /**
108
     * @return integer
109
     */
110
    public function getMinTestScore()
111
    {
112
        return $this->config()->get('min_test_score');
113
    }
114
    /**
115
     * @return integer
116
     */
117
    public function getHistoricCount()
118
    {
119
        return $this->config()->get('historic_count');
120
    }
121
122
    /**
123
     * @return array
124
     */
125
    public function getTests()
126
    {
127
        return $this->config()->get('character_strength_tests');
128
    }
129
130
131
    /**
132
     * @return array
133
     */
134
    public function getTestNames()
135
    {
136
        return array_keys(array_filter($this->getTests()));
137
    }
138
139
    /**
140
     * @param String $password
141
     * @param Member $member
142
     * @return ValidationResult
143
     */
144
    public function validate($password, $member)
145
    {
146
        $valid = ValidationResult::create();
147
148
        $minLength = $this->getMinLength();
149
        if ($minLength && strlen($password) < $minLength) {
150
            $error = _t(
151
                'SilverStripe\\Security\\PasswordValidator.TOOSHORT',
152
                'Password is too short, it must be {minimum} or more characters long',
153
                ['minimum' => $this->minLength]
154
            );
155
156
            $valid->addError($error, 'bad', 'TOO_SHORT');
157
        }
158
159
        $minTestScore = $this->getMinTestScore();
160
        if ($minTestScore) {
161
            $missedTests = [];
162
            $testNames = $this->getTestNames();
163
            $tests = $this->getTests();
164
165
            foreach ($testNames as $name) {
166
                if (preg_match($tests[$name], $password)) {
167
                    continue;
168
                }
169
                $missedTests[] = _t(
170
                    'SilverStripe\\Security\\PasswordValidator.STRENGTHTEST' . strtoupper($name),
171
                    $name,
172
                    'The user needs to add this to their password for more complexity'
173
                );
174
            }
175
176
            $score = count($this->testNames) - count($missedTests);
177
            if ($score < $minTestScore) {
178
                $error = _t(
179
                    'SilverStripe\\Security\\PasswordValidator.LOWCHARSTRENGTH',
180
                    'Please increase password strength by adding some of the following characters: {chars}',
181
                    ['chars' => implode(', ', $missedTests)]
182
                );
183
                $valid->addError($error, 'bad', 'LOW_CHARACTER_STRENGTH');
184
            }
185
        }
186
187
        $historicCount = $this->getHistoricCount();
188
        if ($historicCount) {
189
            $previousPasswords = MemberPassword::get()
190
                ->where(array('"MemberPassword"."MemberID"' => $member->ID))
191
                ->sort('"Created" DESC, "ID" DESC')
192
                ->limit($historicCount);
193
            /** @var MemberPassword $previousPassword */
194
            foreach ($previousPasswords as $previousPassword) {
195
                if ($previousPassword->checkPassword($password)) {
196
                    $error =  _t(
197
                        'SilverStripe\\Security\\PasswordValidator.PREVPASSWORD',
198
                        'You\'ve already used that password in the past, please choose a new password'
199
                    );
200
                    $valid->addError($error, 'bad', 'PREVIOUS_PASSWORD');
201
                    break;
202
                }
203
            }
204
        }
205
206
        $this->extend('updateValidatePassword', $password, $member, $valid);
207
208
        return $valid;
209
    }
210
}
211