Completed
Push — search ( 076ba7...2a5267 )
by Simon
16:36 queued 11:48
created

IdentificationVerifier::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 10
c 1
b 0
f 0
cc 1
nc 1
nop 3
crap 1
1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 *                                                                            *
5
 * All code in this file is released into the public domain by the ACC        *
6
 * Development Team. Please see team.json for a list of contributors.         *
7
 ******************************************************************************/
8
9
namespace Waca;
10
11
use PDO;
12
13
use Waca\Exceptions\CurlException;
14
use Waca\Exceptions\EnvironmentException;
15
use Waca\Helpers\HttpHelper;
16
17
/**
18
 * Class IdentificationVerifier
19
 *
20
 * Handles automatically verifying if users are identified with the Wikimedia Foundation or not.  Intended to be used
21
 * as necessary by the User class when a user's "forceidentified" attribute is NULL.
22
 *
23
 * @package  Waca
24
 * @author   Andrew "FastLizard4" Adams
25
 * @category Security-Critical
26
 */
27
class IdentificationVerifier
28
{
29
    /**
30
     * This field is an array of parameters, in key => value format, that should be appended to the Meta Wikimedia
31
     * Web Service Endpoint URL to query if a user is listed on the Identification Noticeboard.  Note that URL encoding
32
     * of these values is *not* necessary; this is done automatically.
33
     *
34
     * @var string[]
35
     * @category Security-Critical
36
     */
37
    private static $apiQueryParameters = array(
38
        'action'   => 'query',
39
        'format'   => 'json',
40
        'prop'     => 'links',
41
        // Populated from SiteConfiguration->getIdentificationNoticeboardPage
42
        'titles'   => '',
43
        // Username of the user to be checked, with User: prefix, goes here!  Set in isIdentifiedOnWiki()
44
        'pltitles' => '',
45
    );
46
    /** @var HttpHelper */
47
    private $httpHelper;
48
    /** @var SiteConfiguration */
0 ignored issues
show
Bug introduced by
The type Waca\SiteConfiguration was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
49
    private $siteConfiguration;
50
    /** @var PdoDatabase */
51
    private $dbObject;
52
53
    /**
54
     * IdentificationVerifier constructor.
55
     *
56
     * @param HttpHelper        $httpHelper
57
     * @param SiteConfiguration $siteConfiguration
58
     * @param PdoDatabase       $dbObject
59
     */
60 1
    public function __construct(HttpHelper $httpHelper, SiteConfiguration $siteConfiguration, PdoDatabase $dbObject)
61
    {
62 1
        $this->httpHelper = $httpHelper;
63 1
        $this->siteConfiguration = $siteConfiguration;
64 1
        $this->dbObject = $dbObject;
65 1
    }
66
67
    /**
68
     * Checks if the given user is identified to the Wikimedia Foundation.
69
     *
70
     * @param string $onWikiName The Wikipedia username of the user
71
     *
72
     * @return bool
73
     * @category Security-Critical
74
     * @throws EnvironmentException
75
     */
76
    public function isUserIdentified($onWikiName)
77
    {
78
        if ($this->checkIdentificationCache($onWikiName)) {
79
            return true;
80
        }
81
        else {
82
            if ($this->isIdentifiedOnWiki($onWikiName)) {
83
                $this->cacheIdentificationStatus($onWikiName);
84
85
                return true;
86
            }
87
            else {
88
                return false;
89
            }
90
        }
91
    }
92
93
    /**
94
     * Checks if the given user has a valid entry in the idcache table.
95
     *
96
     * @param string $onWikiName The Wikipedia username of the user
97
     *
98
     * @return bool
99
     * @category Security-Critical
100
     */
101
    private function checkIdentificationCache($onWikiName)
102
    {
103
        $interval = $this->siteConfiguration->getIdentificationCacheExpiry();
104
105
        $query = <<<SQL
106
			SELECT COUNT(`id`)
107
			FROM `idcache`
108
			WHERE `onwikiusername` = :onwikiname
109
				AND DATE_ADD(`checktime`, INTERVAL {$interval}) >= NOW();
110
SQL;
111
        $stmt = $this->dbObject->prepare($query);
112
        $stmt->bindValue(':onwikiname', $onWikiName, PDO::PARAM_STR);
113
        $stmt->execute();
114
115
        // Guaranteed by the query to only return a single row with a single column
116
        $results = $stmt->fetch(PDO::FETCH_NUM);
117
118
        // I don't expect this to ever be a value other than 0 or 1 since the `onwikiusername` column is declared as a
119
        // unique key - but meh.
120
        return $results[0] != 0;
121
    }
122
123
    /**
124
     * Does pretty much exactly what it says on the label - this method will clear all expired idcache entries from the
125
     * idcache table.  Meant to be called periodically by a maintenance script.
126
     *
127
     * @param SiteConfiguration $siteConfiguration
128
     * @param PdoDatabase       $dbObject
129
     *
130
     * @return void
131
     */
132
    public static function clearExpiredCacheEntries(SiteConfiguration $siteConfiguration, PdoDatabase $dbObject)
133
    {
134
        $interval = $siteConfiguration->getIdentificationCacheExpiry();
135
136
        $query = <<<SQL
137
			DELETE FROM `idcache`
138
			WHERE DATE_ADD(`checktime`, INTERVAL {$interval}) < NOW();
139
SQL;
140
        $dbObject->prepare($query)->execute();
141
    }
142
143
    /**
144
     * This method will add an entry to the idcache that the given Wikipedia user has been verified as identified.  This
145
     * is so we don't have to hit the API every single time we check.  The cache entry is valid for as long as specified
146
     * in the ACC configuration (validity enforced by checkIdentificationCache() and clearExpiredCacheEntries()).
147
     *
148
     * @param string $onWikiName The Wikipedia username of the user
149
     *
150
     * @return void
151
     * @category Security-Critical
152
     */
153
    private function cacheIdentificationStatus($onWikiName)
154
    {
155
        $query = <<<SQL
156
			INSERT INTO `idcache`
157
				(`onwikiusername`)
158
			VALUES
159
				(:onwikiname)
160
			ON DUPLICATE KEY UPDATE
161
				`onwikiusername` = VALUES(`onwikiusername`),
162
				`checktime` = CURRENT_TIMESTAMP;
163
SQL;
164
        $stmt = $this->dbObject->prepare($query);
165
        $stmt->bindValue(':onwikiname', $onWikiName, PDO::PARAM_STR);
166
        $stmt->execute();
167
    }
168
169
    /**
170
     * Queries the Wikimedia API to determine if the specified user is listed on the identification noticeboard.
171
     *
172
     * @param string $onWikiName The Wikipedia username of the user
173
     *
174
     * @return bool
175
     * @throws EnvironmentException
176
     * @category Security-Critical
177
     */
178 1
    private function isIdentifiedOnWiki($onWikiName)
179
    {
180 1
        $strings = new StringFunctions();
181
182
        // First character of Wikipedia usernames is always capitalized.
183 1
        $onWikiName = $strings->upperCaseFirst($onWikiName);
184
185 1
        $parameters = self::$apiQueryParameters;
186 1
        $parameters['pltitles'] = "User:" . $onWikiName;
187 1
        $parameters['titles'] = $this->siteConfiguration->getIdentificationNoticeboardPage();
188
189
        try {
190 1
            $endpoint = $this->siteConfiguration->getMetaWikimediaWebServiceEndpoint();
191 1
            $response = $this->httpHelper->get($endpoint, $parameters);
192 1
            $response = json_decode($response, true);
193
        } catch (CurlException $ex) {
194
            // failed getting identification status, so throw a nicer error.
195
            $message = 'Could not contact metawiki API to determine user\' identification status. '
196
                . 'This is probably a transient error, so please try again.';
197
198
            throw new EnvironmentException($message);
199
        }
200
201 1
        $page = @array_pop($response['query']['pages']);
202
203 1
        return @$page['links'][0]['title'] === "User:" . $onWikiName;
204
    }
205
}
206