Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 66
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 / 66
0.00% covered (danger)
0.00%
0 / 4
210
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 onUserLoginComplete
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
72
 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 / 25
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\Context\RequestContext;
25use MediaWiki\Extension\CentralAuth\CentralAuthTokenManager;
26use MediaWiki\Extension\CentralAuth\Config\CAMainConfigNames;
27use MediaWiki\Extension\CentralAuth\Hooks\CentralAuthHookRunner;
28use MediaWiki\Extension\CentralAuth\SharedDomainUtils;
29use MediaWiki\Extension\CentralAuth\Special\SpecialCentralLogin;
30use MediaWiki\Extension\CentralAuth\User\CentralAuthUser;
31use MediaWiki\Hook\TempUserCreatedRedirectHook;
32use MediaWiki\Hook\UserLoginCompleteHook;
33use MediaWiki\HookContainer\HookContainer;
34use MediaWiki\Logger\LoggerFactory;
35use MediaWiki\Session\Session;
36use MediaWiki\Title\Title;
37use MediaWiki\User\User;
38use MediaWiki\User\UserIdentity;
39use MediaWiki\WikiMap\WikiMap;
40use MWCryptRand;
41
42class LoginCompleteHookHandler implements
43    UserLoginCompleteHook,
44    TempUserCreatedRedirectHook
45{
46
47    private Config $config;
48    private CentralAuthTokenManager $tokenManager;
49    private CentralAuthHookRunner $caHookRunner;
50    private SharedDomainUtils $sharedDomainUtils;
51
52    /**
53     * @param HookContainer $hookContainer
54     * @param Config $config
55     * @param CentralAuthTokenManager $tokenManager
56     * @param SharedDomainUtils $sharedDomainUtils
57     */
58    public function __construct(
59        HookContainer $hookContainer,
60        Config $config,
61        CentralAuthTokenManager $tokenManager,
62        SharedDomainUtils $sharedDomainUtils
63    ) {
64        $this->caHookRunner = new CentralAuthHookRunner( $hookContainer );
65        $this->config = $config;
66        $this->tokenManager = $tokenManager;
67        $this->sharedDomainUtils = $sharedDomainUtils;
68    }
69
70    /**
71     * Start a central login redirect when it seems safe to do so.
72     * Otherwise, trigger edge login on the next request.
73     *
74     * @param User $user
75     * @param string &$inject_html
76     * @param bool|null $direct Was this directly after a login? (see T140853)
77     * @return bool
78     *
79     * @see SpecialCentralLogin
80     */
81    public function onUserLoginComplete( $user, &$inject_html, $direct = null ) {
82        if ( !$this->config->get( CAMainConfigNames::CentralAuthCookies )
83            || !$this->config->get( CAMainConfigNames::CentralAuthLoginWiki )
84            || $this->sharedDomainUtils->isSul3Enabled( RequestContext::getMain()->getRequest() )
85        ) {
86            // Use local sessions only.
87            return true;
88        }
89
90        $logger = LoggerFactory::getInstance( 'CentralAuth' );
91        $centralUser = CentralAuthUser::getInstance( $user );
92
93        $context = RequestContext::getMain();
94        $request = $context->getRequest();
95
96        // Check that this is actually for a special login page view
97        $title = $context->getTitle();
98        if ( $title && ( $title->isSpecial( 'Userlogin' ) ||
99            $title->isSpecial( 'CreateAccount' ) )
100        ) {
101            $logger->debug( 'CentralLogin triggered in UserLoginComplete' );
102            $redirectUrl = $this->getRedirectUrl(
103                $request->getSession(),
104                $centralUser,
105                $request->getVal( 'returnto', '' ),
106                $request->getVal( 'returntoquery', '' ),
107                '',
108                $title->isSpecial( 'CreateAccount' ) ? 'signup' : ''
109            );
110            $context->getOutput()->redirect( $redirectUrl );
111            // Set $inject_html to some text to bypass the LoginForm redirection
112            $inject_html .= '<!-- do CentralAuth redirect -->';
113        }
114
115        return true;
116    }
117
118    /**
119     * Initiate a central login redirect that sets up the central session for the temp user,
120     * then returns.
121     *
122     * @param Session $session
123     * @param UserIdentity $user
124     * @param string $returnTo
125     * @param string $returnToQuery
126     * @param string $returnToAnchor
127     * @param string &$redirectUrl
128     * @return bool
129     *
130     * @see SpecialCentralLogin
131     */
132    public function onTempUserCreatedRedirect(
133        Session $session,
134        UserIdentity $user,
135        string $returnTo,
136        string $returnToQuery,
137        string $returnToAnchor,
138        &$redirectUrl
139    ) {
140        if ( !$this->config->get( CAMainConfigNames::CentralAuthLoginWiki ) ) {
141            return true;
142        }
143
144        $logger = LoggerFactory::getInstance( 'CentralAuth' );
145        $centralUser = CentralAuthUser::getPrimaryInstance( $user );
146
147        $logger->debug( 'CentralLogin triggered in TempUserCreatedRedirect' );
148        $redirectUrl = $this->getRedirectUrl(
149            $session,
150            $centralUser,
151            $returnTo,
152            $returnToQuery,
153            $returnToAnchor,
154            'signup'
155        );
156        return false;
157    }
158
159    /**
160     * Sets up central login so the caller can start it.
161     * - Stores a random-generated login secret, along with generic information about the
162     *   user and the returnTo target, in the local session.
163     * - Composes an URL to the next step of the central login, Special:CentralLogin/start, and
164     *   uses the token store and a query parameter in the URL to pass the secret, and information
165     *   about the user and the session, in a secure way.
166     * - Returns the redirect URL.
167     *
168     * @param Session $session
169     * @param CentralAuthUser $centralUser
170     * @param string $returnTo
171     * @param string $returnToQuery
172     * @param string $returnToAnchor
173     * @param string $loginType 'signup' or the empty string for normal login
174     * @return string
175     *
176     * @see SpecialCentralLogin
177     */
178    private function getRedirectUrl(
179        Session $session,
180        CentralAuthUser $centralUser,
181        $returnTo,
182        $returnToQuery,
183        $returnToAnchor,
184        $loginType
185    ) {
186        // User will be redirected to Special:CentralLogin/start (central wiki),
187        // then redirected back to Special:CentralLogin/complete (this wiki).
188        // Sanity check that "returnto" is not one of the central login pages. If it
189        // is, then clear the "returnto" options (LoginForm will use the main page).
190        $returnToTitle = Title::newFromText( $returnTo );
191        if ( $returnToTitle && $returnToTitle->isSpecial( 'CentralLogin' ) ) {
192            $returnTo = '';
193            $returnToQuery = '';
194        }
195
196        $remember = $session->shouldRememberUser();
197
198        // When POSTs triggered from Special:CentralLogin/start are sent back to
199        // this wiki, the token will be checked to see if it was signed with this.
200        // This is needed as Special:CentralLogin/start only takes a token argument
201        // and we need to make sure an agent requesting such a URL actually initiated
202        // the login request that spawned that token server-side.
203        $secret = MWCryptRand::generateHex( 32 );
204        $session->set( 'CentralAuth:autologin:current-attempt', [
205            'secret' => $secret,
206            'remember' => $remember,
207            'returnTo' => $returnTo,
208            'returnToQuery' => $returnToQuery,
209            'returnToAnchor' => $returnToAnchor,
210            'type' => $loginType
211        ] );
212
213        // Create a new token to pass to Special:CentralLogin/start (central wiki)
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        $token = $this->tokenManager->tokenize( $data, 'central-login-start-token' );
222
223        $query = [ 'token' => $token ];
224        $wiki = WikiMap::getWiki( $this->config->get( CAMainConfigNames::CentralAuthLoginWiki ) );
225        return wfAppendQuery( $wiki->getCanonicalUrl( 'Special:CentralLogin/start' ), $query );
226    }
227}