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 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
TestOAuthConsumer
0.00% covered (danger)
0.00%
0 / 91
0.00% covered (danger)
0.00%
0 / 2
342
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 81
0.00% covered (danger)
0.00%
0 / 1
306
1<?php
2
3namespace MediaWiki\Extension\OAuth;
4
5use Maintenance;
6use MediaWiki\Extension\OAuth\Lib\OAuthConsumer;
7use MediaWiki\Extension\OAuth\Lib\OAuthException;
8use MediaWiki\Extension\OAuth\Lib\OAuthRequest;
9use MediaWiki\Extension\OAuth\Lib\OAuthSignatureMethod_HMAC_SHA1;
10use MediaWiki\Extension\OAuth\Lib\OAuthSignatureMethod_RSA_SHA1;
11
12/**
13 * @ingroup Maintenance
14 */
15if ( getenv( 'MW_INSTALL_PATH' ) ) {
16    $IP = getenv( 'MW_INSTALL_PATH' );
17} else {
18    $IP = __DIR__ . '/../../..';
19}
20
21require_once "$IP/maintenance/Maintenance.php";
22
23class TestOAuthConsumer extends Maintenance {
24    public function __construct() {
25        parent::__construct();
26        $this->addDescription( "Test an OAuth consumer" );
27        $this->addOption( 'consumerKey', 'Consumer key', true, true );
28        $this->addOption( 'consumerSecret', 'Consumer secret', false, true );
29        $this->addOption( 'RSAKeyFile',
30            'File containing the RSA private key for the consumer', false, true
31        );
32        $this->addOption( 'useSSL', 'Use SSL' );
33        $this->addOption( 'verbose', 'Verbose output (e.g. HTTP request/response headers)' );
34        $this->requireExtension( "OAuth" );
35    }
36
37    public function execute() {
38        global $wgServer, $wgScriptPath;
39
40        $consumerKey = $this->getOption( 'consumerKey' );
41        $consumerSecret = $this->getOption( 'consumerSecret' );
42        $rsaKeyFile = $this->getOption( 'RSAKeyFile' );
43        $baseurl = wfExpandUrl(
44            "{$wgServer}{$wgScriptPath}/index.php?title=Special:OAuth", PROTO_CANONICAL );
45        $endpoint = "{$baseurl}/initiate&format=json&oauth_callback=oob";
46
47        $endpoint_acc = "{$baseurl}/token&format=json";
48
49        if ( !$consumerSecret && !$rsaKeyFile ) {
50            $this->error( "Either consumerSecret or RSAKeyFile required!" );
51            $this->maybeHelp( true );
52        }
53
54        $c = new OAuthConsumer( $consumerKey, $consumerSecret );
55        $parsed = parse_url( $endpoint );
56        $params = [];
57        // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
58        parse_str( $parsed['query'], $params );
59        $req_req = OAuthRequest::from_consumer_and_token( $c, null, "GET", $endpoint, $params );
60        if ( $rsaKeyFile ) {
61            try {
62                $sig_method = new class ( $rsaKeyFile ) extends OAuthSignatureMethod_RSA_SHA1 {
63                    /** @var string */
64                    private $privKey;
65                    /** @var string */
66                    private $pubKey;
67
68                    public function __construct( $privKeyFile ) {
69                        $key = file_get_contents( $privKeyFile );
70                        if ( !$key ) {
71                            throw new OAuthException( "Could not read private key file $privKeyFile" );
72                        }
73
74                        $privKey = openssl_pkey_get_private( $key );
75                        if ( !$privKey ) {
76                            throw new OAuthException( "File $privKeyFile does not contain a private key" );
77                        }
78
79                        $details = openssl_pkey_get_details( $privKey );
80                        if ( $details['type'] !== OPENSSL_KEYTYPE_RSA ) {
81                            throw new OAuthException( "Key is not an RSA key" );
82                        }
83                        if ( !$details['key'] ) {
84                            throw new OAuthException( "Could not get public key from private key" );
85                        }
86
87                        $this->privKey = $key;
88                        $this->pubKey = $details['key'];
89                    }
90
91                    protected function fetch_public_cert( &$request ) {
92                        return $this->pubKey;
93                    }
94
95                    protected function fetch_private_cert( &$request ) {
96                        return $this->privKey;
97                    }
98                };
99            } catch ( OAuthException $ex ) {
100                $this->fatalError( $ex->getMessage() );
101            }
102        } else {
103            $sig_method = new OAuthSignatureMethod_HMAC_SHA1();
104        }
105        $req_req->sign_request( $sig_method, $c, null );
106
107        $this->output( "Calling: $req_req\n" );
108
109        $ch = curl_init();
110        curl_setopt( $ch, CURLOPT_URL, (string)$req_req );
111        if ( $this->hasOption( 'useSSL' ) ) {
112            curl_setopt( $ch, CURLOPT_PORT, 443 );
113        }
114        if ( $this->hasOption( 'verbose' ) ) {
115            curl_setopt( $ch, CURLOPT_VERBOSE, true );
116        }
117        curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 );
118        curl_setopt( $ch, CURLOPT_HEADER, 0 );
119        curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
120        $data = curl_exec( $ch );
121
122        if ( !$data ) {
123            $this->output( 'Curl error: ' . curl_error( $ch ) );
124        }
125
126        $this->output( "Returned: $data\n\n" );
127
128        $token = json_decode( $data );
129        if ( !$token || !isset( $token->key ) ) {
130            $this->fatalError( 'Could not fetch token' );
131        }
132
133        $this->output( "Visit $baseurl/authorize" .
134            "&oauth_token={$token->key}&oauth_consumer_key=$consumerKey\n" );
135
136        // ACCESS TOKEN
137        $this->output( "Enter the verification code:\n" );
138        $fh = fopen( "php://stdin", "r" );
139        $line = fgets( $fh );
140
141        $rc = new OAuthConsumer( $token->key, $token->secret );
142        $parsed = parse_url( $endpoint_acc );
143        // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
144        parse_str( $parsed['query'], $params );
145        $params['oauth_verifier'] = trim( $line );
146
147        $acc_req = OAuthRequest::from_consumer_and_token( $c, $rc, "GET", $endpoint_acc, $params );
148        $acc_req->sign_request( $sig_method, $c, $rc );
149
150        $this->output( "Calling: $acc_req\n" );
151
152        unset( $ch );
153        $ch = curl_init();
154        curl_setopt( $ch, CURLOPT_URL, (string)$acc_req );
155        if ( $this->hasOption( 'useSSL' ) ) {
156            curl_setopt( $ch, CURLOPT_PORT, 443 );
157        }
158        if ( $this->hasOption( 'verbose' ) ) {
159            curl_setopt( $ch, CURLOPT_VERBOSE, true );
160        }
161        curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 );
162        curl_setopt( $ch, CURLOPT_HEADER, 0 );
163        curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
164        $data = curl_exec( $ch );
165        if ( !$data ) {
166            $this->output( 'Curl error: ' . curl_error( $ch ) );
167        }
168
169        $this->output( "Returned: $data\n\n" );
170    }
171}
172
173$maintClass = TestOAuthConsumer::class;
174require_once RUN_MAINTENANCE_IF_MAIN;