Issues (88)

src/Rules/Password.php (2 issues)

1
<?php
2
3
/**
4
 * This file is part of Dimtrovich/Validation.
5
 *
6
 * (c) 2023 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace Dimtrovich\Validation\Rules;
13
14
use BlitzPHP\Utilities\Iterable\Arr;
15
use Dimtrovich\Validation\Validator;
16
17
class Password extends AbstractRule
18
{
19
    /**
20
     * @var array
21
     */
22
    protected $fillableParams = ['min'];
23
24
    /**
25
     * The minimum size of the password.
26
     */
27
    protected int $min = 8;
28
29
    /**
30
     * If the password requires at least one uppercase and one lowercase letter.
31
     */
32
    protected bool $mixedCase = false;
33
34
    /**
35
     * If the password requires at least one letter.
36
     */
37
    protected bool $letters = false;
38
39
    /**
40
     * If the password requires at least one number.
41
     */
42
    protected bool $numbers = false;
43
44
    /**
45
     * If the password requires at least one symbol.
46
     */
47
    protected bool $symbols = false;
48
49
    /**
50
     * If the password should not have been compromised in data leaks.
51
     */
52
    protected bool $uncompromised = false;
53
54
    /**
55
     * The number of times a password can appear in data leaks before being considered compromised.
56
     */
57
    protected int $compromisedThreshold = 0;
58
59
    /**
60
     * Additional validation rules that should be merged into the default rules during validation.
61
     */
62
    protected array $customRules = [];
63
64
    /**
65
     * The callback that will generate the "default" version of the password rule.
66
     *
67
     * @var array|callable|string|null
68
     */
69
    public static $defaultCallback;
70
71
    /**
72
     * Get the default configuration of the password rule.
73
     */
74
    public function default(): self
75
    {
76
        $password = is_callable(static::$defaultCallback)
77
                            ? call_user_func(static::$defaultCallback)
0 ignored issues
show
It seems like static::defaultCallback can also be of type null; however, parameter $callback of call_user_func() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

77
                            ? call_user_func(/** @scrutinizer ignore-type */ static::$defaultCallback)
Loading history...
78
                            : static::$defaultCallback;
79
80
        return $password instanceof self ? $password : $this->min(8);
81
    }
82
83
    /**
84
     * Get configuration for the strong password
85
     */
86
    public function strong(): self
87
    {
88
        return (clone $this)->min(8)->symbols()->letters()->numbers()->mixedCase();
89
    }
90
91
    /**
92
     * Sets the minimum size of the password.
93
     */
94
    public function min(int $size): self
95
    {
96
        $this->min = $size;
97
98
        return $this;
99
    }
100
101
    /**
102
     * Ensures the password has not been compromised in data leaks.
103
     */
104
    public function uncompromised(int $threshold = 0): self
105
    {
106
        $this->uncompromised = true;
107
108
        $this->compromisedThreshold = $threshold;
109
110
        return $this;
111
    }
112
113
    /**
114
     * Makes the password require at least one uppercase and one lowercase letter.
115
     */
116
    public function mixedCase(): self
117
    {
118
        $this->mixedCase = true;
119
120
        return $this;
121
    }
122
123
    /**
124
     * Makes the password require at least one letter.
125
     */
126
    public function letters(): self
127
    {
128
        $this->letters = true;
129
130
        return $this;
131
    }
132
133
    /**
134
     * Makes the password require at least one number.
135
     */
136
    public function numbers(): self
137
    {
138
        $this->numbers = true;
139
140
        return $this;
141
    }
142
143
    /**
144
     * Makes the password require at least one symbol.
145
     */
146
    public function symbols(): self
147
    {
148
        $this->symbols = true;
149
150
        return $this;
151
    }
152
153
    /**
154
     * Specify additional validation rules that should be merged with the default rules during validation.
155
     */
156
    public function rules(array|string $rules): self
157
    {
158
        $this->customRules = Arr::wrap($rules);
159
160
        return $this;
161
    }
162
163
    /**
164
     * {@inheritDoc}
165
     */
166
    public function check($value): bool
167
    {
168
        if (! is_string($value)) {
169
            return false;
170
        }
171
172
        if (is_numeric($this->params['min'] ?? '')) {
173
            $this->min = (int) $this->params['min'];
174
        }
175
176
        $attribute = $this->getAttribute()->getKey();
177
178
        $validator = Validator::make(
179
            [$attribute => $value],
180
            [$attribute => array_merge(['string', 'min:' . $this->min], $this->customRules)],
181
            $this->validation->getMessages(),
0 ignored issues
show
The method getMessages() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

181
            $this->validation->/** @scrutinizer ignore-call */ 
182
                               getMessages(),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
182
        );
183
184
        if ($this->mixedCase && ! preg_match('/(\p{Ll}+.*\p{Lu})|(\p{Lu}+.*\p{Ll})/u', $value)) {
185
            $this->validation->errors()->add($attribute, static::name() . '.mixed', $this->processTranslate('password.mixed'));
186
        }
187
        if ($this->letters && ! preg_match('/\pL/u', $value)) {
188
            $this->validation->errors()->add($attribute, static::name() . '.letters', $this->processTranslate('password.letters'));
189
        }
190
        if ($this->symbols && ! preg_match('/\p{Z}|\p{S}|\p{P}/u', $value)) {
191
            $this->validation->errors()->add($attribute, static::name() . '.symbols', $this->processTranslate('password.symbols'));
192
        }
193
        if ($this->numbers && ! preg_match('/\pN/u', $value)) {
194
            $this->validation->errors()->add($attribute, static::name() . '.numbers', $this->processTranslate('password.numbers'));
195
        }
196
197
        return $validator->setValidation($this->validation)->passes();
198
    }
199
200
    private function processTranslate(string $key): string
201
    {
202
        return str_replace(
203
            ':attribute',
204
            $this->getAttributeAlias($this->getAttribute()->getKey()),
205
            $this->translate($key)
206
        );
207
    }
208
}
209