Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 110
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
CreateOAuthConsumer
0.00% covered (danger)
0.00%
0 / 110
0.00% covered (danger)
0.00%
0 / 2
240
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 71
0.00% covered (danger)
0.00%
0 / 1
210
1<?php
2/**
3 * Example:
4 *
5 * createOAuthConsumer.php
6 *   --callbackIsPrefix
7 *   --callbackUrl="https://foourl"
8 *   --description="Application description"
9 *   --grants="editprotected"
10 *   --grants="createaccount"
11 *   --name="Application name"
12 *   --user="Admin"
13 *   --version="0.2"
14 *   --wiki=default
15 *   --approve
16 *
17 * You can optionally output successful results as json using --jsonOnSuccess
18 */
19
20namespace MediaWiki\Extension\OAuth;
21
22use MediaWiki\Context\RequestContext;
23use MediaWiki\Extension\OAuth\Backend\Consumer;
24use MediaWiki\Extension\OAuth\Backend\Utils;
25use MediaWiki\Extension\OAuth\Control\ConsumerSubmitControl;
26use MediaWiki\Extension\OAuth\Entity\ClientEntity;
27use MediaWiki\Maintenance\Maintenance;
28use MediaWiki\User\User;
29use MediaWiki\Utils\MWRestrictions;
30
31/**
32 * @ingroup Maintenance
33 */
34
35// @codeCoverageIgnoreStart
36if ( getenv( 'MW_INSTALL_PATH' ) ) {
37    $IP = getenv( 'MW_INSTALL_PATH' );
38} else {
39    $IP = __DIR__ . '/../../..';
40}
41
42require_once "$IP/maintenance/Maintenance.php";
43// @codeCoverageIgnoreEnd
44
45class CreateOAuthConsumer extends Maintenance {
46    public function __construct() {
47        parent::__construct();
48        $this->addDescription( "Create an OAuth consumer" );
49        $this->addOption(
50            'oauthVersion',
51            'OAuth version (' . Consumer::OAUTH_VERSION_1 . ' or ' . Consumer::OAUTH_VERSION_2 .
52                ', default ' . Consumer::OAUTH_VERSION_1 . ')',
53            false,
54            true
55        );
56        $this->addOption( 'user', 'User to run the script as', true, true );
57        $this->addOption( 'name', 'Application name', true, true );
58        $this->addOption( 'description', 'Application description', true, true );
59        $this->addOption( 'version', 'Application version', true, true );
60        $this->addOption( 'callbackUrl', 'Callback URL', true, true );
61        $this->addOption(
62            'callbackIsPrefix',
63            'Allow a consumer to specify a callback in requests (OAuth 1 only)'
64        );
65        $this->addOption( 'grants', 'Grants', true, true, false, true );
66        $this->addOption( 'jsonOnSuccess', 'Output successful results as JSON' );
67        $this->addOption( 'approve', 'Accept the consumer' );
68        $this->addOption(
69            'ownerOnly',
70            'Make the consumer only usable by the given user; see ' .
71                'https://www.mediawiki.org/wiki/OAuth/Owner-only_consumers.'
72        );
73        $this->addOption(
74            'oauth2IsNotConfidential',
75            'Mark the client as *not* confidential (OAuth 2 only). By default, clients are confidential.'
76        );
77        $this->addOption(
78            'oauth2GrantTypes',
79            'The OAuth 2 grant types: authorization_code, refresh_token, and/or client_credentials.',
80            false,
81            true,
82            false,
83            true
84        );
85        $this->requireExtension( "OAuth" );
86    }
87
88    public function execute() {
89        $user = User::newFromName( $this->getOption( 'user' ) );
90        if ( !$user->isNamed() ) {
91            $this->fatalError( 'User must be registered' );
92        }
93        if ( $user->getEmail() === '' ) {
94            $this->fatalError( 'User must have an email' );
95        }
96        $oauthVersion = (int)$this->getOption( 'oauthVersion', Consumer::OAUTH_VERSION_1 );
97        if ( !in_array( $oauthVersion, [ Consumer::OAUTH_VERSION_1, Consumer::OAUTH_VERSION_2 ], true ) ) {
98            $this->fatalError(
99                'Invalid oauthVersion, must be ' . Consumer::OAUTH_VERSION_1 .
100                    ' or ' . Consumer::OAUTH_VERSION_2 . '!'
101            );
102        }
103        if ( $oauthVersion === Consumer::OAUTH_VERSION_2 ) {
104            if ( $this->hasOption( 'callbackIsPrefix' ) ) {
105                $this->fatalError( 'callbackIsPrefix is only available in oauthVersion 1' );
106            }
107        } else {
108            if ( $this->hasOption( 'oauth2IsNotConfidential' ) ) {
109                $this->fatalError( 'oauth2IsNotConfidential is only available in oauthVersion 2' );
110            }
111            if ( $this->hasOption( 'oauth2GrantTypes' ) ) {
112                $this->fatalError( 'oauth2GrantTypes is only available in oauthVersion 2' );
113            }
114        }
115
116        $data = [
117            'action' => 'propose',
118            'name' => $this->getOption( 'name' ),
119            'version' => $this->getOption( 'version' ),
120            'description' => $this->getOption( 'description' ),
121            'callbackUrl' => $this->getOption( 'callbackUrl' ),
122            'oauthVersion' => $oauthVersion,
123            'callbackIsPrefix' => $this->hasOption( 'callbackIsPrefix' ),
124            'grants' => '["' . implode( '","', $this->getOption( 'grants' ) ) . '"]',
125            'granttype' => 'normal',
126            'ownerOnly' => $this->hasOption( 'ownerOnly' ),
127            'oauth2IsConfidential' => !$this->hasOption( 'oauth2IsNotConfidential' ),
128            'oauth2GrantTypes' => $this->getOption( 'oauth2GrantTypes', [
129                ClientEntity::GRANT_TYPE_AUTHORIZATION_CODE,
130                ClientEntity::GRANT_TYPE_REFRESH_TOKEN,
131            ] ),
132            'email' => $user->getEmail(),
133            // All wikis
134            'wiki' => '*',
135            // Generate a key
136            'rsaKey' => '',
137            'agreement' => true,
138            'restrictions' => MWRestrictions::newDefault(),
139        ];
140
141        $context = RequestContext::getMain();
142        $context->setUser( $user );
143
144        $dbw = Utils::getOAuthDB( DB_PRIMARY );
145        $control = new ConsumerSubmitControl( $context, $data, $dbw );
146        $status = $control->submit();
147
148        if ( !$status->isGood() ) {
149            $this->fatalError( $status->getMessage()->text() );
150        }
151
152        /** @var Consumer $cmr */
153        $cmr = $status->value['result']['consumer'];
154
155        if ( $this->hasOption( 'approve' ) ) {
156            $data = [
157                'action' => 'approve',
158                'consumerKey'  => $cmr->getConsumerKey(),
159                'reason'       => 'Approved by maintenance script',
160                'changeToken'  => $cmr->getChangeToken( $context ),
161            ];
162            $control = new ConsumerSubmitControl( $context, $data, $dbw );
163            $approveStatus = $control->submit();
164        }
165
166        $outputData = [
167            'created' => true,
168            'id' => $cmr->getId(),
169            'name' => $cmr->getName(),
170            'key' => $cmr->getConsumerKey(),
171            'secret' => Utils::hmacDBSecret( $cmr->getSecretKey() ),
172        ];
173
174        if ( isset( $approveStatus ) ) {
175            $outputData['approved'] = $approveStatus->isGood() ?
176                1 : $approveStatus->getWikiText( false, false, 'en' );
177        }
178
179        if ( $this->hasOption( 'jsonOnSuccess' ) ) {
180            $this->output( json_encode( $outputData ) );
181        } else {
182            foreach ( $outputData as $key => $value ) {
183                $this->output( $key . ': ' . $value . PHP_EOL );
184            }
185        }
186    }
187}
188
189// @codeCoverageIgnoreStart
190$maintClass = CreateOAuthConsumer::class;
191require_once RUN_MAINTENANCE_IF_MAIN;
192// @codeCoverageIgnoreEnd