Completed
Push — v2 ( 5f504a...4d3385 )
by Berend
04:16
created

Password::validatePasswordResetToken()   A

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 8
	protected function initPassword()
31
	{
32 8
		$this->extendTableDefinition(TRAIT_PASSWORD_FIELD_PASSWORD, [
33 8
			'value' => &$this->password,
34 8
			'validate' => [$this, 'validatePassword'],
35 8
			'type' => 'VARCHAR',
36 8
			'length' => 1024,
37
			'properties' => null
38
		]);
39
40 8
		$this->extendTableDefinition(TRAIT_PASSWORD_FIELD_RESET_TOKEN, [
41 8
			'value' => &$this->passwordResetToken,
42
			'validate' => null,
43 8
			'default' => 0,
44 8
			'type' => 'VARCHAR',
45 8
			'length' => 1024
46
		]);
47
48 8
		$this->extendTableDefinition(TRAIT_PASSWORD_FIELD_RESET_TOKEN_EXPIRY, [
49 8
			'value' => &$this->passwordExpiryDate,
50
			'validate' => null,
51 8
			'type' => 'DATETIME',
52
		]);
53 8
	}
54
55
56
	/**
57
	 * Returns whether the users password has been set
58
	 * @return boolean true if the user has a password
59
	 */
60 2
	public function hasPasswordBeenSet()
61
	{
62 2
		return $this->password !== null;
63
	}
64
65
	/**
66
	 * Returns true if the credentials are correct.
67
	 *
68
	 * @param string $password
69
	 * @return boolean true if the credentials are correct
70
	 */
71 2
	public function isPassword($password)
72
	{ 
73 2
		if (!$this->hasPasswordBeenSet())
74
		{
75 1
			throw new ActiveRecordTraitException("Password field has not been set");
76
		}
77
78 1
		if (!password_verify($password, $this->password)) {
79 1
			return false;
80
		}
81
82 1
		if (password_needs_rehash($this->password, TRAIT_PASSWORD_ENCRYPTION, ['cost' => TRAIT_PASSWORD_STRENTH])) {
83
			$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

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