Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.23% covered (success)
98.23%
111 / 113
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
ListClients
98.23% covered (success)
98.23%
111 / 113
83.33% covered (warning)
83.33%
5 / 6
16
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 needsWriteAccess
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 run
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 getParamSettings
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
1
 getDbResults
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
1 / 1
3
 processDbResults
94.44% covered (success)
94.44%
34 / 36
0.00% covered (danger)
0.00%
0 / 1
8.01
1<?php
2
3namespace MediaWiki\Extension\OAuth\Rest\Handler;
4
5use MediaWiki\Context\RequestContext;
6use MediaWiki\Extension\OAuth\Backend\Consumer;
7use MediaWiki\Extension\OAuth\Backend\Utils;
8use MediaWiki\Extension\OAuth\Control\ConsumerAccessControl;
9use MediaWiki\Extension\OAuth\Entity\ClientEntity;
10use MediaWiki\Extension\OAuth\OAuthServices;
11use MediaWiki\MediaWikiServices;
12use MediaWiki\Rest\LocalizedHttpException;
13use MediaWiki\Rest\ResponseInterface;
14use MediaWiki\Rest\SimpleHandler;
15use Wikimedia\Message\MessageValue;
16use Wikimedia\ParamValidator\ParamValidator;
17use Wikimedia\Rdbms\IConnectionProvider;
18use Wikimedia\Rdbms\IResultWrapper;
19use Wikimedia\Rdbms\SelectQueryBuilder;
20
21/**
22 * Handles the oauth2/consumers endpoint, which returns
23 * a list of registered consumers for the user
24 */
25class ListClients extends SimpleHandler {
26
27    /** @var string[] */
28    protected $propertyMapping = [
29        'id' => 'oarc_id',
30        'client_key' => 'oarc_consumer_key',
31        'name' => 'oarc_name',
32        'version' => 'oarc_version',
33        'email' => 'oarc_email',
34        'callback_url' => 'oarc_callback_url',
35        'scopes' => 'oarc_grants',
36        'registration' => 'oarc_registration',
37        'stage' => 'oarc_stage',
38        'oauth_version' => 'oarc_oauth_version',
39        'description' => 'oarc_description',
40        'allowed_grants' => 'oarc_oauth2_allowed_grants',
41        'restrictions' => 'oarc_restrictions',
42        'user_id' => 'oarc_user_id',
43        'callback_is_prefix' => 'oarc_callback_is_prefix',
44        'email_authenticated' => 'oarc_email_authenticated',
45        'developer_agreement' => 'oarc_developer_agreement',
46        'owner_only' => 'oarc_owner_only',
47        'wiki' => 'oarc_wiki',
48        'secret_key' => 'oarc_secret_key',
49        'rsa_key' => 'oarc_rsa_key',
50        'stage_timestamp' => 'oarc_stage_timestamp',
51        'deleted' => 'oarc_deleted',
52        'oauth2_is_confidential' => 'oarc_oauth2_is_confidential',
53    ];
54
55    public function __construct(
56        private readonly IConnectionProvider $connProvider,
57    ) {
58    }
59
60    /**
61     * @return bool
62     */
63    public function needsWriteAccess() {
64        return false;
65    }
66
67    /**
68     * @return ResponseInterface
69     * @throws LocalizedHttpException
70     */
71    public function run(): ResponseInterface {
72        $user = $this->getAuthority()->getUser();
73
74        $centralId = Utils::getCentralIdLookup()->centralIdFromName( $user->getName() );
75        $responseFactory = $this->getResponseFactory();
76
77        if ( !$centralId ) {
78            throw new LocalizedHttpException(
79                new MessageValue( 'rest-nonexistent-user', [ $user->getName() ] ), 404
80            );
81        }
82        $response = $this->getDbResults( $centralId );
83
84        return $responseFactory->createJson( $response );
85    }
86
87    /** @inheritDoc */
88    public function getParamSettings() {
89        return [
90            'limit' => [
91                self::PARAM_SOURCE => 'query',
92                ParamValidator::PARAM_TYPE => 'integer',
93                ParamValidator::PARAM_REQUIRED => false,
94                ParamValidator::PARAM_DEFAULT => 25,
95                self::PARAM_DESCRIPTION => new MessageValue( 'mwoauth-rest-param-desc-limit' ),
96            ],
97            'offset' => [
98                self::PARAM_SOURCE => 'query',
99                ParamValidator::PARAM_TYPE => 'integer',
100                ParamValidator::PARAM_REQUIRED => false,
101                ParamValidator::PARAM_DEFAULT => 0,
102                self::PARAM_DESCRIPTION => new MessageValue( 'mwoauth-rest-param-desc-offset' ),
103            ],
104            'oauth_version' => [
105                self::PARAM_SOURCE => 'query',
106                ParamValidator::PARAM_TYPE => [ '1', '2' ],
107                ParamValidator::PARAM_REQUIRED => false,
108                ParamValidator::PARAM_DEFAULT => '2',
109                self::PARAM_DESCRIPTION => new MessageValue( 'mwoauth-rest-param-desc-oauth_version' ),
110            ],
111            'grant_type' => [
112                self::PARAM_SOURCE => 'query',
113                ParamValidator::PARAM_TYPE => [
114                    ClientEntity::GRANT_TYPE_CLIENT_CREDENTIALS,
115                    ClientEntity::GRANT_TYPE_AUTHORIZATION_CODE,
116                ],
117                ParamValidator::PARAM_REQUIRED => false,
118                self::PARAM_DESCRIPTION => new MessageValue( 'mwoauth-rest-param-desc-grant_type-list' ),
119            ]
120        ];
121    }
122
123    /**
124     * @param int $centralId the user id of calling user
125     * @return array the results
126     */
127    private function getDbResults( int $centralId ) {
128        $dbr = Utils::getOAuthDB( DB_REPLICA );
129
130        $params = $this->getValidatedParams();
131        $limit = $params['limit'];
132        $offset = $params['offset'];
133
134        $oauthVersion = $params['oauth_version'];
135        $conds = [ 'oarc_user_id' => $centralId ];
136        if ( $oauthVersion !== null ) {
137            $conds['oarc_oauth_version'] = (int)$oauthVersion;
138        }
139        if ( isset( $params['grant_type'] ) ) {
140            $conds['oarc_oauth_version'] = Consumer::OAUTH_VERSION_2;
141            $conds[] = 'oarc_oauth2_allowed_grants ' . $dbr->buildLike(
142                $dbr->anyString(),
143                '"' . $params['grant_type'] . '"',
144                $dbr->anyString()
145            );
146        }
147
148        $res = $dbr->newSelectQueryBuilder()
149            ->select( array_values( $this->propertyMapping ) )
150            ->from( 'oauth_registered_consumer' )
151            ->where( $conds )
152            ->orderBy( 'oarc_id', SelectQueryBuilder::SORT_DESC )
153            ->limit( $limit )
154            ->offset( $offset )
155            ->caller( __METHOD__ )
156            ->fetchResultSet();
157
158        $total = $dbr->newSelectQueryBuilder()
159            ->select( 'oarc_consumer_key' )
160            ->from( 'oauth_registered_consumer' )
161            ->where( $conds )
162            ->caller( __METHOD__ )
163            ->fetchRowCount();
164
165        return [
166            'clients' => $this->processDbResults( $res ),
167            'total' => $total
168        ];
169    }
170
171    /**
172     * @param IResultWrapper $res database results, or an empty array if none
173     * @return array consumer data
174     */
175    private function processDbResults( $res ) {
176        $consumers = [];
177        $requestContext = RequestContext::getMain();
178        $user = $this->getAuthority()->getUser();
179        $consumerRepository = OAuthServices::wrap( MediaWikiServices::getInstance() )->getConsumerRepository();
180
181        foreach ( $res as $row ) {
182
183            $consumer = [];
184
185            $cmr = $consumerRepository->newFromRow( $row );
186            $cmrAc = ConsumerAccessControl::wrap( $cmr, $requestContext );
187
188            if ( !$cmrAc ) {
189                continue;
190            }
191
192            $consumer['email'] = $cmrAc->getEmail();
193            $consumer['name'] = $cmrAc->getName();
194            $consumer['version'] = $cmrAc->getVersion();
195            $consumer['callback_url'] = $cmrAc->getCallbackUrl();
196            $consumer['description'] = $cmrAc->getDescription();
197            $consumer['client_key'] = $cmrAc->getConsumerKey();
198            $consumer['owner_only'] = $cmrAc->getOwnerOnly();
199
200            $consumer['stage'] = (int)$cmrAc->getStage();
201            $consumer['oauth_version'] = $cmrAc->getOAuthVersion();
202            $consumer['registration_formatted'] = $requestContext->getLanguage()->userTimeAndDate(
203                $cmrAc->getRegistration(),
204                $user
205            );
206
207            if ( $consumer['oauth_version'] === Consumer::OAUTH_VERSION_2 ) {
208                $consumer['allowed_grants'] = $cmrAc->get( 'oauth2GrantTypes' );
209                $consumer['client_credentials_legacy'] = $cmr instanceof ClientEntity
210                    && $cmr->usesLegacyClientCredentialsBehavior();
211            }
212
213            $consumer['scopes'] = $cmrAc->getGrants();
214            $consumer['restrictions'] = $cmrAc->getRestrictions();
215
216            if ( $cmrAc->getDAO()->isConfigurationBased() ) {
217                $consumer['configuration_based'] = true;
218            }
219
220            foreach ( $consumer as $key => $value ) {
221                if ( is_object( $consumer[$key] ) ) {
222                    unset( $consumer[$key] );
223                }
224            }
225
226            $consumers[] = $consumer;
227        }
228
229        return $consumers;
230    }
231}