Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
65.31% covered (warning)
65.31%
64 / 98
16.67% covered (danger)
16.67%
1 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
RequestClient
65.31% covered (warning)
65.31%
64 / 98
16.67% covered (danger)
16.67%
1 / 6
22.18
0.00% covered (danger)
0.00%
0 / 1
 getFixedParams
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 getParamSettings
100.00% covered (success)
100.00%
63 / 63
100.00% covered (success)
100.00%
1 / 1
1
 getUnifiedParams
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 adjustScopes
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
 findAndRemoveScope
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 validate
14.29% covered (danger)
14.29%
1 / 7
0.00% covered (danger)
0.00%
0 / 1
20.74
1<?php
2
3namespace MediaWiki\Extension\OAuth\Rest\Handler;
4
5use FormatJson;
6use MediaWiki\Extension\OAuth\Repository\ScopeRepository;
7use MediaWiki\Rest\LocalizedHttpException;
8use MediaWiki\Rest\Validator\Validator;
9use MWRestrictions;
10use Wikimedia\Message\MessageValue;
11use Wikimedia\ParamValidator\ParamValidator;
12
13/**
14 * Handles the oauth2/client endpoint, which creates
15 * a new consumer for the user
16 */
17class RequestClient extends AbstractClientHandler {
18
19    /**
20     * @inheritDoc
21     */
22    protected function getFixedParams(): array {
23        return [
24            'oauthVersion' => '2.0',
25            'agreement' => true,
26            'action' => 'propose',
27            'granttype' => 'normal',
28            'rsaKey' => '',
29            'restrictions' => MWRestrictions::newDefault(),
30        ];
31    }
32
33    /**
34     * @inheritDoc
35     */
36    public function getParamSettings() {
37        $scopeRepo = new ScopeRepository();
38        return [
39            'name' => [
40                self::PARAM_SOURCE => 'post',
41                ParamValidator::PARAM_TYPE => 'string',
42                ParamValidator::PARAM_REQUIRED => true,
43            ],
44            'version' => [
45                self::PARAM_SOURCE => 'post',
46                ParamValidator::PARAM_TYPE => [ '1.0', '2.0' ],
47                ParamValidator::PARAM_REQUIRED => false,
48                ParamValidator::PARAM_DEFAULT => '1.0',
49            ],
50            'description' => [
51                self::PARAM_SOURCE => 'post',
52                ParamValidator::PARAM_TYPE => 'string',
53                ParamValidator::PARAM_REQUIRED => true,
54            ],
55            'wiki' => [
56                self::PARAM_SOURCE => 'post',
57                ParamValidator::PARAM_TYPE => 'string',
58                ParamValidator::PARAM_REQUIRED => false,
59                ParamValidator::PARAM_DEFAULT => '*',
60            ],
61            'owner_only' => [
62                self::PARAM_SOURCE => 'post',
63                ParamValidator::PARAM_TYPE => 'boolean'
64            ],
65            'callback_url' => [
66                self::PARAM_SOURCE => 'post',
67                ParamValidator::PARAM_TYPE => 'string',
68                ParamValidator::PARAM_REQUIRED => false,
69                ParamValidator::PARAM_DEFAULT => ''
70            ],
71            'callback_is_prefix' => [
72                self::PARAM_SOURCE => 'post',
73                ParamValidator::PARAM_TYPE => 'boolean',
74                ParamValidator::PARAM_REQUIRED => false,
75                ParamValidator::PARAM_DEFAULT => false,
76            ],
77            'email' => [
78                self::PARAM_SOURCE => 'post',
79                ParamValidator::PARAM_TYPE => 'string',
80                ParamValidator::PARAM_REQUIRED => true,
81            ],
82            'is_confidential' => [
83                self::PARAM_SOURCE => 'post',
84                ParamValidator::PARAM_TYPE => 'boolean',
85                ParamValidator::PARAM_REQUIRED => true,
86            ],
87            'grant_types'  => [
88                self::PARAM_SOURCE => 'post',
89                ParamValidator::PARAM_TYPE => 'string',
90                ParamValidator::PARAM_REQUIRED => true,
91                ParamValidator::PARAM_ISMULTI => true
92            ],
93            'scopes' => [
94                self::PARAM_SOURCE => 'post',
95                ParamValidator::PARAM_TYPE => $scopeRepo->getAllowedScopes(),
96                ParamValidator::PARAM_REQUIRED => true,
97                ParamValidator::PARAM_ISMULTI => true
98            ]
99        ];
100    }
101
102    /**
103     * @inheritDoc
104     */
105    protected function getUnifiedParams(): array {
106        $params = parent::getUnifiedParams();
107        return $this->adjustScopes( $params );
108    }
109
110    /**
111     * This is needed to adjust OAuth2 scope array to old grant/granttype params
112     *
113     * @param array $finalParams
114     * @return array
115     */
116    private function adjustScopes( array $finalParams ): array {
117        $scopeRepo = new ScopeRepository();
118        $allowedScopes = $scopeRepo->getAllowedScopes();
119
120        $scopes = array_filter( $finalParams['grants'], static function ( $scope ) use ( $allowedScopes ) {
121            return in_array( $scope, $allowedScopes );
122        } );
123
124        if ( $this->findAndRemoveScope( 'mwoauth-authonly', $scopes ) ) {
125            $finalParams['granttype'] = 'authonly';
126        }
127        if ( $this->findAndRemoveScope( 'mwoauth-authonlyprivate', $scopes ) ) {
128            $finalParams['granttype'] = 'authonlyprivate';
129        }
130
131        if ( !in_array( 'basic', $scopes ) ) {
132            $scopes[] = 'basic';
133        }
134        $finalParams['grants'] = FormatJson::encode( $scopes );
135
136        return $finalParams;
137    }
138
139    /**
140     * @param string $searchKey
141     * @param array &$values
142     * @return bool
143     */
144    private function findAndRemoveScope( $searchKey, array &$values ): bool {
145        $index = array_search( $searchKey, $values );
146        if ( $index === false ) {
147            return false;
148        }
149        array_splice( $values, $index, 1 );
150
151        return true;
152    }
153
154    /**
155     * @inheritDoc
156     */
157    public function validate( Validator $restValidator ) {
158        parent::validate( $restValidator );
159
160        $params = $this->getValidatedParams();
161
162        if (
163            ( isset( $params['owner_only'] ) && !$params['owner_only'] ) &&
164            ( isset( $params['callback_url'] ) && !$params['callback_url'] )
165        ) {
166            throw new LocalizedHttpException(
167                new MessageValue( 'mwoauth-error-missing-callback-url-non-owner', [] ), 400
168            );
169        }
170    }
171}