'string', 'username' => 'string', 'password' => 'string', 'returl' => 'url_local' ]; foreach ($vars as $var => $type) { $this->form[$var] = get_form_var($var, $type, null, INPUT_POST); } // Allow the target_url to be a GET or POST value to help password managers (the target_url can // be stored as a query string parameter in the password manager). $this->form['target_url'] = get_form_var('target_url', 'url_local'); if (isset($this->form['username'])) { // It's easy for extra spaces to appear, especially on a mobile device $this->form['username'] = trim($this->form['username']); } } // Gets the username and password. Returns: Nothing // // $target_url The URL to go to after successful login // $returl The URL to return to eventually public function authGet(?string $target_url=null, ?string $returl=null, ?string $error=null, bool $raw=false) : void { if (!isset($target_url)) { $target_url = $this->form['target_url'] ?? this_page(true); } // Omit the Login link in the header when we're on the login page itself print_header(null, false, true); $action = multisite(this_page()); $this->printLoginForm($action, $target_url, $returl, $error, $raw); exit; } // Returns the parameters ('method', 'action' and 'hidden_inputs') for a // Logon form. Returns an array. public function getLogonFormParams() : ?array { return array( 'action' => multisite('admin.php'), 'method' => Form::METHOD_POST, 'hidden_inputs' => array('target_url' => this_page(true), 'action' => 'QueryName') ); } // Returns the parameters ('method', 'action' and 'hidden_inputs') for a // logoff form. Returns an array of parameters, or null if no form is to be // shown. public function getLogoffFormParams() : ?array { return array( 'action' => multisite('admin.php'), 'method' => Form::METHOD_POST, 'hidden_inputs' => array('target_url' => this_page(true), 'action' => 'SetName', 'username' => '', 'password' => '') ); } public function processForm() : void { if (isset($this->form['action'])) { // Target of the form with sets the URL argument "action=QueryName". // Will eventually return to URL argument "target_url=whatever". if ($this->form['action'] == 'QueryName') { $this->authGet($this->form['target_url']); exit(); // unnecessary because authGet() exits, but just included for clarity } // Target of the form with sets the URL argument "action=SetName". // Will eventually return to URL argument "target_url=whatever". if ($this->form['action'] == 'SetName') { // First make sure the password is valid if (!isset($this->form['username']) || ($this->form['username'] == '')) { $this->logoffUser(); } else { // If we're going to do something then check the CSRF token first. // (Don't check the token before logging off the user because if the session has // expired due to inactivity, the token will be invalid, but that won't matter because // the result will be the same anyway - logging off the user - and we avoid // generating an unnecessary CSRF error message.) Form::checkToken(); // Get a valid user $valid_username = $this->getValidUser($this->form['username'], $this->form['password']); // Successful login. You can't get out of getValidUser() without a valid username and password // ===== 等保整改:登录成功审计 ===== Audit::log('LOGIN_OK', $valid_username); // ===== 等保整改:口令到期 / 存量账号首登 → 强制改密 ===== // 必须在 logonUser()(其内部调用 session_write_close())之前写入会话,否则会丢失 $auth_obj = auth(); if (method_exists($auth_obj, 'needsPasswordChange') && $auth_obj->needsPasswordChange($valid_username)) { $_SESSION['mrbs_force_pwd_change'] = 1; } $this->logonUser($valid_username); if (!empty($this->form['returl'])) { // check to see whether there's a query string already $this->form['target_url'] .= (mb_strpos($this->form['target_url'], '?') === false) ? '?' : '&'; $this->form['target_url'] .= 'returl=' . urlencode($this->form['returl']); } } location_header($this->form['target_url']); // Redirect browser to initial page } } } // Can only return a valid username. If the username and password are not valid it will ask for new ones. protected function getValidUser( #[\SensitiveParameter] ?string $username, #[\SensitiveParameter] ?string $password) : string { if (!isset($this->form['password']) || (($valid_username = auth()->validateUser($this->form['username'], $this->form['password'])) === false)) { // ===== 等保整改:登录失败 / 账号锁定 审计与提示区分 ===== $login_name = $this->form['username'] ?? ''; $auth_obj = auth(); $error = get_vocab('unknown_user'); if (method_exists($auth_obj, 'getLoginBlocked') && $auth_obj->getLoginBlocked()) { global $login_lock_duration; $minutes = (int)ceil(($login_lock_duration ?? 900) / 60); $error = get_vocab('account_locked', $minutes); Audit::log('LOGIN_BLOCKED', $login_name); } else { Audit::log('LOGIN_FAIL', $login_name); } $this->authGet($this->form['target_url'], $this->form['returl'], $error); exit(); // unnecessary because authGet() exits, but just included for clarity } return $valid_username; } protected function logonUser(string $username) : void { } public function logoffUser() : void { } // Displays the login form. // Will eventually return to $target_url with query string returl=$returl // If $error is set then an $error is printed. // If $raw is true then the message is not HTML escaped private function printLoginForm(string $action, ?string $target_url, ?string $returl, ?string $error=null, bool $raw=false) : void { $form = new Form(Form::METHOD_POST); $form->setAttributes(array('class' => 'standard', 'id' => 'logon', 'action' => $action)); // Hidden inputs $hidden_inputs = array('returl' => $returl, 'target_url' => $target_url, 'action' => 'SetName'); $form->addHiddenInputs($hidden_inputs); // Now for the visible fields if (isset($error)) { $p = new ElementP(); $p->setText($error, false, $raw); $form->addElement($p); } $fieldset = new ElementFieldset(); $fieldset->addLegend(get_vocab('please_login')); // The username field if (auth()->canValidateByEmail() && auth()->canValidateByUsername()) { $tag = 'username_or_email'; } elseif (auth()->canValidateByUsername()) { $tag = 'users.name'; } else { $tag = 'users.email'; } $placeholder = get_vocab($tag); $field = new FieldInputText(); $field->setLabel(get_vocab('user')) ->setLabelAttributes(array('title' => $placeholder)) ->setControlAttributes(array('id' => 'username', 'name' => 'username', 'placeholder' => $placeholder, 'required' => true, 'autofocus' => true, 'autocomplete' => 'username')); $fieldset->addElement($field); // The password field $field = new FieldInputPassword(); $field->setLabel(get_vocab('users.password')) ->setControlAttributes(array('id' => 'password', 'name' => 'password', 'autocomplete' => 'current-password')); $fieldset->addElement($field); $form->addElement($fieldset); // The submit button $fieldset = new ElementFieldset(); $field = new FieldInputSubmit(); $field->setControlAttributes(array('value' => get_vocab('login'))); $fieldset->addElement($field); $form->addElement($fieldset); if (auth()->canResetPassword()) { $fieldset = new ElementFieldset(); $field = new FieldDiv(); $a = new ElementA(); $a->setAttribute('href', multisite('reset_password.php')) ->setText(get_vocab('lost_password')); $field->addControl($a); $fieldset->addElement($field); $form->addElement($fieldset); } $form->render(); // Print footer and exit print_footer(true); } // Check we've got the right authentication type for the session scheme. // To be called for those session schemes which require the same // authentication type protected function checkTypeMatchesSession() : void { global $auth; if ($auth['type'] !== $auth['session']) { $class = get_called_class(); $message = "MRBS configuration error: $class needs \$auth['type'] set to '" . $auth['session'] . "'"; die($message); } } }