Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
VariablesBlobStore
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
3 / 3
10
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 storeVarDump
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 loadVarDump
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3namespace MediaWiki\Extension\AbuseFilter\Variables;
4
5use InvalidArgumentException;
6use MediaWiki\Json\FormatJson;
7use MediaWiki\Storage\BlobAccessException;
8use MediaWiki\Storage\BlobStore;
9use MediaWiki\Storage\BlobStoreFactory;
10use stdClass;
11
12/**
13 * This service is used to store and load var dumps to a BlobStore
14 */
15class VariablesBlobStore {
16    public const SERVICE_NAME = 'AbuseFilterVariablesBlobStore';
17
18    /** @var VariablesManager */
19    private $varManager;
20
21    /** @var BlobStoreFactory */
22    private $blobStoreFactory;
23
24    /** @var BlobStore */
25    private $blobStore;
26
27    /** @var string|null */
28    private $centralDB;
29
30    /**
31     * @param VariablesManager $varManager
32     * @param BlobStoreFactory $blobStoreFactory
33     * @param BlobStore $blobStore
34     * @param string|null $centralDB
35     */
36    public function __construct(
37        VariablesManager $varManager,
38        BlobStoreFactory $blobStoreFactory,
39        BlobStore $blobStore,
40        ?string $centralDB
41    ) {
42        $this->varManager = $varManager;
43        $this->blobStoreFactory = $blobStoreFactory;
44        $this->blobStore = $blobStore;
45        $this->centralDB = $centralDB;
46    }
47
48    /**
49     * Store a var dump to a BlobStore.
50     *
51     * @param VariableHolder $varsHolder
52     * @param bool $global
53     *
54     * @return string Address of the record
55     */
56    public function storeVarDump( VariableHolder $varsHolder, $global = false ) {
57        // Get all variables yet set and compute old and new wikitext if not yet done
58        // as those are needed for the diff view on top of the abuse log pages
59        $vars = $this->varManager->dumpAllVars( $varsHolder, [ 'old_wikitext', 'new_wikitext' ] );
60
61        // if user_unnamed_ip exists it can't be saved, as var dump blobs are stored in an append-only
62        // database and stored IPs eventually need to be cleared.
63        // Set the value to something safe here, as by now it's been used in the filter and if
64        // logs later need it, it can be reconstructed from afl_ip.
65        if ( isset( $vars[ 'user_unnamed_ip' ] ) && $vars[ 'user_unnamed_ip' ] ) {
66            $vars[ 'user_unnamed_ip' ] = true;
67        }
68
69        // Vars is an array with native PHP data types (non-objects) now
70        $text = FormatJson::encode( $vars );
71
72        $dbDomain = $global ? $this->centralDB : false;
73        $blobStore = $this->blobStoreFactory->newBlobStore( $dbDomain );
74
75        $hints = [
76            BlobStore::DESIGNATION_HINT => 'AbuseFilter',
77            BlobStore::MODEL_HINT => 'AbuseFilter',
78        ];
79        return $blobStore->storeBlob( $text, $hints );
80    }
81
82    /**
83     * Retrieve a var dump from a BlobStore.
84     *
85     * The entire $row is passed through but only the following columns are actually required:
86     * - afl_var_dump: the main variable store to load
87     * - afl_ip: the IP value to use if necessary
88     *
89     * @param stdClass $row
90     *
91     * @return VariableHolder
92     */
93    public function loadVarDump( stdClass $row ): VariableHolder {
94        if ( !isset( $row->afl_var_dump ) || !isset( $row->afl_ip ) ) {
95            throw new InvalidArgumentException( 'Both afl_var_dump and afl_ip must be set' );
96        }
97
98        try {
99            $varDump = $row->afl_var_dump;
100            $blob = $this->blobStore->getBlob( $varDump );
101        } catch ( BlobAccessException $ex ) {
102            return new VariableHolder;
103        }
104
105        $vars = FormatJson::decode( $blob, true );
106        $obj = VariableHolder::newFromArray( $vars );
107        $this->varManager->translateDeprecatedVars( $obj );
108
109        // If user_unnamed_ip was set when afl_var_dump was saved, it was saved as a visibility boolean
110        // and needs to be translated back into an IP
111        // user_unnamed_ip uses afl_ip instead of saving the value because afl_ip gets purged and the blob
112        // that contains user_unnamed_ip can't be modified
113        if (
114            $this->varManager->getVar( $obj, 'user_unnamed_ip', $this->varManager::GET_LAX )->toNative()
115        ) {
116            $obj->setVar( 'user_unnamed_ip', $row->afl_ip );
117        }
118
119        return $obj;
120    }
121}