File modules/cms/action/login/LoginLoginAction.class.php

Last commit: Fri Apr 15 14:51:22 2022 +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.
1 <?php 2 namespace cms\action\login; 3 use cms\action\Action; 4 use cms\action\LoginAction; 5 use cms\action\Method; 6 use cms\action\RequestParams; 7 use cms\auth\Auth; 8 use cms\auth\AuthRunner; 9 use cms\base\Configuration; 10 use cms\base\DB; 11 use cms\base\Startup; 12 use cms\model\User; 13 use language\Language; 14 use language\Messages; 15 use logger\Logger; 16 use security\Password; 17 use util\Browser; 18 use util\exception\ObjectNotFoundException; 19 use util\exception\SecurityException; 20 use util\exception\ValidationException; 21 use util\mail\Mail; 22 use util\Request; 23 use util\Session; 24 use util\text\TextMessage; 25 26 27 class LoginLoginAction extends LoginAction implements Method { 28 public function view() { 29 $loginConfig = Configuration::subset('security'); 30 $securityConfig = Configuration::subset('security'); 31 $authenticateConfig = Configuration::subset('authenticate'); 32 33 $authenticateEnabled = $authenticateConfig->is('enable',true); 34 $oidcList = []; 35 36 $oidcConfig = Configuration::subset(['security','oidc']); 37 38 if ( $oidcConfig->is('enabled',true) ) { 39 foreach ( $oidcConfig->subset('provider')->subsets() as $name=>$providerConfig ) { 40 if ( $providerConfig->is('enabled',true)) { 41 $oidcList[ $name ] = $providerConfig->get('label',$name ); 42 } 43 } 44 } 45 46 $this->setTemplateVar('enableUserPasswordLogin',$authenticateEnabled); 47 $this->setTemplateVar('enableOpenIdConnect' ,(boolean)$oidcList ); 48 $this->setTemplateVar('provider' ,$oidcList ); 49 50 $dbids = $this->getSelectableDatabases(); 51 52 if ( ! $dbids ) 53 $this->addWarningFor( null,Messages::NO_DATABASE_CONFIGURATION ); 54 55 $this->setTemplateVar( 'dbids',$dbids ); 56 57 // Database was already connected in the Dispatcher. So we MUST have a db connection here. 58 $dbId = Request::getDatabaseId(); 59 $this->setTemplateVar('dbid',$dbId); 60 61 $this->setTemplateVar('register' ,$loginConfig->get('register' )); 62 $this->setTemplateVar('send_password',$loginConfig->get('send_password')); 63 64 // Versuchen, einen Benutzernamen zu ermitteln, der im Eingabeformular vorausgewählt wird. 65 $username = AuthRunner::getUsername('preselect'); 66 67 $this->setTemplateVar('login_name',$username); 68 69 // If the preselected user is the default user, we have a password. 70 if ( $username == $securityConfig->subset('default')->get('username') ) 71 $this->setTemplateVar('login_password', $securityConfig->subset('default')->get('password') ); 72 } 73 74 75 public function post() { 76 77 Request::setUser(null); // Altes Login entfernen. 78 79 if ( Configuration::subset('login')->is('nologin',false ) ) 80 throw new SecurityException('login disabled'); 81 82 $loginName = $this->request->getAlphanum('login_name' ); 83 $loginPassword = $this->request->getText('login_password'); 84 $newPassword1 = $this->request->getText('password1' ); 85 $newPassword2 = $this->request->getText('password2' ); 86 $token = $this->request->getText('user_token' ); 87 88 89 // Jedes Authentifizierungsmodul durchlaufen, bis ein Login erfolgreich ist. 90 $authResult = AuthRunner::checkLogin('authenticate',$loginName,$loginPassword, $token ); 91 Password::delay(); 92 93 if ( $authResult & Auth::STATUS_PW_EXPIRED ) { 94 95 // Der Benutzer hat zwar ein richtiges Kennwort eingegeben, aber dieses ist abgelaufen. 96 // Wir versuchen hier, das neue zu setzen (sofern eingegeben). 97 if ( $newPassword1 ) 98 { 99 $passwordConfig = Configuration::subset(['security','password']); 100 101 if ( $newPassword1 != $newPassword2 ) 102 throw new ValidationException('password2',Messages::PASSWORDS_DO_NOT_MATCH); 103 elseif ( strlen($newPassword1) < $passwordConfig->get('min_length',10) ) 104 throw new ValidationException('password1',Messages::PASSWORD_MINLENGTH,array('minlength'=>$passwordConfig->get('min_length',10))); 105 else 106 { 107 // Kennwoerter identisch und lang genug. 108 $user = User::loadWithName($loginName,User::AUTH_TYPE_INTERNAL); 109 $user->setPassword( $newPassword1,true ); 110 $loginPassword = $newPassword1; 111 112 $authResult -= Auth::STATUS_PW_EXPIRED; 113 } 114 } 115 } 116 117 if ( $authResult & Auth::STATUS_TOKEN_NEEDED ) 118 // Token falsch. 119 throw new ValidationException('user_token',Messages::LOGIN_FAILED_TOKEN_FAILED ); 120 121 if ( $authResult & Auth::STATUS_PW_EXPIRED ) { 122 123 if ( $authResult & Auth::STATUS_FAILED ) 124 // Anmeldung gescheitert, Benutzer muss Kennwort ?ndern. 125 throw new ValidationException('password1',Messages::LOGIN_FAILED_MUSTCHANGEPASSWORD); 126 } 127 128 $ip = getenv("REMOTE_ADDR"); 129 130 if ( $authResult & Auth::STATUS_FAILED ) { 131 Logger::debug(TextMessage::create('login failed for user ${name} from IP ${ip}', 132 [ 133 'name' => $loginName, 134 'ip' => $ip 135 ] 136 )); 137 138 // Login failed. 139 throw new ValidationException('login_password',Messages::LOGIN_FAILED, ['name' => $loginName] ); 140 } 141 142 if ( $authResult & Auth::STATUS_SUCCESS ) { 143 144 Logger::info(TextMessage::create('Login successful for user ${0}', [$loginName])); 145 146 $browser = new Browser(); 147 Logger::debug(TextMessage::create('Login successful for user ${0} from IP ${1} with ${2} (${3})',[$loginName,$ip,$browser->name,$browser->platform])); 148 149 try { 150 // Benutzer über den Benutzernamen laden. 151 $user = User::loadWithName($loginName, User::AUTH_TYPE_INTERNAL, null); 152 $user->setCurrent(); 153 $user->updateLoginTimestamp(); 154 155 if ($user->passwordAlgo != Password::bestAlgoAvailable()) 156 // Re-Hash the password with a better hash algo. 157 $user->setPassword($loginPassword); 158 159 } catch (ObjectNotFoundException $ex) { 160 // Benutzer wurde zwar authentifiziert, ist aber in der 161 // internen Datenbank nicht vorhanden 162 if (Configuration::subset(['security', 'newuser'])->is('autoadd', true)) { 163 164 if ( Startup::readonly() ) 165 throw new \LogicException('System is readonly so this user cannot be inserted.'); 166 167 // Neue Benutzer in die interne Datenbank uebernehmen. 168 $user = new User(); 169 $user->name = $loginName; 170 $user->fullname = $loginName; 171 $user->persist(); 172 Logger::debug( TextMessage::create('user ${0} authenticated successful and added to internal user table',[$loginName]) ); 173 $user->updateLoginTimestamp(); 174 } else { 175 // Benutzer soll nicht angelegt werden. 176 // Daher ist die Anmeldung hier gescheitert. 177 // Anmeldung gescheitert. 178 Logger::warn( TextMessage::create('user ${0} authenticated successful, but not found in internal user table',[$loginName]) ); 179 180 throw new ValidationException('login_password',Messages::LOGIN_FAILED,['name' => $loginName ]); 181 } 182 } 183 184 // Cookie setzen 185 $this->setCookie(Action::COOKIE_DB_ID ,DB::get()->id ); 186 $this->setCookie(Action::COOKIE_USERNAME,$user->name ); 187 188 if ( $this->request->isTrue('remember') ) { 189 // Sets the login token cookie 190 $this->setCookie(Action::COOKIE_TOKEN ,$user->createNewLoginToken() ); 191 } 192 193 // Anmeldung erfolgreich. 194 if ( Configuration::subset('security')->is('renew_session_login',false) ) 195 $this->recreateSession(); 196 197 // Send mail to user to inform about the new login. 198 if ( $user->mail && Configuration::subset('security')->is('inform_user_about_new_login',true) ) { 199 $mail = new Mail( $user->mail, Messages::MAIL_NEW_LOGIN_SUBJECT, Messages::MAIL_NEW_LOGIN_TEXT ); 200 $browser = new \util\Browser(); 201 $mail->setVar( 'platform',$browser->platform ); 202 $mail->setVar( 'browser' ,$browser->name ); 203 $mail->setVar( 'username',$user->name ); 204 $mail->setVar( 'name' ,$user->getName() ); 205 $mail->send(); 206 } 207 208 $this->addNoticeFor( $user,Messages::LOGIN_OK, array('name' => $user->getName() )); 209 210 // Setting the user-defined language 211 $config = Request::getConfig(); 212 $language = new Language(); 213 $config['language'] = $language->getLanguage($user->language); 214 $config['language']['language_code'] = $user->language; 215 216 Request::setConfig( $config ); 217 218 return; // everything ok, user logged in. 219 } 220 221 throw new \LogicException('Auth module must return either SUCCESS or FAIL, but got '.$authResult); 222 } 223 }
Download modules/cms/action/login/LoginLoginAction.class.php
History 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 13:28:52 +0100 dankert Refactoring: Checkbox values are always sent to the server. In the actions we must test the value with 'isTrue()' 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. Wed, 27 Oct 2021 02:27:59 +0200 Jan Dankert Refactoring: Splitted the mail client into a.) sendmail and b.) smtp. Sun, 14 Mar 2021 23:51:49 +0100 Jan Dankert Refactoring: Using the ValidationException where possible. Wed, 10 Mar 2021 23:51:22 +0100 Jan Dankert Refactoring: Cleaned the Request params. Fri, 26 Feb 2021 01:06:01 +0100 Jan Dankert Refactoring accessing the request parameter values. Mon, 30 Nov 2020 10:31:50 +0100 Jan Dankert Fix: Reset password fail counter after successful login. Sun, 29 Nov 2020 21:46:57 +0100 Jan Dankert Auth modules should only use the Auth::STATUS_* constants as return value. Sat, 28 Nov 2020 00:53:41 +0100 Jan Dankert New: Lock password after a number of login fails. Fri, 27 Nov 2020 20:11:28 +0100 Jan Dankert New: Send mail to user after login and after the password has changed. Thu, 19 Nov 2020 23:25:29 +0100 Jan Dankert Fix: Calculation of enabled databases was totally broken. Thu, 19 Nov 2020 14:49:58 +0100 Jan Dankert Fix: Action::addNotice() is replaced by Action::addNoticeFor() 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 20:42:57 +0100 Jan Dankert Getting/Setting cookies with constants, this is more safe. Wed, 18 Nov 2020 01:46:36 +0100 Jan Dankert Refactoring of model classes: New method persist() and some other cleanups. Tue, 17 Nov 2020 23:51:00 +0100 Jan Dankert Refactoring: Every Actionmethod has now its own class.