openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

User.class.php (27635B)


      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 	public $userid   = 0;
     49 
     50 	/**
     51 	 * Username.
     52 	 * This name should not be displayed to other users.
     53 	 *
     54 	 * @var string
     55 	 */
     56 	public $name     = '';
     57 
     58 	/**
     59 	 * natural name of this user.
     60 	 *
     61 	 * @var string
     62 	 */
     63 	public $fullname = '';
     64 	public $tel;
     65 	public $mail;
     66 	public $desc;
     67 	public $style;
     68 	public $isAdmin;
     69 	public $loginDate = 0;
     70 
     71 	/**
     72 	 * User prefered language.
     73 	 *
     74 	 * As ISO string.
     75 	 *
     76 	 * @var string
     77 	 */
     78 	public $language;
     79 
     80 	public $timezone;
     81 	public $passwordExpires;
     82 	public $passwordAlgo;
     83 
     84 	public $lastLogin;
     85 	public $otpSecret;
     86 	public $hotp     ;
     87 	public $hotpCount;
     88 	public $totp     ;
     89 
     90 	public $issuer = null;
     91 	public $type = User::AUTH_TYPE_INTERNAL;
     92 
     93 	public $passwordFailedCount = 0;
     94 	public $passwordLockedUntil = 0;
     95 
     96 	private $groups = null;
     97 
     98 
     99 	// Konstruktor
    100 	public function __construct( $userid='' )
    101 	{
    102 		if   ( is_numeric($userid) )
    103 			$this->userid = $userid;
    104 	}
    105 
    106 
    107     /**
    108      * get all users.
    109      *
    110      * @return array
    111      */
    112 	public static function listAll()
    113 	{
    114 		$sql = Db::sql( <<<SQL
    115 			SELECT id,name
    116 		      FROM {{user}}
    117 		     ORDER BY name
    118 SQL
    119 		);
    120 		return $sql->getAssoc();
    121 	}
    122 
    123 
    124     /**
    125      * Get all users.
    126      *
    127      * @return array with user objects
    128      */
    129 	public static function getAllUsers()
    130 	{
    131 		$list = array();
    132 		$sql = Db::sql( <<<SQL
    133                  SELECT *
    134 		           FROM {{user}}
    135 		          ORDER BY name
    136 SQL
    137 		);
    138 		foreach($sql->getAll() as $row )
    139 		{
    140 			$user = new User();
    141 			$user->setDatabaseRow( $row );
    142 
    143 			$list[] = $user;
    144 		}
    145 		
    146 		return $list;
    147 	}
    148 
    149 
    150 	/**
    151 	  * Benutzer als aktiven Benutzer in die Session schreiben.
    152 	  */
    153 	public function setCurrent()
    154 	{
    155 		$this->loginDate = time();
    156 
    157 		Request::setUser( $this );
    158 	}
    159 
    160 
    161 
    162 	/**
    163 	  * Benutzer als aktiven Benutzer in die Session schreiben.
    164 	  */
    165 	public function updateLoginTimestamp()
    166 	{
    167 	    $stmt = Db::sql( <<<SQL
    168                      UPDATE {{user}}
    169 	                 SET last_login={time}
    170 	                 WHERE id={userid}
    171 SQL
    172 	        );
    173 	    $stmt->setInt( 'time'  ,time() );
    174 	    $stmt->setInt( 'userid',$this->userid  );
    175 
    176 	    // Datenbankabfrage ausfuehren
    177 	    $stmt->execute();
    178 	}
    179 
    180 
    181 	/**
    182 	 * Creates an SQL-WHERE-Clause.
    183 	 *
    184 	 * A "WHERE IN()" clause is difficult, because groups may be part of another groups.
    185 	 *
    186 	 * @return String SQL-WHERE-Clause
    187 	 */
    188 	public function getGroupClause()
    189 	{
    190 		$groupIds = $this->getEffectiveGroups();
    191 		
    192 		if	( count($groupIds) > 0 )
    193 			$groupclause = ' groupid='.implode(' OR groupid=',$groupIds );
    194 		else
    195 			$groupclause = ' 1=0 ';
    196 
    197 		return $groupclause;
    198 	}
    199 
    200 
    201 	/**
    202 	 * Lesen aller Projekte, fuer die der Benutzer berechtigt ist.
    203 	 *
    204 	 * @return array [Projekt-Id] = Projekt-Name
    205 	 */
    206 	public function getReadableProjects()
    207 	{
    208 		$db = Db::get();
    209 
    210 		if	( $this->isAdmin )
    211 		{
    212 			// Administratoren haben Rechte auf alle Projekte.
    213 			return Project::getAllProjects();
    214 		}
    215 		else
    216 		{
    217 			$groupClause = $this->getGroupClause();
    218 			$sql = $db->sql(<<<SQL
    219 				SELECT DISTINCT {{project}}.id,{{project}}.name
    220 				  FROM {{object}}
    221 				  LEFT JOIN {{acl}}     ON {{object}}.id  = {{acl}}.objectid 
    222 				  LEFT JOIN {{project}} ON {{project}}.id = {{object}}.projectid 
    223 				 WHERE {{object}}.parentid IS NULL     AND
    224 					   {{acl}}.id          IS NOT NULL AND
    225 						 (
    226 							{{acl}}.type = {usertype}  AND {{acl}}.userid={userid}
    227 						 OR {{acl}}.type = {grouptype} AND $groupClause 
    228 						 OR ({{acl}}.type = {alltype}
    229 						 OR ({{acl}}.type = {guesttype}
    230 					   ) 
    231 				 ORDER BY {{project}}.name
    232 SQL
    233 );
    234 			$sql->setInt( 'userid',$this->userid );
    235 			$sql->setInt( 'usertype' ,Permission::TYPE_USER  );
    236 			$sql->setInt( 'grouptype',Permission::TYPE_GROUP );
    237 			$sql->setInt( 'alltype'  ,Permission::TYPE_AUTH  );
    238 			$sql->setInt( 'guesttype',Permission::TYPE_GUEST );
    239 
    240 			return $sql->getAssoc();
    241 		}
    242 		
    243 	}
    244 
    245 
    246 
    247 	/**
    248 	 * Ermittelt alls Projekte, fuer die der Benutzer berechtigt ist.
    249 	 * @return array [0..n] = Projekt-Id
    250 	 */
    251 	function getReadableProjectIds()
    252 	{
    253 		return array_keys( $this->getReadableProjects() );
    254 	}
    255 
    256 
    257 	/**
    258 	 * Gets all login tokens for this user.
    259 	 *
    260 	 * @return array
    261 	 */
    262 	public function getLoginTokens() {
    263 
    264 		$stmt = Db::sql( <<<SQL
    265               SELECT selector,expires,create_date,platform,name
    266                 FROM {{auth}} 
    267                WHERE userid={userid}
    268 SQL
    269 		);
    270 
    271 		$stmt->setInt('userid',$this->getId() );
    272 
    273 		return $stmt->getAll();
    274 	}
    275 
    276 	/**
    277 	 * Creates a completly new login token.
    278 	 *
    279 	 * @return string new login token
    280 	 */
    281 	function createNewLoginToken()
    282 	{
    283 	    $selector = Password::randomHexString(24);
    284 	    $token    = Password::randomHexString(24);
    285 
    286 	    $tokenHash = Password::hash($token,Password::ALGO_SHA1);
    287 
    288 		$stmt = Db::sql( 'SELECT max(id) FROM {{auth}}');
    289 		$count = $stmt->getOne();
    290 
    291 		$stmt = Db::sql( <<<SQL
    292               INSERT INTO {{auth}} (id,userid,selector,token,token_algo,expires,create_date,platform,name)
    293                  VALUES( {id},{userid},{selector},{token},{token_algo},{expires},{create_date},{platform},{name} )
    294 SQL
    295         );
    296 		$expirationPeriodDays = Configuration::Conf()->subset('user')->subset('security')->get('token_expires_after_days',730);
    297 
    298 		$stmt->setInt( 'id'         ,++$count      );
    299 		$stmt->setInt( 'userid'     ,$this->userid );
    300 
    301 		$stmt->setString( 'selector'   ,$selector     );
    302 		$stmt->setString( 'token'      ,$tokenHash    );
    303 		$stmt->setInt   ( 'token_algo' ,Password::ALGO_SHA1        );
    304 
    305 		$stmt->setInt( 'expires'    ,time() + ($expirationPeriodDays*24*60*60) );
    306 		$stmt->setInt( 'create_date',time()                           );
    307 
    308 		$browser = new \util\Browser();
    309 		$stmt->setString( 'platform',$browser->platform );
    310 		$stmt->setString( 'name'    ,$browser->name     );
    311 		$row = $stmt->getRow();
    312 
    313 		// Zusammensetzen des Tokens
    314 		return $selector.'.'.$token;
    315 	}
    316 
    317 
    318 
    319 	/**
    320 	 * Creates a new login token for a serial.
    321 	 *
    322 	 * @param $selector string selector
    323 	 * @return string new login token
    324 	 */
    325 	public function createNewLoginTokenForSerial( $selector )
    326 	{
    327 		$algo      = Password::ALGO_SHA1;
    328 		$token     = Password::randomHexString(24);
    329 
    330 		$tokenHash = Password::hash($token,$algo);
    331 
    332 		$stmt = Db::sql( <<<SQL
    333               UPDATE {{auth}}
    334                  SET token={token},token_algo={token_algo}
    335                  WHERE selector={selector}
    336 SQL
    337 		);
    338 
    339 		$stmt->setString( 'selector'   ,$selector     );
    340 		$stmt->setString( 'token'      ,$tokenHash    );
    341 		$stmt->setInt   ( 'token_algo' ,$algo         );
    342 		$stmt->execute();
    343 
    344 		// Zusammensetzen des Tokens
    345 		return $selector.'.'.$token;
    346 	}
    347 
    348 
    349 
    350 
    351 	/**
    352      * Deletes a login token.
    353 	 * @param $selector string selector
    354      */
    355     function deleteLoginToken( $selector )
    356     {
    357         $stmt = Db::sql( <<<SQL
    358               DELETE FROM {{auth}}
    359                WHERE selector = {selector}
    360 SQL
    361         );
    362         $stmt->setString('selector',$selector );
    363         $stmt->execute();
    364     }
    365 
    366 
    367     /**
    368 	 * Lesen Benutzer aus der Datenbank.
    369 	 */ 
    370 	public function load()
    371 	{
    372 		$stmt = Db::sql( 'SELECT * FROM {{user}}'.
    373 		                ' WHERE id={userid}' );
    374 		$stmt->setInt( 'userid',$this->userid );
    375 		$row = $stmt->getRow();
    376 
    377 		if	( count($row) == 0 )
    378 			throw new \util\exception\ObjectNotFoundException();
    379 		
    380 		$this->setDatabaseRow( $row );		
    381 	}
    382 
    383 
    384 	/**
    385 	 * Benutzerobjekt �ber Benutzernamen ermitteln.<br>
    386 	 * Liefert ein neues Benutzerobjekt zur�ck.
    387 	 * 
    388 	 * @static 
    389 	 * @param $name string Benutzername
    390 	 * @param $authType int authentication type
    391 	 * @param $issuer string issuer who created this user
    392 	 */
    393 	public static function loadWithName( $name,$authType,$issuer = null )
    394 	{
    395 		// Search user with name
    396 		$sql = Db::sql( <<<SQL
    397 			SELECT id FROM {{user}}
    398 		                WHERE name={name}
    399 		                  AND auth_type={type}
    400 		                  AND ( issuer={issuer}
    401 		                        OR (auth_type=1 AND issuer IS NULL)
    402 		                      )
    403 SQL
    404 		);
    405 		$sql->setString( 'name'  ,$name     );
    406 		$sql->setString( 'type'  ,$authType );
    407 		$sql->setString( 'issuer',$issuer   );
    408 
    409 		$userId = $sql->getOne();
    410 
    411 		if (empty($userId))
    412 		    return null; // no user found.
    413 
    414 		// Benutzer �ber Id instanziieren
    415 		$neuerUser = new User( $userId );
    416 		
    417 		$neuerUser->load();
    418 		
    419 		return $neuerUser;
    420 	}
    421 	
    422 	
    423 	
    424 	/**
    425 	 * Stellt fest, ob der Benutzer korrekt geladen ist.
    426 	 */
    427 	public function isValid()
    428 	{
    429 		return intval($this->userid) > 0;
    430 	}
    431 
    432 
    433 
    434 	/**
    435 	 * Lesen Benutzer aus der Datenbank
    436 	 */
    437 	protected function setDatabaseRow( $row )
    438 	{
    439 		$this->userid    = $row['id'      ];
    440 		$this->name      = $row['name'    ];
    441 		$this->style     = $row['style'   ];
    442 		$this->isAdmin   = ( $row['is_admin'] == '1');
    443 		$this->fullname  = $row['fullname'];
    444 		$this->tel       = $row['tel'     ];
    445 		$this->mail      = $row['mail'    ];
    446 		$this->desc      = $row['descr'   ];
    447 		$this->language  = $row['language'];
    448 		$this->timezone  = $row['timezone'];
    449 		$this->lastLogin = $row['last_login'];
    450 		$this->otpSecret = $row['otp_secret'];
    451 		$this->hotp      = ($row['hotp']==1);
    452 		$this->hotpCount = $row['hotp_counter'];
    453 		$this->totp      = ($row['totp']==1);
    454 		$this->passwordExpires = $row['password_expires'];
    455 		$this->passwordAlgo    = $row['password_algo'];
    456 		$this->passwordLockedUntil = $row['password_locked_until'];
    457 		$this->passwordFailedCount = $row['password_fail_count'  ];
    458 		$this->type      = $row['auth_type'];
    459 		$this->issuer    = $row['issuer'];
    460 
    461 		if	( ! $this->fullname )
    462 			$this->fullname = $this->name;
    463 			
    464 		if	( ! $this->style )
    465 			$this->style = Configuration::get(['interface','style','default']);
    466 	}
    467 
    468 
    469 
    470 	/**
    471 	 * Namen ermitteln.<br>
    472 	 * Wenn "fullname" gefuellt, dann diesen benutzen, sonst den Benutzernamen.
    473 	 */
    474 	function getName()
    475 	{
    476 		if	( ! $this->fullname )
    477 			return $this->name;
    478 		else
    479 			return $this->fullname;
    480 	}
    481 	
    482 	
    483 	
    484 	/**
    485 	 * Liest einen Benutzernamen aus der Datenbank.
    486 	 * 
    487 	 * @param int Benutzer-Id
    488 	 * @return String Benutzername
    489 	 */
    490 	public static function getUserName( $userid )
    491 	{
    492 		$db = Db::get();
    493 
    494 		$sql = $db->sql( 'SELECT name FROM {{user}}'.
    495 		                ' WHERE id={userid}' );
    496 		$sql->setInt( 'userid',$userid );
    497 
    498 		$name = $sql->getOne();
    499 		
    500 		if	( $name == '' )
    501 			return Language::lang(Messages::UNKNOWN);
    502 		else return $name;
    503 	}
    504 
    505 
    506 	/**
    507 	 * Speichern Benutzer in der Datenbank.
    508 	 */
    509 	protected function save()
    510 	{
    511 		$sql = Db::sql( <<<SQL
    512                          UPDATE {{user}}
    513 		                 SET name={name},
    514 		                     fullname={fullname},
    515 		                     tel     ={tel}     ,
    516 		                     descr   ={desc}    ,
    517 		                     mail    ={mail}    ,
    518 		                     style   ={style}   ,
    519 		                     language = {language},
    520 		                     timezone = {timezone},
    521 		                     is_admin = {isAdmin},
    522 		                     totp     = {totp},
    523 		                     hotp     = {hotp},
    524 		                     password_fail_count   = {fail_count},
    525 		                     password_locked_until = {locked_until}
    526 		                 WHERE id={userid}
    527 SQL
    528  );
    529 		$sql->setString ( 'name'    ,$this->name    );
    530 		$sql->setString ( 'fullname',$this->fullname);
    531 		$sql->setString ( 'tel'     ,$this->tel     );
    532 		$sql->setString ( 'desc'    ,$this->desc    );
    533 		$sql->setString ( 'mail'    ,$this->mail    );
    534 		$sql->setString ( 'style'   ,$this->style   );
    535 		$sql->setStringOrNull( 'language',$this->language);
    536 		$sql->setStringOrNull( 'timezone',$this->timezone);
    537 		$sql->setBoolean( 'isAdmin' ,$this->isAdmin );
    538 		$sql->setBoolean( 'totp'    ,$this->totp    );
    539 		$sql->setBoolean( 'hotp'    ,$this->hotp    );
    540 		$sql->setInt    ( 'userid'  ,$this->userid  );
    541 		$sql->setInt    ( 'fail_count'  ,$this->passwordFailedCount  );
    542 		$sql->setInt    ( 'locked_until',$this->passwordLockedUntil  );
    543 
    544 		// Datenbankabfrage ausfuehren
    545 		$sql->execute();
    546 	}
    547 
    548 
    549 	/**
    550 	 * Benutzer hinzuf�gen
    551 	 *
    552 	 * @param String $name Benutzername
    553 	 */
    554 	public function add()
    555 	{
    556 		$sql = Db::sql( <<<SQL
    557 	SELECT MAX(id) FROM {{user}}
    558 SQL
    559 		);
    560 		$this->userid = intval($sql->getOne())+1;
    561 
    562 		$sql = Db::sql( <<<SQL
    563 	INSERT INTO {{user}}
    564 		(id,name,password_hash,fullname,tel,mail,descr,style,is_admin,password_salt,auth_type,issuer)
    565 		VALUES( {userid},{name},'','','','','','default',0,'',{type},{issuer} )
    566 SQL
    567 		);
    568 		$sql->setInt         ('userid',$this->userid );
    569 		$sql->setString      ('name'  ,$this->name   );
    570 		$sql->setStringOrNull('issuer',$this->issuer );
    571 		$sql->setString      ('type'  ,$this->type   );
    572 
    573 		// Datenbankbefehl ausfuehren
    574 		$sql->execute();
    575 		
    576 		$this->addNewUserGroups(); // Neue Gruppen hinzufuegen.
    577 		
    578 		$this->renewOTPSecret();
    579 	}
    580 
    581 	
    582 
    583 	/**
    584 	 * Enrich a new user with groups.
    585 	 *
    586 	 * Called from add()
    587 	 */
    588 	protected function addNewUserGroups()
    589 	{
    590 		$newUserConfig = Configuration::subset( ['security','newuser'] );
    591 		$templateUser = $newUserConfig->get('copy_user');
    592 
    593 		$userToCopy = null;
    594 
    595 		if   ( is_int($templateUser)) {
    596 			$userToCopy = new User( $templateUser );
    597 			try {
    598 				$userToCopy->load();
    599 			} catch( ObjectNotFoundException $onfe) {
    600 				$userToCopy = null;
    601 			}
    602 		}elseif ( is_string($templateUser)) {
    603 			$userToCopy = User::loadWithName( $templateUser,User::AUTH_TYPE_INTERNAL );
    604 		}
    605 
    606 		if   ( $userToCopy ) {
    607 			foreach( $userToCopy->getGroupIds() as $groupId ) {
    608 				$this->addGroup( $groupId );
    609 			}
    610 
    611 			$this->fullname = $userToCopy->fullname;
    612 			$this->desc     = $userToCopy->desc;
    613 			$this->style    = $userToCopy->style;
    614 			$this->mail     = $userToCopy->mail;
    615 			$this->tel      = $userToCopy->tel;
    616 			$this->language = $userToCopy->language;
    617 			$this->timezone = $userToCopy->timezone;
    618 		}
    619 
    620 
    621 		foreach ( $newUserConfig->get('groups',[]) as $group) {
    622 
    623 			if   ( is_int($group)) {
    624 				$groupToAdd = new Group( $group );
    625 				$groupToAdd->load();
    626 				if   ( ! $groupToAdd->groupid )
    627 					$groupToAdd = null;
    628 			}
    629 			elseif ( is_string($group)) {
    630 				$groupToAdd = Group::loadWithName($group);
    631 			}
    632 
    633 			if   ( $groupToAdd )
    634 				$this->addGroup( $groupToAdd->groupid );
    635 		}
    636 		
    637 	}
    638 
    639 
    640 	/**
    641 	 * Benutzer entfernen.<br>
    642 	 * Vor dem Entfernen werden alle Referenzen auf diesen Benutzer entfernt:<br>
    643 	 * - "Erzeugt von" f�r diesen Benutzer entfernen.<br>
    644 	 * - "Letzte �nderung von" f�r diesen Benutzer entfernen<br>
    645 	 * - Alle Archivdaten in Dateien mit diesem Benutzer entfernen<br>
    646 	 * - Alle Berechtigungen dieses Benutzers l?schen<br>
    647 	 * - Alle Gruppenzugehoerigkeiten dieses Benutzers l?schen<br>
    648 	 * - Benutzer loeschen<br>
    649 	 */
    650 	public function delete()
    651 	{
    652 		$db = Db::get();
    653 
    654 		// "Erzeugt von" f�r diesen Benutzer entfernen.
    655 		$sql = $db->sql( 'UPDATE {{object}} '.
    656 		                'SET create_userid=null '.
    657 		                'WHERE create_userid={userid}' );
    658 		$sql->setInt   ('userid',$this->userid );
    659 		$sql->execute();
    660 
    661 		// "Letzte �nderung von" f�r diesen Benutzer entfernen
    662 		$sql = $db->sql( 'UPDATE {{object}} '.
    663 		                'SET lastchange_userid=null '.
    664 		                'WHERE lastchange_userid={userid}' );
    665 		$sql->setInt   ('userid',$this->userid );
    666 		$sql->execute();
    667 
    668 		// Alle Archivdaten in Dateien mit diesem Benutzer entfernen
    669 		$sql = $db->sql( 'UPDATE {{value}} '.
    670 		                'SET lastchange_userid=null '.
    671 		                'WHERE lastchange_userid={userid}' );
    672 		$sql->setInt   ('userid',$this->userid );
    673 		$sql->execute();
    674 
    675 		// Alle Berechtigungen dieses Benutzers l?schen
    676 		$sql = $db->sql( 'DELETE FROM {{acl}} '.
    677 		                'WHERE userid={userid}' );
    678 		$sql->setInt   ('userid',$this->userid );
    679 		$sql->execute();
    680 
    681 		// Alle Gruppenzugehoerigkeiten dieses Benutzers l?schen
    682 		$sql = $db->sql( 'DELETE FROM {{usergroup}} '.
    683 		                'WHERE userid={userid}' );
    684 		$sql->setInt   ('userid',$this->userid );
    685 		$sql->execute();
    686 
    687 		$this->deleteAllLoginTokens();
    688         // Benutzer loeschen
    689 		$sql = $db->sql( 'DELETE FROM {{user}} '.
    690 		                'WHERE id={userid}' );
    691 		$sql->setInt   ('userid',$this->userid );
    692 		$sql->execute();
    693 
    694 		$this->userid = null;
    695 	}
    696 
    697 
    698 	/**
    699 	 * Delete all Login tokens for this user.
    700 	 *
    701 	 * @throws \util\exception\DatabaseException
    702 	 */
    703 	public function deleteAllLoginTokens() {
    704 
    705 		$stmt = Db::sql( <<<SQL
    706               DELETE FROM {{auth}}
    707                WHERE userid={userid}
    708 SQL
    709 		);
    710 		$stmt->setInt   ('userid',$this->userid );
    711 		$stmt->execute();
    712 	}
    713 
    714 
    715 	/**
    716 	 * Ermitteln der Eigenschaften zu diesem Benutzer
    717 	 *
    718 	 * @return array Liste der Eigenschaften als assoziatives Array
    719 	 */
    720 	public function getProperties()
    721 	{
    722 	    return array_merge( parent::getProperties(), [
    723 	    	'id'        => $this->userid,
    724 			'is_admin'  => $this->isAdmin,
    725 			'auth_type' => ($this->type==User::AUTH_TYPE_INTERNAL?'local':'remote')
    726 		] );
    727 	}
    728 
    729 
    730 
    731 	/**
    732 	 * Sets a new password for the user.
    733 	 *
    734 	 * if the user account was locked it will be unlocked.
    735 	 * 
    736 	 * @param $password string new password
    737 	 * @param $forever int true, wenn Kennwort dauerhaft.
    738 	 */
    739 	public function setPassword($password, $forever = true )
    740 	{
    741 		$sql = DB::sql( <<<SQL
    742            UPDATE {{user}}
    743               SET password_hash={password}, password_algo={algo}, password_expires={expires}, password_fail_count=0, password_locked_until=NULL
    744 		    WHERE id={userid}
    745 SQL
    746 		);
    747 		if	( $forever ) {
    748 			$algo   = Password::bestAlgoAvailable();
    749 			$expire = null;
    750 		}
    751 		else {
    752 			// cleartext-password, the user must change the password on the next login.
    753 			$algo   = Password::ALGO_PLAIN;
    754 			$expire = time();
    755 		}
    756 
    757 		// Hashsumme für Kennwort erzeugen
    758 		$sql->setIntOrNull('expires',$expire);
    759 		$sql->setInt   ('algo'    ,$algo                                                  );
    760 		$sql->setString('password',Password::hash(Password::pepperPassword($password),$algo) );
    761 		$sql->setInt   ('userid'  ,$this->userid  );
    762 
    763 		$sql->execute(); // Updating the password
    764 
    765 		// Delete all login tokens, because the user should
    766 		// use the new password on all devices
    767 		$this->deleteAllLoginTokens();
    768 	}
    769 
    770 
    771 	/**
    772 	 * Gruppen ermitteln, in denen der Benutzer Mitglied ist.
    773 	 *
    774 	 * @return array mit Id:Name
    775 	 */
    776 	function getGroups()
    777 	{
    778 		if	( !is_array($this->groups) )
    779 		{
    780 			$sql = DB::sql( <<<SQL
    781 				SELECT {{group}}.id,{{group}}.name FROM {{group}}
    782 			                LEFT JOIN {{usergroup}} ON {{usergroup}}.groupid={{group}}.id
    783 			                WHERE {{usergroup}}.userid={userid}
    784 SQL
    785 			);
    786 			$sql->setInt('userid',$this->userid );
    787 			$this->groups = $sql->getAssoc();
    788 		}
    789 		
    790 		return $this->groups;
    791 	}
    792 
    793 
    794 
    795 	/**
    796 	 * Calculates the effective group list.
    797 	 *
    798 	 * @return array
    799 	 */
    800 	function getEffectiveGroups()
    801 	{
    802 		$groupIds = array_keys( $this->getGroups() );
    803 		$effectiveGroupIds = $groupIds;
    804 
    805 		foreach( $groupIds as $id ) {
    806 			$group = new Group( $id );
    807 			$group->load();
    808 			$effectiveGroupIds = array_merge( $effectiveGroupIds,$group->getParentGroups() );
    809 		}
    810 		$effectiveGroupIds = array_unique( $effectiveGroupIds );
    811 
    812 		return $effectiveGroupIds;
    813 	}
    814 
    815 
    816 
    817 
    818 	// Gruppen ermitteln, in denen der Benutzer Mitglied ist
    819 	function getGroupIds()
    820 	{
    821 		return array_keys( $this->getGroups() );
    822 
    823 		/*
    824 		$db = \cms\base\DB::get();
    825 
    826 		$sql = $db->sql( 'SELECT groupid FROM {{usergroup}} '.
    827 		                'WHERE userid={userid}' );
    828 		$sql->setInt('userid',$this->userid );
    829 
    830 		return $sql->getCol( $sql );
    831 		*/
    832 	}
    833 	
    834 
    835 	// Gruppen ermitteln, in denen der Benutzer *nicht* Mitglied ist
    836 	function getOtherGroups()
    837 	{
    838 		$db = Db::get();
    839 
    840 		$sql = $db->sql( 'SELECT {{group}}.id,{{group}}.name FROM {{group}}'.
    841 		                '   LEFT JOIN {{usergroup}} ON {{usergroup}}.groupid={{group}}.id AND {{usergroup}}.userid={userid}'.
    842 		                '   WHERE {{usergroup}}.userid IS NULL' );
    843 		$sql->setInt('userid'  ,$this->userid );
    844 
    845 		return $sql->getAssoc();
    846 	}
    847 
    848 
    849 	
    850 	/**
    851 	 * Benutzer zu einer Gruppe hinzufuegen.
    852 	 * 
    853 	 * @param $groupid int die Gruppen-Id
    854 	 */
    855 	function addGroup( $groupid )
    856 	{
    857 		$db = Db::get();
    858 
    859 		$sql = $db->sql('SELECT MAX(id) FROM {{usergroup}}');
    860 		$usergroupid = intval($sql->getOne())+1;
    861 
    862 		$sql = $db->sql( 'INSERT INTO {{usergroup}} '.
    863 		                '       (id,userid,groupid) '.
    864 		                '       VALUES( {usergroupid},{userid},{groupid} )' );
    865 		$sql->setInt('usergroupid',$usergroupid  );
    866 		$sql->setInt('userid'     ,$this->userid );
    867 		$sql->setInt('groupid'    ,$groupid      );
    868 
    869 		$sql->execute();
    870 	
    871 	}
    872 
    873 
    874 	
    875 	/**
    876 	 * Benutzer aus Gruppe entfernen.
    877 	 * 
    878 	 * @param $groupid int die Gruppen-Id
    879 	 */
    880 	function delGroup( $groupid )
    881 	{
    882 		$db = Db::get();
    883 
    884 		$sql = $db->sql( 'DELETE FROM {{usergroup}} '.
    885 		                '  WHERE userid={userid} AND groupid={groupid}' );
    886 		$sql->setInt   ('userid'  ,$this->userid );
    887 		$sql->setInt   ('groupid' ,$groupid      );
    888 
    889 		$sql->execute();
    890 	}
    891 	
    892 
    893 	/**
    894 	 * Ermitteln aller Berechtigungen des Benutzers.<br>
    895 	 * Diese Daten werden auf der Benutzerseite in der Administration angezeigt.
    896 	 *
    897 	 * @return array
    898 	 */
    899 	public function getAllAcls()
    900 	{
    901 		$groupClause = $this->getGroupClause();
    902 
    903 		$sql = Db::sql( <<<SQL
    904 			              SELECT    {{acl}}.*,
    905 			                        {{object}}.projectid,
    906 			                        {{language}}.name AS languagename
    907 			                   FROM {{acl}}
    908 		                  LEFT JOIN {{object}}
    909 		                         ON {{object}}.id={{acl}}.objectid
    910 		                  LEFT JOIN {{language}}
    911 		                         ON {{language}}.id={{acl}}.languageid
    912 		                  WHERE ( 
    913 										{{acl}}.type = {usertype}  AND {{acl}}.userid={userid}
    914 									 OR {{acl}}.type = {grouptype} AND ($groupClause) 
    915 									 OR {{acl}}.type = {alltype}
    916 									 OR {{acl}}.type = {guesttype}
    917 						  )
    918 		                  ORDER BY {{object}}.projectid,{{acl}}.languageid
    919 SQL
    920 		);
    921 		$sql->setInt( 'userid'   ,$this->userid                );
    922 		$sql->setInt( 'usertype' ,Permission::TYPE_USER  );
    923 		$sql->setInt( 'grouptype',Permission::TYPE_GROUP );
    924 		$sql->setInt( 'alltype'  ,Permission::TYPE_AUTH  );
    925 		$sql->setInt( 'guesttype',Permission::TYPE_GUEST );
    926 
    927 		$aclList = array();
    928 
    929 		foreach($sql->getAll() as $row )
    930 		{
    931 			$permission = new Permission();
    932 			$permission->setDatabaseRow( $row );
    933 			$permission->projectid    = $row['projectid'   ];
    934 			if	( intval($permission->languageid) == 0 )
    935 				$permission->languagename = Language::lang( Messages::ALL_LANGUAGES);
    936 			else
    937 				$permission->languagename = $row['languagename'];
    938 			$aclList[] = $permission;
    939 		}
    940 		
    941 		return $aclList;
    942 	}
    943 
    944 
    945 	/**
    946 	 * Ermitteln aller Berechtigungen.
    947 	 * @return array Berechtigungen
    948 	 */
    949 	function getRights()
    950 	{
    951 		throw new \DomainException('User.class::getRights()');
    952 		
    953 //		$db = \cms\base\DB::get();
    954 //		$var = array();
    955 //
    956 //		// Alle Projekte lesen
    957 //		$sql = $db->sql( 'SELECT id,name FROM {{project}}' );
    958 //		$projects = $sql->getAssoc( $sql );	
    959 //
    960 //		foreach( $projects as $projectid=>$projectname )
    961 //		{
    962 //			$var[$projectid] = array();
    963 //			$var[$projectid]['name'] = $projectname;
    964 //			$var[$projectid]['folders'] = array();
    965 //			$var[$projectid]['rights'] = array();
    966 //
    967 //			$sql = $db->sql( 'SELECT {{acl}}.* FROM {{acl}}'.
    968 //			                '  LEFT JOIN {{folder}} ON {{acl}}.folderid = {{folder}}.id'.
    969 //			                '  WHERE {{folder}}.projectid={projectid}'.
    970 //			                '    AND {{acl}}.userid={userid}' );
    971 //			$sql->setInt('projectid',$projectid    );
    972 //			$sql->setInt('userid'   ,$this->userid );
    973 //			
    974 //			$acls = $sql->getAll( $sql );
    975 //
    976 //			foreach( $acls as $acl )
    977 //			{
    978 //				$aclid = $acl['id'];
    979 //				$folder = new Folder( $acl['folderid'] );
    980 //				$folder->load();
    981 //				$var[$projectid]['rights'][$aclid] = $acl;
    982 //				$var[$projectid]['rights'][$aclid]['foldername'] = implode(' &raquo; ',$folder->parentfolder( false,true ));
    983 //				$var[$projectid]['rights'][$aclid]['delete_url'] = Html::url(array('action'=>'user','subaction'=>'delright','aclid'=>$aclid));
    984 //			}
    985 //			
    986 //			$sql = $db->sql( 'SELECT id FROM {{folder}}'.
    987 //			                '  WHERE projectid={projectid}' );
    988 //			$sql->setInt('projectid',$projectid);
    989 //			$folders = $sql->getCol( $sql );
    990 //
    991 //			$var[$projectid]['folders'] = array();
    992 //
    993 //			foreach( $folders as $folderid )
    994 //			{
    995 //				$folder = new Folder( $folderid );
    996 //				$folder->load();
    997 //				$var[$projectid]['folders'][$folderid] = implode(' &raquo; ',$folder->parentfolder( false,true ));
    998 //			}
    999 //
   1000 //			asort( $var[$projectid]['folders'] );
   1001 //		}
   1002 //		
   1003 //		return $var;
   1004 	}
   1005 
   1006 
   1007 
   1008 
   1009 	/**
   1010 	 * Ermitteln aller zur Verfuegung stehenden Stylesheets
   1011 	 * @return array
   1012 	 */
   1013 	public function getAvailableStyles()
   1014 	{
   1015 		return array_map( function($styleConfig) {
   1016 			return $styleConfig->get('name','');
   1017 		},Configuration::subset('style')->subsets());
   1018 	}
   1019 	
   1020 	/**
   1021 	 * Ueberpruefen des Kennwortes.
   1022 	 *
   1023 	 * Es wird festgestellt, ob das Kennwort dem des Benutzers entspricht.
   1024 	 * Es wird dabei nur gegen die interne Datenbank geprüft. Weitere
   1025 	 * Loginmodule werden nicht aufgerufen!
   1026 	 * Diese Methode darf kein Bestandteil des Logins sein, da nur das Kennwort geprüft wird!
   1027 	 * Kennwortablauf und Token werden nicht geprüft!
   1028 	 */
   1029 	function checkPassword( $password )
   1030 	{
   1031 		$db = Db::get();
   1032 		// Laden des Benutzers aus der Datenbank, um Password-Hash zu ermitteln.
   1033 		$sql = $db->sql( 'SELECT * FROM {{user}}'.
   1034 			' WHERE id={userid}' );
   1035 		$sql->setInt( 'userid',$this->userid );
   1036 		$row_user = $sql->getRow();
   1037 		
   1038 		// Pruefen ob Kennwort mit Datenbank uebereinstimmt.
   1039 		return Password::check(Password::pepperPassword($password),$row_user['password_hash'],$row_user['password_algo']);
   1040 	}
   1041 	
   1042 
   1043 
   1044 	/**
   1045 	 * Ermittelt projektübergreifend die letzten Änderungen des Benutzers.
   1046 	 *
   1047 	 * @return array <string, unknown>
   1048 	 */
   1049 	public function getLastChanges()
   1050 	{
   1051 		$db = Db::get();
   1052 	
   1053 		$sql = $db->sql( <<<SQL
   1054 		SELECT {{object}}.id       as objectid,
   1055 		       {{object}}.filename as filename,
   1056 		       {{object}}.typeid   as typeid,
   1057 		       {{object}}.lastchange_date as lastchange_date,
   1058 		       {{project}}.id      as projectid,
   1059 			   {{project}}.name    as projectname
   1060 		  FROM {{object}}
   1061 		LEFT JOIN {{project}}
   1062 		       ON {{object}}.projectid = {{project}}.id
   1063 		   WHERE {{object}}.lastchange_userid = {userid}
   1064 		ORDER BY {{object}}.lastchange_date DESC
   1065 SQL
   1066 		);
   1067 	
   1068 		$sql->setInt( 'userid', $this->userid );
   1069 	
   1070 		return $sql->getAll();
   1071 	
   1072 	}
   1073 	
   1074 	
   1075 
   1076 	
   1077 	/**
   1078 	 * Erzeugt ein neues OTP-Secret.
   1079 	 */
   1080 	public function renewOTPSecret() {
   1081 	    
   1082 	    $secret = Password::randomHexString(64);
   1083 	    
   1084 	    $stmt = DB::sql('UPDATE {{user}} SET otp_secret={secret} WHERE id={id}');
   1085 	    
   1086 	    $stmt->setString( 'secret', $secret       );
   1087 	    $stmt->setInt   ( 'id'    , $this->userid );
   1088 	    
   1089 	    $stmt->execute();
   1090 	}
   1091 
   1092 
   1093 	public function getId()
   1094 	{
   1095 		return $this->userid;
   1096 	}
   1097 
   1098 
   1099 }
   1100