Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
CentralAuthRedirectingPrimaryAuthenticationProvider
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 12
462
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
 getAuthenticationRequests
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 beginPrimaryAuthentication
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
 continuePrimaryAuthentication
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
12
 testUserCanAuthenticate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 providerNormalizeUsername
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 testUserExists
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 providerAllowsAuthenticationDataChange
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 providerChangeAuthenticationData
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 accountCreationType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 beginPrimaryAccountCreation
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCentralLoginUrl
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\CentralAuth;
4
5use IDBAccessObject;
6use LogicException;
7use MediaWiki\Auth\AbstractPrimaryAuthenticationProvider;
8use MediaWiki\Auth\AuthenticationRequest;
9use MediaWiki\Auth\AuthenticationResponse;
10use MediaWiki\Auth\AuthManager;
11use MediaWiki\Extension\CentralAuth\Hooks\Handlers\RedirectingLoginHookHandler;
12use MediaWiki\Extension\CentralAuth\User\CentralAuthUser;
13use MediaWiki\Title\TitleFactory;
14use MediaWiki\User\UserNameUtils;
15use MediaWiki\WikiMap\WikiMap;
16use RuntimeException;
17use StatusValue;
18
19/**
20 * Redirect-based provider which sends the user to another domain, assumed to be
21 * served by the same wiki farm, to log in, and expects to receive the result of
22 * that authentication process when the user returns.
23 */
24class CentralAuthRedirectingPrimaryAuthenticationProvider
25    extends AbstractPrimaryAuthenticationProvider
26{
27    public const NON_LOGIN_WIKI_BUTTONREQUEST_NAME = 'non-loginwiki';
28
29    /**
30     * @internal
31     * @var string The storage key prefix for the URL token used for continuing
32     *   authentication in the central login wiki.
33     */
34    public const RETURN_URL_TOKEN_KEY_PREFIX = 'centralauth-homewiki-return-url-token';
35
36    private TitleFactory $titleFactory;
37    private CentralAuthSessionManager $sessionManager;
38    private CentralAuthUtilityService $centralAuthUtility;
39    private SharedDomainUtils $sharedDomainUtils;
40
41    public function __construct(
42        TitleFactory $titleFactory,
43        CentralAuthSessionManager $sessionManager,
44        CentralAuthUtilityService $centralAuthUtility,
45        SharedDomainUtils $sharedDomainUtils
46    ) {
47        $this->titleFactory = $titleFactory;
48        $this->sessionManager = $sessionManager;
49        $this->centralAuthUtility = $centralAuthUtility;
50        $this->sharedDomainUtils = $sharedDomainUtils;
51    }
52
53    /** @inheritDoc */
54    public function getAuthenticationRequests( $action, array $options ) {
55        if ( $action === AuthManager::ACTION_LOGIN
56            && $this->sharedDomainUtils->isSul3Enabled( $this->manager->getRequest() )
57            && !$this->sharedDomainUtils->isSharedDomain()
58        ) {
59            return [ new CentralAuthRedirectingAuthenticationRequest() ];
60        }
61        return [];
62    }
63
64    /** @inheritDoc */
65    public function beginPrimaryAuthentication( array $reqs ) {
66        $req = CentralAuthRedirectingAuthenticationRequest::getRequestByName(
67            $reqs,
68            self::NON_LOGIN_WIKI_BUTTONREQUEST_NAME
69        );
70
71        if ( !$req ) {
72            return AuthenticationResponse::newAbstain();
73        }
74
75        $this->sharedDomainUtils->assertSul3Enabled( $this->manager->getRequest() );
76        $this->sharedDomainUtils->assertIsNotSharedDomain();
77
78        $returnUrlToken = $this->centralAuthUtility->tokenize(
79            $req->returnToUrl, self::RETURN_URL_TOKEN_KEY_PREFIX, $this->sessionManager
80        );
81
82        $url = wfAppendQuery(
83            $this->getCentralLoginUrl(),
84            [ 'returnUrlToken' => $returnUrlToken ]
85        );
86        return AuthenticationResponse::newRedirect( [ new CentralAuthReturnRequest() ], $url );
87    }
88
89    /** @inheritDoc */
90    public function continuePrimaryAuthentication( array $reqs ) {
91        $this->sharedDomainUtils->assertSul3Enabled( $this->manager->getRequest() );
92        $this->sharedDomainUtils->assertIsNotSharedDomain();
93
94        $req = AuthenticationRequest::getRequestByClass(
95            $reqs, CentralAuthReturnRequest::class
96        );
97
98        if ( !$req ) {
99            throw new LogicException( 'Local authentication failed, please try again.' );
100        }
101
102        $username = $this->centralAuthUtility->detokenize(
103            $req->token,
104            RedirectingLoginHookHandler::LOGIN_CONTINUE_USERNAME_KEY_PREFIX,
105            $this->sessionManager
106        );
107
108        if ( !$username ) {
109            throw new RuntimeException( 'Invalid user token, try to login again' );
110        }
111
112        return AuthenticationResponse::newPass( $username );
113    }
114
115    /** @inheritDoc */
116    public function testUserCanAuthenticate( $username ) {
117        return false;
118    }
119
120    /** @inheritDoc */
121    public function providerNormalizeUsername( $username ) {
122        return null;
123    }
124
125    /** @inheritDoc */
126    public function testUserExists( $username, $flags = IDBAccessObject::READ_NORMAL ) {
127        if ( $this->sharedDomainUtils->isSharedDomain() ) {
128            return false;
129        }
130
131        $username = $this->userNameUtils->getCanonical( $username, UserNameUtils::RIGOR_USABLE );
132        if ( $username === false ) {
133            return false;
134        }
135
136        $centralUser = CentralAuthUser::getInstanceByName( $username );
137        return $centralUser && $centralUser->exists();
138    }
139
140    /** @inheritDoc */
141    public function providerAllowsAuthenticationDataChange( AuthenticationRequest $req, $checkData = true ) {
142        return StatusValue::newGood( 'ignored' );
143    }
144
145    /** @inheritDoc */
146    public function providerChangeAuthenticationData( AuthenticationRequest $req ) {
147    }
148
149    /** @inheritDoc */
150    public function accountCreationType() {
151        return self::TYPE_NONE;
152    }
153
154    /** @inheritDoc */
155    public function beginPrimaryAccountCreation( $user, $creator, array $reqs ) {
156        return AuthenticationResponse::newAbstain();
157    }
158
159    /**
160     * Get the login URL on the shared login domain wiki.
161     *
162     * @return string
163     */
164    private function getCentralLoginUrl(): string {
165        $localUrl = $this->titleFactory->newFromText( 'Special:UserLogin' )->getLocalURL();
166        $url = $this->config->get( 'CentralAuthSsoUrlPrefix' ) . $localUrl;
167
168        return wfAppendQuery( $url, [
169            // At this point, we should just be leaving the local
170            // wiki before hitting the loginwiki.
171            'wikiid' => WikiMap::getCurrentWikiId(),
172            // TODO: Fix T369467
173            'returnto' => 'Main_Page',
174            'usesul3' => '1',
175        ] );
176    }
177
178}