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