1<?php
2
3use MediaWiki\Auth\AuthenticationResponse;
4use MediaWiki\Auth\AuthManager;
5use MediaWiki\MediaWikiServices;
6use MediaWiki\Session\SessionManager;
7
8class SpecialUnlinkAccounts extends AuthManagerSpecialPage {
9	protected static $allowedActions = [ AuthManager::ACTION_UNLINK ];
10
11	public function __construct() {
12		parent::__construct( 'UnlinkAccounts' );
13	}
14
15	protected function getLoginSecurityLevel() {
16		return 'UnlinkAccount';
17	}
18
19	protected function getDefaultAction( $subPage ) {
20		return AuthManager::ACTION_UNLINK;
21	}
22
23	/**
24	 * Under which header this special page is listed in Special:SpecialPages.
25	 * @return string
26	 */
27	protected function getGroupName() {
28		return 'users';
29	}
30
31	public function isListed() {
32		return MediaWikiServices::getInstance()->getAuthManager()->canLinkAccounts();
33	}
34
35	protected function getRequestBlacklist() {
36		return $this->getConfig()->get( 'RemoveCredentialsBlacklist' );
37	}
38
39	public function execute( $subPage ) {
40		$this->setHeaders();
41		$this->loadAuth( $subPage );
42
43		if ( !$this->isActionAllowed( $this->authAction ) ) {
44			if ( $this->authAction === AuthManager::ACTION_UNLINK ) {
45				// Looks like there are no linked accounts to unlink
46				$titleMessage = $this->msg( 'cannotunlink-no-provider-title' );
47				$errorMessage = $this->msg( 'cannotunlink-no-provider' );
48				throw new ErrorPageError( $titleMessage, $errorMessage );
49			} else {
50				// user probably back-button-navigated into an auth session that no longer exists
51				// FIXME would be nice to show a message
52				$this->getOutput()->redirect( $this->getPageTitle()->getFullURL( '', false, PROTO_HTTPS ) );
53				return;
54			}
55		}
56
57		$this->outputHeader();
58
59		$status = $this->trySubmit();
60
61		if ( $status === false || !$status->isOK() ) {
62			$this->displayForm( $status );
63			return;
64		}
65
66		/** @var AuthenticationResponse $response */
67		$response = $status->getValue();
68
69		if ( $response->status === AuthenticationResponse::FAIL ) {
70			$this->displayForm( StatusValue::newFatal( $response->message ) );
71			return;
72		}
73
74		$status = StatusValue::newGood();
75		$status->warning( $this->msg( 'unlinkaccounts-success' ) );
76		$this->loadAuth( $subPage, null, true ); // update requests so the unlinked one doesn't show up
77
78		// Reset sessions - if the user unlinked an account because it was compromised,
79		// log attackers out from sessions obtained via that account.
80		$session = $this->getRequest()->getSession();
81		$user = $this->getUser();
82		SessionManager::singleton()->invalidateSessionsForUser( $user );
83		$session->setUser( $user );
84		$session->resetId();
85
86		$this->displayForm( $status );
87	}
88
89	public function handleFormSubmit( $data ) {
90		// unlink requests do not accept user input so repeat parent code but skip call to
91		// AuthenticationRequest::loadRequestsFromSubmission
92		$response = $this->performAuthenticationStep( $this->authAction, $this->authRequests );
93		return Status::newGood( $response );
94	}
95}
96