forked from phpnode/yiipassword
-
Notifications
You must be signed in to change notification settings - Fork 0
/
APasswordStrategy.php
executable file
·216 lines (201 loc) · 7.03 KB
/
APasswordStrategy.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
<?php
/**
* A base class for password strategies.
*
* Password strategies encapsulate the logic for encoding and verifying user supplied passwords,
* as well as specifiying their minimum complexity.
*
* Password strategies allow authentication methods to be changed and upgraded
* progressively without affecting the user experience.
*
* @package packages.passwordStrategy
* @author Charles Pick
*/
abstract class APasswordStrategy extends CValidator {
/**
* The name of this password strategy
* @var string
*/
public $name;
/**
* The number of days a password is valid for before it should be changed.
* Defaults to false, meaning passwords do not expire
* @var integer|boolean
*/
public $daysValid = false;
/**
* The minimum password length
* @var integer
*/
public $minLength = 6;
/**
* The maximum password length.
* There is no good reason to set this value unless you're using it for legacy authentication
* Defaults to false meaning no maximum password length.
* @var integer|boolean
*/
public $maxLength = false;
/**
* The minimum number of upper case letters that should appear in passwords.
* Defaults to 0 meaning no minimum.
* @var integer
*/
public $minUpperCaseLetters = 0;
/**
* The minimum number of lower case letters that should appear in passwords.
* Defaults to 0 meaning no minimum.
* @var integer
*/
public $minLowerCaseLetters = 0;
/**
* The minimum number of digits that should appear in passwords.
* Defaults to 0 meaning no minimum.
* @var integer
*/
public $minDigits = 0;
/**
* The minimum number of special characters that should appear in passwords.
* Defaults to 0 meaning no minimum.
* @var integer
*/
public $minSpecialCharacters = 0;
/**
* The special characters that should appear in passwords if $minSpecialCharacters is set
* @var array
*/
public $specialCharacters = array(" ","'","~","!","@","#","£","$","%","^","&","\*","(",")","_","-","\+","=","[","]","\\","\|","{","}",";",":",'"',"\.",",","\/","<",">","\?","`");
/**
* @var string the salt to use for this password, if supported by this strategy
*/
private $_salt;
/**
* Sets the salt to use with this strategy, if supported
* @param string $salt the salt
*/
public function setSalt($salt)
{
$this->_salt = $salt;
}
/**
* Gets the salt to use with this strategy, if supported.
* @param boolean $forceRefresh whether to force generate a new salt
* @return string the generated salt
*/
public function getSalt($forceRefresh = false)
{
if ($this->_salt === null || $forceRefresh) {
$this->_salt = $this->generateSalt();
}
return $this->_salt;
}
/**
* Generates a random salt.
* @return string|boolean the generated salt, or false if not supported by this strategy
*/
protected function generateSalt() {
return false;
}
/**
* Validates a new password to ensure that it meets the minimum complexity requirements
* @param CModel $object the data object being validated
* @param string $attribute the name of the attribute to be validated.
* @return boolean true if validation succeeded
*/
protected function validateAttribute($object, $attribute)
{
$password = $object->{$attribute};
$length = mb_strlen($password);
if ($this->minLength && $length < $this->minLength) {
$this->addError($object,$attribute,"{attribute} is too short, minimum is ".$this->minLength." characters.");
return false;
}
if ($this->maxLength && $length > $this->maxLength) {
$this->addError($object,$attribute,"{attribute} is too long, maximum is ".$this->maxLength." characters.");
return false;
}
if ($this->minDigits) {
$digits = "";
if (preg_match_all("/[\d+]/u",$password,$matches)) {
$digits = implode("",$matches[0]);
}
if (mb_strlen($digits) < $this->minDigits) {
$this->addError($object,$attribute,"{attribute} should contain at least ".$this->minDigits." ".($this->minDigits == 1 ? "digit" : "digits"));
return false;
}
}
if ($this->minUpperCaseLetters) {
$upper = "";
if (preg_match_all("/[A-Z]/u",$password,$matches)) {
$upper = implode("",$matches[0]);
}
if (mb_strlen($upper) < $this->minUpperCaseLetters) {
$this->addError($object,$attribute,"{attribute} should contain at least ".$this->minUpperCaseLetters." upper case ".($this->minUpperCaseLetters == 1 ? "character" : "characters"));
return false;
}
}
if ($this->minLowerCaseLetters) {
$lower = "";
if (preg_match_all("/[a-z]/u",$password,$matches)) {
$lower = implode("",$matches[0]);
}
if (mb_strlen($lower) < $this->minLowerCaseLetters) {
$this->addError($object,$attribute,"{attribute} should contain at least ".$this->minLowerCaseLetters." lower case ".($this->minLowerCaseLetters == 1 ? "character" : "characters"));
return false;
}
}
if ($this->minSpecialCharacters) {
$special = "";
if (preg_match_all("/[".implode("|",$this->specialCharacters)."]/u",$password,$matches)) {
$special = implode("",$matches[0]);
}
if (mb_strlen($special) < $this->minSpecialCharacters) {
$this->addError($object,$attribute,"{attribute} should contain at least ".$this->minSpecialCharacters." non alpha numeric ".($this->minSpecialCharacters == 1 ? "character" : "characters"));
return false;
}
}
return true;
}
/**
* Encode a plain text password.
* Child classes should implement this method and do their encoding here
* @param string $password the plain text password to encode
* @return string the encoded password
*/
abstract public function encode($password);
/**
* Compare a plain text password to the given encoded password
* @param string $password the plain text password to compare
* @param string $encoded the encoded password to compare to
* @return boolean true if the passwords are equal, otherwise false
*/
public function compare($password, $encoded) {
$hash = $this->encode($password);
return $hash == $encoded;
}
/**
* Checks whether this strategy can be upgraded to another given strategy.
* If this strategy's complexity requirements are equal or greater than that
* of the given strategy, then it can be upgraded. Otherwise the user must be
* prompted to enter a new password that meets the complexity requirements.
* @param APasswordStrategy $strategy the strategy to upgrade to
* @return boolean true if this strategy can be upgraded to the given strategy
*/
public function canUpgradeTo(APasswordStrategy $strategy) {
if ($strategy->minLength && $strategy->minLength > $this->minLength) {
return false;
}
if ($strategy->minDigits > $this->minDigits) {
return false;
}
if ($strategy->minLowerCaseLetters > $this->minLowerCaseLetters) {
return false;
}
if ($strategy->minUpperCaseLetters > $this->minUpperCaseLetters) {
return false;
}
if ($strategy->minSpecialCharacters > $this->minSpecialCharacters) {
return false;
}
return true;
}
}