Completed
Push — master ( dd5c07...b4647f )
by Wanderson
02:12
created

User::__sleep()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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
	public function getImage() {
155
		return $this->image;
156
	}
157
158
	public function getLoginDate() {
159
		return $this->loginDate;
160
	}
161
162
	public function getLoginLockDate() {
163
		return $this->loginLockDate;
164
	}
165
166
	/** @return Date retorna data que poderá logar novamente sem bloqueio */
167
	public function getLoginUnlockDate() {
168
		$date = clone $this->getLoginLockDate();
169
		$date->sumTime(static::LOCK_TIME_MINUTES, 'minutes');
170
		return $date;
171
	}
172
173
	public function getLockedMsg() {
174
		return 'Você foi bloqueado por realizar ' . static::LOCK_TRIES . ' tentativas de login.<br /> Você poderá tentar novamente ' . $this->getLoginUnlockDate()->toHumanFormat() . '.';
175
	}
176
177
	public function setId($id) {
178
		$this->id = (int) $id;
179
	}
180
181
	public function setEnabled($enabled) {
182
		$this->isEnabled = (boolean) $enabled;
183
	}
184
185
	public function setAccessLevel($accessLevel) {
186
		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...
187
			$this->accessLevel = (int) $accessLevel;
188
		}
189
	}
190
191
	public function setGroup(Group $group) {
192
		$this->group = $group;
193
	}
194
195
	public function setGroupId($groupId) {
196
		$this->groupId = (int) $groupId;
197
	}
198
199
	public function setPerson(Person $person) {
200
		$this->person = $person;
201
	}
202
203
	public function setName($name) {
204
		$this->name = $name;
205
	}
206
207
	public function setEmail($email) {
208
		$this->email = strClear($email);
209
	}
210
211
	public function setConfirmEmail($confirmEmail) {
212
		$this->confirmEmail = strClear($confirmEmail);
213
	}
214
215
	public function setPassword($password) {
216
		$this->password = $password;
217
		$this->passwordHash = static::encryptPassword($password);
218
	}
219
220
	public function setConfirmPassword($confirmPassword) {
221
		$this->confirmPassword = $confirmPassword;
222
	}
223
224
	public function setPasswordHash($passwordHash) {
225
		$this->passwordHash = $passwordHash;
226
	}
227
228
	public function setRecoreryHash($recoreryHash) {
229
		$this->recoreryHash = $recoreryHash;
230
	}
231
232
	public function setLoginDate(Date $loginDate) {
233
		$this->loginDate = $loginDate;
234
	}
235
236
	public function setImage(Image $image) {
237
		$this->image = $image;
238
	}
239
240
	/**
241
	 * Tenta realizar login
242
	 * @return boolean
243
	 */
244
	public function login() {
245
		$filters = [
246
			'is_enabled = ?' => true,
247
			'access_level > ?' => 0,
248
			'email = ?' => $this->email,
249
			'password_hash = ?' => $this->passwordHash
250
		];
251
		$uDAO = new UserDAO();
252
		$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...
253
		$this->setCurrentUser($user);
254
255
		if ($user->getId() > 0 && !$this->isLocked()) {
256
			$this->isLogged = true;
257
			$uDAO->clearRecoveryHash($user);
258
			$uDAO->updateLoginDate($user);
259
			$this->loginFailCount = 0;
260
		} else {
261
			$this->incrementLoginFail();
262
		}
263
264
		return $this->isLogged;
265
	}
266
267
	/** Realiza logout */
268
	public function logout() {
269
		unset($_SESSION['user']);
270
	}
271
272
	private function incrementLoginFail() {
273
		$this->loginFailCount++;
274
		if ($this->loginFailCount >= static::LOCK_TRIES && !$this->isLocked()) {
275
			$this->loginLockDate = new Date();
276
			$this->loginFailCount = 0;
277
		}
278
	}
279
280
	/** @return boolean retorna TRUE se está bloqueado por tentativas de login */
281
	public function isLocked() {
282
		$diff = Date::diffSeconds($this->getLoginUnlockDate(), new Date);
283
		return (boolean) ($diff <= 0 );
284
	}
285
286
	/** @return int total de tentativas restantes até ser bloqueado */
287
	public function getLoginTriesLeft() {
288
		return (static::LOCK_TRIES - $this->loginFailCount);
289
	}
290
291
	/** Objeto > Sessão */
292
	private function setCurrentUser(User $user) {
293
		$_SESSION['user'] = $this;
294
		$this->id = $user->getId();
295
		$this->accessLevel = $user->getAccessLevel();
296
		$this->name = $user->getName();
297
		$this->loginDate = $user->getLoginDate();
298
		$this->image = $user->getImage();
299
	}
300
301
	/** Objeto < Sessão */
302
	public static function getCurrentUser() {
303
		return (isset($_SESSION['user'])) ? $_SESSION['user'] : new User();
304
	}
305
306
	/** Obriga o usuário a se logar */
307
	public function requireLogin() {
308
		if (!$this->isLogged) {
309
			Url::instance()->redirect('login');
310
		}
311
	}
312
313
	/** Obriga o usuário a logar como ADMIN */
314
	public function requireAdmin() {
315
		$this->requireLogin();
316
		if ($this->getAccessLevel() != static::ACCESS_ADMIN) {
317
			Application::app()->errorPage(403);
318
		}
319
	}
320
321
	/**
322
	 * Envia link de recuperacao de senha via Email
323
	 * @return string | null
324
	 */
325
	public function sendRecoveryHash() {
326
		$filters = ['is_enabled = ?' => true, 'access_level > ?' => 0, 'email = ?' => $this->email];
327
		$uDAO = new UserDAO();
328
		$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...
329
330
		if ($user->getId() > 0) {
331
			$uDAO->updateRecoveryHash($user);
332
			$content = new Block('email/content/recovery-password', ['user' => $user]);
333
334
			$mail = new Email();
335
			$mail->setFrom(EMAIL_FROM, Application::app()->getName());
336
			$mail->setSubject('Recuperação de Senha');
337
			$mail->addAddress($user->getEmail(), $user->getName());
338
			$mail->setContent($content);
339
			return $mail->send();
340
		} else {
341
			return 'Este E-mail não está cadastrado no sistema.';
342
		}
343
	}
344
345
	/** Define os atributos que são salvos na SESSAO */
346
	public function __sleep() {
347
		return ['id', 'isEnabled', 'isLogged', 'accessLevel', 'name', 'email', 'image', 'loginDate', 'groupId', 'loginFailCount', 'loginLockDate'];
348
	}
349
350
	/**
351
	 * Adiciona maior segura na senha/ utilizar esta função ao inves de um simples md5
352
	 * @param string $password
353
	 */
354
	public static function encryptPassword($password) {
355
		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...
356
	}
357
358
	/**
359
	 * Retorna uma senha aleatoria
360
	 * A senha tem sempre pelo menos: 1 caracter especial e 2 numeros;
361
	 * @param int $length
362
	 * @return string
363
	 */
364
	public static function generatePassword($length = 6) {
365
		$letters = str_shuffle('abcdefghijkmnopqrstwxyzABCDEFGHJKLMNPQRSTWXY');
366
		$numbers = str_shuffle('23456789');
367
		$specials = str_shuffle('@#&');
368
369
		$password = substr($letters, 0, $length - 3)
370
				. substr($numbers, 0, 2)
371
				. substr($specials, 0, 1);
372
373
		return str_shuffle($password);
374
	}
375
376
}
377