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