Completed
Push — master ( 2676d9...a4ebee )
by Nazar
04:30
created

Controller::authenticate_hybridauth()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 9

Duplication

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