Completed
Push — master ( a9fbcc...a7b0c2 )
by Wanderson
02:19
created

User::preventDeletedAndLogged()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Win\Authentication;
4
5
use Local\Person\Person;
6
use Local\Person\PersonDAO;
7
use Win\Alert\AlertError;
8
use Win\Authentication\UserDAO;
9
use Win\Calendar\Date;
10
use Win\File\Image;
11
use Win\Helper\Url;
12
use Win\Mailer\Email;
13
use Win\Mvc\Application;
14
use Win\Mvc\Block;
15
use const EMAIL_FROM;
16
use function strClear;
17
18
/**
19
 * Usuários do sistema
20
 */
21
class User {
22
23
	private static $accessLevels = [0, 1, 2];
24
25
	const ACCESS_DENIED = 0;
26
	const ACCESS_ALLOWED = 1;
27
	const ACCESS_ADMIN = 2;
28
29
	/* Lock after many login fails */
30
	const LOCK_TRIES = 5;
31
	const LOCK_TIME_MINUTES = 10;
32
33
	private static $passwordSalt = 'E50H%gDui#';
34
	private $id;
35
	private $isEnabled;
36
	private $isLogged;
37
	private $accessLevel;
38
	private $name;
39
	private $email;
40
	private $confirmEmail;
41
	private $password;
42
	private $confirmPassword;
43
	private $passwordHash;
44
	private $recoreryHash;
45
46
	/** @var Date */
47
	private $loginDate;
48
49
	/** @var Date */
50
	private $loginLockDate;
51
	public $loginFailCount = 0;
52
53
	/** @var Image */
54
	private $image;
55
56
	/** @var Group */
57
	private $group;
58
	private $groupId;
59
60
	/** @var Person */
61
	private $person;
62
63
	public function __construct() {
64
		$this->id = 0;
65
		$this->isEnabled = true;
66
		$this->isLogged = false;
67
		$this->accessLevel = self::ACCESS_DENIED;
68
		$this->name = '';
69
		$this->email = '';
70
		$this->confirmEmail = '';
71
		$this->password = null;
72
		$this->confirmPassword = null;
73
		$this->passwordHash = null;
74
		$this->recoreryHash = null;
75
		$this->image = new Image();
76
		$this->image->setDirectory('data/upload/user');
77
		$this->loginDate = new Date('00/00/0000');
78
		$this->loginLockDate = new Date('00/00/0000');
79
		$this->group = null;
80
		$this->groupId = 0;
81
		$this->person = null;
82
	}
83
84
	public function getId() {
85
		return $this->id;
86
	}
87
88
	public function isEnabled() {
89
		return $this->isEnabled;
90
	}
91
92
	public function isLogged() {
93
		return $this->isLogged;
94
	}
95
96
	public function getAccessLevel() {
97
		return $this->accessLevel;
98
	}
99
100
	public function accessIsDenied() {
101
		return ($this->accessLevel == self::ACCESS_DENIED);
102
	}
103
104
	/** @return boolean */
105
	public function isAdmin() {
106
		return ($this->accessLevel == self::ACCESS_ADMIN);
107
	}
108
109
	public function getGroup() {
110
		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...
111
// groupDAO
112
		}
113
		return $this->group;
114
	}
115
116
	public function getGroupId() {
117
		return $this->groupId;
118
	}
119
120
	/** @return Person */
121
	public function getPerson() {
122
		if (is_null($this->person)) {
123
			$pDAO = new PersonDAO();
124
			$this->person = $pDAO->fetchById($this->id);
125
		}
126
		return $this->person;
127
	}
128
129
	public function getName() {
130
		return $this->name;
131
	}
132
133
	public function getEmail() {
134
		return $this->email;
135
	}
136
137
	public function getConfirmEmail() {
138
		return $this->confirmEmail;
139
	}
140
141
	public function getPassword() {
142
		return $this->password;
143
	}
144
145
	public function getConfirmPassword() {
146
		return $this->confirmPassword;
147
	}
148
149
	public function getPasswordHash() {
150
		return $this->passwordHash;
151
	}
152
153
	public function getRecoreryHash() {
154
		return $this->recoreryHash;
155
	}
156
157
	/** @return string */
158
	public function getRecoveryUrl() {
159
		return Application::app()->getBaseUrl() . 'login/alterar-senha/' . $this->getRecoreryHash() . '/';
160
	}
161
162
	public function getImage() {
163
		return $this->image;
164
	}
165
166
	public function getLoginDate() {
167
		return $this->loginDate;
168
	}
169
170
	public function getLoginLockDate() {
171
		return $this->loginLockDate;
172
	}
173
174
	/** @return Date retorna data que poderá logar novamente sem bloqueio */
175
	public function getLoginUnlockDate() {
176
		$date = clone $this->getLoginLockDate();
177
		$date->sumTime(static::LOCK_TIME_MINUTES, 'minutes');
178
		return $date;
179
	}
180
181
	public function getLockedMsg() {
182
		return 'Você foi bloqueado por realizar ' . static::LOCK_TRIES . ' tentativas de login.<br /> Você poderá tentar novamente ' . $this->getLoginUnlockDate()->toHumanFormat() . '.';
183
	}
184
185
	public function setId($id) {
186
		$this->id = (int) $id;
187
	}
188
189
	public function setEnabled($enabled) {
190
		$this->isEnabled = (boolean) $enabled;
191
	}
192
193
	public function setAccessLevel($accessLevel) {
194
		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...
195
			$this->accessLevel = (int) $accessLevel;
196
		}
197
	}
198
199
	public function setGroup(Group $group) {
200
		$this->group = $group;
201
	}
202
203
	public function setGroupId($groupId) {
204
		$this->groupId = (int) $groupId;
205
	}
206
207
	public function setPerson(Person $person) {
208
		$this->person = $person;
209
	}
210
211
	public function setName($name) {
212
		$this->name = $name;
213
	}
214
215
	public function setEmail($email) {
216
		$this->email = strClear($email);
217
	}
218
219
	public function setConfirmEmail($confirmEmail) {
220
		$this->confirmEmail = strClear($confirmEmail);
221
	}
222
223
	public function setPassword($password) {
224
		$this->password = $password;
225
		$this->passwordHash = static::encryptPassword($password);
226
	}
227
228
	public function setConfirmPassword($confirmPassword) {
229
		$this->confirmPassword = $confirmPassword;
230
	}
231
232
	public function setPasswordHash($passwordHash) {
233
		$this->passwordHash = $passwordHash;
234
	}
235
236
	public function setRecoreryHash($recoreryHash) {
237
		$this->recoreryHash = $recoreryHash;
238
	}
239
240
	public function setLoginDate(Date $loginDate) {
241
		$this->loginDate = $loginDate;
242
	}
243
244
	public function setImage(Image $image) {
245
		$this->image = $image;
246
	}
247
248
	/**
249
	 * Tenta realizar login
250
	 * @return boolean
251
	 */
252
	public function login() {
253
		$filters = [
254
			'is_enabled = ?' => true,
255
			'access_level > ?' => 0,
256
			'email = ?' => $this->email,
257
			'password_hash = ?' => $this->passwordHash
258
		];
259
		$uDAO = new UserDAO();
260
		$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...
261
		$this->setCurrentUser($user);
262
263
		if ($user->getId() > 0 && !$this->isLocked()) {
264
			$this->isLogged = true;
265
			$uDAO->clearRecoveryHash($user);
266
			$uDAO->updateLoginDate($user);
267
			$this->loginFailCount = 0;
268
		} else {
269
			$this->incrementLoginFail();
270
		}
271
272
		return $this->isLogged;
273
	}
274
275
	/** Realiza logout */
276
	public function logout() {
277
		unset($_SESSION['user']);
278
	}
279
280
	private function incrementLoginFail() {
281
		$this->loginFailCount++;
282
		if ($this->loginFailCount >= static::LOCK_TRIES && !$this->isLocked()) {
283
			$this->loginLockDate = new Date();
284
			$this->loginFailCount = 0;
285
		}
286
	}
287
288
	/** @return boolean retorna TRUE se está bloqueado por tentativas de login */
289
	public function isLocked() {
290
		$diff = Date::diffSeconds($this->getLoginUnlockDate(), new Date);
291
		return (boolean) ($diff <= 0 );
292
	}
293
294
	/** @return int total de tentativas restantes até ser bloqueado */
295
	public function getLoginTriesLeft() {
296
		return (static::LOCK_TRIES - $this->loginFailCount);
297
	}
298
299
	/** Objeto > Sessão */
300
	private function setCurrentUser(User $user) {
301
		$_SESSION['user'] = $this;
302
		$this->id = $user->getId();
303
		$this->accessLevel = $user->getAccessLevel();
304
		$this->name = $user->getName();
305
		$this->loginDate = $user->getLoginDate();
306
		$this->image = $user->getImage();
307
	}
308
309
	/** Objeto < Sessão */
310
	public static function getCurrentUser() {
311
		/* @var $user User */
312
		$user = (isset($_SESSION['user'])) ? $_SESSION['user'] : new User();
313
		static::preventDeletedAndLogged($user);
0 ignored issues
show
Bug introduced by
Since preventDeletedAndLogged() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of preventDeletedAndLogged() to at least protected.

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

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

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 {
      private static function getTemperature() {
        return "-182 °C";
    }
}

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

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

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
314
		return $user;
315
	}
316
317
	/**
318
	 * Evita que um usuário seja removido e continue logado
319
	 * @param User $user
320
	 */
321
	private static function preventDeletedAndLogged(User $user) {
322
		if ($user->isLogged) {
323
			$dao = new UserDAO();
324
			if (!$dao->objExists($user)) {
325
				$user->logout();
326
				new AlertError('Sua sessão foi finalizada, tente realizar o login novamente.');
327
				Application::app()->refresh();
328
			}
329
		}
330
	}
331
332
	/** Obriga o usuário a se logar */
333
	public function requireLogin() {
334
		if (!$this->isLogged) {
335
			Url::instance()->redirect('login');
336
		}
337
	}
338
339
	/** Obriga o usuário a logar como ADMIN */
340
	public function requireAdmin() {
341
		$this->requireLogin();
342
		if ($this->getAccessLevel() != static::ACCESS_ADMIN) {
343
			Application::app()->errorPage(403);
344
		}
345
	}
346
347
	/**
348
	 * Envia link de recuperacao de senha via Email
349
	 * @return string | null
350
	 */
351
	public function sendRecoveryHash() {
352
		$filters = ['is_enabled = ?' => true, 'access_level > ?' => 0, 'email = ?' => $this->email];
353
		$uDAO = new UserDAO();
354
		$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...
355
356
		if ($user->getId() > 0) {
357
			$uDAO->updateRecoveryHash($user);
358
			$content = new Block('email/content/recovery-password', ['user' => $user]);
359
360
			$mail = new Email();
361
			$mail->setFrom(EMAIL_FROM, Application::app()->getName());
362
			$mail->setSubject('Recuperação de Senha');
363
			$mail->addAddress($user->getEmail(), $user->getName());
364
			$mail->setContent($content);
365
			return $mail->send();
366
		} else {
367
			return 'Este E-mail não está cadastrado no sistema.';
368
		}
369
	}
370
371
	/** Define os atributos que são salvos na SESSAO */
372
	public function __sleep() {
373
		return ['id', 'isEnabled', 'isLogged', 'accessLevel', 'name', 'email', 'image', 'loginDate', 'groupId', 'loginFailCount', 'loginLockDate'];
374
	}
375
376
	/**
377
	 * Adiciona maior segura na senha/ utilizar esta função ao inves de um simples md5
378
	 * @param string $password
379
	 */
380
	public static function encryptPassword($password) {
381
		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...
382
	}
383
384
	/**
385
	 * Retorna uma senha aleatoria
386
	 * A senha tem sempre pelo menos: 1 caracter especial e 2 numeros;
387
	 * @param int $length
388
	 * @return string
389
	 */
390
	public static function generatePassword($length = 6) {
391
		$letters = str_shuffle('abcdefghijkmnopqrstwxyzABCDEFGHJKLMNPQRSTWXY');
392
		$numbers = str_shuffle('23456789');
393
		$specials = str_shuffle('@#&');
394
395
		$password = substr($letters, 0, $length - 3)
396
				. substr($numbers, 0, 2)
397
				. substr($specials, 0, 1);
398
399
		return str_shuffle($password);
400
	}
401
402
}
403