Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Auth
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 9
420
0.00% covered (danger)
0.00%
0 / 1
 factory
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getCreateDescriptors
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 autoLogin
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 requestLogin
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getVoterFromSession
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
42
 getVoter
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
6
 newAutoSession
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 newRequestedSession
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3namespace MediaWiki\Extension\SecurePoll\User;
4
5use InvalidArgumentException;
6use MediaWiki\Extension\SecurePoll\Context;
7use MediaWiki\Extension\SecurePoll\Entities\Election;
8use MediaWiki\Session\SessionManager;
9use MediaWiki\Status\Status;
10
11/**
12 * Class for handling guest logins and sessions. Creates Voter objects.
13 */
14class Auth {
15    /** @var Context */
16    public $context;
17
18    /**
19     * @var string[] List of available authorization modules (subclasses)
20     */
21    private static $authTypes = [
22        'local' => LocalAuth::class,
23        'remote-mw' => RemoteMWAuth::class,
24    ];
25
26    /**
27     * Create an auth object of the given type
28     * @param Context $context
29     * @param string $type
30     * @return LocalAuth|RemoteMWAuth
31     * @throws InvalidArgumentException
32     */
33    public static function factory( $context, $type ) {
34        if ( !isset( self::$authTypes[$type] ) ) {
35            throw new InvalidArgumentException( "Invalid authentication type: $type" );
36        }
37        $class = self::$authTypes[$type];
38
39        return new $class( $context );
40    }
41
42    /**
43     * Return descriptors for any additional properties or messages this type
44     * requires for poll creation.
45     *
46     * The descriptors should have an additional key, "SecurePoll_type", with
47     * the value being "property" or "message".
48     *
49     * @return array
50     */
51    public static function getCreateDescriptors() {
52        return [];
53    }
54
55    /**
56     * @param Context $context
57     */
58    public function __construct( $context ) {
59        $this->context = $context;
60    }
61
62    /**
63     * Create a voter transparently, without user interaction.
64     * Sessions authorized against local accounts are created this way.
65     * @param Election $election
66     * @return Status
67     */
68    public function autoLogin( $election ) {
69        return Status::newFatal( 'securepoll-not-logged-in' );
70    }
71
72    /**
73     * Create a voter on a direct request from a remote site.
74     * @param Election $election
75     * @return Status
76     */
77    public function requestLogin( $election ) {
78        return $this->autoLogin( $election );
79    }
80
81    /**
82     * Get the voter associated with the current session. Returns false if
83     * there is no session.
84     * @param Election $election
85     * @return Voter|bool
86     */
87    public function getVoterFromSession( $election ) {
88        $session = SessionManager::getGlobalSession();
89        $session->persist();
90        if ( isset( $session['securepoll_voter'][$election->getId()] ) ) {
91            $voterId = $session['securepoll_voter'][$election->getId()];
92
93            # Perform cookie fraud check
94            $status = $this->autoLogin( $election );
95            if ( $status->isOK() ) {
96                $otherVoter = $status->value;
97                '@phan-var Voter $otherVoter'; /** @var Voter $otherVoter */
98                if ( $otherVoter->getId() != $voterId ) {
99                    $otherVoter->addCookieDup( $voterId );
100                    $session['securepoll_voter'][$election->getId()] = $otherVoter->getId();
101
102                    return $otherVoter;
103                }
104            }
105
106            # Check election ID explicitly on DB_PRIMARY
107            $voter = $this->context->getVoter( $voterId, DB_PRIMARY );
108            if ( !$voter || $voter->getElectionId() != $election->getId() ) {
109                return false;
110            } else {
111                return $voter;
112            }
113        } else {
114            return false;
115        }
116    }
117
118    /**
119     * Get a voter object with the relevant parameters.
120     * If no voter exists with those parameters, a new one is created. If one
121     * does exist already, it is returned.
122     * @param array $params
123     * @return Voter
124     */
125    public function getVoter( $params ) {
126        $dbw = $this->context->getDB();
127
128        # This needs to be protected by FOR UPDATE
129        # Otherwise a race condition could lead to duplicate users for a single remote user,
130        # and thus to duplicate votes.
131        $dbw->startAtomic( __METHOD__ );
132        $row = $dbw->newSelectQueryBuilder()
133            ->select( '*' )
134            ->from( 'securepoll_voters' )
135            ->where( [
136                'voter_name' => $params['name'],
137                'voter_election' => $params['electionId'],
138                'voter_domain' => $params['domain'],
139                'voter_url' => $params['url']
140            ] )
141            ->forUpdate()
142            ->caller( __METHOD__ )
143            ->fetchRow();
144        if ( $row ) {
145            $user = $this->context->newVoterFromRow( $row );
146        } else {
147            $user = $this->context->createVoter( $params );
148        }
149        $dbw->endAtomic( __METHOD__ );
150
151        return $user;
152    }
153
154    /**
155     * Create a voter without user interaction, and create a session for it.
156     * @param Election $election
157     * @return Status
158     */
159    public function newAutoSession( $election ) {
160        $status = $this->autoLogin( $election );
161        if ( $status->isGood() ) {
162            $voter = $status->value;
163            '@phan-var Voter $voter'; /** @var Voter $voter */
164            $session = SessionManager::getGlobalSession();
165            $session['securepoll_voter'][$election->getId()] = $voter->getId();
166            $voter->doCookieCheck();
167        }
168
169        return $status;
170    }
171
172    /**
173     * Create a voter on an explicit request, and create a session for it.
174     * @param Election $election
175     * @return Status
176     */
177    public function newRequestedSession( $election ) {
178        $session = SessionManager::getGlobalSession();
179        $session->persist();
180
181        $status = $this->requestLogin( $election );
182        if ( !$status->isOK() ) {
183            return $status;
184        }
185
186        # Do cookie dup flagging
187        $voter = $status->value;
188        '@phan-var Voter $voter'; /** @var Voter $voter */
189        if ( isset( $session['securepoll_voter'][$election->getId()] ) ) {
190            $otherVoterId = $session['securepoll_voter'][$election->getId()];
191            if ( $voter->getId() != $otherVoterId ) {
192                $voter->addCookieDup( $otherVoterId );
193            }
194        } else {
195            $voter->doCookieCheck();
196        }
197
198        $session['securepoll_voter'][$election->getId()] = $voter->getId();
199
200        return $status;
201    }
202}