Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
RemoteMWAuth
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 3
272
0.00% covered (danger)
0.00%
0 / 1
 getCreateDescriptors
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 requestLogin
0.00% covered (danger)
0.00%
0 / 64
0.00% covered (danger)
0.00%
0 / 1
210
 encodeToken
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\SecurePoll\User;
4
5use InvalidArgumentException;
6use MediaWiki\Extension\SecurePoll\Entities\Election;
7use MediaWiki\MediaWikiServices;
8use MediaWiki\Status\Status;
9
10/**
11 * Class for guest login from one MW instance running SecurePoll to another.
12 */
13class RemoteMWAuth extends Auth {
14    public static function getCreateDescriptors() {
15        return [
16            'script-path' => [
17                'label-message' => 'securepoll-create-label-remote_mw_script_path',
18                'type' => 'url',
19                'required' => true,
20                'SecurePoll_type' => 'property',
21            ],
22        ];
23    }
24
25    /**
26     * Create a voter on a direct request from a remote site.
27     * @param Election $election
28     * @return Status
29     */
30    public function requestLogin( $election ) {
31        global $wgRequest, $wgConf;
32
33        $urlParamNames = [
34            'id',
35            'token',
36            'wiki',
37            'site',
38            'lang',
39            'domain'
40        ];
41        $vars = [];
42        $params = [];
43        foreach ( $urlParamNames as $name ) {
44            $value = $wgRequest->getVal( $name );
45            if ( !preg_match( '/^[\w.-]*$/', (string)$value ) ) {
46                throw new InvalidArgumentException( "Invalid parameter: $name" );
47            }
48            $params[$name] = $value;
49            $vars["\$$name"] = $value;
50        }
51
52        $wgConf->loadFullData();
53
54        // Get the site and language from $wgConf, if necessary.
55        if ( !isset( $params['site'] ) || !isset( $params['lang'] ) ) {
56            // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
57            [ $site, $lang ] = $wgConf->siteFromDB( $params['wiki'] );
58            if ( !isset( $params['site'] ) ) {
59                $params['site'] = $site;
60                $vars['$site'] = $site;
61            }
62            if ( !isset( $params['lang'] ) ) {
63                $params['lang'] = $lang;
64                $vars['$lang'] = $lang;
65            }
66        }
67
68        // In some cases it doesn't matter what we pass for $suffix. When it
69        // does, the correct value is $params['site'] unless there is a string
70        // back-mapping for it in $wgConf->suffixes.
71        $suffixes = array_flip( $wgConf->suffixes );
72        $suffix = isset( $suffixes[$params['site']] ) && is_string(
73            $suffixes[$params['site']]
74        ) ? $suffixes[$params['site']] : $params['site'];
75
76        $server = $wgConf->get( 'wgServer', $params['wiki'], $suffix, $params );
77        $params['wgServer'] = $server;
78        $vars["\$wgServer"] = $server;
79
80        $url = $election->getProperty( 'remote-mw-script-path' );
81        $url = strtr( $url, $vars );
82        if ( substr( $url, -1 ) != '/' ) {
83            $url .= '/';
84        }
85        $url .= 'api.php';
86
87        $options = [
88            // Use the default SSL certificate file
89            // Necessary on some versions of cURL, others do this by default
90            CURLOPT_CAINFO => '/etc/ssl/certs/ca-certificates.crt',
91            'timeout' => 20,
92            'postData' => [
93                'action' => 'securepollauth',
94                'id' => $params['id'],
95                'format' => 'json',
96                'token' => $params['token'],
97            ],
98        ];
99
100        $response = MediaWikiServices::getInstance()->getHttpRequestFactory()
101            ->post( $url, $options, __METHOD__ );
102
103        if ( !$response ) {
104            return Status::newFatal( 'securepoll-remote-auth-error' );
105        }
106
107        /** @var array|null $json */
108        $json = json_decode( $response, true );
109        if ( $json === null ) {
110            return Status::newFatal( 'securepoll-remote-parse-error' );
111        }
112
113        if ( isset( $json['error'] ) ) {
114            return Status::newFatal( $json['error']['code'] );
115        }
116
117        $params = $json['securepollauth'];
118        $params['type'] = 'remote-mw';
119        $params['electionId'] = $election->getId();
120
121        $qualStatus = $election->getQualifiedStatus( $params );
122        if ( !$qualStatus->isOK() ) {
123            return $qualStatus;
124        }
125
126        return Status::newGood( $this->getVoter( $params ) );
127    }
128
129    /**
130     * Apply a one-way hash function to a string.
131     *
132     * The aim is to encode a user's login token so that it can be transmitted to the
133     * voting server without giving the voting server any special rights on the wiki
134     * (apart from the ability to verify the user). We truncate the hash at 26
135     * hexadecimal digits, to provide 24 bits less information than original token.
136     * This makes discovery of the token difficult even if the hash function is
137     * completely broken.
138     * @param string $token
139     * @return string
140     */
141    public static function encodeToken( $token ) {
142        return substr( sha1( __CLASS__ . '-' . $token ), 0, 26 );
143    }
144}