Completed
Push — master ( 0e1017...87dcd8 )
by Nazar
04:21
created

Controller::authenticate_hybridauth()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 1
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package   HybridAuth
4
 * @category  modules
5
 * @author    Nazar Mokrynskyi <[email protected]>
6
 * @copyright Copyright (c) 2011-2016, Nazar Mokrynskyi
7
 * @license   MIT License, see license.txt
8
 */
9
namespace cs\modules\HybridAuth;
10
use
11
	Exception,
12
	h,
13
	Hybrid_Endpoint,
14
	cs\Config,
15
	cs\Event,
16
	cs\ExitException,
17
	cs\Key,
18
	cs\Language\Prefix,
19
	cs\Mail,
20
	cs\Page,
21
	cs\Request,
22
	cs\Response,
23
	cs\Session,
24
	cs\User;
25
26
/**
27
 * Provides next events:
28
 *  HybridAuth/registration/before
29
 *  [
30
 *   'provider'   => provider   //Provider name
31
 *   'email'      => email      //Email
32
 *   'identifier' => identifier //Identifier given by provider
33
 *   'profile'    => profile    //Profile url
34
 *  ]
35
 *
36
 *  HybridAuth/add_session/before
37
 *  [
38
 *   'adapter'  => $Adapter //instance of Hybrid_Provider_Adapter
39
 *   'provider' => provider //Provider name
40
 *  ]
41
 *
42
 *  HybridAuth/add_session/after
43
 *  [
44
 *   'adapter'  => $Adapter //instance of Hybrid_Provider_Adapter
45
 *   'provider' => provider //Provider name
46
 *  ]
47
 */
48
class Controller {
49
	/**
50
	 * @param Request  $Request
51
	 * @param Response $Response
52
	 *
53
	 * @throws ExitException
54
	 */
55
	static function index ($Request, $Response) {
56
		$route = $Request->route;
57
		/**
58
		 * This should be present in any case, if not - exit from here
59
		 */
60
		if (!isset($route[0])) {
61
			self::redirect();
62
			return;
63
		}
64
		$Config             = Config::instance();
65
		$Page               = Page::instance();
66
		$User               = User::instance();
67
		$Social_integration = Social_integration::instance();
68
		$L                  = new Prefix('hybridauth_');
69
		/**
70
		 * Confirmation of accounts merging
71
		 */
72
		if ($route[0] == 'merge_confirmation') {
73
			self::merge_confirmation($route, $Page, $L);
74
			return;
75
		}
76
		$provider = $route[0];
77
		/**
78
		 * Authenticated users are not allowed to sign in, also provider should exist and be enabled
79
		 */
80
		if (
81
			$User->user() ||
82
			!@$Config->module('HybridAuth')->providers[$provider]['enabled']
83
		) {
84
			self::redirect();
85
			return;
86
		}
87
		$referer = $Request->header('referer');
88
		/**
89
		 * If referer is internal website address, but not HybridAuth module - save referer to cookie
90
		 */
91
		if (
92
			strpos($referer, $Config->base_url()) === 0 &&
93
			strpos($referer, $Config->base_url().'/HybridAuth') === false
94
		) {
95
			$Response->cookie('HybridAuth_referer', $referer, 0, true);
96
		}
97
		require_once __DIR__.'/Hybrid/Auth.php';
98
		require_once __DIR__.'/Hybrid/Endpoint.php';
99
		/**
100
		 * Handle authentication endpoint
101
		 */
102
		if (isset($route[1]) && $route[1] == 'endpoint') {
103
			/**
104
			 * `$rc[2]` should be present and contain special hash for security reasons (this is called as callback from provider)
105
			 */
106
			if (
107
				!isset($route[2]) ||
108
				strpos($route[2], md5($provider.Session::instance()->get_id())) !== 0
109
			) {
110
				self::redirect();
111
				return;
112
			}
113
			Hybrid_Endpoint::process($_REQUEST);
114
			return;
115
		}
116
		/**
117
		 * If user did not specified email
118
		 */
119
		if (!isset($_POST['email'])) {
120
			self::email_not_specified($provider, $Social_integration, $User, $Page, $L);
121
			return;
122
		}
123
		/**
124
		 * If user specified email
125
		 */
126
		self::email_was_specified($provider, $Social_integration, $User, $Page, $L, $Config, $Response);
127
	}
128
	/**
129
	 * Redirect to referer or home page
130
	 *
131
	 * @param bool $with_delay If `true` - redirect will be made in 5 seconds after page load
132
	 *
133
	 * @throws ExitException
134
	 */
135
	protected static function redirect ($with_delay = false) {
136
		$Response    = Response::instance();
137
		$redirect_to = Request::instance()->cookie('HybridAuth_referer') ?: Config::instance()->base_url();
138
		$Response->cookie('HybridAuth_referer', '');
139
		if ($with_delay) {
140
			$Response->header('refresh', "5; url=$redirect_to");
141
		} else {
142
			$Response->redirect($redirect_to, 301);
143
			Page::instance()->interface = false;
144
			throw new ExitException;
145
		}
146
	}
147
	/**
148
	 * @param string[] $route
149
	 * @param Page     $Page
150
	 * @param Prefix   $L
151
	 *
152
	 * @throws ExitException
153
	 */
154
	protected static function merge_confirmation ($route, $Page, $L) {
155
		if (!isset($route[1])) {
156
			self::redirect();
157
		}
158
		/**
159
		 * Check confirmation code
160
		 */
161
		$data = self::get_data_by_confirmation_code($route[1]);
162
		if (!$data) {
163
			$Page->warning($L->merge_confirm_code_invalid);
164
			return;
165
		}
166
		/**
167
		 * If confirmation key is valid  - merge social profile with main account
168
		 */
169
		self::add_integration_create_session(
170
			$data['id'],
171
			$data['provider'],
172
			$data['identifier'],
173
			$data['profile']
174
		);
175
		self::save_hybridauth_session();
176
		$Page->success(
177
			$L->merging_confirmed_successfully($L->{$data['provider']})
178
		);
179
	}
180
	/**
181
	 * @param int    $id
182
	 * @param string $provider
183
	 * @param string $identifier
184
	 * @param string $profile
185
	 */
186
	protected static function add_integration_create_session ($id, $provider, $identifier, $profile) {
187
		Social_integration::instance()->add($id, $provider, $identifier, $profile);
188
		$User = User::instance();
189
		/**
190
		 * If user was not activated before - activate him
191
		 */
192
		if ($User->get('status', $id) == User::STATUS_NOT_ACTIVATED) {
193
			$User->set('status', User::STATUS_ACTIVE, $id);
194
		}
195
		self::add_session_and_update_data($id, $provider, true);
196
	}
197
	/**
198
	 * Save HybridAuth session in user's data in order to restore it next time when calling `get_hybridauth_instance()`
199
	 */
200
	protected static function save_hybridauth_session () {
201
		$User = User::instance();
202
		$User->set_data(
203
			'HybridAuth_session',
204
			array_merge(
205
				$User->get_data('HybridAuth_session') ?: [],
206
				unserialize(get_hybridauth_instance()->getSessionData())
207
			)
208
		);
209
	}
210
	/**
211
	 * @param string $code
212
	 *
213
	 * @return false|array
214
	 */
215
	protected static function get_data_by_confirmation_code ($code) {
216
		return Key::instance()->get(
217
			Config::instance()->module('HybridAuth')->db('integration'),
218
			$code,
219
			true
220
		);
221
	}
222
	/**
223
	 * @param string             $provider
224
	 * @param Social_integration $Social_integration
225
	 * @param User               $User
226
	 * @param Page               $Page
227
	 * @param Prefix             $L
228
	 *
229
	 * @throws ExitException
230
	 */
231
	protected static function email_not_specified ($provider, $Social_integration, $User, $Page, $L) {
232
		$profile = self::authenticate_hybridauth($provider);
233
		/**
234
		 * Check whether this account was already registered in system. If registered - make login
235
		 */
236
		$user = $Social_integration->find_integration($provider, $profile->identifier);
237
		if (
238
			$user &&
239
			$User->get('status', $user) == User::STATUS_ACTIVE
240
		) {
241
			self::add_session_and_update_data($user, $provider);
242
			return;
243
		}
244
		if (!Config::instance()->core['allow_user_registration']) {
245
			Page::instance()
246
				->title($L->registration_prohibited)
247
				->warning($L->registration_prohibited);
248
			return;
249
		}
250
		$email = strtolower($profile->emailVerified ?: $profile->email);
251
		/**
252
		 * If integrated service does not returns email - ask user for email
253
		 */
254
		if (!$email) {
255
			self::email_form($Page, $L);
256
			return;
257
		}
258
		/**
259
		 * Search for user with such email
260
		 */
261
		$user = $User->get_id(hash('sha224', $email));
262
		/**
263
		 * If email is already registered - merge social profile with main account
264
		 */
265
		if ($user) {
266
			self::add_integration_create_session($user, $provider, $profile->identifier, $profile->profileURL);
267
			return;
268
		}
269
		/**
270
		 * If user doesn't exists - try to register user
271
		 */
272
		$result = self::try_to_register($provider, $email, false);
273
		if (!$result) {
274
			return;
275
		}
276
		$Social_integration->add($result['id'], $provider, $profile->identifier, $profile->profileURL);
277
		/**
278
		 * Registration is successful, confirmation is not needed
279
		 */
280
		self::finish_registration_send_email($result['id'], $result['password'], $provider);
281
	}
282
	/**
283
	 * Returns profile
284
	 *
285
	 * @param string $provider
286
	 *
287
	 * @return \Hybrid_User_Profile
288
	 *
289
	 * @throws ExitException
290
	 */
291
	protected static function authenticate_hybridauth ($provider) {
292
		try {
293
			return get_hybridauth_instance($provider)::authenticate($provider)->getUserProfile();
294
		} catch (ExitException $e) {
295
			throw $e;
296
		} catch (Exception $e) {
297
			trigger_error($e->getMessage());
298
			self::redirect(true);
299
			throw new ExitException;
300
		}
301
	}
302
	/**
303
	 * @throws ExitException
304
	 *
305
	 * @param string $provider
306
	 * @param string $email
307
	 * @param bool   $email_from_user
308
	 *
309
	 * @return array|false|string
310
	 */
311
	protected static function try_to_register ($provider, $email, $email_from_user) {
312
		$profile = self::authenticate_hybridauth($provider);
313
		if (!Event::instance()->fire(
314
			'HybridAuth/registration/before',
315
			[
316
				'provider'   => $provider,
317
				'email'      => $email,
318
				'identifier' => $profile->identifier,
319
				'profile'    => $profile->profileURL
320
			]
321
		)
322
		) {
323
			return false;
324
		}
325
		$L      = new Prefix('hybridauth_');
326
		$Page   = Page::instance();
327
		$User   = User::instance();
328
		$result = $email_from_user ? $User->registration($email) : $User->registration($email, false, false);
329
		if (!$result && $email_from_user) {
330
			$Page
331
				->title($L->please_type_correct_email)
332
				->warning($L->please_type_correct_email);
333
			self::email_form($Page, $L);
334
			return false;
335
		}
336
		if (!$result || $result == 'error') {
337
			$Page
338
				->title($L->registration_server_error)
339
				->warning($L->registration_server_error);
340
			self::redirect(true);
341
			return false;
342
		}
343
		return $result;
344
	}
345
	/**
346
	 * @param Page   $Page
347
	 * @param Prefix $L
348
	 */
349
	protected static function email_form ($Page, $L) {
350
		$Page->content(
351
			h::{'form[is=cs-form]'}(
352
				h::{'p.cs-text-center'}(
353
					$L->please_type_your_email.':'.
354
					h::{'input[name=email]'}(
355
						isset($_POST['email']) ? $_POST['email'] : ''
356
					)
357
				).
358
				h::{'button[is=cs-button][type=submit]'}(
359
					$L->submit
360
				)
361
			)
362
		);
363
	}
364
	/**
365
	 * @param string             $provider
366
	 * @param Social_integration $Social_integration
367
	 * @param User               $User
368
	 * @param Page               $Page
369
	 * @param Prefix             $L
370
	 * @param Config             $Config
371
	 * @param Response           $Response
372
	 *
373
	 * @throws ExitException
374
	 */
375
	protected static function email_was_specified ($provider, $Social_integration, $User, $Page, $L, $Config, $Response) {
376
		$profile = self::authenticate_hybridauth($provider);
377
		/**
378
		 * Try to register user
379
		 */
380
		$result = self::try_to_register($provider, $_POST['email'], true);
381
		if (!$result) {
382
			return;
383
		}
384
		$core_url = $Config->core_url();
385
		/**
386
		 * If email is already registered
387
		 */
388
		if ($result == 'exists') {
389
			/**
390
			 * Send merging confirmation email
391
			 */
392
			$id                    = $User->get_id(hash('sha224', strtolower($_POST['email'])));
393
			$HybridAuth_data['id'] = $id;
394
			$confirmation_code     = self::set_data_generate_confirmation_code($HybridAuth_data);
395
			$title                 = $L->merge_confirmation_mail_title(get_core_ml_text('name'));
396
			$body                  = $L->merge_confirmation_mail_body(
397
				$User->username($id) ?: strstr($_POST['email'], '@', true),
398
				get_core_ml_text('name'),
399
				$L->$provider,
400
				"$core_url/HybridAuth/merge_confirmation/$confirmation_code",
401
				$L->time($Config->core['registration_confirmation_time'], 'd')
402
			);
403
			if (self::send_registration_mail($_POST['email'], $title, $body)) {
404
				$Response->cookie('HybridAuth_referer', '');
405
				$Page->content(
406
					h::p(
407
						$L->merge_confirmation($L->$provider)
408
					)
409
				);
410
			}
411
			return;
412
		}
413
		/**
414
		 * Registration is successful and confirmation is not required
415
		 */
416
		if ($result['reg_key'] === true) {
417
			$Social_integration->add($result['id'], $provider, $profile->identifier, $profile->profileURL);
418
			self::finish_registration_send_email($result['id'], $result['password'], $provider);
419
			return;
420
		}
421
		/**
422
		 * Registration is successful, but confirmation is needed
423
		 */
424
		$title = $L->registration_need_confirmation_mail(get_core_ml_text('name'));
425
		$body  = $L->registration_need_confirmation_mail_body(
426
			self::get_adapter($provider)->getUserProfile()->displayName ?: strstr($result['email'], '@', true),
427
			get_core_ml_text('name'),
428
			"$core_url/profile/registration_confirmation/$result[reg_key]",
429
			$L->time($Config->core['registration_confirmation_time'], 'd')
430
		);
431
		if (self::send_registration_mail($_POST['email'], $title, $body)) {
432
			self::update_data($provider);
433
			$Response->cookie('HybridAuth_referer', '');
434
			$Page->content($L->registration_confirmation);
435
		}
436
	}
437
	/**
438
	 * @param string $email
439
	 * @param string $title
440
	 * @param string $body
441
	 *
442
	 * @return bool
443
	 *
444
	 * @throws ExitException
445
	 */
446
	protected static function send_registration_mail ($email, $title, $body) {
447
		$result = Mail::instance()->send_to($email, $title, $body);
448
		/**
449
		 * If mail sending failed - cancel registration, show error message and redirect to referrer page
450
		 */
451
		if (!$result) {
452
			User::instance()->registration_cancel();
453
			$L = new Prefix('hybridauth_');
454
			Page::instance()
455
				->title($L->registration_mail_sending_error_title)
456
				->warning($L->registration_mail_sending_error);
457
			self::redirect(true);
458
		}
459
		return $result;
460
	}
461
	/**
462
	 * @param array $data
463
	 *
464
	 * @return false|string
465
	 *
466
	 * @throws ExitException
467
	 */
468
	protected static function set_data_generate_confirmation_code ($data) {
469
		$code = Key::instance()->add(
470
			Config::instance()->module('HybridAuth')->db('integration'),
471
			false,
472
			$data,
473
			TIME + Config::instance()->core['registration_confirmation_time'] * 86400
474
		);
475
		if (!$code) {
476
			throw new ExitException(500);
477
		}
478
		return $code;
479
	}
480
	/**
481
	 * @param int    $user_id
482
	 * @param string $provider
483
	 * @param bool   $redirect_with_delay
484
	 *
485
	 * @throws ExitException
486
	 */
487
	protected static function add_session_and_update_data ($user_id, $provider, $redirect_with_delay = false) {
488
		$adapter = self::get_adapter($provider);
489
		Event::instance()->fire(
490
			'HybridAuth/add_session/before',
491
			[
492
				'adapter'  => $adapter,
493
				'provider' => $provider
494
			]
495
		);
496
		Session::instance()->add($user_id);
497
		self::save_hybridauth_session();
498
		Event::instance()->fire(
499
			'HybridAuth/add_session/after',
500
			[
501
				'adapter'  => $adapter,
502
				'provider' => $provider
503
			]
504
		);
505
		self::update_data($provider);
506
		self::redirect($redirect_with_delay);
507
	}
508
	/**
509
	 * @param string $provider
510
	 */
511
	protected static function update_data ($provider) {
512
		$User    = User::instance();
513
		$user_id = $User->id;
514
		if ($user_id != User::GUEST_ID) {
515
			$adapter       = self::get_adapter($provider);
516
			$profile       = $adapter->getUserProfile();
517
			$profile_info  = [
518
				'username' => $profile->displayName,
519
				'avatar'   => $profile->photoURL
520
			];
521
			$profile_info  = array_filter($profile_info);
522
			$existing_data = $User->get(array_keys($profile_info), $user_id);
523
			foreach ($profile_info as $item => $value) {
524
				if (!$existing_data[$item] || $existing_data[$item] != $value) {
525
					$User->set($item, $value, $user_id);
526
				}
527
			}
528
		}
529
	}
530
	/**
531
	 * @param int    $user_id
532
	 * @param string $password
533
	 * @param string $provider
534
	 */
535
	protected static function finish_registration_send_email ($user_id, $password, $provider) {
536
		$L         = new Prefix('hybridauth_');
537
		$User      = User::instance();
538
		$user_data = $User->$user_id;
539
		$base_url  = Config::instance()->base_url();
540
		$title     = $L->registration_success_mail(get_core_ml_text('name'));
541
		$body      = $L->registration_success_mail_body(
542
			self::get_adapter($provider)->getUserProfile()->displayName ?: $user_data->username(),
543
			get_core_ml_text('name'),
544
			"$base_url/profile/settings",
545
			$user_data->login,
546
			$password
547
		);
548
		/**
549
		 * Send notification email
550
		 */
551
		if (self::send_registration_mail($user_data->email, $title, $body)) {
552
			self::add_session_and_update_data($user_id, $provider);
553
		}
554
	}
555
	/**
556
	 * @param string $provider
557
	 *
558
	 * @return \Hybrid_Provider_Adapter
559
	 *
560
	 * @throws ExitException
561
	 */
562
	protected static function get_adapter ($provider) {
563
		try {
564
			return get_hybridauth_instance($provider)::getAdapter($provider);
565
		} catch (ExitException $e) {
566
			throw $e;
567
		} catch (Exception $e) {
568
			trigger_error($e->getMessage());
569
			throw new ExitException(500);
570
		}
571
	}
572
}
573