File modules/cms/model/User.class.php
Last commit: Mon Jan 6 22:48:11 2025 +0100 Jan Dankert New: Now it is possible to remove and create new login/api token for a user; new API authorization with the authorization scheme "api".
1 <?php 2 3 namespace cms\model; 4 5 // OpenRat Content Management System 6 // Copyright (C) 2002-2012 Jan Dankert, cms@jandankert.de 7 // 8 // This program is free software; you can redistribute it and/or 9 // modify it under the terms of the GNU General Public License 10 // as published by the Free Software Foundation; either version 2 11 // of the License, or (at your option) any later version. 12 // 13 // This program is distributed in the hope that it will be useful, 14 // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 // GNU General Public License for more details. 17 // 18 // You should have received a copy of the GNU General Public License 19 // along with this program; if not, write to the Free Software 20 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 21 use cms\base\Configuration; 22 use cms\base\DB; 23 use cms\base\Startup; 24 use cms\base\Language; 25 use language\Messages; 26 use security\Password; 27 use util\exception\ObjectNotFoundException; 28 use util\Request; 29 30 31 /** 32 * Darstellen eines Benutzers 33 * 34 * @author Jan Dankert 35 */ 36 class User extends ModelBase 37 { 38 /** 39 * Local user database 40 */ 41 const AUTH_TYPE_INTERNAL = 1; 42 43 /** 44 * OpenId Connect 45 */ 46 const AUTH_TYPE_OIDC = 2; 47 48 const STYLE_SCHEME_AUTO = 1; 49 const STYLE_SCHEME_LIGHT = 2; 50 const STYLE_SCHEME_DARK = 3; 51 52 public $userid = 0; 53 54 /** 55 * Username. 56 * This name should not be displayed to other users. 57 * 58 * @var string 59 */ 60 public $name = ''; 61 62 /** 63 * natural name of this user. 64 * 65 * @var string 66 */ 67 public $fullname = ''; 68 public $tel; 69 public $mail; 70 public $desc; 71 public $style; 72 public $styleScheme; 73 public $isAdmin; 74 public $loginDate = 0; 75 76 /** 77 * User prefered language. 78 * 79 * As ISO string. 80 * 81 * @var string 82 */ 83 public $language; 84 85 public $timezone; 86 public $passwordExpires; 87 public $passwordAlgo; 88 89 public $lastLogin; 90 public $otpSecret; 91 public $hotp ; 92 public $hotpCount; 93 public $totp ; 94 95 public $issuer = null; 96 public $type = User::AUTH_TYPE_INTERNAL; 97 98 public $passwordFailedCount = 0; 99 public $passwordLockedUntil = 0; 100 101 private $groups = null; 102 103 104 // Konstruktor 105 public function __construct( $userid='' ) 106 { 107 if ( is_numeric($userid) ) 108 $this->userid = $userid; 109 } 110 111 112 /** 113 * get all users. 114 * 115 * @return array 116 */ 117 public static function listAll() 118 { 119 $sql = Db::sql( <<<SQL 120 SELECT id,name 121 FROM {{user}} 122 ORDER BY name 123 SQL 124 ); 125 return $sql->getAssoc(); 126 } 127 128 129 /** 130 * Get all users. 131 * 132 * @return array with user objects 133 */ 134 public static function getAllUsers() 135 { 136 $list = array(); 137 $sql = Db::sql( <<<SQL 138 SELECT * 139 FROM {{user}} 140 ORDER BY name 141 SQL 142 ); 143 foreach($sql->getAll() as $row ) 144 { 145 $user = new User(); 146 $user->setDatabaseRow( $row ); 147 148 $list[] = $user; 149 } 150 151 return $list; 152 } 153 154 155 /** 156 * Benutzer als aktiven Benutzer in die Session schreiben. 157 */ 158 public function setCurrent() 159 { 160 $this->loginDate = time(); 161 162 Request::setUser( $this ); 163 } 164 165 166 167 /** 168 * Benutzer als aktiven Benutzer in die Session schreiben. 169 */ 170 public function updateLoginTimestamp() 171 { 172 $stmt = Db::sql( <<<SQL 173 UPDATE {{user}} 174 SET last_login={time} 175 WHERE id={userid} 176 SQL 177 ); 178 $stmt->setInt( 'time' ,time() ); 179 $stmt->setInt( 'userid',$this->userid ); 180 181 // Datenbankabfrage ausfuehren 182 $stmt->execute(); 183 } 184 185 186 /** 187 * Creates an SQL-WHERE-Clause. 188 * 189 * A "WHERE IN()" clause is difficult, because groups may be part of another groups. 190 * 191 * @return String SQL-WHERE-Clause 192 */ 193 public function getGroupClause() 194 { 195 $groupIds = $this->getEffectiveGroups(); 196 197 if ( count($groupIds) > 0 ) 198 $groupclause = ' groupid='.implode(' OR groupid=',$groupIds ); 199 else 200 $groupclause = ' 1=0 '; 201 202 return $groupclause; 203 } 204 205 206 /** 207 * Lesen aller Projekte, fuer die der Benutzer berechtigt ist. 208 * 209 * @return array [Projekt-Id] = Projekt-Name 210 */ 211 public function getReadableProjects() 212 { 213 $db = Db::get(); 214 215 if ( $this->isAdmin ) 216 { 217 // Administratoren haben Rechte auf alle Projekte. 218 return Project::getAllProjects(); 219 } 220 else 221 { 222 $groupClause = $this->getGroupClause(); 223 $sql = $db->sql(<<<SQL 224 SELECT DISTINCT {{project}}.id,{{project}}.name 225 FROM {{object}} 226 LEFT JOIN {{acl}} ON {{object}}.id = {{acl}}.objectid 227 LEFT JOIN {{project}} ON {{project}}.id = {{object}}.projectid 228 WHERE {{object}}.parentid IS NULL AND 229 {{acl}}.id IS NOT NULL AND 230 ( 231 {{acl}}.type = {usertype} AND {{acl}}.userid={userid} 232 OR {{acl}}.type = {grouptype} AND $groupClause 233 OR ({{acl}}.type = {alltype} 234 OR ({{acl}}.type = {guesttype} 235 ) 236 ORDER BY {{project}}.name 237 SQL 238 ); 239 $sql->setInt( 'userid',$this->userid ); 240 $sql->setInt( 'usertype' ,Permission::TYPE_USER ); 241 $sql->setInt( 'grouptype',Permission::TYPE_GROUP ); 242 $sql->setInt( 'alltype' ,Permission::TYPE_AUTH ); 243 $sql->setInt( 'guesttype',Permission::TYPE_GUEST ); 244 245 return $sql->getAssoc(); 246 } 247 248 } 249 250 251 252 /** 253 * Ermittelt alls Projekte, fuer die der Benutzer berechtigt ist. 254 * @return array [0..n] = Projekt-Id 255 */ 256 function getReadableProjectIds() 257 { 258 return array_keys( $this->getReadableProjects() ); 259 } 260 261 262 /** 263 * Gets all login tokens for this user. 264 * 265 * @return array 266 */ 267 public function getLoginTokens() { 268 269 $stmt = Db::sql( <<<SQL 270 SELECT id,selector,expires,create_date,platform,name 271 FROM {{auth}} 272 WHERE userid={userid} 273 SQL 274 ); 275 276 $stmt->setInt('userid',$this->getId() ); 277 278 return $stmt->getAll(); 279 } 280 281 /** 282 * Creates a completly new login token. 283 * 284 * @return string new login token 285 */ 286 function createNewLoginToken() 287 { 288 $selector = Password::randomHexString(24); 289 $token = Password::randomHexString(24); 290 291 $tokenHash = Password::hash($token,Password::ALGO_SHA1); 292 293 $stmt = Db::sql( 'SELECT max(id) FROM {{auth}}'); 294 $count = $stmt->getOne(); 295 296 $stmt = Db::sql( <<<SQL 297 INSERT INTO {{auth}} (id,userid,selector,token,token_algo,expires,create_date,platform,name) 298 VALUES( {id},{userid},{selector},{token},{token_algo},{expires},{create_date},{platform},{name} ) 299 SQL 300 ); 301 $expirationPeriodDays = Configuration::Conf()->subset('user')->subset('security')->get('token_expires_after_days',730); 302 303 $stmt->setInt( 'id' ,++$count ); 304 $stmt->setInt( 'userid' ,$this->userid ); 305 306 $stmt->setString( 'selector' ,$selector ); 307 $stmt->setString( 'token' ,$tokenHash ); 308 $stmt->setInt ( 'token_algo' ,Password::ALGO_SHA1 ); 309 310 $stmt->setInt( 'expires' ,time() + ($expirationPeriodDays*24*60*60) ); 311 $stmt->setInt( 'create_date',time() ); 312 313 $browser = new \util\Browser(); 314 $stmt->setString( 'platform',$browser->platform ); 315 $stmt->setString( 'name' ,$browser->name ); 316 $row = $stmt->getRow(); 317 318 // Zusammensetzen des Tokens 319 return $selector.'.'.$token; 320 } 321 322 323 324 /** 325 * Creates a new login token for a serial. 326 * 327 * @param $selector string selector 328 * @return string new login token 329 */ 330 public function createNewLoginTokenForSerial( $selector ) 331 { 332 $algo = Password::ALGO_SHA1; 333 $token = Password::randomHexString(24); 334 335 $tokenHash = Password::hash($token,$algo); 336 337 $stmt = Db::sql( <<<SQL 338 UPDATE {{auth}} 339 SET token={token},token_algo={token_algo} 340 WHERE selector={selector} 341 SQL 342 ); 343 344 $stmt->setString( 'selector' ,$selector ); 345 $stmt->setString( 'token' ,$tokenHash ); 346 $stmt->setInt ( 'token_algo' ,$algo ); 347 $stmt->execute(); 348 349 // Zusammensetzen des Tokens 350 return $selector.'.'.$token; 351 } 352 353 354 355 356 /** 357 * Deletes a login token. 358 * @param $selector string selector 359 */ 360 function deleteLoginToken( $selector ) 361 { 362 $stmt = Db::sql( <<<SQL 363 DELETE FROM {{auth}} 364 WHERE selector = {selector} 365 SQL 366 ); 367 $stmt->setString('selector',$selector ); 368 $stmt->execute(); 369 } 370 371 372 /** 373 * Lesen Benutzer aus der Datenbank. 374 * 375 * @return User 376 */ 377 public function load() 378 { 379 $stmt = Db::sql( 'SELECT * FROM {{user}}'. 380 ' WHERE id={userid}' ); 381 $stmt->setInt( 'userid',$this->userid ); 382 $row = $stmt->getRow(); 383 384 if ( count($row) == 0 ) 385 throw new \util\exception\ObjectNotFoundException(); 386 387 $this->setDatabaseRow( $row ); 388 return $this; 389 } 390 391 392 /** 393 * Gets a user by its name. 394 * 395 * @static 396 * @param $name string user name 397 * @param $authType int authentication type 398 * @param $issuer string issuer who created this user 399 * @return User or null, if user is not found 400 */ 401 public static function loadWithName( $name,$authType,$issuer = null ) 402 { 403 // Search user with name 404 $sql = Db::sql( <<<SQL 405 SELECT id FROM {{user}} 406 WHERE name={name} 407 AND auth_type={type} 408 AND ( issuer={issuer} 409 OR (auth_type=1 AND issuer IS NULL) 410 ) 411 SQL 412 ); 413 $sql->setString( 'name' ,$name ); 414 $sql->setString( 'type' ,$authType ); 415 $sql->setString( 'issuer',$issuer ); 416 417 $userId = $sql->getOne(); 418 419 if ( ! $userId ) 420 return null; // no user found. 421 422 // Create the user by id. 423 $namedUser = new User( $userId ); 424 425 $namedUser->load(); 426 427 return $namedUser; 428 } 429 430 431 432 /** 433 * Stellt fest, ob der Benutzer korrekt geladen ist. 434 */ 435 public function isValid() 436 { 437 return intval($this->userid) > 0; 438 } 439 440 441 442 /** 443 * Lesen Benutzer aus der Datenbank 444 */ 445 protected function setDatabaseRow( $row ) 446 { 447 $this->userid = $row['id' ]; 448 $this->name = $row['name' ]; 449 $this->style = $row['style' ]; 450 $this->styleScheme = $row['style_scheme' ]; 451 $this->isAdmin = ( $row['is_admin'] == '1'); 452 $this->fullname = $row['fullname']; 453 $this->tel = $row['tel' ]; 454 $this->mail = $row['mail' ]; 455 $this->desc = $row['descr' ]; 456 $this->language = $row['language']; 457 $this->timezone = $row['timezone']; 458 $this->lastLogin = $row['last_login']; 459 $this->otpSecret = $row['otp_secret']; 460 $this->hotp = ($row['hotp']==1); 461 $this->hotpCount = $row['hotp_counter']; 462 $this->totp = ($row['totp']==1); 463 $this->passwordExpires = $row['password_expires']; 464 $this->passwordAlgo = $row['password_algo']; 465 $this->passwordLockedUntil = $row['password_locked_until']; 466 $this->passwordFailedCount = $row['password_fail_count' ]; 467 $this->type = $row['auth_type']; 468 $this->issuer = $row['issuer']; 469 470 if ( ! $this->fullname ) 471 $this->fullname = $this->name; 472 473 if ( ! $this->style ) 474 $this->style = Configuration::get(['interface','style','default']); 475 } 476 477 478 479 /** 480 * Namen ermitteln.<br> 481 * Wenn "fullname" gefuellt, dann diesen benutzen, sonst den Benutzernamen. 482 */ 483 function getName() 484 { 485 if ( ! $this->fullname ) 486 return $this->name; 487 else 488 return $this->fullname; 489 } 490 491 492 493 /** 494 * Liest einen Benutzernamen aus der Datenbank. 495 * 496 * @param int Benutzer-Id 497 * @return String Benutzername 498 */ 499 public static function getUserName( $userid ) 500 { 501 $db = Db::get(); 502 503 $sql = $db->sql( 'SELECT name FROM {{user}}'. 504 ' WHERE id={userid}' ); 505 $sql->setInt( 'userid',$userid ); 506 507 $name = $sql->getOne(); 508 509 if ( $name == '' ) 510 return Language::lang(Messages::UNKNOWN); 511 else return $name; 512 } 513 514 public function deleteToken( $tokenId ) 515 { 516 $stmt = Db::sql( <<<SQL 517 DELETE FROM {{auth}} 518 WHERE userid={userid} 519 AND id={tokenid} 520 SQL 521 ); 522 $stmt->setInt ('userid' ,$this->userid ); 523 $stmt->setInt ('tokenid',$tokenId ); 524 $stmt->execute(); 525 } 526 527 public function addApiToken($selector, $token, $platform, $name, $expireDate) 528 { 529 $tokenHash = Password::hash($token,Password::ALGO_SHA1); 530 531 $stmt = Db::sql( 'SELECT max(id) FROM {{auth}}'); 532 $count = $stmt->getOne(); 533 534 $stmt = Db::sql( <<<SQL 535 INSERT INTO {{auth}} 536 (id,userid,selector,token,token_algo,expires,create_date,platform,name) 537 VALUES( {id},{userid},{selector},{token},{token_algo},{expires},{create_date},{platform},{name} ) 538 SQL 539 ); 540 541 $stmt->setInt( 'id' ,++$count ); 542 $stmt->setInt( 'userid' ,$this->userid ); 543 544 $stmt->setString( 'selector' ,$selector ); 545 $stmt->setString( 'token' ,$tokenHash ); 546 $stmt->setInt ( 'token_algo' ,Password::ALGO_SHA1 ); 547 548 $stmt->setInt( 'expires' , $expireDate ); 549 $stmt->setInt( 'create_date',time() ); 550 551 $stmt->setString( 'platform',$platform ); 552 $stmt->setString( 'name' ,$name ); 553 $stmt->execute(); 554 } 555 556 557 /** 558 * Speichern Benutzer in der Datenbank. 559 */ 560 protected function save() 561 { 562 $sql = Db::sql( <<<SQL 563 UPDATE {{user}} 564 SET name={name}, 565 fullname={fullname}, 566 tel ={tel} , 567 descr ={desc} , 568 mail ={mail} , 569 style ={style} , 570 style_scheme = {style_scheme}, 571 language = {language}, 572 timezone = {timezone}, 573 is_admin = {isAdmin}, 574 totp = {totp}, 575 hotp = {hotp}, 576 password_fail_count = {fail_count}, 577 password_locked_until = {locked_until} 578 WHERE id={userid} 579 SQL 580 ); 581 $sql->setString ( 'name' ,$this->name ); 582 $sql->setString ( 'fullname',$this->fullname); 583 $sql->setString ( 'tel' ,$this->tel ); 584 $sql->setString ( 'desc' ,$this->desc ); 585 $sql->setString ( 'mail' ,$this->mail ); 586 $sql->setString ( 'style' ,$this->style ); 587 $sql->setInt ( 'style_scheme',$this->styleScheme ); 588 $sql->setStringOrNull( 'language',$this->language); 589 $sql->setStringOrNull( 'timezone',$this->timezone); 590 $sql->setBoolean( 'isAdmin' ,$this->isAdmin ); 591 $sql->setBoolean( 'totp' ,$this->totp ); 592 $sql->setBoolean( 'hotp' ,$this->hotp ); 593 $sql->setInt ( 'userid' ,$this->userid ); 594 $sql->setInt ( 'fail_count' ,$this->passwordFailedCount ); 595 $sql->setInt ( 'locked_until',$this->passwordLockedUntil ); 596 597 // Datenbankabfrage ausfuehren 598 $sql->execute(); 599 } 600 601 602 /** 603 * Benutzer hinzuf�gen 604 * 605 * @param String $name Benutzername 606 */ 607 public function add() 608 { 609 $sql = Db::sql( <<<SQL 610 SELECT MAX(id) FROM {{user}} 611 SQL 612 ); 613 $this->userid = intval($sql->getOne())+1; 614 615 $sql = Db::sql( <<<SQL 616 INSERT INTO {{user}} 617 (id,name,password_hash,fullname,tel,mail,descr,style,is_admin,password_salt,auth_type,issuer) 618 VALUES( {userid},{name},'','','','','','default',0,'',{type},{issuer} ) 619 SQL 620 ); 621 $sql->setInt ('userid',$this->userid ); 622 $sql->setString ('name' ,$this->name ); 623 $sql->setStringOrNull('issuer',$this->issuer ); 624 $sql->setString ('type' ,$this->type ); 625 626 // Datenbankbefehl ausfuehren 627 $sql->execute(); 628 629 $this->addNewUserGroups(); // Neue Gruppen hinzufuegen. 630 631 $this->renewOTPSecret(); 632 } 633 634 635 636 /** 637 * Enrich a new user with groups. 638 * 639 * Called from add() 640 */ 641 protected function addNewUserGroups() 642 { 643 $newUserConfig = Configuration::subset( ['security','newuser'] ); 644 $templateUser = $newUserConfig->get('copy_user'); 645 646 $userToCopy = null; 647 648 if ( is_int($templateUser)) { 649 $userToCopy = new User( $templateUser ); 650 try { 651 $userToCopy->load(); 652 } catch( ObjectNotFoundException $onfe) { 653 $userToCopy = null; 654 } 655 }elseif ( is_string($templateUser)) { 656 $userToCopy = User::loadWithName( $templateUser,User::AUTH_TYPE_INTERNAL ); 657 } 658 659 if ( $userToCopy ) { 660 foreach( $userToCopy->getGroupIds() as $groupId ) { 661 $this->addGroup( $groupId ); 662 } 663 664 $this->fullname = $userToCopy->fullname; 665 $this->desc = $userToCopy->desc; 666 $this->style = $userToCopy->style; 667 $this->mail = $userToCopy->mail; 668 $this->tel = $userToCopy->tel; 669 $this->language = $userToCopy->language; 670 $this->timezone = $userToCopy->timezone; 671 } 672 673 674 foreach ( $newUserConfig->get('groups',[]) as $group) { 675 676 if ( is_int($group)) { 677 $groupToAdd = new Group( $group ); 678 $groupToAdd->load(); 679 if ( ! $groupToAdd->groupid ) 680 $groupToAdd = null; 681 } 682 elseif ( is_string($group)) { 683 $groupToAdd = Group::loadWithName($group); 684 } 685 686 if ( $groupToAdd ) 687 $this->addGroup( $groupToAdd->groupid ); 688 } 689 690 } 691 692 693 /** 694 * Benutzer entfernen.<br> 695 * Vor dem Entfernen werden alle Referenzen auf diesen Benutzer entfernt:<br> 696 * - "Erzeugt von" f�r diesen Benutzer entfernen.<br> 697 * - "Letzte �nderung von" f�r diesen Benutzer entfernen<br> 698 * - Alle Archivdaten in Dateien mit diesem Benutzer entfernen<br> 699 * - Alle Berechtigungen dieses Benutzers l?schen<br> 700 * - Alle Gruppenzugehoerigkeiten dieses Benutzers l?schen<br> 701 * - Benutzer loeschen<br> 702 */ 703 public function delete() 704 { 705 $db = Db::get(); 706 707 // "Erzeugt von" f�r diesen Benutzer entfernen. 708 $sql = $db->sql( 'UPDATE {{object}} '. 709 'SET create_userid=null '. 710 'WHERE create_userid={userid}' ); 711 $sql->setInt ('userid',$this->userid ); 712 $sql->execute(); 713 714 // "Letzte �nderung von" f�r diesen Benutzer entfernen 715 $sql = $db->sql( 'UPDATE {{object}} '. 716 'SET lastchange_userid=null '. 717 'WHERE lastchange_userid={userid}' ); 718 $sql->setInt ('userid',$this->userid ); 719 $sql->execute(); 720 721 // Alle Archivdaten in Dateien mit diesem Benutzer entfernen 722 $sql = $db->sql( 'UPDATE {{value}} '. 723 'SET lastchange_userid=null '. 724 'WHERE lastchange_userid={userid}' ); 725 $sql->setInt ('userid',$this->userid ); 726 $sql->execute(); 727 728 // Alle Berechtigungen dieses Benutzers l?schen 729 $sql = $db->sql( 'DELETE FROM {{acl}} '. 730 'WHERE userid={userid}' ); 731 $sql->setInt ('userid',$this->userid ); 732 $sql->execute(); 733 734 // Alle Gruppenzugehoerigkeiten dieses Benutzers l?schen 735 $sql = $db->sql( 'DELETE FROM {{usergroup}} '. 736 'WHERE userid={userid}' ); 737 $sql->setInt ('userid',$this->userid ); 738 $sql->execute(); 739 740 // Delete all bookmarks 741 $sql = $db->sql( 'DELETE FROM {{bookmark}} '. 742 'WHERE userid={userid}' ); 743 $sql->setInt ('userid',$this->userid ); 744 $sql->execute(); 745 746 $this->deleteAllLoginTokens(); 747 // Benutzer loeschen 748 $sql = $db->sql( 'DELETE FROM {{user}} '. 749 'WHERE id={userid}' ); 750 $sql->setInt ('userid',$this->userid ); 751 $sql->execute(); 752 753 $this->userid = null; 754 } 755 756 757 /** 758 * Delete all Login tokens for this user. 759 * 760 * @throws \util\exception\DatabaseException 761 */ 762 public function deleteAllLoginTokens() { 763 764 $stmt = Db::sql( <<<SQL 765 DELETE FROM {{auth}} 766 WHERE userid={userid} 767 SQL 768 ); 769 $stmt->setInt ('userid',$this->userid ); 770 $stmt->execute(); 771 } 772 773 774 /** 775 * Ermitteln der Eigenschaften zu diesem Benutzer 776 * 777 * @return array Liste der Eigenschaften als assoziatives Array 778 */ 779 public function getProperties() 780 { 781 return array_merge( parent::getProperties(), [ 782 'id' => $this->userid, 783 'is_admin' => $this->isAdmin, 784 'auth_type' => ($this->type==User::AUTH_TYPE_INTERNAL?'local':'remote') 785 ] ); 786 } 787 788 789 790 /** 791 * Sets a new password for the user. 792 * 793 * if the user account was locked it will be unlocked. 794 * 795 * @param $password string new password 796 * @param $forever int true, wenn Kennwort dauerhaft. 797 */ 798 public function setPassword($password, $forever = true ) 799 { 800 $sql = DB::sql( <<<SQL 801 UPDATE {{user}} 802 SET password_hash={password}, password_algo={algo}, password_expires={expires}, password_fail_count=0, password_locked_until=NULL 803 WHERE id={userid} 804 SQL 805 ); 806 if ( $forever ) { 807 $algo = Password::bestAlgoAvailable(); 808 $expire = null; 809 } 810 else { 811 // cleartext-password, the user must change the password on the next login. 812 $algo = Password::ALGO_PLAIN; 813 $expire = time(); 814 } 815 816 // Hashsumme für Kennwort erzeugen 817 $sql->setIntOrNull('expires',$expire); 818 $sql->setInt ('algo' ,$algo ); 819 $sql->setString('password',Password::hash(Password::pepperPassword($password),$algo) ); 820 $sql->setInt ('userid' ,$this->userid ); 821 822 $sql->execute(); // Updating the password 823 824 // Delete all login tokens, because the user should 825 // use the new password on all devices 826 $this->deleteAllLoginTokens(); 827 } 828 829 830 /** 831 * Gruppen ermitteln, in denen der Benutzer Mitglied ist. 832 * 833 * @return array mit Id:Name 834 */ 835 function getGroups() 836 { 837 if ( !is_array($this->groups) ) 838 { 839 $sql = DB::sql( <<<SQL 840 SELECT {{group}}.id,{{group}}.name FROM {{group}} 841 LEFT JOIN {{usergroup}} ON {{usergroup}}.groupid={{group}}.id 842 WHERE {{usergroup}}.userid={userid} 843 SQL 844 ); 845 $sql->setInt('userid',$this->userid ); 846 $this->groups = $sql->getAssoc(); 847 } 848 849 return $this->groups; 850 } 851 852 853 854 /** 855 * Calculates the effective group list. 856 * 857 * @return array 858 */ 859 function getEffectiveGroups() 860 { 861 $groupIds = array_keys( $this->getGroups() ); 862 $effectiveGroupIds = $groupIds; 863 864 foreach( $groupIds as $id ) { 865 $group = new Group( $id ); 866 $group->load(); 867 $effectiveGroupIds = array_merge( $effectiveGroupIds,$group->getParentGroups() ); 868 } 869 $effectiveGroupIds = array_unique( $effectiveGroupIds ); 870 871 return $effectiveGroupIds; 872 } 873 874 875 876 877 // Gruppen ermitteln, in denen der Benutzer Mitglied ist 878 function getGroupIds() 879 { 880 return array_keys( $this->getGroups() ); 881 882 /* 883 $db = \cms\base\DB::get(); 884 885 $sql = $db->sql( 'SELECT groupid FROM {{usergroup}} '. 886 'WHERE userid={userid}' ); 887 $sql->setInt('userid',$this->userid ); 888 889 return $sql->getCol( $sql ); 890 */ 891 } 892 893 894 // Gruppen ermitteln, in denen der Benutzer *nicht* Mitglied ist 895 function getOtherGroups() 896 { 897 $db = Db::get(); 898 899 $sql = $db->sql( 'SELECT {{group}}.id,{{group}}.name FROM {{group}}'. 900 ' LEFT JOIN {{usergroup}} ON {{usergroup}}.groupid={{group}}.id AND {{usergroup}}.userid={userid}'. 901 ' WHERE {{usergroup}}.userid IS NULL' ); 902 $sql->setInt('userid' ,$this->userid ); 903 904 return $sql->getAssoc(); 905 } 906 907 908 909 /** 910 * Benutzer zu einer Gruppe hinzufuegen. 911 * 912 * @param $groupid int die Gruppen-Id 913 */ 914 function addGroup( $groupid ) 915 { 916 $db = Db::get(); 917 918 $sql = $db->sql('SELECT MAX(id) FROM {{usergroup}}'); 919 $usergroupid = intval($sql->getOne())+1; 920 921 $sql = $db->sql( 'INSERT INTO {{usergroup}} '. 922 ' (id,userid,groupid) '. 923 ' VALUES( {usergroupid},{userid},{groupid} )' ); 924 $sql->setInt('usergroupid',$usergroupid ); 925 $sql->setInt('userid' ,$this->userid ); 926 $sql->setInt('groupid' ,$groupid ); 927 928 $sql->execute(); 929 930 } 931 932 933 934 /** 935 * Benutzer aus Gruppe entfernen. 936 * 937 * @param $groupid int die Gruppen-Id 938 */ 939 function delGroup( $groupid ) 940 { 941 $db = Db::get(); 942 943 $sql = $db->sql( 'DELETE FROM {{usergroup}} '. 944 ' WHERE userid={userid} AND groupid={groupid}' ); 945 $sql->setInt ('userid' ,$this->userid ); 946 $sql->setInt ('groupid' ,$groupid ); 947 948 $sql->execute(); 949 } 950 951 952 /** 953 * Ermitteln aller Berechtigungen des Benutzers.<br> 954 * Diese Daten werden auf der Benutzerseite in der Administration angezeigt. 955 * 956 * @return array 957 */ 958 public function getAllAcls() 959 { 960 $groupClause = $this->getGroupClause(); 961 962 $sql = Db::sql( <<<SQL 963 SELECT {{acl}}.*, 964 {{object}}.projectid, 965 {{language}}.name AS languagename 966 FROM {{acl}} 967 LEFT JOIN {{object}} 968 ON {{object}}.id={{acl}}.objectid 969 LEFT JOIN {{language}} 970 ON {{language}}.id={{acl}}.languageid 971 WHERE ( 972 {{acl}}.type = {usertype} AND {{acl}}.userid={userid} 973 OR {{acl}}.type = {grouptype} AND ($groupClause) 974 OR {{acl}}.type = {alltype} 975 OR {{acl}}.type = {guesttype} 976 ) 977 ORDER BY {{object}}.projectid,{{acl}}.languageid 978 SQL 979 ); 980 $sql->setInt( 'userid' ,$this->userid ); 981 $sql->setInt( 'usertype' ,Permission::TYPE_USER ); 982 $sql->setInt( 'grouptype',Permission::TYPE_GROUP ); 983 $sql->setInt( 'alltype' ,Permission::TYPE_AUTH ); 984 $sql->setInt( 'guesttype',Permission::TYPE_GUEST ); 985 986 $aclList = array(); 987 988 foreach($sql->getAll() as $row ) 989 { 990 $permission = new Permission(); 991 $permission->setDatabaseRow( $row ); 992 $permission->projectid = $row['projectid' ]; 993 if ( intval($permission->languageid) == 0 ) 994 $permission->languagename = Language::lang( Messages::ALL_LANGUAGES); 995 else 996 $permission->languagename = $row['languagename']; 997 $aclList[] = $permission; 998 } 999 1000 return $aclList; 1001 } 1002 1003 1004 /** 1005 * Ermitteln aller Berechtigungen. 1006 * @return array Berechtigungen 1007 */ 1008 function getRights() 1009 { 1010 throw new \DomainException('User.class::getRights()'); 1011 1012 // $db = \cms\base\DB::get(); 1013 // $var = array(); 1014 // 1015 // // Alle Projekte lesen 1016 // $sql = $db->sql( 'SELECT id,name FROM {{project}}' ); 1017 // $projects = $sql->getAssoc( $sql ); 1018 // 1019 // foreach( $projects as $projectid=>$projectname ) 1020 // { 1021 // $var[$projectid] = array(); 1022 // $var[$projectid]['name'] = $projectname; 1023 // $var[$projectid]['folders'] = array(); 1024 // $var[$projectid]['rights'] = array(); 1025 // 1026 // $sql = $db->sql( 'SELECT {{acl}}.* FROM {{acl}}'. 1027 // ' LEFT JOIN {{folder}} ON {{acl}}.folderid = {{folder}}.id'. 1028 // ' WHERE {{folder}}.projectid={projectid}'. 1029 // ' AND {{acl}}.userid={userid}' ); 1030 // $sql->setInt('projectid',$projectid ); 1031 // $sql->setInt('userid' ,$this->userid ); 1032 // 1033 // $acls = $sql->getAll( $sql ); 1034 // 1035 // foreach( $acls as $acl ) 1036 // { 1037 // $aclid = $acl['id']; 1038 // $folder = new Folder( $acl['folderid'] ); 1039 // $folder->load(); 1040 // $var[$projectid]['rights'][$aclid] = $acl; 1041 // $var[$projectid]['rights'][$aclid]['foldername'] = implode(' » ',$folder->parentfolder( false,true )); 1042 // $var[$projectid]['rights'][$aclid]['delete_url'] = Html::url(array('action'=>'user','subaction'=>'delright','aclid'=>$aclid)); 1043 // } 1044 // 1045 // $sql = $db->sql( 'SELECT id FROM {{folder}}'. 1046 // ' WHERE projectid={projectid}' ); 1047 // $sql->setInt('projectid',$projectid); 1048 // $folders = $sql->getCol( $sql ); 1049 // 1050 // $var[$projectid]['folders'] = array(); 1051 // 1052 // foreach( $folders as $folderid ) 1053 // { 1054 // $folder = new Folder( $folderid ); 1055 // $folder->load(); 1056 // $var[$projectid]['folders'][$folderid] = implode(' » ',$folder->parentfolder( false,true )); 1057 // } 1058 // 1059 // asort( $var[$projectid]['folders'] ); 1060 // } 1061 // 1062 // return $var; 1063 } 1064 1065 1066 1067 1068 /** 1069 * Ermitteln aller zur Verfuegung stehenden Stylesheets 1070 * @return array 1071 */ 1072 public function getAvailableStyles() 1073 { 1074 return array_map( function($styleConfig) { 1075 return $styleConfig->get('name',''); 1076 },Configuration::subset('style')->subsets()); 1077 } 1078 1079 /** 1080 * Ueberpruefen des Kennwortes. 1081 * 1082 * Es wird festgestellt, ob das Kennwort dem des Benutzers entspricht. 1083 * Es wird dabei nur gegen die interne Datenbank geprüft. Weitere 1084 * Loginmodule werden nicht aufgerufen! 1085 * Diese Methode darf kein Bestandteil des Logins sein, da nur das Kennwort geprüft wird! 1086 * Kennwortablauf und Token werden nicht geprüft! 1087 */ 1088 function checkPassword( $password ) 1089 { 1090 $db = Db::get(); 1091 // Laden des Benutzers aus der Datenbank, um Password-Hash zu ermitteln. 1092 $sql = $db->sql( 'SELECT * FROM {{user}}'. 1093 ' WHERE id={userid}' ); 1094 $sql->setInt( 'userid',$this->userid ); 1095 $row_user = $sql->getRow(); 1096 1097 // Pruefen ob Kennwort mit Datenbank uebereinstimmt. 1098 return Password::check(Password::pepperPassword($password),$row_user['password_hash'],$row_user['password_algo']); 1099 } 1100 1101 1102 1103 /** 1104 * Ermittelt projektübergreifend die letzten Änderungen des Benutzers. 1105 * 1106 * @return array <string, unknown> 1107 */ 1108 public function getLastChanges( $fromDate ) 1109 { 1110 $sql = Db::sql( <<<SQL 1111 SELECT {{object}}.id as objectid, 1112 {{object}}.filename as filename, 1113 {{object}}.typeid as typeid, 1114 {{object}}.lastchange_date as lastchange_date, 1115 {{project}}.id as projectid, 1116 {{project}}.name as projectname 1117 FROM {{object}} 1118 LEFT JOIN {{project}} 1119 ON {{object}}.projectid = {{project}}.id 1120 WHERE {{object}}.lastchange_userid = {userid} AND 1121 {{object}}.lastchange_date >= {fromdate} 1122 ORDER BY {{object}}.lastchange_date DESC 1123 SQL 1124 ); 1125 1126 $sql->setInt( 'userid' , $this->userid ); 1127 $sql->setInt( 'fromdate', $fromDate ); 1128 1129 return $sql->getAll(); 1130 1131 } 1132 1133 1134 1135 1136 /** 1137 * Timeline 1138 * 1139 * @return array <string, unknown> 1140 */ 1141 public function getValueChanges( $fromDate ) 1142 { 1143 $sql = Db::sql( <<<SQL 1144 SELECT {{value}}.lastchange_date as lastchange_date, 1145 {{value}}.lastchange_userid as userid, 1146 {{user}}.fullname as username, 1147 {{value}}.linkobjectid as linkobjectid, 1148 {{value}}.text as linkobjectid, 1149 {{value}}.number as linkobjectid, 1150 {{value}}.date as date, 1151 {{value}}.format as format, 1152 {{value}}.id as valueid, 1153 {{templatemodel}}.templateid as templateid, 1154 {{file}}.objectid as fileid, 1155 {{pagecontent}}.elementid as elementid, 1156 {{pagecontent}}.languageid as languageid, 1157 {{page}}.objectid as pageid 1158 FROM {{value}} 1159 LEFT JOIN {{templatemodel}} 1160 ON {{templatemodel}}.contentid = {{value}}.contentid 1161 LEFT JOIN {{file}} 1162 ON {{value}}.contentid = {{file}}.contentid 1163 LEFT JOIN {{pagecontent}} 1164 ON {{value}}.contentid = {{pagecontent}}.contentid 1165 LEFT JOIN {{page}} 1166 ON {{pagecontent}}.pageid = {{page}}.id 1167 LEFT JOIN {{user}} 1168 ON {{value}}.lastchange_userid = {{user}}.id 1169 WHERE {{value}}.lastchange_userid = {userid} AND 1170 {{value}}.lastchange_date >= {fromdate} 1171 ORDER BY {{value}}.lastchange_date DESC 1172 SQL 1173 ); 1174 1175 $sql->setInt( 'userid' , $this->userid ); 1176 $sql->setInt( 'fromdate', $fromDate ); 1177 1178 return $sql->getAll(); 1179 1180 } 1181 1182 1183 1184 1185 /** 1186 * Erzeugt ein neues OTP-Secret. 1187 */ 1188 public function renewOTPSecret() { 1189 1190 $secret = Password::randomHexString(64); 1191 1192 $stmt = DB::sql('UPDATE {{user}} SET otp_secret={secret} WHERE id={id}'); 1193 1194 $stmt->setString( 'secret', $secret ); 1195 $stmt->setInt ( 'id' , $this->userid ); 1196 1197 $stmt->execute(); 1198 } 1199 1200 1201 public function getId() 1202 { 1203 return $this->userid; 1204 } 1205 1206 1207 } 1208
Downloadmodules/cms/model/User.class.php
History Mon, 6 Jan 2025 22:48:11 +0100 Jan Dankert New: Now it is possible to remove and create new login/api token for a user; new API authorization with the authorization scheme "api". Sun, 5 Jan 2025 23:14:07 +0100 Jan Dankert New UI feature: Show a timeline in the start page. Sun, 8 Dec 2024 20:57:25 +0100 Jan Dankert History-Size is now limited to 1 year. Sun, 8 Dec 2024 20:56:47 +0100 Jan Dankert New: Users are now able to store bookmarks. Sat, 7 Dec 2024 20:34:52 +0100 Jan Dankert UI: New Selection for user color scheme. Sun, 4 Dec 2022 16:40:34 +0100 Jan Dankert Refactoring without changes. Fri, 15 Apr 2022 20:45:19 +0200 dankert Refactoring: Code cleanup. Fri, 15 Apr 2022 14:51:22 +0200 dankert Refactoring: User,Config and Database info is now stored in the Request, because so there is no session required for clients which are using Basic Authorization. Wed, 9 Mar 2022 00:53:10 +0100 dankert Fix: Setting the correct timezone from the user property. Mon, 7 Feb 2022 22:52:12 +0100 dankert Password lock check is moved into "InternalAuth", because it must be called on all authentication requests. Mon, 6 Dec 2021 21:34:53 +0100 dankert Fix: Read ACL for a user. Sun, 5 Dec 2021 20:33:24 +0100 dankert Cleanup: Removed unusable properties from class 'Value' and 'BaseObject'. Sun, 7 Mar 2021 00:10:20 +0100 Jan Dankert Refactoring: Hopefully more performance while accessing the database resultsets. Thu, 4 Mar 2021 01:15:06 +0100 Jan Dankert Fix: Calculate correct parent groups of group. Mon, 4 Jan 2021 23:14:09 +0100 Jan Dankert New: Groups may contain subgroups. Users within a group inherit the permissions of all parent groups. Mon, 4 Jan 2021 19:03:18 +0100 Jan Dankert Refactoring: ACL class is renamed to Permission, because most RBAC/DMAC concepts are calling it a permission. Mon, 30 Nov 2020 10:31:50 +0100 Jan Dankert Fix: Reset password fail counter after successful login. Sat, 28 Nov 2020 00:53:41 +0100 Jan Dankert New: Lock password after a number of login fails. Thu, 19 Nov 2020 00:45:44 +0100 Jan Dankert Security fix: We must update the login token on every login; Administrators are able to see the login tokens of users. Wed, 18 Nov 2020 01:46:36 +0100 Jan Dankert Refactoring of model classes: New method persist() and some other cleanups. Sat, 14 Nov 2020 22:02:21 +0100 Jan Dankert Fixed: Notices may display a message. Sun, 1 Nov 2020 22:27:37 +0100 Jan Dankert Refactoring: The active menu entrys are now controlled by the action class; The user switch function is now available as a menu entry. Sun, 1 Nov 2020 20:29:38 +0100 Jan Dankert Fix: Password change only possible for internal users. Sun, 1 Nov 2020 03:08:55 +0100 Jan Dankert Replaced the calls to "Configuration::rawConfig()" with the OO style calls; Cleanup LoginAction. Sat, 31 Oct 2020 03:52:03 +0100 Jan Dankert Fix: Better detection if password change is possbile. Sat, 31 Oct 2020 00:43:29 +0100 Jan Dankert New: Support for OpenId Connect; Removed: Support for LDAP. Sun, 25 Oct 2020 02:11:58 +0200 Jan Dankert Fix: The config was not read in the templates :-O. Fri, 2 Oct 2020 23:11:48 +0200 Jan Dankert Cleanup: No '.inputholder' any more, notices with links to objects. Tue, 29 Sep 2020 22:17:11 +0200 Jan Dankert Refactoring: Do not use global constants. Sat, 26 Sep 2020 10:32:02 +0200 Jan Dankert Refactoring: No global $conf array any more. Sat, 26 Sep 2020 04:26:55 +0200 Jan Dankert Refactoring: read configuration values with a class. Sat, 26 Sep 2020 04:03:53 +0200 Jan Dankert Refactoring: read language keys with a class. Sat, 26 Sep 2020 02:26:39 +0200 Jan Dankert Refactoring: No global functions any more, the database object is read from the Db class. Thu, 10 Sep 2020 18:30:16 +0200 Jan Dankert Some code cleanup, killing the old "checkMenu()"-methods. Mon, 17 Aug 2020 22:52:37 +0200 Jan Dankert Cleanup: Killing the old odd 'GLOBAL_' message prefixes. Sun, 23 Feb 2020 04:01:30 +0100 Jan Dankert Refactoring with Namespaces for the cms modules, part 1: moving.