Completed
Push — master ( 82d82e...a9fbcc )
by Wanderson
02:14
created

User::getRecoveryUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Win\Authentication;
4
5
use Win\Mvc\Application;
6
use Win\Authentication\UserDAO;
7
use Local\Person\Person;
8
use Local\Person\PersonDAO;
9
use Win\Helper\Url;
10
use Win\Mvc\Block;
11
use Win\Mailer\Email;
12
use Win\File\Image;
13
use Win\Calendar\Date;
14
15
/**
16
 * Usuários do sistema
17
 */
18
class User {
19
20
	private static $accessLevels = [0, 1, 2];
21
22
	const ACCESS_DENIED = 0;
23
	const ACCESS_ALLOWED = 1;
24
	const ACCESS_ADMIN = 2;
25
26
	/* Lock after many login fails */
27
	const LOCK_TRIES = 5;
28
	const LOCK_TIME_MINUTES = 10;
29
30
	private static $passwordSalt = 'E50H%gDui#';
31
	private $id;
32
	private $isEnabled;
33
	private $isLogged;
34
	private $accessLevel;
35
	private $name;
36
	private $email;
37
	private $confirmEmail;
38
	private $password;
39
	private $confirmPassword;
40
	private $passwordHash;
41
	private $recoreryHash;
42
43
	/** @var Date */
44
	private $loginDate;
45
46
	/** @var Date */
47
	private $loginLockDate;
48
	public $loginFailCount = 0;
49
50
	/** @var Image */
51
	private $image;
52
53
	/** @var Group */
54
	private $group;
55
	private $groupId;
56
57
	/** @var Person */
58
	private $person;
59
60
	public function __construct() {
61
		$this->id = 0;
62
		$this->isEnabled = true;
63
		$this->isLogged = false;
64
		$this->accessLevel = self::ACCESS_DENIED;
65
		$this->name = '';
66
		$this->email = '';
67
		$this->confirmEmail = '';
68
		$this->password = null;
69
		$this->confirmPassword = null;
70
		$this->passwordHash = null;
71
		$this->recoreryHash = null;
72
		$this->image = new Image();
73
		$this->image->setDirectory('data/upload/user');
74
		$this->loginDate = new Date('00/00/0000');
75
		$this->loginLockDate = new Date('00/00/0000');
76
		$this->group = null;
77
		$this->groupId = 0;
78
		$this->person = null;
79
	}
80
81
	public function getId() {
82
		return $this->id;
83
	}
84
85
	public function isEnabled() {
86
		return $this->isEnabled;
87
	}
88
89
	public function isLogged() {
90
		return $this->isLogged;
91
	}
92
93
	public function getAccessLevel() {
94
		return $this->accessLevel;
95
	}
96
97
	public function accessIsDenied() {
98
		return ($this->accessLevel == self::ACCESS_DENIED);
99
	}
100
101
	/** @return boolean */
102
	public function isAdmin() {
103
		return ($this->accessLevel == self::ACCESS_ADMIN);
104
	}
105
106
	public function getGroup() {
107
		if (is_null($this->group)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
108
// groupDAO
109
		}
110
		return $this->group;
111
	}
112
113
	public function getGroupId() {
114
		return $this->groupId;
115
	}
116
117
	/** @return Person */
118
	public function getPerson() {
119
		if (is_null($this->person)) {
120
			$pDAO = new PersonDAO();
121
			$this->person = $pDAO->fetchById($this->id);
122
		}
123
		return $this->person;
124
	}
125
126
	public function getName() {
127
		return $this->name;
128
	}
129
130
	public function getEmail() {
131
		return $this->email;
132
	}
133
134
	public function getConfirmEmail() {
135
		return $this->confirmEmail;
136
	}
137
138
	public function getPassword() {
139
		return $this->password;
140
	}
141
142
	public function getConfirmPassword() {
143
		return $this->confirmPassword;
144
	}
145
146
	public function getPasswordHash() {
147
		return $this->passwordHash;
148
	}
149
150
	public function getRecoreryHash() {
151
		return $this->recoreryHash;
152
	}
153
154
	/** @return string */
155
	public function getRecoveryUrl() {
156
		return Application::app()->getBaseUrl() . 'login/alterar-senha/' . $this->getRecoreryHash() . '/';
157
	}
158
159
	public function getImage() {
160
		return $this->image;
161
	}
162
163
	public function getLoginDate() {
164
		return $this->loginDate;
165
	}
166
167
	public function getLoginLockDate() {
168
		return $this->loginLockDate;
169
	}
170
171
	/** @return Date retorna data que poderá logar novamente sem bloqueio */
172
	public function getLoginUnlockDate() {
173
		$date = clone $this->getLoginLockDate();
174
		$date->sumTime(static::LOCK_TIME_MINUTES, 'minutes');
175
		return $date;
176
	}
177
178
	public function getLockedMsg() {
179
		return 'Você foi bloqueado por realizar ' . static::LOCK_TRIES . ' tentativas de login.<br /> Você poderá tentar novamente ' . $this->getLoginUnlockDate()->toHumanFormat() . '.';
180
	}
181
182
	public function setId($id) {
183
		$this->id = (int) $id;
184
	}
185
186
	public function setEnabled($enabled) {
187
		$this->isEnabled = (boolean) $enabled;
188
	}
189
190
	public function setAccessLevel($accessLevel) {
191
		if (in_array($accessLevel, static::$accessLevels)) {
0 ignored issues
show
Bug introduced by
Since $accessLevels is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $accessLevels to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
192
			$this->accessLevel = (int) $accessLevel;
193
		}
194
	}
195
196
	public function setGroup(Group $group) {
197
		$this->group = $group;
198
	}
199
200
	public function setGroupId($groupId) {
201
		$this->groupId = (int) $groupId;
202
	}
203
204
	public function setPerson(Person $person) {
205
		$this->person = $person;
206
	}
207
208
	public function setName($name) {
209
		$this->name = $name;
210
	}
211
212
	public function setEmail($email) {
213
		$this->email = strClear($email);
214
	}
215
216
	public function setConfirmEmail($confirmEmail) {
217
		$this->confirmEmail = strClear($confirmEmail);
218
	}
219
220
	public function setPassword($password) {
221
		$this->password = $password;
222
		$this->passwordHash = static::encryptPassword($password);
223
	}
224
225
	public function setConfirmPassword($confirmPassword) {
226
		$this->confirmPassword = $confirmPassword;
227
	}
228
229
	public function setPasswordHash($passwordHash) {
230
		$this->passwordHash = $passwordHash;
231
	}
232
233
	public function setRecoreryHash($recoreryHash) {
234
		$this->recoreryHash = $recoreryHash;
235
	}
236
237
	public function setLoginDate(Date $loginDate) {
238
		$this->loginDate = $loginDate;
239
	}
240
241
	public function setImage(Image $image) {
242
		$this->image = $image;
243
	}
244
245
	/**
246
	 * Tenta realizar login
247
	 * @return boolean
248
	 */
249
	public function login() {
250
		$filters = [
251
			'is_enabled = ?' => true,
252
			'access_level > ?' => 0,
253
			'email = ?' => $this->email,
254
			'password_hash = ?' => $this->passwordHash
255
		];
256
		$uDAO = new UserDAO();
257
		$user = $uDAO->fetch($filters);
0 ignored issues
show
Documentation introduced by
$filters is of type array<string,boolean|int...sh = ?":"null|string"}>, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
258
		$this->setCurrentUser($user);
259
260
		if ($user->getId() > 0 && !$this->isLocked()) {
261
			$this->isLogged = true;
262
			$uDAO->clearRecoveryHash($user);
263
			$uDAO->updateLoginDate($user);
264
			$this->loginFailCount = 0;
265
		} else {
266
			$this->incrementLoginFail();
267
		}
268
269
		return $this->isLogged;
270
	}
271
272
	/** Realiza logout */
273
	public function logout() {
274
		unset($_SESSION['user']);
275
	}
276
277
	private function incrementLoginFail() {
278
		$this->loginFailCount++;
279
		if ($this->loginFailCount >= static::LOCK_TRIES && !$this->isLocked()) {
280
			$this->loginLockDate = new Date();
281
			$this->loginFailCount = 0;
282
		}
283
	}
284
285
	/** @return boolean retorna TRUE se está bloqueado por tentativas de login */
286
	public function isLocked() {
287
		$diff = Date::diffSeconds($this->getLoginUnlockDate(), new Date);
288
		return (boolean) ($diff <= 0 );
289
	}
290
291
	/** @return int total de tentativas restantes até ser bloqueado */
292
	public function getLoginTriesLeft() {
293
		return (static::LOCK_TRIES - $this->loginFailCount);
294
	}
295
296
	/** Objeto > Sessão */
297
	private function setCurrentUser(User $user) {
298
		$_SESSION['user'] = $this;
299
		$this->id = $user->getId();
300
		$this->accessLevel = $user->getAccessLevel();
301
		$this->name = $user->getName();
302
		$this->loginDate = $user->getLoginDate();
303
		$this->image = $user->getImage();
304
	}
305
306
	/** Objeto < Sessão */
307
	public static function getCurrentUser() {
308
		return (isset($_SESSION['user'])) ? $_SESSION['user'] : new User();
309
	}
310
311
	/** Obriga o usuário a se logar */
312
	public function requireLogin() {
313
		if (!$this->isLogged) {
314
			Url::instance()->redirect('login');
315
		}
316
	}
317
318
	/** Obriga o usuário a logar como ADMIN */
319
	public function requireAdmin() {
320
		$this->requireLogin();
321
		if ($this->getAccessLevel() != static::ACCESS_ADMIN) {
322
			Application::app()->errorPage(403);
323
		}
324
	}
325
326
	/**
327
	 * Envia link de recuperacao de senha via Email
328
	 * @return string | null
329
	 */
330
	public function sendRecoveryHash() {
331
		$filters = ['is_enabled = ?' => true, 'access_level > ?' => 0, 'email = ?' => $this->email];
332
		$uDAO = new UserDAO();
333
		$user = $uDAO->fetch($filters);
0 ignored issues
show
Documentation introduced by
$filters is of type array<string,boolean|int...,"email = ?":"string"}>, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
334
335
		if ($user->getId() > 0) {
336
			$uDAO->updateRecoveryHash($user);
337
			$content = new Block('email/content/recovery-password', ['user' => $user]);
338
339
			$mail = new Email();
340
			$mail->setFrom(EMAIL_FROM, Application::app()->getName());
341
			$mail->setSubject('Recuperação de Senha');
342
			$mail->addAddress($user->getEmail(), $user->getName());
343
			$mail->setContent($content);
344
			return $mail->send();
345
		} else {
346
			return 'Este E-mail não está cadastrado no sistema.';
347
		}
348
	}
349
350
	/** Define os atributos que são salvos na SESSAO */
351
	public function __sleep() {
352
		return ['id', 'isEnabled', 'isLogged', 'accessLevel', 'name', 'email', 'image', 'loginDate', 'groupId', 'loginFailCount', 'loginLockDate'];
353
	}
354
355
	/**
356
	 * Adiciona maior segura na senha/ utilizar esta função ao inves de um simples md5
357
	 * @param string $password
358
	 */
359
	public static function encryptPassword($password) {
360
		return md5($password . static::$passwordSalt);
0 ignored issues
show
Bug introduced by
Since $passwordSalt is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $passwordSalt to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
361
	}
362
363
	/**
364
	 * Retorna uma senha aleatoria
365
	 * A senha tem sempre pelo menos: 1 caracter especial e 2 numeros;
366
	 * @param int $length
367
	 * @return string
368
	 */
369
	public static function generatePassword($length = 6) {
370
		$letters = str_shuffle('abcdefghijkmnopqrstwxyzABCDEFGHJKLMNPQRSTWXY');
371
		$numbers = str_shuffle('23456789');
372
		$specials = str_shuffle('@#&');
373
374
		$password = substr($letters, 0, $length - 3)
375
				. substr($numbers, 0, 2)
376
				. substr($specials, 0, 1);
377
378
		return str_shuffle($password);
379
	}
380
381
}
382