User::isPasswordCorrectAndActivitedAccount()   B
last analyzed

Complexity

Conditions 11
Paths 20

Size

Total Lines 71
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 38
nc 20
nop 1
dl 0
loc 71
c 0
b 0
f 0
cc 11
rs 7.3166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 Copyright (C) 2018-2020 KANOUN Salim
4
 This program is free software; you can redistribute it and/or modify
5
 it under the terms of the Affero GNU General Public v.3 License as published by
6
 the Free Software Foundation;
7
 This program is distributed in the hope that it will be useful,
8
 but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
 Affero GNU General Public Public for more details.
11
 You should have received a copy of the Affero GNU General Public Public along
12
 with this program; if not, write to the Free Software Foundation, Inc.,
13
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
14
 */
15
16
/**
17
 * Check User's permissions status : Handles Connexion credential and ressource permissions check for scripts / form / apis...
18
 */
19
20
class User {
21
22
	private $linkpdo;
23
    
24
	//user details
25
	public $username;
26
	public $lastName;
27
	public $firstName;
28
	public $userEmail;
29
	public $userPhone;
30
	public $creationDateUser;
31
	public $lastConnexionDate;
32
    
33
	//Public variables to retrieve status account for the index page
34
	public $isAdministrator;
35
	public $userStatus;
36
	public $passwordDateValide;
37
	public $loginAttempt;
38
	public $isExistingUser;
39
	public $passwordCorrect;
40
    
41
	public $creationDatePassword;
42
	public $tempPassword;
43
	public $password;
44
    
45
	public $previousPassword1;
46
	public $previousPassword2;
47
	public $mainCenter;
48
	public $userJob;
49
    
50
	public $orthancAddress;
51
	public $orthancLogin;
52
	public $orthancPassword;
53
    
54
	//Constants roles available
55
	const ADMINISTRATOR="Administrator";
56
	const SUPERVISOR="Supervisor";
57
	const CONTROLLER="Controller";
58
	const MONITOR="Monitor";
59
	const INVESTIGATOR="Investigator";
60
	const REVIEWER="Reviewer";
61
    
62
	//Constants user status available
63
	const ACTIVATED="Activated";
64
	const DEACTIVATED="Deactivated";
65
	const BLOCKED="Blocked";
66
	const UNCONFIRMED="Unconfirmed";
67
    
68
    
69
70
	public function __construct(string $username, PDO $linkpdo) {
71
		$this->linkpdo=$linkpdo;
72
		//Get the username from DB to get the case sensitive username
73
		$connecter=$this->linkpdo->prepare('SELECT * FROM users WHERE username = :username');
74
		$connecter->execute(array("username" => $username));
75
		$queryResults=$connecter->fetch(PDO::FETCH_ASSOC);
76
		//If no match in database => User doesn't exist
77
		if (empty($queryResults)) {
78
			$this->isExistingUser=false;
79
		}
80
		else {
81
			$this->isExistingUser=true;
82
		}
83
        
84
		$this->username=$queryResults['username'];
85
		$this->lastName=$queryResults['last_name'];
86
		$this->firstName=$queryResults['first_name'];
87
		$this->userStatus=$queryResults['status'];
88
		$this->isAdministrator=$queryResults['is_administrator'];
89
		$this->userEmail=$queryResults['email'];
90
		$this->userPhone=$queryResults['phone'];
91
		$this->creationDateUser=$queryResults['creation_date'];
92
		$this->lastConnexionDate=$queryResults['connexion_date'];
93
		$this->creationDatePassword=$queryResults['creation_date_password'];
94
		$this->tempPassword=$queryResults['temp_password'];
95
		$this->password=$queryResults['password'];
96
		$this->previousPassword1=$queryResults['previous_password_1'];
97
		$this->previousPassword2=$queryResults['previous_password_2'];
98
		$this->loginAttempt=$queryResults['number_attempts'];
99
		$this->mainCenter=$queryResults['center'];
100
		$this->userJob=$queryResults['job'];
101
        
102
		$this->orthancAddress=$queryResults['orthanc_address'];
103
		$this->orthancLogin=$queryResults['orthanc_login'];
104
		$this->orthancPassword=$queryResults['orthanc_password'];
105
        
106
       
107
	}
108
    
109
	public static function getUserByEmail(String $email, PDO $linkpdo) {
110
        
111
		$connecter=$linkpdo->prepare('SELECT username FROM users WHERE email = :email');
112
		$connecter->execute(array("email" => $email));
113
		$username=$connecter->fetch(PDO::FETCH_COLUMN);
114
        
115
		return new User($username, $linkpdo);
116
        
117
	}
118
119
	/**
120
	 * Check connexion credential, number of tentatives and update status account if needed
121
	 * @param String $password
122
	 * @return boolean
123
	 */
124
	public function isPasswordCorrectAndActivitedAccount(string $password) {
125
        
126
		$date=new DateTime($this->creationDatePassword);
127
		$now=new DateTime();
128
		$delayDay=$date->diff($now)->format("%a");
129
        
130
		//If password delay over 90 => out dated password, need to be changed
131
		if (intVal($delayDay) <= 90) {
132
			$this->passwordDateValide=true;
133
		}
134
		else {
135
			$this->passwordDateValide=false;
136
		}
137
138
		//Check password correct
139
		if ($this->userStatus == User::UNCONFIRMED) {
140
			//Use the temp password for check
141
			$this->passwordCorrect=password_verify($password, $this->tempPassword);
142
		}else {
143
			//use the current password for check
144
			$this->passwordCorrect=password_verify($password, $this->password);
145
		}
146
        
147
		// If password OK, password date OK, and Account status active return OK for connexion
148
		if ($this->passwordCorrect && $this->passwordDateValide && $this->userStatus == User::ACTIVATED) {
149
			//Update the last connexion date in DB and number attempt account to zero
150
			$now=date("Y-m-d H:i:s");
151
			$reset_tentatives=$this->linkpdo->prepare('UPDATE users SET number_attempts = 0, connexion_date=:datenow WHERE username = :username');
152
			//Exécution
153
			$reset_tentatives->execute(array('username' => $this->username, 'datenow' =>$now));
154
			$this->loginAttempt=0;
155
            
156
			Session::logInfo('Connected Login : '.$this->username.' Admin '.(($this->isAdministrator) ? 'true' : 'false'));
157
            
158
			return true;
159
		}
160
		//Else, return false, add +1 to attempt account and block account if over 3
161
		else if (!$this->passwordCorrect) {
162
            
163
			Session::logInfo('Wrong Password : '.$this->username.' Admin '.(($this->isAdministrator) ? 'true' : 'false'));
164
            
165
			//Add +1 to attempt account
166
			$res=$this->linkpdo->prepare('UPDATE users SET number_attempts = number_attempts+1 WHERE username = :username');
167
			$res->execute(array('username' => $this->username));
168
			//Look at the new value
169
			$tentatives=$this->linkpdo->prepare('SELECT number_attempts FROM users WHERE username = :username');
170
			$tentatives->execute(array("username" => $this->username));
171
			$nb_tentatives=$tentatives->fetch(PDO::FETCH_ASSOC);
172
			$this->loginAttempt=$nb_tentatives['number_attempts'];
173
			//If over three block account
174
			if ($this->loginAttempt > 2) {
175
				$bloquer=$this->linkpdo->prepare('UPDATE users SET status = "Blocked" WHERE username = :username');
176
				$bloquer->execute(array('username' => $this->username));
177
				$this->userStatus="Blocked";
178
				//Log login event
179
				$log['message']="Blocked";
0 ignored issues
show
Comprehensibility Best Practice introduced by
$log was never initialized. Although not strictly required by PHP, it is generally a good practice to add $log = array(); before regardless.
Loading history...
180
				Tracker::logActivity($this->username, "User", null, null, "Account Blocked", $log);
181
				//Send email notification
182
				$this->sendBlockedEmail();
183
                
184
				return false;
185
186
			}
187
		    
188
		}else {
189
			//If blocked status, re-send email notification
190
			if ($this->userStatus == "Blocked") {
191
				$this->sendBlockedEmail();
192
			}
193
	        
194
			return false;
195
		        
196
		}
197
198
	}
199
    
200
	/**
201
	 * Return all studies available for the users (no matter it's role)
202
	 * @return array
203
	 */
204
	public function getAllStudiesWithRole() {
205
    	
206
		$connecter=$this->linkpdo->prepare('SELECT DISTINCT roles.study FROM roles, studies WHERE roles.username =:username
207
                                    AND studies.name=roles.study AND studies.active=1 ORDER BY roles.study');
208
		$connecter->execute(array(
209
				"username" => $this->username,
210
		));
211
    	
212
		$AvailableStudies=$connecter->fetchall(PDO::FETCH_COLUMN);
213
    	
214
		return $AvailableStudies;
215
	}
216
    
217
	/**
218
	 * Return if the requested Role in the requested study is allowed for the current user
219
	 * @param string $study
220
	 * @param string $role
221
	 * @param $job
222
	 * @return boolean
223
	 */
224
	public function isRoleAllowed(string $study, string $role) {
225
        
226
		$query='SELECT * FROM roles, studies, users WHERE roles.username = :username 
227
                                                            AND roles.name=:role 
228
                                                            AND studies.name=roles.study 
229
                                                            AND studies.active=1 
230
                                                            AND studies.name=:study 
231
                                                            AND users.username=roles.username 
232
                                                            AND users.status="Activated" ';
233
		$executeArray=array(
234
				"username" => $this->username,
235
				"role"=>$role,
236
				"study"=>$study
237
		);
238
    	
239
    	
240
		$connecter=$this->linkpdo->prepare($query);
241
		$connecter->execute($executeArray);
242
        
243
		$rownb=$connecter->rowCount();
244
       
245
		if ($rownb == 0) {
246
			return false;
247
		}
248
		else {
249
			return true;
250
		}
251
252
	}
253
254
	/**
255
	 * Return main and affiliated centers for the current user
256
	 * @return Array  : main centers in array
257
	 */
258
	public function getInvestigatorsCenters() {
259
		$centers=$this->getAffiliatedCenters();
260
		$centers[]=$this->mainCenter;
261
		return $centers;
262
	}
263
    
264
	/**
265
	 * Return main center object of this user
266
	 * @return Center
267
	 */
268
	public function getMainCenter() {
269
		return new Center($this->linkpdo, $this->mainCenter);
270
	}
271
    
272
	/**
273
	 * Return affiliated center of the user
274
	 */
275
	public function getAffiliatedCenters() {
276
		$result_center=$this->linkpdo->prepare('SELECT center FROM affiliated_centers WHERE affiliated_centers.username = :username ORDER BY center');
277
		$result_center->execute(array('username' => $this->username));
278
    	
279
		$affiliatedCenterBdd=$result_center->fetchAll(PDO::FETCH_COLUMN);
280
    	
281
		return $affiliatedCenterBdd;
282
	}
283
    
284
	/**
285
	 * Return affiliated center of the user as Object
286
	 */
287
	public function getAffiliatedCentersAsObjects() {
288
		$centersCode=$this->getAffiliatedCenters();
289
		$centersObjects=[];
290
		foreach ($centersCode as $code) {
291
			$centersObjects[]=new Center($this->linkpdo, $code);
292
		}
293
		return $centersObjects;
294
	}
295
    
296
	/**
297
	 * Return all available role in the called study for an user
298
	 * @param $study
299
	 * @return array of available roles
300
	 */
301
	public function getRolesInStudy(string $study) {
302
		$role=$this->linkpdo->prepare('SELECT name FROM roles WHERE roles.username = :username AND roles.study = :study ORDER BY roles.name');
303
		$role->execute(array('username' => $this->username, 'study' => $study));
304
		$data_role=$role->fetchall(PDO::FETCH_COLUMN);
305
		return $data_role;
306
	}
307
    
308
	/**
309
	 * Retrun role map of users  : each study as key with an array of available roles
310
	 * @return array[]
311
	 */
312
	public function getRolesMap() {
313
		$studies=$this->getAllStudiesWithRole();
314
		$map=[];
315
		foreach ($studies as $study) {
316
			$map[$study]=$this->getRolesInStudy($study);
317
		}
318
		return $map;
319
	}
320
    
321
	/**
322
	 * Check permission for a patient according to role
323
	 * @param number $patientNumber
324
	 * @param string $role
325
	 * @return boolean
326
	 */
327
	public function isPatientAllowed($patientCode, string $role) {
328
        
329
		if (empty($role)) return false;
330
331
		$patientObject=new Patient($patientCode, $this->linkpdo);
332
        
333
		//If Investigator check the current patient is from one of the centers of the user
334
		if ($role == $this::INVESTIGATOR) {
335
			$userCenters=$this->getInvestigatorsCenters();
336
			if (in_array($patientObject->patientCenter, $userCenters) && $this->isRoleAllowed($patientObject->patientStudy, $role)) {
337
				return true;
338
			}
339
        
340
		//For other patient's permission is defined by patient's study availabilty
341
		}else {
342
			if ($this->isRoleAllowed($patientObject->patientStudy, $role)) {
343
				return true;
344
			}
345
		}
346
        
347
		return false;
348
        
349
	}
350
    
351
	/**
352
	 * Check if the Visit and Role is available for the current user
353
	 * Checks for 
354
	 * - All : Check Role availability and Only access to non deleted visits
355
	 * - Investigator : Check Patient is allowed for this user
356
	 * - Reviewer : Check Review is availabl
357
	 * @param $id_visit
358
	 * @param string $role
359
	 * @return boolean
360
	 */
361
	public function isVisitAllowed($id_visit, string $role) {
362
        
363
		if (empty($role)) return false;
364
        
365
		$visitData=new Visit($id_visit, $this->linkpdo);
366
        
367
		//Check that called Role exists for users and visit is not deleted
368
		if ($this->isRoleAllowed($visitData->study, $role) && !$visitData->deleted) {
369
			if ($role == $this::INVESTIGATOR) { 
370
				if ($this->isPatientAllowed($visitData->patientCode, $role)) return true;
371
			}else if ($role == $this::REVIEWER) {
372
				//For reviewer the visit access is allowed if one of the created visits is still awaiting review
373
				//This is made to allow access to references scans
374
				$patientObject=$visitData->getPatient();
375
				$isAwaitingReview=$patientObject->getPatientStudy()->isHavingAwaitingReviewImagingVisit();
376
				return $isAwaitingReview;
377
			}else {
378
				//Controller, Supervisor, Admin, Monitor simply accept when role is available in patient's study (no specific rules)
379
				return true;
380
			}
381
            
382
		}
383
        
384
		return false;
385
        
386
	}
387
    
388
	/**
389
	 * Send warning emails to notify that the account is blocked (to administrators + user)
390
	 */
391
	private function sendBlockedEmail() {
392
		//Get all studies assosciated with account
393
		$linkedStudies=$this->getAllStudiesWithRole();
394
		//Send Email notification
395
		$sendEmail=new Send_Email($this->linkpdo);
396
		$sendEmail->addAminEmails()->addEmail($this->userEmail);
397
		$sendEmail->sendBlockedAccountNotification($this->username, $linkedStudies);
398
399
	}
400
    
401
	/**
402
	 * Add a role to the user
403
	 * @param string $role
404
	 * @param string $study
405
	 */
406
	public function addRole(string $role, string $study) {
407
		$addRole=$this->linkpdo->prepare('INSERT INTO roles(name, username, study)
408
                                        VALUES(:role, :username, :study)');
409
		//Exécution
410
		$addRole->execute(array('role' => $role,
411
								'username' => $this->username,
412
								'study' => $study));
413
        
414
	}
415
    
416
	/**
417
	 * Add Affiliated center to the user
418
	 * @param $centerCode
419
	 */
420
	public function addAffiliatedCenter($centerCode) {
421
		$addCenter=$this->linkpdo->prepare('INSERT INTO affiliated_centers(username, center)
422
                                                  VALUES(:username, :centerCode)');
423
		$addCenter->execute(array('username' => $this->username, 'centerCode' => $centerCode));
424
        
425
	}
426
    
427
	/**
428
	 * Remove an affiliated center to the user
429
	 * @param $centerCode
430
	 */
431
	public function removeAffiliatedCenter($centerCode) {
432
		$res=$this->linkpdo->prepare('DELETE FROM affiliated_centers WHERE username = :username
433
                  AND center = :centerCode');
434
		$res->execute(array('username' => $this->username,
435
				'centerCode' => $centerCode));
436
	}
437
    
438
	/**
439
	 * Delete a role in a study to the user
440
	 * @param string $study
441
	 * @param string $role
442
	 */
443
	public function deleteRole(string $study, string $role) {
444
		$res=$this->linkpdo->prepare('DELETE FROM roles WHERE username = :username
445
                  AND study = :study
446
                  AND name = :role');
447
		$res->execute(array('username' => $this->username,
448
				'study' => $study,
449
				'role' => $role));
450
	}
451
    
452
    
453
	/**
454
	 * Update password and account status of an user
455
	 * @param $password
456
	 * @param $status
457
	 */
458
	public function updateUserPassword(string $password, string $status) {
459
		//Update the database with new password and switch old passwords
460
		$req=$this->linkpdo->prepare('UPDATE users
461
                                    SET previous_password_2=users.previous_password_1,
462
                                        previous_password_1=users.password,
463
                                        password = :mdp,
464
                                        number_attempts = 0,
465
                                        creation_date_password = :datePassword,
466
                                        status = :status
467
                                    WHERE username = :username');
468
        
469
		$req->execute(array('username' => $this->username,
470
			'mdp' => password_hash($password, PASSWORD_DEFAULT),
471
			'status'=>$status,
472
			'datePassword' => date('Y-m-d')));
473
        
474
	}
475
    
476
	/**
477
	 * Generate a temp password and set account to unconfirmed
478
	 * @param $password
479
	 */
480
	public function setUnconfirmedAccount(string $password) {
481
        
482
		$req=$this->linkpdo->prepare('UPDATE users
483
                                    SET temp_password = :mdp,
484
                                        number_attempts = 0,
485
                                        creation_date_password = :datePassword,
486
                                        status = :status
487
                                    WHERE username = :username');
488
        
489
		$req->execute(array('username' => $this->username,
490
			'mdp' => password_hash($password, PASSWORD_DEFAULT),
491
			'status'=>User::UNCONFIRMED,
492
			'datePassword' => date('Y-m-d')));
493
        
494
	}
495
    
496
	/**
497
	 * Update users data
498
	 * @param $last_name
499
	 * @param $first_name
500
	 * @param $email
501
	 * @param $phone
502
	 * @param $job
503
	 * @param $status
504
	 * @param bool $administrator
505
	 * @param $mainCenterCode
506
	 * @param $orthancAddress
507
	 * @param $orthancLogin
508
	 * @param s$orthancPassword
509
	 */
510
	public function updateUser($last_name, $first_name, $email, $phone, $job, $status, bool $administrator, $mainCenterCode, $orthancAddress, $orthancLogin, $orthancPassword) {
511
        
512
		$req=$this->linkpdo->prepare('UPDATE users SET
513
    								last_name = :nom,
514
    								first_name = :prenom,
515
    								email = :email,
516
    								creation_date_password = :date_Utilisateur,
517
    								phone = :telephone,
518
    								job = :job,
519
    								is_administrator= :admin,
520
    								status = :statut,
521
									center= :numero_centre,
522
                                    orthanc_address= :orthancAddress, 
523
                                    orthanc_login= :orthancLogin, 
524
                                    orthanc_password= :orthancPassword
525
						        WHERE users.username = :username');
526
        
527
		$req->execute(array(
528
			'username'=> $this->username,
529
			'nom' => $last_name,
530
			'prenom' => $first_name,
531
			'email' => $email,
532
			'date_Utilisateur' => date('Y-m-d'),
533
			'telephone' => empty($phone) ? null : $phone,
534
			'job' => $job,
535
			'statut' => $status,
536
			'admin'=>intval($administrator),
537
			'numero_centre' => $mainCenterCode,
538
			'orthancAddress'=>empty($orthancAddress) ? null : $orthancAddress,
539
			'orthancLogin'=>empty($orthancLogin) ? null : $orthancLogin,
540
			'orthancPassword'=>empty($orthancPassword) ? null : $orthancPassword
541
		));
542
	}
543
    
544
	/**
545
	 * Create a new user
546
	 * @param $username
547
	 * @param $last_name
548
	 * @param $first_name
549
	 * @param $email
550
	 * @param $phone
551
	 * @param $mdp
552
	 * @param $job
553
	 * @param $mainCenter
554
	 * @param $administrator
555
	 * @param $orthancAddress
556
	 * @param $orthancLogin
557
	 * @param $orthancPassword
558
	 * @param PDO $linkpdo
559
	 * @throws Exception
560
	 */
561
	public static function createUser($username, $last_name, $first_name, $email, $phone,
562
				$mdp, $job, $mainCenter, $administrator, $orthancAddress, $orthancLogin, $orthancPassword, PDO $linkpdo) {
563
            
564
			//Check that new users is not already existing
565
			$accountQuery=$linkpdo->prepare('SELECT * FROM users WHERE (users.username=:username OR users.email=:email)');
566
			$accountQuery->execute(array(
567
				'username' => $username,
568
				'email' => $email
569
			));
570
			$existingAccount=$accountQuery->fetchAll();
571
            
572
			if (!empty($existingAccount)) {
573
			   throw new Exception("Account already existing");
574
			}
575
            
576
            
577
			// If new user, write it in database        
578
			$req=$linkpdo->prepare('INSERT INTO users(username, last_name, first_name, email, creation_date_password, phone, password, temp_password, job, center, creation_date, is_administrator, orthanc_address, orthanc_login, orthanc_password)
579
                  VALUES(:username, :nom, :prenom, :email, :creation_date_password, :telephone, :password, :tempPassword, :job, :numero_centre, :date_creation_account, :admin, :orthancAddress, :orthancLogin, :orthancPassword)');
580
            
581
			$req->execute(array(
582
				'username' => $username,
583
				'nom' => $last_name,
584
				'prenom' => $first_name,
585
				'email' => $email,
586
				'creation_date_password' => date('Y-m-d'),
587
				'telephone' => empty($phone) ? $phone : null,
588
				'password'=> password_hash($mdp, PASSWORD_DEFAULT),
589
				'tempPassword' => password_hash($mdp, PASSWORD_DEFAULT),
590
				'job' => $job,
591
				'numero_centre' => $mainCenter,
592
				'date_creation_account' => date("Y-m-d H:i:s"),
593
				'admin' => intval($administrator),
594
				'orthancAddress'=>empty($orthancAddress) ? null : $orthancAddress,
595
				'orthancLogin'=>empty($orthancLogin) ? null : $orthancLogin,
596
				'orthancPassword'=>empty($orthancPassword) ? null : $orthancPassword
597
			));
598
	}
599
    
600
}
601