Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
61.29% covered (warning)
61.29%
19 / 31
50.00% covered (danger)
50.00%
5 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
CentralAuthDatabaseManager
61.29% covered (warning)
61.29%
19 / 31
50.00% covered (danger)
50.00%
5 / 10
39.94
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 resolveDatabaseDomain
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 assertNotReadOnly
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 isReadOnly
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getCentralReadOnlyReason
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 getCentralPrimaryDB
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
1.12
 getCentralReplicaDB
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCentralDBFromRecency
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getLocalDB
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 centralLBHasRecentPrimaryChanges
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\CentralAuth;
4
5use InvalidArgumentException;
6use MediaWiki\Config\ServiceOptions;
7use MediaWiki\Extension\CentralAuth\Config\CAMainConfigNames;
8use MediaWiki\WikiMap\WikiMap;
9use ReadOnlyError;
10use Wikimedia\Rdbms\DBAccessObjectUtils;
11use Wikimedia\Rdbms\IDatabase;
12use Wikimedia\Rdbms\IDBAccessObject;
13use Wikimedia\Rdbms\IReadableDatabase;
14use Wikimedia\Rdbms\LBFactory;
15use Wikimedia\Rdbms\ReadOnlyMode;
16
17/**
18 * Service providing access to the CentralAuth internal database.
19 *
20 * @since 1.37
21 * @author Taavi "Majavah" Väänänen <hi@taavi.wtf>
22 */
23class CentralAuthDatabaseManager {
24
25    /** @internal Only public for service wiring use */
26    public const CONSTRUCTOR_OPTIONS = [
27        'CentralAuthReadOnly',
28    ];
29
30    private ServiceOptions $options;
31    private LBFactory $lbFactory;
32    private ReadOnlyMode $readOnlyMode;
33
34    public function __construct( ServiceOptions $options, LBFactory $lbFactory, ReadOnlyMode $readOnlyMode ) {
35        $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
36        $this->options = $options;
37        $this->lbFactory = $lbFactory;
38        $this->readOnlyMode = $readOnlyMode;
39    }
40
41    /**
42     * Determine the database domain for this CentralAuth instance.
43     *
44     * @return false|string
45     */
46    private function resolveDatabaseDomain() {
47        return $this->lbFactory->getPrimaryDatabase( 'virtual-centralauth' )->getDomainID();
48    }
49
50    /**
51     * Throw an exception if the database is read-only.
52     *
53     * @throws CentralAuthReadOnlyError
54     */
55    public function assertNotReadOnly() {
56        if ( $this->readOnlyMode->isReadOnly() ) {
57            throw new ReadOnlyError;
58        }
59        $reason = $this->getCentralReadOnlyReason();
60        if ( $reason ) {
61            throw new CentralAuthReadOnlyError( $reason );
62        }
63    }
64
65    /**
66     * Determine if either the local or the shared CentralAuth database is
67     * read only. This should determine whether assertNotReadOnly() would
68     * throw.
69     *
70     * @return bool
71     */
72    public function isReadOnly(): bool {
73        return $this->readOnlyMode->isReadOnly()
74            || ( $this->getCentralReadOnlyReason() !== false );
75    }
76
77    /**
78     * Return the reason why either the shared CentralAuth database is read
79     * only, false otherwise.
80     *
81     * @return bool|string
82     */
83    private function getCentralReadOnlyReason() {
84        $configReason = $this->options->get( CAMainConfigNames::CentralAuthReadOnly );
85        if ( $configReason === true ) {
86            return '(no reason given)';
87        } elseif ( $configReason ) {
88            return $configReason;
89        }
90
91        return $this->readOnlyMode->getReason( $this->resolveDatabaseDomain() );
92    }
93
94    /**
95     * @return IDatabase a connection to the CentralAuth database primary.
96     */
97    public function getCentralPrimaryDB(): IDatabase {
98        $this->assertNotReadOnly();
99        return $this->lbFactory->getPrimaryDatabase( 'virtual-centralauth' );
100    }
101
102    /**
103     * @return IReadableDatabase a connection to a CentralAuth database replica
104     */
105    public function getCentralReplicaDB(): IReadableDatabase {
106        return $this->lbFactory->getReplicaDatabase( 'virtual-centralauth' );
107    }
108
109    /**
110     * @param int $recency IDBAccessObject::READ_* constant
111     * @return IReadableDatabase
112     */
113    public function getCentralDBFromRecency( int $recency ): IReadableDatabase {
114        if ( DBAccessObjectUtils::hasFlags( $recency, IDBAccessObject::READ_LATEST ) ) {
115            return $this->getCentralPrimaryDB();
116        } else {
117            return $this->getCentralReplicaDB();
118        }
119    }
120
121    /**
122     * Gets a database connection to the local database based on a wikiId
123     *
124     * @param int $index DB_PRIMARY or DB_REPLICA
125     * @param string $wikiId
126     *
127     * @todo Split to two for IReadableDatabase support or drop entirely
128     *
129     * @return IDatabase
130     * @throws CentralAuthReadOnlyError
131     * @throws InvalidArgumentException
132     */
133    public function getLocalDB( int $index, string $wikiId ): IDatabase {
134        if ( $index !== DB_PRIMARY && $index !== DB_REPLICA ) {
135            throw new InvalidArgumentException( "Unknown index $index, expected DB_PRIMARY or DB_REPLICA" );
136        }
137
138        if ( WikiMap::isCurrentWikiId( $wikiId ) ) {
139            $wikiId = false;
140        }
141
142        return $this->lbFactory->getMainLB( $wikiId )
143            ->getConnection( $index, [], $wikiId );
144    }
145
146    /**
147     * Check hasOrMadeRecentPrimaryChanges() on the CentralAuth load balancer
148     *
149     * @return bool
150     */
151    public function centralLBHasRecentPrimaryChanges() {
152        return $this->lbFactory->getLoadBalancer( 'virtual-centralauth' )->hasOrMadeRecentPrimaryChanges();
153    }
154
155}