Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
FilteredRequestTracker
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 4
156
0.00% covered (danger)
0.00%
0 / 1
 markRequestAsFiltered
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 saveState
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
30
 isCurrentAuthenticationFlowFiltered
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 reset
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace MediaWiki\Extension\CentralAuth;
4
5use LogicException;
6use MediaWiki\Auth\AuthManager;
7use MediaWiki\Request\WebRequest;
8use MWExceptionHandler;
9
10/**
11 * A trivial service to track whether authentication providers have been filtered in the current
12 * request, used as shared state between SharedDomainHookHandler (which does the filtering, and
13 * needs to know at the end of the authentication whether filtering was done, but can't easily
14 * preserve that information through a multi-request authentication process) and
15 * CentralAuthSharedDomainPreAuthenticationProvider (which can use AuthManager to preserve state
16 * during an authentication flow).
17 * @see \MediaWiki\Extension\CentralAuth\Hooks\Handlers\SharedDomainHookHandler
18 * @see \MediaWiki\Extension\CentralAuth\CentralAuthSharedDomainPreAuthenticationProvider
19 */
20class FilteredRequestTracker {
21
22    private const SESSION_KEY = 'CentralAuth.filtered';
23
24    /** @var WebRequest|null The authentication request for which we disabled non-SUL3 local providers. */
25    private ?WebRequest $filteredRequest = null;
26
27    /**
28     * Mark the given request as using relaxed security due to the AuthManagerVerifyAuthentication hook.
29     * Note that on authentication requests this is called before the authentication process starts,
30     * and it can be called on non-authentication requests as well (initializing authentication
31     * providers is involved in e.g. skin logic).
32     */
33    public function markRequestAsFiltered( WebRequest $request ): void {
34        $this->filteredRequest = $request;
35    }
36
37    /**
38     * Store filtered state in the authentication session. Must be invoked inside the
39     * authentication flow.
40     * @param AuthManager $authManager
41     */
42    public function saveState( AuthManager $authManager ): void {
43        $arePreviousRequestsFiltered = $authManager->getAuthenticationSessionData( self::SESSION_KEY, false );
44        if ( !$arePreviousRequestsFiltered && $this->filteredRequest ) {
45            $authManager->setAuthenticationSessionData( self::SESSION_KEY, true );
46        } elseif ( $arePreviousRequestsFiltered && !$this->filteredRequest ) {
47            // TODO This is probably possible if the user manipulates request data mid-authentication;
48            //   maybe we should prevent that in a cleaner way. For now just log. The session flag
49            //   will stick, so security-wise the AuthManagerVerifyAuthentication hook will
50            //   handle it correctly.
51            MWExceptionHandler::logException( new LogicException( 'Filter flag applied inconsistently' ) );
52        }
53    }
54
55    /**
56     * Check whether the current authentication flow is using relaxed security due to the
57     * AuthManagerVerifyAuthentication hook (invoked in either the current request or a subsequent
58     * authentication step).
59     */
60    public function isCurrentAuthenticationFlowFiltered( AuthManager $authManager ): bool {
61        $arePreviousRequestsFiltered = $authManager->getAuthenticationSessionData( self::SESSION_KEY, false );
62        if ( $this->filteredRequest && $authManager->getRequest() !== $this->filteredRequest ) {
63            // Maybe a unit test where some but not all services got reset?
64            // Should not happen during real authentication.
65            MWExceptionHandler::logException( new LogicException(
66                'Request changed between AuthManagerFilterProviders and AuthManagerVerifyAuthentication' ) );
67        }
68        return $this->filteredRequest || $arePreviousRequestsFiltered;
69    }
70
71    /**
72     * Reset the state of the tracker.
73     * @internal For use in tests only
74     */
75    public function reset(): void {
76        if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
77            throw new LogicException( 'reset() is for testing only' );
78        }
79        $this->filteredRequest = null;
80    }
81
82}