Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
76 / 76
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
ConfirmLinkSecondaryAuthenticationProvider
100.00% covered (success)
100.00%
76 / 76
100.00% covered (success)
100.00%
7 / 7
22
100.00% covered (success)
100.00%
1 / 1
 getAuthenticationRequests
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 beginSecondaryAuthentication
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 continueSecondaryAuthentication
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 beginSecondaryAccountCreation
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 continueSecondaryAccountCreation
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 beginLinkAttempt
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
4
 continueLinkAttempt
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
1 / 1
13
1<?php
2
3namespace MediaWiki\Auth;
4
5use MediaWiki\User\User;
6
7/**
8 * Links third-party authentication to the user's account
9 *
10 * If the user logged into linking provider accounts that aren't linked to a
11 * local user, this provider will prompt the user to link them after a
12 * successful login or account creation.
13 *
14 * To avoid confusing behavior, this provider should be later in the
15 * configuration list than any provider that can abort the authentication
16 * process, so that it is only invoked for successful authentication.
17 */
18class ConfirmLinkSecondaryAuthenticationProvider extends AbstractSecondaryAuthenticationProvider {
19
20    /** @inheritDoc */
21    public function getAuthenticationRequests( $action, array $options ) {
22        return [];
23    }
24
25    /** @inheritDoc */
26    public function beginSecondaryAuthentication( $user, array $reqs ) {
27        return $this->beginLinkAttempt( $user, AuthManager::AUTHN_STATE );
28    }
29
30    /** @inheritDoc */
31    public function continueSecondaryAuthentication( $user, array $reqs ) {
32        return $this->continueLinkAttempt( $user, AuthManager::AUTHN_STATE, $reqs );
33    }
34
35    /** @inheritDoc */
36    public function beginSecondaryAccountCreation( $user, $creator, array $reqs ) {
37        return $this->beginLinkAttempt( $user, AuthManager::ACCOUNT_CREATION_STATE );
38    }
39
40    /** @inheritDoc */
41    public function continueSecondaryAccountCreation( $user, $creator, array $reqs ) {
42        return $this->continueLinkAttempt( $user, AuthManager::ACCOUNT_CREATION_STATE, $reqs );
43    }
44
45    /**
46     * Begin the link attempt
47     * @param User $user
48     * @param string $key Session key to look in
49     * @return AuthenticationResponse
50     */
51    protected function beginLinkAttempt( $user, $key ) {
52        $session = $this->manager->getRequest()->getSession();
53        $state = $session->getSecret( $key );
54        if ( !is_array( $state ) ) {
55            return AuthenticationResponse::newAbstain();
56        }
57
58        $maybeLink = array_filter( $state['maybeLink'], function ( $req ) use ( $user ) {
59            if ( !$req->action ) {
60                $req->action = AuthManager::ACTION_CHANGE;
61            }
62            $req->username = $user->getName();
63            return $this->manager->allowsAuthenticationDataChange( $req )->isGood();
64        } );
65        if ( !$maybeLink ) {
66            return AuthenticationResponse::newAbstain();
67        }
68
69        $req = new ConfirmLinkAuthenticationRequest( $maybeLink );
70        return AuthenticationResponse::newUI(
71            [ $req ],
72            wfMessage( 'authprovider-confirmlink-message' ),
73            'warning'
74        );
75    }
76
77    /**
78     * Continue the link attempt
79     * @param User $user
80     * @param string $key Session key to look in
81     * @param AuthenticationRequest[] $reqs
82     * @return AuthenticationResponse
83     */
84    protected function continueLinkAttempt( $user, $key, array $reqs ) {
85        $req = ButtonAuthenticationRequest::getRequestByName( $reqs, 'linkOk' );
86        if ( $req ) {
87            return AuthenticationResponse::newPass();
88        }
89
90        $req = AuthenticationRequest::getRequestByClass( $reqs, ConfirmLinkAuthenticationRequest::class );
91        if ( !$req ) {
92            // WTF? Retry.
93            return $this->beginLinkAttempt( $user, $key );
94        }
95
96        $session = $this->manager->getRequest()->getSession();
97        $state = $session->getSecret( $key );
98        if ( !is_array( $state ) ) {
99            return AuthenticationResponse::newAbstain();
100        }
101
102        $maybeLink = [];
103        foreach ( $state['maybeLink'] as $linkReq ) {
104            $maybeLink[$linkReq->getUniqueId()] = $linkReq;
105        }
106        if ( !$maybeLink ) {
107            return AuthenticationResponse::newAbstain();
108        }
109
110        $state['maybeLink'] = [];
111        $session->setSecret( $key, $state );
112
113        $statuses = [];
114        $anyFailed = false;
115        foreach ( $req->confirmedLinkIDs as $id ) {
116            if ( isset( $maybeLink[$id] ) ) {
117                $req = $maybeLink[$id];
118                $req->username = $user->getName();
119                if ( !$req->action ) {
120                    // Make sure the action is set, but don't override it if
121                    // the provider filled it in.
122                    $req->action = AuthManager::ACTION_CHANGE;
123                }
124                $status = $this->manager->allowsAuthenticationDataChange( $req );
125                $statuses[] = [ $req, $status ];
126                if ( $status->isGood() ) {
127                    // We're not changing credentials, just adding a new link
128                    // to an already-known user.
129                    $this->manager->changeAuthenticationData( $req, /* $isAddition */ true );
130                } else {
131                    $anyFailed = true;
132                }
133            }
134        }
135        if ( !$anyFailed ) {
136            return AuthenticationResponse::newPass();
137        }
138
139        $combinedStatus = \MediaWiki\Status\Status::newGood();
140        foreach ( $statuses as [ $req, $status ] ) {
141            $descriptionInfo = $req->describeCredentials();
142            $description = wfMessage(
143                'authprovider-confirmlink-option',
144                $descriptionInfo['provider']->text(), $descriptionInfo['account']->text()
145            )->text();
146            if ( $status->isGood() ) {
147                $combinedStatus->error( 'authprovider-confirmlink-success-line', $description );
148            } else {
149                $combinedStatus->error(
150                    'authprovider-confirmlink-failed-line', $description, $status->getMessage()->text()
151                );
152            }
153        }
154        return AuthenticationResponse::newUI(
155            [
156                new ButtonAuthenticationRequest(
157                    'linkOk', wfMessage( 'ok' ), wfMessage( 'authprovider-confirmlink-ok-help' )
158                )
159            ],
160            $combinedStatus->getMessage( 'authprovider-confirmlink-failed' ),
161            'error'
162        );
163    }
164}