Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 97
0.00% covered (danger)
0.00%
0 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
ConsumerAcceptance
0.00% covered (danger)
0.00%
0 / 97
0.00% covered (danger)
0.00%
0 / 19
992
0.00% covered (danger)
0.00%
0 / 1
 getSchema
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
2
 getFieldPermissionChecks
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 newFromToken
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 newFromUserConsumerWiki
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 getId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWiki
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUserId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getConsumerId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAccessToken
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAccessSecret
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getGrants
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAccepted
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOAuthVersion
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 normalizeValues
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 encodeRow
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 decodeRow
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 userCanSee
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 userCanSeePrivate
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 userCanSeeSecret
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\OAuth\Backend;
4
5use MediaWiki\Context\IContextSource;
6use MediaWiki\Json\FormatJson;
7use MediaWiki\MediaWikiServices;
8use MediaWiki\Message\Message;
9use Wikimedia\Rdbms\IDatabase;
10use Wikimedia\Rdbms\IDBAccessObject;
11
12/**
13 * (c) Aaron Schulz 2013, GPL
14 *
15 * @license GPL-2.0-or-later
16 */
17
18/**
19 * Representation of an OAuth consumer acceptance.
20 * Created when the user clicks through the OAuth authorization dialog, this allows
21 * the specified consumer to perform actions in the name of the user
22 * (subject to the grant and wiki restrictions stored in the acceptance object).
23 */
24class ConsumerAcceptance extends MWOAuthDAO {
25    /** @var int Unique ID */
26    protected $id;
27
28    /** @var string Wiki ID the application can be used on (or "*" for all) */
29    protected $wiki;
30
31    /** @var int Publisher user ID (on central wiki) */
32    protected $userId;
33
34    /** @var int */
35    protected $consumerId;
36
37    /** @var string Hex token */
38    protected $accessToken;
39
40    /** @var string Secret HMAC key */
41    protected $accessSecret;
42
43    /** @var string[] List of grants */
44    protected $grants;
45
46    /** @var string TS_MW timestamp of acceptance */
47    protected $accepted;
48
49    /** @var string */
50    protected $oauth_version;
51
52    protected static function getSchema(): array {
53        return [
54            'table'               => 'oauth_accepted_consumer',
55            'fieldColumnMap'      => [
56                'id'              => 'oaac_id',
57                'wiki'            => 'oaac_wiki',
58                'userId'          => 'oaac_user_id',
59                'consumerId'      => 'oaac_consumer_id',
60                'accessToken'     => 'oaac_access_token',
61                'accessSecret'    => 'oaac_access_secret',
62                'grants'          => 'oaac_grants',
63                'accepted'        => 'oaac_accepted',
64                'oauth_version'   => 'oaac_oauth_version',
65            ],
66            'idField'             => 'id',
67            'autoIncrField'       => 'id',
68        ];
69    }
70
71    protected static function getFieldPermissionChecks(): array {
72        return [
73            'wiki'          => 'userCanSee',
74            'userId'        => 'userCanSee',
75            'consumerId'    => 'userCanSee',
76            'accessToken'   => 'userCanSeePrivate',
77            'accessSecret'  => 'userCanSeeSecret',
78            'grants'        => 'userCanSee',
79            'accepted'      => 'userCanSee',
80            'oauth_version' => 'userCanSee',
81        ];
82    }
83
84    /**
85     * @param IDatabase $db
86     * @param string $token Access token
87     * @param int $flags ConsumerAcceptance::READ_* bitfield
88     * @return ConsumerAcceptance|bool
89     */
90    public static function newFromToken( IDatabase $db, $token, $flags = 0 ) {
91        $queryBuilder = $db->newSelectQueryBuilder()
92            ->select( array_values( static::getFieldColumnMap() ) )
93            ->from( static::getTable() )
94            ->where( [ 'oaac_access_token' => (string)$token ] )
95            ->caller( __METHOD__ );
96        if ( $flags & IDBAccessObject::READ_LOCKING ) {
97            $queryBuilder->forUpdate();
98        }
99        $row = $queryBuilder->fetchRow();
100
101        if ( $row ) {
102            $consumer = new self();
103            $consumer->loadFromRow( $db, $row );
104            return $consumer;
105        } else {
106            return false;
107        }
108    }
109
110    /**
111     * @param IDatabase $db
112     * @param int $userId of user who authorized (central wiki's id)
113     * @param Consumer $consumer
114     * @param string $wiki wiki associated with the acceptance
115     * @param int $flags ConsumerAcceptance::READ_* bitfield
116     * @param int $oauthVersion
117     * @return ConsumerAcceptance|bool
118     */
119    public static function newFromUserConsumerWiki(
120        IDatabase $db, $userId, $consumer,
121        $wiki, $flags = 0, $oauthVersion = Consumer::OAUTH_VERSION_1
122    ) {
123        $queryBuilder = $db->newSelectQueryBuilder()
124            ->select( array_values( static::getFieldColumnMap() ) )
125            ->from( static::getTable() )
126            ->where( [
127                'oaac_user_id' => $userId,
128                'oaac_consumer_id' => $consumer->getId(),
129                'oaac_oauth_version' => $oauthVersion,
130                'oaac_wiki' => (string)$wiki
131            ] )
132            ->caller( __METHOD__ );
133        if ( $flags & IDBAccessObject::READ_LOCKING ) {
134            $queryBuilder->forUpdate();
135        }
136        $row = $queryBuilder->fetchRow();
137
138        if ( $row ) {
139            $consumer = new self();
140            $consumer->loadFromRow( $db, $row );
141            return $consumer;
142        } else {
143            return false;
144        }
145    }
146
147    /**
148     * Database ID.
149     * @return int
150     */
151    public function getId() {
152        return $this->get( 'id' );
153    }
154
155    /**
156     * Wiki on which the user has authorized the consumer to access their account. Wiki ID or '*'
157     * for all.
158     * @return string
159     */
160    public function getWiki() {
161        return $this->get( 'wiki' );
162    }
163
164    /**
165     * Central user ID of the authorizing user.
166     * @return int
167     */
168    public function getUserId() {
169        return $this->get( 'userId' );
170    }
171
172    /**
173     * Database ID of the consumer.
174     * @return int
175     */
176    public function getConsumerId() {
177        return $this->get( 'consumerId' );
178    }
179
180    /**
181     * The access token for the OAuth protocol
182     * @return string
183     */
184    public function getAccessToken() {
185        return $this->get( 'accessToken' );
186    }
187
188    /**
189     * Secret key used to derive the access secret for the OAuth protocol.
190     * The actual access secret will be calculated via Utils::hmacDBSecret() to mitigate
191     * DB leaks.
192     * @return string
193     */
194    public function getAccessSecret() {
195        return $this->get( 'accessSecret' );
196    }
197
198    /**
199     * The list of grants which have been granted.
200     * @return string[]
201     */
202    public function getGrants() {
203        return $this->get( 'grants' );
204    }
205
206    /**
207     * Date of the authorization, in TS_MW format.
208     * @return string
209     */
210    public function getAccepted() {
211        return $this->get( 'accepted' );
212    }
213
214    /**
215     * @return int
216     */
217    public function getOAuthVersion() {
218        return (int)$this->get( 'oauth_version' );
219    }
220
221    protected function normalizeValues() {
222        $this->userId = (int)$this->userId;
223        $this->consumerId = (int)$this->consumerId;
224        $this->accepted = wfTimestamp( TS_MW, $this->accepted );
225        $this->grants = (array)$this->grants;
226    }
227
228    /** @inheritDoc */
229    protected function encodeRow( IDatabase $db, $row ) {
230        if ( (int)$row['oaac_user_id'] === 0 ) {
231            throw new MWOAuthException( 'mwoauth-consumer-access-no-user', [
232                'consumer_id' => $row['oaac_consumer_id'],
233            ] );
234        }
235        // For compatibility with other wikis in the farm, un-remap some grants
236        foreach ( Consumer::$mapBackCompatGrants as $old => $new ) {
237            // @phan-suppress-next-next-line PhanPossiblyInfiniteLoop False positive
238            // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
239            while ( ( $i = array_search( $new, $row['oaac_grants'], true ) ) !== false ) {
240                $row['oaac_grants'][$i] = $old;
241            }
242        }
243
244        $row['oaac_grants'] = FormatJson::encode( $row['oaac_grants'] );
245        $row['oaac_accepted'] = $db->timestamp( $row['oaac_accepted'] );
246        return $row;
247    }
248
249    /** @inheritDoc */
250    protected function decodeRow( IDatabase $db, $row ) {
251        $row['oaac_grants'] = FormatJson::decode( $row['oaac_grants'], true );
252        $row['oaac_accepted'] = wfTimestamp( TS_MW, $row['oaac_accepted'] );
253
254        // For backwards compatibility, remap some grants
255        foreach ( Consumer::$mapBackCompatGrants as $old => $new ) {
256            // @phan-suppress-next-next-line PhanPossiblyInfiniteLoop False positive
257            // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
258            while ( ( $i = array_search( $old, $row['oaac_grants'], true ) ) !== false ) {
259                $row['oaac_grants'][$i] = $new;
260            }
261        }
262
263        return $row;
264    }
265
266    /**
267     * @param string $name
268     * @param IContextSource $context
269     * @return Message|true
270     */
271    protected function userCanSee( $name, IContextSource $context ) {
272        $centralUserId = Utils::getCentralIdFromLocalUser( $context->getUser() );
273        $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
274
275        if ( $this->userId != $centralUserId
276            && !$permissionManager->userHasRight( $context->getUser(), 'mwoauthviewprivate' )
277        ) {
278            return $context->msg( 'mwoauth-field-private' );
279        } else {
280            return true;
281        }
282    }
283
284    /**
285     * @param string $name
286     * @param IContextSource $context
287     * @return Message|true
288     */
289    protected function userCanSeePrivate( $name, IContextSource $context ) {
290        $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
291
292        if ( !$permissionManager->userHasRight( $context->getUser(), 'mwoauthviewprivate' ) ) {
293            return $context->msg( 'mwoauth-field-private' );
294        } else {
295            return $this->userCanSee( $name, $context );
296        }
297    }
298
299    /**
300     * @param string $name
301     * @param IContextSource $context
302     * @return Message|true
303     */
304    protected function userCanSeeSecret( $name, IContextSource $context ) {
305        return $context->msg( 'mwoauth-field-private' );
306    }
307}