openrat-cms

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

InternalAuth.class.php (5818B)


      1 <?php
      2 
      3 namespace cms\auth;
      4 
      5 use cms\base\Configuration;
      6 use cms\base\DB as Db;
      7 use cms\base\Startup;
      8 use cms\model\User;
      9 use language\Messages;
     10 use logger\Logger;
     11 use LogicException;
     12 use security\OTP;
     13 use security\Password;
     14 use util\mail\Mail;
     15 
     16 /**
     17  * Authentifizierungsmodul für die interne Benutzerdatenbank.
     18  *
     19  * @author Jan Dankert
     20  *
     21  */
     22 class InternalAuth implements Auth
     23 {
     24 	/**
     25 	 * Ueberpruefen des Kennwortes
     26 	 * ueber die Benutzertabelle in der Datenbank.
     27 	 */
     28 	function login($username, $password, $token)
     29 	{
     30 		// Lesen des Benutzers aus der DB-Tabelle
     31 		$sql = Db::sql(<<<SQL
     32 SELECT * FROM {{user}}
     33  WHERE name={name}
     34 SQL
     35 		);
     36 		$sql->setString('name', $username);
     37 
     38 		$row_user = $sql->getRow();
     39 
     40 		if (empty($row_user)) {
     41 
     42 			// Benutzer ist nicht vorhanden.
     43 			// Trotzdem das Kennwort hashen, um Timingattacken zu verhindern.
     44 			$unusedHash = Password::hash(Password::pepperPassword($password), Password::bestAlgoAvailable());
     45 			if ( DEVELOPMENT )
     46 				Logger::debug('user not found');
     47 			return Auth::STATUS_FAILED ;
     48 		}
     49 
     50 		$lockedUntil = $row_user['password_locked_until'];
     51 		if ( $lockedUntil && $lockedUntil > Startup::getStartTime() ) {
     52 			if ( DEVELOPMENT )
     53 				Logger::debug('user account ist locked until '.date('r',$lockedUntil));
     54 			return Auth::STATUS_FAILED + Auth::STATUS_ACCOUNT_LOCKED; // Password is locked
     55 		}
     56 
     57 		// Pruefen ob Kennwort mit Datenbank uebereinstimmt.
     58 		if (!Password::check(Password::pepperPassword($password), $row_user['password_hash'], $row_user['password_algo'])) {
     59 			// Password does NOT match.
     60 
     61 			// Increase password fail counter
     62 			$sql = Db::sql(<<<SQL
     63 UPDATE {{user}}
     64  SET password_fail_count=password_fail_count+1
     65  WHERE name={name}
     66 SQL
     67 			);
     68 			$sql->setString('name', $username);
     69 			$sql->execute();
     70 
     71 			$row_user['password_fail_count']++;
     72 
     73 			$lockAfter = Configuration::subset(['security','password'])->get('lock_after_fail_count',10);
     74 			if   ( $lockAfter && $row_user['password_fail_count'] % $lockAfter == 0 ) {
     75 				// exponentially increase the lock duration.
     76 				$factor         = pow(2, intval($row_user['password_fail_count']/$lockAfter) - 1 ) ;
     77 				$lockedDuration = Configuration::subset(['security','password'])->get('lock_duration',120) * $factor * 60;
     78 
     79 				$lockedUntil = Startup::getStartTime() + $lockedDuration;
     80 
     81 				$sql = Db::sql(<<<SQL
     82 UPDATE {{user}}
     83  SET password_locked_until={locked_until}
     84  WHERE name={name}
     85 SQL
     86 				);
     87 				$sql->setString('name'        , $username  );
     88 				$sql->setInt   ('locked_until',$lockedUntil);
     89 				$sql->execute();
     90 
     91 				// Inform the user about the lock.
     92 				if   ( $row_user['mail'] ) {
     93 					$mail = new Mail( $row_user['mail'],Messages::MAIL_PASSWORD_LOCKED_SUBJECT,Messages::MAIL_PASSWORD_LOCKED);
     94 					$mail->setVar('username',$row_user['name'    ]                                                         );
     95 					$mail->setVar('name'    ,$row_user['fullname']                                                         );
     96 					$mail->setVar('until'   ,date( \cms\base\Language::lang(Messages::DATE_FORMAT_FULL ), $lockedUntil ) );
     97 					$mail->send();
     98 				}
     99 			}
    100 			Db::get()->commit();
    101 
    102 			return Auth::STATUS_FAILED;
    103 		}
    104 
    105 		// Password match :)
    106 
    107 		// Clear password fail counter
    108 		$sql = Db::sql(<<<SQL
    109 UPDATE {{user}}
    110  SET password_fail_count=0
    111  WHERE name={name}
    112 SQL
    113 		);
    114 		$sql->setString('name', $username);
    115 		$sql->execute();
    116 
    117 		// Behandeln von Klartext-Kennwoertern (Igittigitt).
    118 		if ($row_user['password_algo'] == Password::ALGO_PLAIN) {
    119 			if (Configuration::subset(['security', 'password'] )->is('force_change_if_cleartext',true))
    120 				// Kennwort steht in der Datenbank im Klartext.
    121 				// Das Kennwort muss geaendert werden
    122 				return Auth::STATUS_FAILED + Auth::STATUS_PW_EXPIRED;
    123 
    124 			// Anderenfalls ist das Login zwar moeglich, aber das Kennwort wird automatisch neu gehasht, weil der beste Algo erzwungen wird.
    125 			// Das Klartextkennwort waere danach ueberschrieben.
    126 		}
    127 
    128 		if ($row_user['password_expires'] != null && $row_user['password_expires'] < time()) {
    129 			// Kennwort ist abgelaufen.
    130 
    131 			// Wenn das kennwort abgelaufen ist, kann es eine bestimmte Dauer noch benutzt und geändert werden.
    132 			// Nach Ablauf dieser Dauer wird das Login abgelehnt.
    133 			if ($row_user['password_expires'] + (Configuration::subset('security')->getSeconds('deny_after_expiration_duration',3*24*60*60)) < time())
    134 				return Auth::STATUS_FAILED + Auth::STATUS_PW_EXPIRED; // Abgelaufenes Kennwort wird nicht mehr akzeptiert.
    135 			else
    136 				return Auth::STATUS_SUCCESS + Auth::STATUS_PW_EXPIRED; // Kennwort ist abgelaufen, kann aber noch geändert werden.
    137 		}
    138 
    139 		if ($row_user['totp'] == 1) {
    140 			$user = new User($row_user['id']);
    141 			$user->load();
    142 			if ( DEVELOPMENT )
    143 				Logger::info("valid TOTP tokens are \n".print_r(OTP::getValidTOTPCodes($user->otpSecret),true));
    144 			if ( in_array( $token, OTP::getValidTOTPCodes($user->otpSecret) ) )
    145 				return Auth::STATUS_SUCCESS;
    146 			else
    147 				return Auth::STATUS_FAILED + Auth::STATUS_TOKEN_NEEDED;
    148 		}
    149 		elseif ($row_user['hotp'] == 1) {
    150 			$user = new User($row_user['id']);
    151 			$user->load();
    152 			$validHOTPCodes = OTP::getValidHOTPCodes($user->otpSecret,$user->hotpCount);
    153 			if ( DEVELOPMENT )
    154 				Logger::info("valid HOTP tokens are \n".print_r($validHOTPCodes,true));
    155 			if ( in_array( $token, $validHOTPCodes ) ) {
    156 				// Synchronize the internal counter
    157 				$newCount = array_flip($validHOTPCodes)[$token]+1;
    158 				$sql = Db::sql(<<<SQL
    159 UPDATE {{user}}
    160  SET hotp_counter={count}
    161  WHERE name={name}
    162 SQL
    163 				);
    164 				$sql->setString('name' ,$username );
    165 				$sql->setInt   ('count',$newCount );
    166 				$sql->execute();
    167 				return Auth::STATUS_SUCCESS;
    168 			}
    169 			else
    170 				return Auth::STATUS_FAILED + Auth::STATUS_TOKEN_NEEDED;
    171 		}
    172 
    173 		// Benutzer wurde erfolgreich authentifiziert.
    174 		return Auth::STATUS_SUCCESS;
    175 	}
    176 
    177 	public function username()
    178 	{
    179 		return null;
    180 	}
    181 }