This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /** |
||
4 | * OpauthController |
||
5 | * Wraps around Opauth for handling callbacks. |
||
6 | * The SS equivalent of "index.php" and "callback.php" in the Opauth package. |
||
7 | * @author Will Morgan <@willmorgan> |
||
8 | * @author Dan Hensby <@dhensby> |
||
9 | * @copyright Copyright (c) 2013, Better Brief LLP |
||
10 | */ |
||
11 | class OpauthController extends ContentController { |
||
12 | |||
13 | private static |
||
14 | $allowed_actions = array( |
||
15 | 'index', |
||
16 | 'finished', |
||
17 | 'profilecompletion', |
||
18 | 'RegisterForm', |
||
19 | ), |
||
20 | $url_handlers = array( |
||
21 | 'finished' => 'finished', |
||
22 | ); |
||
23 | |||
24 | /** |
||
25 | * Bitwise indicators to extensions what sort of action is happening |
||
26 | */ |
||
27 | const |
||
28 | /** |
||
29 | * LOGIN = already a user with an OAuth ID |
||
30 | */ |
||
31 | AUTH_FLAG_LOGIN = 2, |
||
32 | /** |
||
33 | * LINK = already a user, linking a new OAuth ID |
||
34 | */ |
||
35 | AUTH_FLAG_LINK = 4, |
||
36 | /** |
||
37 | * REGISTER = new user, linking OAuth ID |
||
38 | */ |
||
39 | AUTH_FLAG_REGISTER = 8; |
||
40 | |||
41 | protected |
||
42 | $registerForm; |
||
43 | |||
44 | /** |
||
45 | * Fake a Page_Controller by using that class as a failover |
||
46 | */ |
||
47 | public function __construct($dataRecord = null) { |
||
48 | if(class_exists('Page_Controller')) { |
||
49 | $dataRecord = new Page_Controller($dataRecord); |
||
50 | } |
||
51 | parent::__construct($dataRecord); |
||
52 | } |
||
53 | |||
54 | /** |
||
55 | * This function only catches the request to pass it straight on. |
||
56 | * Opauth uses the last segment of the URL to identify the auth method. |
||
57 | * In _routes.yml we enforce a $Strategy request parameter to enforce this. |
||
58 | * Equivalent to "index.php" in the Opauth package. |
||
59 | * @todo: Validate the strategy works before delegating to Opauth. |
||
60 | */ |
||
61 | public function index(SS_HTTPRequest $request) { |
||
62 | |||
63 | $strategy = $request->param('Strategy'); |
||
64 | $method = $request->param('StrategyMethod'); |
||
65 | |||
66 | if(!isset($strategy)) { |
||
67 | return Security::permissionFailure($this); |
||
68 | } |
||
69 | |||
70 | // If there is no method then we redirect (not a callback) |
||
71 | if(!isset($method)) { |
||
72 | // Redirects: |
||
73 | OpauthAuthenticator::opauth(true); |
||
74 | } |
||
75 | else { |
||
76 | return $this->oauthCallback($request); |
||
77 | } |
||
78 | } |
||
79 | |||
80 | /** |
||
81 | * This is executed when the Oauth provider redirects back to us |
||
82 | * Opauth handles everything sent back in this request. |
||
83 | */ |
||
84 | protected function oauthCallback(SS_HTTPRequest $request) { |
||
85 | |||
86 | // Set up and run opauth with the correct params from the strategy: |
||
87 | OpauthAuthenticator::opauth(true, array( |
||
88 | 'strategy' => $request->param('Strategy'), |
||
89 | 'action' => $request->param('StrategyMethod'), |
||
90 | )); |
||
91 | |||
92 | } |
||
93 | |||
94 | /** |
||
95 | * Equivalent to "callback.php" in the Opauth package. |
||
96 | * If there is a problem with the response, we throw an HTTP error. |
||
97 | * When done validating, we return back to the Authenticator continue auth. |
||
98 | * @throws SS_HTTPResponse_Exception if any validation errors |
||
99 | */ |
||
100 | public function finished(SS_HTTPRequest $request) { |
||
101 | |||
102 | $opauth = OpauthAuthenticator::opauth(false); |
||
103 | |||
104 | $response = $this->getOpauthResponse(); |
||
105 | |||
106 | if (!$response) { |
||
0 ignored issues
–
show
|
|||
107 | $response = array(); |
||
108 | } |
||
109 | // Clear the response as it is only to be read once (if Session) |
||
110 | Session::clear('opauth'); |
||
111 | |||
112 | // Handle all Opauth validation in this handy function |
||
113 | try { |
||
114 | $this->validateOpauthResponse($opauth, $response); |
||
115 | } |
||
116 | catch(OpauthValidationException $e) { |
||
117 | return $this->handleOpauthException($e); |
||
118 | } |
||
119 | |||
120 | $identity = OpauthIdentity::factory($response); |
||
121 | |||
122 | $member = $identity->findOrCreateMember(); |
||
123 | |||
124 | // If the member exists, associate it with the identity and log in |
||
125 | if($member->isInDB() && $member->validate()->valid()) { |
||
126 | if(!$identity->exists()) { |
||
127 | $identity->write(); |
||
128 | $flag = self::AUTH_FLAG_LINK; |
||
129 | } |
||
130 | else { |
||
131 | $flag = self::AUTH_FLAG_LOGIN; |
||
132 | } |
||
133 | |||
134 | Session::set('OpauthIdentityID', $identity->ID); |
||
135 | } |
||
136 | else { |
||
137 | |||
138 | $flag = self::AUTH_FLAG_REGISTER; |
||
139 | |||
140 | // Write the identity |
||
141 | $identity->write(); |
||
142 | |||
143 | // Keep a note of the identity ID |
||
144 | Session::set('OpauthIdentityID', $identity->ID); |
||
145 | |||
146 | // Even if written, check validation - we might not have full fields |
||
147 | $validationResult = $member->validate(); |
||
148 | if(!$validationResult->valid()) { |
||
149 | // Set up the register form before it's output |
||
150 | $regForm = $this->RegisterForm(); |
||
151 | $regForm->loadDataFrom($member); |
||
152 | $regForm->setSessionData($member); |
||
153 | $regForm->validate(); |
||
154 | return $this->redirect($this->Link('profilecompletion')); |
||
155 | } |
||
156 | else { |
||
157 | $member->extend('onBeforeOpauthRegister'); |
||
158 | $member->write(); |
||
159 | $identity->MemberID = $member->ID; |
||
160 | $identity->write(); |
||
161 | } |
||
162 | } |
||
163 | return $this->loginAndRedirect($member, $identity, $flag); |
||
164 | } |
||
165 | |||
166 | /** |
||
167 | * @param Member |
||
168 | * @param OpauthIdentity |
||
169 | * @param int $mode One or more AUTH_FLAGs. |
||
170 | */ |
||
171 | protected function loginAndRedirect(Member $member, OpauthIdentity $identity, $mode) { |
||
172 | // Back up the BackURL as Member::logIn regenerates the session |
||
173 | $backURL = Session::get('BackURL'); |
||
174 | |||
175 | // Check if we can log in: |
||
176 | $canLogIn = $member->canLogIn(); |
||
177 | |||
178 | if(!$canLogIn->valid()) { |
||
179 | $extendedURLs = $this->extend('getCantLoginBackURL', $member, $identity, $canLogIn, $mode); |
||
180 | if(count($extendedURLs)) { |
||
181 | $redirectURL = array_pop($extendedURLs); |
||
182 | $this->redirect($redirectURL, 302); |
||
183 | return; |
||
184 | } |
||
185 | Security::permissionFailure($this, $canLogIn->message()); |
||
186 | return; |
||
187 | } |
||
188 | |||
189 | // Decide where to go afterwards... |
||
190 | if(!empty($backURL)) { |
||
191 | $redirectURL = $backURL; |
||
192 | } |
||
193 | else { |
||
194 | $redirectURL = Security::config()->default_login_dest; |
||
195 | } |
||
196 | |||
197 | $extendedURLs = $this->extend('getSuccessBackURL', $member, $identity, $redirectURL, $mode); |
||
198 | |||
199 | if(count($extendedURLs)) { |
||
200 | $redirectURL = array_pop($extendedURLs); |
||
201 | } |
||
202 | |||
203 | $member->logIn(); |
||
204 | |||
205 | // Clear any identity ID |
||
206 | Session::clear('OpauthIdentityID'); |
||
207 | |||
208 | // Clear the BackURL |
||
209 | Session::clear('BackURL'); |
||
210 | |||
211 | return $this->redirect($redirectURL); |
||
212 | } |
||
213 | |||
214 | public function profilecompletion(SS_HTTPRequest $request = null) { |
||
215 | if(!Session::get('OpauthIdentityID')) { |
||
216 | Security::permissionFailure($this); |
||
217 | } |
||
218 | // Redirect to complete register step by adding in extra info |
||
219 | return $this->renderWith(array( |
||
220 | 'OpauthController_profilecompletion', |
||
221 | 'Security_profilecompletion', |
||
222 | 'Page', |
||
223 | ) |
||
224 | ); |
||
225 | } |
||
226 | |||
227 | public function RegisterForm(SS_HTTPRequest $request = null, Member $member = null, $result = null) { |
||
228 | if(!isset($this->registerForm)) { |
||
229 | $form = Injector::inst()->create('OpauthRegisterForm', $this, 'RegisterForm', $result); |
||
230 | $form->populateFromSources($request, $member, $result); |
||
231 | // Set manually the form action due to how routing works |
||
232 | $form->setFormAction(Controller::join_links( |
||
233 | self::config()->opauth_path, |
||
234 | 'RegisterForm' |
||
235 | )); |
||
236 | $this->registerForm = $form; |
||
237 | } |
||
238 | else { |
||
239 | $this->registerForm->populateFromSources($request, $member, $result); |
||
240 | } |
||
241 | return $this->registerForm; |
||
242 | } |
||
243 | |||
244 | public function doCompleteRegister($data, $form, $request) { |
||
245 | $member = new Member(); |
||
246 | $form->saveInto($member); |
||
247 | $identityID = Session::get('OpauthIdentityID'); |
||
248 | $identity = DataObject::get_by_id('OpauthIdentity', $identityID); |
||
249 | $validationResult = $member->validate(); |
||
250 | $existing = Member::get()->filter('Email', $member->Email)->first(); |
||
251 | $emailCollision = $existing && $existing->exists(); |
||
252 | // If not valid then we have to manually transpose errors to the form |
||
253 | if(!$validationResult->valid() || $emailCollision) { |
||
254 | $errors = $validationResult->messageList(); |
||
255 | $form->setRequiredFields($errors); |
||
256 | // Mandatory check on the email address |
||
257 | if($emailCollision) { |
||
258 | $form->addErrorMessage('Email', _t( |
||
259 | 'OpauthRegisterForm.ERROREMAILTAKEN', |
||
260 | 'It looks like this email has already been used' |
||
261 | ), 'required'); |
||
262 | } |
||
263 | return $this->redirect('profilecompletion'); |
||
264 | } |
||
265 | // If valid then write and redirect |
||
266 | else { |
||
267 | $member->extend('onBeforeOpauthRegister'); |
||
268 | $member->write(); |
||
269 | $identity->MemberID = $member->ID; |
||
270 | $identity->write(); |
||
271 | return $this->loginAndRedirect($member, $identity, self::AUTH_FLAG_REGISTER); |
||
272 | } |
||
273 | } |
||
274 | |||
275 | /** |
||
276 | * Returns the response from the Oauth callback. |
||
277 | * @throws InvalidArugmentException |
||
278 | * @return array The response |
||
279 | */ |
||
280 | protected function getOpauthResponse() { |
||
281 | $config = OpauthAuthenticator::get_opauth_config(); |
||
282 | $transportMethod = $config['callback_transport']; |
||
283 | switch($transportMethod) { |
||
284 | case 'session': |
||
285 | return $this->getResponseFromSession(); |
||
286 | case 'get': |
||
287 | case 'post': |
||
288 | return $this->getResponseFromRequest($transportMethod); |
||
289 | default: |
||
290 | throw new InvalidArgumentException('Invalid transport method: ' . $transportMethod); |
||
291 | } |
||
292 | } |
||
293 | |||
294 | /** |
||
295 | * Validates the Oauth response for Opauth. |
||
296 | * @throws InvalidArgumentException |
||
297 | */ |
||
298 | protected function validateOpauthResponse($opauth, $response) { |
||
299 | if(!empty($response['error'])) { |
||
300 | throw new OpauthValidationException('Oauth provider error', 1, $response['error']); |
||
301 | } |
||
302 | |||
303 | // Required components within the response |
||
304 | $this->requireResponseComponents( |
||
305 | array('auth', 'timestamp', 'signature'), |
||
306 | $response |
||
307 | ); |
||
308 | |||
309 | // More required components within the auth section... |
||
310 | $this->requireResponseComponents( |
||
311 | array('provider', 'uid'), |
||
312 | $response['auth'] |
||
313 | ); |
||
314 | |||
315 | $invalidReason = ''; |
||
316 | |||
317 | /** |
||
318 | * @todo: improve this signature check. it's a bit weak. |
||
319 | */ |
||
320 | if(!$opauth->validate( |
||
321 | sha1(print_r($response['auth'], true)), |
||
322 | $response['timestamp'], |
||
323 | $response['signature'], |
||
324 | $invalidReason |
||
325 | )) { |
||
326 | throw new OpauthValidationException('Invalid auth response', 3, $invalidReason); |
||
327 | } |
||
328 | } |
||
329 | |||
330 | /** |
||
331 | * Shorthand for quickly finding missing components and complaining about it |
||
332 | * @throws InvalidArgumentException |
||
333 | */ |
||
334 | protected function requireResponseComponents(array $components, $response) { |
||
335 | foreach($components as $component) { |
||
336 | if(empty($response[$component])) { |
||
337 | throw new OpauthValidationException('Required component missing', 2, $component); |
||
338 | } |
||
339 | } |
||
340 | } |
||
341 | |||
342 | /** |
||
343 | * @return array Opauth response from session |
||
344 | */ |
||
345 | protected function getResponseFromSession() { |
||
346 | return Session::get('opauth'); |
||
347 | } |
||
348 | |||
349 | /** |
||
350 | * @param OpauthValidationException $e |
||
351 | */ |
||
352 | protected function handleOpauthException(OpauthValidationException $e) { |
||
353 | $data = $e->getData(); |
||
354 | $loginFormName = 'OpauthLoginForm_LoginForm'; |
||
355 | $message = ''; |
||
356 | switch($e->getCode()) { |
||
357 | case 1: // provider error |
||
358 | $message = _t( |
||
359 | 'OpauthLoginForm.OAUTHFAILURE', |
||
360 | 'There was a problem logging in with {provider}.', |
||
361 | array( |
||
362 | 'provider' => $data['provider'], |
||
363 | ) |
||
364 | ); |
||
365 | break; |
||
366 | case 2: // validation error |
||
367 | case 3: // invalid auth response |
||
368 | $message = _t( |
||
369 | 'OpauthLoginForm.RESPONSEVALIDATIONFAILURE', |
||
370 | 'There was a problem logging in - {message}', |
||
371 | array( |
||
372 | 'message' => $e->getMessage(), |
||
373 | ) |
||
374 | ); |
||
375 | break; |
||
376 | } |
||
377 | // Set form message, redirect to login with permission failure |
||
378 | Form::messageForForm($loginFormName, $message, 'bad'); |
||
379 | // always redirect to login |
||
380 | Security::permissionFailure($this, $message); |
||
381 | } |
||
382 | |||
383 | /** |
||
384 | * Looks at $method (GET, POST, PUT etc) for the response. |
||
385 | * @return array Opauth response |
||
386 | */ |
||
387 | protected function getResponseFromRequest($method) { |
||
388 | return unserialize(base64_decode($this->request->{$method.'Var'}('opauth'))); |
||
389 | } |
||
390 | |||
391 | public function Link($action = null) { |
||
392 | return Controller::join_links( |
||
393 | self::config()->opauth_path, |
||
394 | $action |
||
395 | ); |
||
396 | } |
||
397 | |||
398 | /** |
||
399 | * 'path' param for use in Opauth's config |
||
400 | * MUST have trailling slash for Opauth needs |
||
401 | * @return string |
||
402 | */ |
||
403 | public static function get_path() { |
||
404 | return Controller::join_links( |
||
405 | self::config()->opauth_path, |
||
406 | 'strategy/' |
||
407 | ); |
||
408 | } |
||
409 | |||
410 | /** |
||
411 | * 'callback_url' param for use in Opauth's config |
||
412 | * MUST have trailling slash for Opauth needs |
||
413 | * @return string |
||
414 | */ |
||
415 | public static function get_callback_path() { |
||
416 | return Controller::join_links( |
||
417 | self::config()->opauth_path, |
||
418 | 'finished/' |
||
419 | ); |
||
420 | } |
||
421 | |||
422 | ////**** Template variables ****//// |
||
423 | function Title() { |
||
0 ignored issues
–
show
|
|||
424 | if($this->action == 'profilecompletion') { |
||
425 | return _t('OpauthController.PROFILECOMPLETIONTITLE', 'Complete your profile'); |
||
426 | } |
||
427 | return _t('OpauthController.TITLE', 'Social Login'); |
||
428 | } |
||
429 | |||
430 | public function Form() { |
||
431 | return $this->RegisterForm(); |
||
432 | } |
||
433 | ////**** END Template variables ****//// |
||
434 | |||
435 | } |
||
436 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.