Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 67
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
LoginCompleteHookHandler
0.00% covered (danger)
0.00%
0 / 67
0.00% covered (danger)
0.00%
0 / 4
182
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 onUserLoginComplete
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
56
 onTempUserCreatedRedirect
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
 getRedirectUrl
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\Extension\CentralAuth\Hooks\Handlers;
22
23use MediaWiki\Config\Config;
24use MediaWiki\Extension\CentralAuth\CentralAuthSessionManager;
25use MediaWiki\Extension\CentralAuth\Hooks\CentralAuthHookRunner;
26use MediaWiki\Extension\CentralAuth\Special\SpecialCentralLogin;
27use MediaWiki\Extension\CentralAuth\User\CentralAuthUser;
28use MediaWiki\Hook\TempUserCreatedRedirectHook;
29use MediaWiki\Hook\UserLoginCompleteHook;
30use MediaWiki\HookContainer\HookContainer;
31use MediaWiki\Logger\LoggerFactory;
32use MediaWiki\Session\Session;
33use MediaWiki\Title\Title;
34use MediaWiki\User\User;
35use MediaWiki\User\UserIdentity;
36use MediaWiki\WikiMap\WikiMap;
37use MWCryptRand;
38use RequestContext;
39
40class LoginCompleteHookHandler implements
41    UserLoginCompleteHook,
42    TempUserCreatedRedirectHook
43{
44    /** @var Config */
45    private $config;
46
47    /** @var CentralAuthSessionManager */
48    private $sessionManager;
49
50    /** @var CentralAuthHookRunner */
51    private $caHookRunner;
52
53    /**
54     * @param HookContainer $hookContainer
55     * @param Config $config
56     * @param CentralAuthSessionManager $sessionManager
57     */
58    public function __construct(
59        HookContainer $hookContainer,
60        Config $config,
61        CentralAuthSessionManager $sessionManager
62    ) {
63        $this->caHookRunner = new CentralAuthHookRunner( $hookContainer );
64        $this->config = $config;
65        $this->sessionManager = $sessionManager;
66    }
67
68    /**
69     * Start a central login redirect when it seems safe to do so.
70     * Otherwise, trigger edge login on the next request.
71     *
72     * @param User $user
73     * @param string &$inject_html
74     * @param bool|null $direct Was this directly after a login? (see T140853)
75     * @return bool
76     *
77     * @see SpecialCentralLogin
78     */
79    public function onUserLoginComplete( $user, &$inject_html, $direct = null ) {
80        if ( !$this->config->get( 'CentralAuthCookies' ) ||
81            !$this->config->get( 'CentralAuthLoginWiki' )
82        ) {
83            // Use local sessions only.
84            return true;
85        }
86
87        $logger = LoggerFactory::getInstance( 'CentralAuth' );
88        $centralUser = CentralAuthUser::getInstance( $user );
89
90        $context = RequestContext::getMain();
91        $request = $context->getRequest();
92
93        // Check that this is actually for a special login page view
94        $title = $context->getTitle();
95        if ( $title && ( $title->isSpecial( 'Userlogin' ) ||
96            $title->isSpecial( 'CreateAccount' ) )
97        ) {
98            $logger->debug( 'CentralLogin triggered in UserLoginComplete' );
99            $redirectUrl = $this->getRedirectUrl(
100                $request->getSession(),
101                $centralUser,
102                $request->getVal( 'returnto', '' ),
103                $request->getVal( 'returntoquery', '' ),
104                '',
105                $title->isSpecial( 'CreateAccount' ) ? 'signup' : ''
106            );
107            $context->getOutput()->redirect( $redirectUrl );
108            // Set $inject_html to some text to bypass the LoginForm redirection
109            $inject_html .= '<!-- do CentralAuth redirect -->';
110        }
111
112        return true;
113    }
114
115    /**
116     * Initiate a central login redirect that sets up the central session for the temp user,
117     * then returns.
118     *
119     * @param Session $session
120     * @param UserIdentity $user
121     * @param string $returnTo
122     * @param string $returnToQuery
123     * @param string $returnToAnchor
124     * @param string &$redirectUrl
125     * @return bool
126     *
127     * @see SpecialCentralLogin
128     */
129    public function onTempUserCreatedRedirect(
130        Session $session,
131        UserIdentity $user,
132        string $returnTo,
133        string $returnToQuery,
134        string $returnToAnchor,
135        &$redirectUrl
136    ) {
137        if ( !$this->config->get( 'CentralAuthLoginWiki' ) ) {
138            return true;
139        }
140
141        $logger = LoggerFactory::getInstance( 'CentralAuth' );
142        $centralUser = CentralAuthUser::getInstance( $user );
143
144        $logger->debug( 'CentralLogin triggered in TempUserCreatedRedirect' );
145        $redirectUrl = $this->getRedirectUrl(
146            $session,
147            $centralUser,
148            $returnTo,
149            $returnToQuery,
150            $returnToAnchor,
151            'signup'
152        );
153        return false;
154    }
155
156    /**
157     * Sets up central login so the caller can start it.
158     * - Stores a random-generated login secret, along with generic information about the
159     *   user and the returnTo target, in the local session.
160     * - Composes an URL to the next step of the central login, Special:CentralLogin/start, and
161     *   uses the token store and a query parameter in the URL to pass the secret, and information
162     *   about the user and the session, in a secure way.
163     * - Returns the redirect URL.
164     *
165     * @param Session $session
166     * @param CentralAuthUser $centralUser
167     * @param string $returnTo
168     * @param string $returnToQuery
169     * @param string $returnToAnchor
170     * @param string $loginType 'signup' or the empty string for normal login
171     * @return string
172     *
173     * @see SpecialCentralLogin
174     */
175    private function getRedirectUrl(
176        Session $session,
177        CentralAuthUser $centralUser,
178        $returnTo,
179        $returnToQuery,
180        $returnToAnchor,
181        $loginType
182    ) {
183        // User will be redirected to Special:CentralLogin/start (central wiki),
184        // then redirected back to Special:CentralLogin/complete (this wiki).
185        // Sanity check that "returnto" is not one of the central login pages. If it
186        // is, then clear the "returnto" options (LoginForm will use the main page).
187        $returnToTitle = Title::newFromText( $returnTo );
188        if ( $returnToTitle && $returnToTitle->isSpecial( 'CentralLogin' ) ) {
189            $returnTo = '';
190            $returnToQuery = '';
191        }
192
193        $remember = $session->shouldRememberUser();
194
195        // When POSTs triggered from Special:CentralLogin/start are sent back to
196        // this wiki, the token will be checked to see if it was signed with this.
197        // This is needed as Special:CentralLogin/start only takes a token argument
198        // and we need to make sure an agent requesting such a URL actually initiated
199        // the login request that spawned that token server-side.
200        $secret = MWCryptRand::generateHex( 32 );
201        $session->set( 'CentralAuth:autologin:current-attempt', [
202            'secret'    => $secret,
203            'remember'      => $remember,
204            'returnTo'      => $returnTo,
205            'returnToQuery' => $returnToQuery,
206            'returnToAnchor' => $returnToAnchor,
207            'type'      => $loginType
208        ] );
209
210        // Create a new token to pass to Special:CentralLogin/start (central wiki)
211        $tokenStore = $this->sessionManager->getTokenStore();
212        $token = MWCryptRand::generateHex( 32 );
213        $key = $this->sessionManager->makeTokenKey( 'central-login-start-token', $token );
214        $data = [
215            'secret'    => $secret,
216            'name'      => $centralUser->getName(),
217            'guid'      => $centralUser->getId(),
218            'wikiId'    => WikiMap::getCurrentWikiId(),
219        ];
220        $this->caHookRunner->onCentralAuthLoginRedirectData( $centralUser, $data );
221        $tokenStore->set( $key, $data, $tokenStore::TTL_MINUTE );
222
223        $query = [ 'token' => $token ];
224
225        $wiki = WikiMap::getWiki( $this->config->get( 'CentralAuthLoginWiki' ) );
226        return wfAppendQuery( $wiki->getCanonicalUrl( 'Special:CentralLogin/start' ), $query );
227    }
228}