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