<?php
/**
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 * @file
 */

namespace MediaWiki\Specials;

use LoginHelper;
use MediaWiki\Auth\AuthManager;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MainConfigNames;
use MediaWiki\SpecialPage\LoginSignupSpecialPage;
use MediaWiki\SpecialPage\SpecialPage;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserIdentityUtils;
use StatusValue;

/**
 * Implements Special:UserLogin
 *
 * @ingroup SpecialPage
 * @ingroup Auth
 */
class SpecialUserLogin extends LoginSignupSpecialPage {
	/** @inheritDoc */
	protected static $allowedActions = [
		AuthManager::ACTION_LOGIN,
		AuthManager::ACTION_LOGIN_CONTINUE
	];

	/** @inheritDoc */
	protected static $messages = [
		'authform-newtoken' => 'nocookiesforlogin',
		'authform-notoken' => 'sessionfailure',
		'authform-wrongtoken' => 'sessionfailure',
	];

	private UserIdentityUtils $identityUtils;

	public function __construct( AuthManager $authManager, UserIdentityUtils $identityUtils ) {
		parent::__construct( 'Userlogin' );
		$this->setAuthManager( $authManager );
		$this->identityUtils = $identityUtils;
	}

	public function doesWrites() {
		return true;
	}

	public function isListed() {
		return $this->getAuthManager()->canAuthenticateNow();
	}

	protected function getLoginSecurityLevel() {
		return false;
	}

	protected function getDefaultAction( $subPage ) {
		return AuthManager::ACTION_LOGIN;
	}

	public function getDescription() {
		return $this->msg( 'login' );
	}

	public function setHeaders() {
		// override the page title if we are doing a forced reauthentication
		parent::setHeaders();
		if ( $this->securityLevel && $this->getUser()->isRegistered() ) {
			$this->getOutput()->setPageTitleMsg( $this->msg( 'login-security' ) );
		}
	}

	protected function isSignup() {
		return false;
	}

	protected function beforeExecute( $subPage ) {
		if ( $subPage === 'signup' || $this->getRequest()->getText( 'type' ) === 'signup' ) {
			// B/C for old account creation URLs
			$title = SpecialPage::getTitleFor( 'CreateAccount' );
			$query = array_diff_key( $this->getRequest()->getQueryValues(),
				array_fill_keys( [ 'type', 'title' ], true ) );
			$url = $title->getFullURL( $query, false, PROTO_CURRENT );
			$this->getOutput()->redirect( $url );
			return false;
		}
		return parent::beforeExecute( $subPage );
	}

	/**
	 * Run any hooks registered for logins, then HTTP redirect to
	 * $this->mReturnTo (or Main Page if that's undefined).  Formerly we had a
	 * nice message here, but that's really not as useful as just being sent to
	 * wherever you logged in from.  It should be clear that the action was
	 * successful, given the lack of error messages plus the appearance of your
	 * name in the upper right.
	 * @param bool $direct True if the action was successful just now; false if that happened
	 *    pre-redirection (so this handler was called already)
	 * @param StatusValue|null $extraMessages
	 */
	protected function successfulAction( $direct = false, $extraMessages = null ) {
		$secureLogin = $this->getConfig()->get( MainConfigNames::SecureLogin );

		$user = $this->targetUser ?: $this->getUser();
		$session = $this->getRequest()->getSession();

		$injected_html = '';
		if ( $direct ) {
			$user->touch();

			$this->clearToken();

			if ( $user->requiresHTTPS() ) {
				$this->mStickHTTPS = true;
			}
			$session->setForceHTTPS( $secureLogin && $this->mStickHTTPS );

			// If the user does not have a session cookie at this point, they probably need to
			// do something to their browser.
			if ( !$this->hasSessionCookie() ) {
				$this->mainLoginForm( [ /*?*/ ], $session->getProvider()->whyNoSession() );
				// TODO something more specific? This used to use nocookieslogin
				return;
			}

			# Run any hooks; display injected HTML if any, else redirect
			$this->getHookRunner()->onUserLoginComplete(
				$user, $injected_html, $direct );
		}

		if ( $injected_html !== '' || $extraMessages ) {
			$this->showSuccessPage( 'success', $this->msg( 'loginsuccesstitle' ),
				'loginsuccess', $injected_html, $extraMessages );
		} else {
			$helper = new LoginHelper( $this->getContext() );
			$helper->showReturnToPage( 'successredirect', $this->mReturnTo, $this->mReturnToQuery,
				$this->mStickHTTPS, $this->mReturnToAnchor );
		}
	}

	protected function getToken() {
		return $this->getRequest()->getSession()->getToken( '', 'login' );
	}

	protected function clearToken() {
		$this->getRequest()->getSession()->resetToken( 'login' );
	}

	protected function getTokenName() {
		return 'wpLoginToken';
	}

	protected function getGroupName() {
		return 'login';
	}

	protected function logAuthResult( $success, UserIdentity $performer, $status = null ) {
		LoggerFactory::getInstance( 'authevents' )->info( 'Login attempt', [
			'event' => 'login',
			'successful' => $success,
			'accountType' => $this->identityUtils->getShortUserTypeInternal( $performer ),
			'status' => strval( $status ),
		] );
	}
}

/**
 * Retain the old class name for backwards compatibility.
 * @deprecated since 1.41
 */
class_alias( SpecialUserLogin::class, 'SpecialUserLogin' );
