Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 63 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
Auth | |
0.00% |
0 / 63 |
|
0.00% |
0 / 9 |
420 | |
0.00% |
0 / 1 |
factory | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getCreateDescriptors | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
autoLogin | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
requestLogin | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getVoterFromSession | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
42 | |||
getVoter | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
6 | |||
newAutoSession | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
newRequestedSession | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\SecurePoll\User; |
4 | |
5 | use InvalidArgumentException; |
6 | use MediaWiki\Extension\SecurePoll\Context; |
7 | use MediaWiki\Extension\SecurePoll\Entities\Election; |
8 | use MediaWiki\Session\SessionManager; |
9 | use MediaWiki\Status\Status; |
10 | |
11 | /** |
12 | * Class for handling guest logins and sessions. Creates Voter objects. |
13 | */ |
14 | class 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 | } |