Password::validatePasswordResetToken()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 10
cc 3
nc 3
nop 1
crap 3
1
<?php
2
3
namespace miBadger\ActiveRecord\Traits;
4
5
use miBadger\ActiveRecord\ColumnProperty;
6
use miBadger\ActiveRecord\ActiveRecordTraitException;
7
8
const TRAIT_PASSWORD_FIELD_PASSWORD = "password";
9
const TRAIT_PASSWORD_ENCRYPTION = \PASSWORD_BCRYPT;
10
const TRAIT_PASSWORD_STRENTH = 10;
11
const TRAIT_PASSWORD_FIELD_RESET_TOKEN = "password_reset_token";
12
const TRAIT_PASSWORD_MIN_LENGTH = 8;
13
const TRAIT_PASSWORD_FIELD_RESET_TOKEN_EXPIRY = "password_reset_token_expiry";
14
15
trait Password
16
{
17
	/** @var string The password hash. */
18
	protected $password;
19
20
	/** @var string|null The password reset token. */
21
	protected $passwordResetToken;
22
23
	/** @var string|null The password expiry date */
24
	protected $passwordExpiryDate;
25
26
	/**
27
	 * this method is required to be called in the constructor for each class that uses this trait. 
28
	 * It adds the fields necessary for the passwords struct to the table definition
29
	 */
30 9
	protected function initPassword()
31
	{
32 9
		$this->extendTableDefinition(TRAIT_PASSWORD_FIELD_PASSWORD, [
33 9
			'value' => &$this->password,
34 9
			'validate' => [$this, 'validatePassword'],
35 9
			'setter' => [$this, 'setPassword'],
36 9
			'type' => 'VARCHAR',
37 9
			'length' => 1024,
38
			'properties' => null
39
		]);
40
41 9
		$this->extendTableDefinition(TRAIT_PASSWORD_FIELD_RESET_TOKEN, [
42 9
			'value' => &$this->passwordResetToken,
43
			'validate' => null,
44 9
			'default' => 0,
45 9
			'type' => 'VARCHAR',
46 9
			'length' => 1024
47
		]);
48
49 9
		$this->extendTableDefinition(TRAIT_PASSWORD_FIELD_RESET_TOKEN_EXPIRY, [
50 9
			'value' => &$this->passwordExpiryDate,
51
			'validate' => null,
52 9
			'type' => 'DATETIME',
53
		]);
54 9
	}
55
56
57
	/**
58
	 * Returns whether the users password has been set
59
	 * @return boolean true if the user has a password
60
	 */
61 2
	public function hasPasswordBeenSet()
62
	{
63 2
		return $this->password !== null;
64
	}
65
66
	/**
67
	 * Returns true if the credentials are correct.
68
	 *
69
	 * @param string $password
70
	 * @return boolean true if the credentials are correct
71
	 */
72 2
	public function isPassword($password)
73
	{ 
74 2
		if (!$this->hasPasswordBeenSet())
75
		{
76 1
			throw new ActiveRecordTraitException("Password field has not been set");
77
		}
78
79 1
		if (!password_verify($password, $this->password)) {
80 1
			return false;
81
		}
82
83 1
		if (password_needs_rehash($this->password, TRAIT_PASSWORD_ENCRYPTION, ['cost' => TRAIT_PASSWORD_STRENTH])) {
84
			$this->setPassword($password)->sync();
0 ignored issues
show
Bug introduced by
It seems like sync() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

84
			$this->setPassword($password)->/** @scrutinizer ignore-call */ sync();
Loading history...
85
		}
86
87 1
		return true;
88
	}
89
90 4
	public function validatePassword($password) {
91 4
		if (strlen($password) < TRAIT_PASSWORD_MIN_LENGTH) {
92 2
			$message = sprintf('\'Password\' must be atleast %s characters long. %s characters provided.', TRAIT_PASSWORD_MIN_LENGTH, strlen($password));
93 2
			return [false, $message];
94
		}
95 3
		return [true, ''];
96
	}
97
98
	/**
99
	 * Set the password.
100
	 *
101
	 * @param string $password
102
	 * @return $this
103
	 * @throws \Exception
104
	 */
105 3
	public function setPassword($password)
106
	{
107 3
		[$status, $error] = $this->validatePassword($password);
108 3
		if (!$status) {
109 1
			throw new ActiveRecordTraitException($error);
110
		}
111
112 2
		$passwordHash = \password_hash($password, TRAIT_PASSWORD_ENCRYPTION, ['cost' => TRAIT_PASSWORD_STRENTH]);
113
114 2
		if ($passwordHash === false) {
115
			throw new ActiveRecordTraitException('\'Password\' hash failed.');
116
		}
117
118 2
		$this->password = $passwordHash;
119
120 2
		return $this;
121
	}
122
123
	/**
124
	 * @return string The Hash of the password
125
	 */
126
	public function getPasswordHash()
127
	{
128
		return $this->password;
129
	}
130
131
	/**
132
	 * Returns the currently set password token for the entity, or null if not set
133
	 * @return string|null The password reset token
134
	 */
135 3
	public function getPasswordResetToken()
136
	{
137 3
		return $this->passwordResetToken;
138
	}
139
140
	/**
141
	 * Generates a new password reset token for the user
142
	 */
143 3
	public function generatePasswordResetToken()
144
	{
145 3
		$this->passwordResetToken = md5(uniqid(mt_rand(), true));
146
147 3
		$validityDuration = new \DateInterval('PT24H');
148
149 3
		$this->passwordExpiryDate = (new \DateTime('now'))->add($validityDuration)->format('Y-m-d H:i:s');
150 3
		return $this;
151
	}
152
153
	/**
154
	 * Clears the current password reset token
155
	 */
156 1
	public function clearPasswordResetToken()
157
	{
158 1
		$this->passwordResetToken = null;
159 1
		$this->passwordExpiryDate = null;
160 1
		return $this;
161
	}
162
163 1
	public function validatePasswordResetToken(string $token)
164
	{
165 1
		return $this->passwordResetToken !== null
166 1
			&& $token === $this->passwordResetToken
167 1
			&& (new \DateTime('now')) < (new \DateTime($this->passwordExpiryDate));
168
	}
169
	
170
	/**
171
	 * @return void
172
	 */
173
	abstract protected function extendTableDefinition(string $columnName, $definition);
174
	
175
	/**
176
	 * @return void
177
	 */
178
	abstract protected function registerSearchHook(string $columnName, $fn);
179
180
	/**
181
	 * @return void
182
	 */
183
	abstract protected function registerDeleteHook(string $columnName, $fn);
184
185
	/**
186
	 * @return void
187
	 */
188
	abstract protected function registerUpdateHook(string $columnName, $fn);
189
190
	/**
191
	 * @return void
192
	 */
193
	abstract protected function registerReadHook(string $columnName, $fn);
194
195
	/**
196
	 * @return void
197
	 */
198
	abstract protected function registerCreateHook(string $columnName, $fn);
199
200
}