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    public function getAuthenticationRequests( $action, array $options ) {
21        return [];
22    }
23
24    public function beginSecondaryAuthentication( $user, array $reqs ) {
25        return $this->beginLinkAttempt( $user, AuthManager::AUTHN_STATE );
26    }
27
28    public function continueSecondaryAuthentication( $user, array $reqs ) {
29        return $this->continueLinkAttempt( $user, AuthManager::AUTHN_STATE, $reqs );
30    }
31
32    public function beginSecondaryAccountCreation( $user, $creator, array $reqs ) {
33        return $this->beginLinkAttempt( $user, AuthManager::ACCOUNT_CREATION_STATE );
34    }
35
36    public function continueSecondaryAccountCreation( $user, $creator, array $reqs ) {
37        return $this->continueLinkAttempt( $user, AuthManager::ACCOUNT_CREATION_STATE, $reqs );
38    }
39
40    /**
41     * Begin the link attempt
42     * @param User $user
43     * @param string $key Session key to look in
44     * @return AuthenticationResponse
45     */
46    protected function beginLinkAttempt( $user, $key ) {
47        $session = $this->manager->getRequest()->getSession();
48        $state = $session->getSecret( $key );
49        if ( !is_array( $state ) ) {
50            return AuthenticationResponse::newAbstain();
51        }
52
53        $maybeLink = array_filter( $state['maybeLink'], function ( $req ) use ( $user ) {
54            if ( !$req->action ) {
55                $req->action = AuthManager::ACTION_CHANGE;
56            }
57            $req->username = $user->getName();
58            return $this->manager->allowsAuthenticationDataChange( $req )->isGood();
59        } );
60        if ( !$maybeLink ) {
61            return AuthenticationResponse::newAbstain();
62        }
63
64        $req = new ConfirmLinkAuthenticationRequest( $maybeLink );
65        return AuthenticationResponse::newUI(
66            [ $req ],
67            wfMessage( 'authprovider-confirmlink-message' ),
68            'warning'
69        );
70    }
71
72    /**
73     * Continue the link attempt
74     * @param User $user
75     * @param string $key Session key to look in
76     * @param AuthenticationRequest[] $reqs
77     * @return AuthenticationResponse
78     */
79    protected function continueLinkAttempt( $user, $key, array $reqs ) {
80        $req = ButtonAuthenticationRequest::getRequestByName( $reqs, 'linkOk' );
81        if ( $req ) {
82            return AuthenticationResponse::newPass();
83        }
84
85        $req = AuthenticationRequest::getRequestByClass( $reqs, ConfirmLinkAuthenticationRequest::class );
86        if ( !$req ) {
87            // WTF? Retry.
88            return $this->beginLinkAttempt( $user, $key );
89        }
90
91        $session = $this->manager->getRequest()->getSession();
92        $state = $session->getSecret( $key );
93        if ( !is_array( $state ) ) {
94            return AuthenticationResponse::newAbstain();
95        }
96
97        $maybeLink = [];
98        foreach ( $state['maybeLink'] as $linkReq ) {
99            $maybeLink[$linkReq->getUniqueId()] = $linkReq;
100        }
101        if ( !$maybeLink ) {
102            return AuthenticationResponse::newAbstain();
103        }
104
105        $state['maybeLink'] = [];
106        $session->setSecret( $key, $state );
107
108        $statuses = [];
109        $anyFailed = false;
110        foreach ( $req->confirmedLinkIDs as $id ) {
111            if ( isset( $maybeLink[$id] ) ) {
112                $req = $maybeLink[$id];
113                $req->username = $user->getName();
114                if ( !$req->action ) {
115                    // Make sure the action is set, but don't override it if
116                    // the provider filled it in.
117                    $req->action = AuthManager::ACTION_CHANGE;
118                }
119                $status = $this->manager->allowsAuthenticationDataChange( $req );
120                $statuses[] = [ $req, $status ];
121                if ( $status->isGood() ) {
122                    // We're not changing credentials, just adding a new link
123                    // to an already-known user.
124                    $this->manager->changeAuthenticationData( $req, /* $isAddition */ true );
125                } else {
126                    $anyFailed = true;
127                }
128            }
129        }
130        if ( !$anyFailed ) {
131            return AuthenticationResponse::newPass();
132        }
133
134        $combinedStatus = \MediaWiki\Status\Status::newGood();
135        foreach ( $statuses as [ $req, $status ] ) {
136            $descriptionInfo = $req->describeCredentials();
137            $description = wfMessage(
138                'authprovider-confirmlink-option',
139                $descriptionInfo['provider']->text(), $descriptionInfo['account']->text()
140            )->text();
141            if ( $status->isGood() ) {
142                $combinedStatus->error( wfMessage( 'authprovider-confirmlink-success-line', $description ) );
143            } else {
144                $combinedStatus->error( wfMessage(
145                    'authprovider-confirmlink-failed-line', $description, $status->getMessage()->text()
146                ) );
147            }
148        }
149        return AuthenticationResponse::newUI(
150            [
151                new ButtonAuthenticationRequest(
152                    'linkOk', wfMessage( 'ok' ), wfMessage( 'authprovider-confirmlink-ok-help' )
153                )
154            ],
155            $combinedStatus->getMessage( 'authprovider-confirmlink-failed' ),
156            'error'
157        );
158    }
159}