Skip to content

Commit

Permalink
Upgraded Facebook login module to PHP SDK 5 and Graph API 2.9
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergey Chernyshev committed Jul 1, 2017
1 parent 7fcb9d7 commit facd658
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 100 deletions.
5 changes: 5 additions & 0 deletions default_config.php
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ class UserConfig {
*/ */
public static $oauth_user_id_key = 'users-oauth-user-id'; public static $oauth_user_id_key = 'users-oauth-user-id';


/**
* @var string cookie name for Facebook access token storage
*/
public static $fb_access_token_key = 'users-fbatkn';

/** /**
* @var string Cookie name for storing referrer between anonymous user's arrival and their registration * @var string Cookie name for storing referrer between anonymous user's arrival and their registration
*/ */
Expand Down
229 changes: 165 additions & 64 deletions modules/facebook/index.php
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,5 @@
<?php <?php
require_once(__DIR__ . '/facebook.php'); require_once(__DIR__ . '/php-graph-sdk/src/Facebook/autoload.php');


/** /**
* Facebook Authentication Module * Facebook Authentication Module
Expand Down Expand Up @@ -64,11 +64,12 @@ public function __construct($appID, $secret, $permissions = array(), $remember =
} }


$config = array( $config = array(
'appId' => $this->appID, 'app_id' => $this->appID,
'secret' => $this->secret 'app_secret' => $this->secret,
'default_graph_version' => 'v2.9'
); );


$this->sdk = new Facebook($config); $this->sdk = new \Facebook\Facebook($config);
} }


public function getID() { public function getID() {
Expand Down Expand Up @@ -247,19 +248,28 @@ public function renderEditUserForm($template_info, $action, $errors, $user, $dat
return $this->renderForm($template_info, $action, 'connect'); return $this->renderForm($template_info, $action, 'connect');
} else { } else {
try { try {
$me = $this->api('/me?fields=id,name,email,link'); $response = $this->sdk->get('/me?fields=id,name,email,link', $this->getAccessToken());
} catch (Exception $e) { } catch(\Facebook\Exceptions\FacebookResponseException $e) {
UserTools::debug("Can't get /me API data: " . $e); // When Graph returns an error
return; UserTools::debug("Can't get Facebook user. Graph returned an error: " . $e->getMessage());
return null;
} catch(\Facebook\Exceptions\FacebookSDKException $e) {
// When validation fails or other local issues
UserTools::debug("Can't get Facebook user. Facebook SDK returned an error: " . $e->getMessage());
return null;
} }


$fbuser = $response->getGraphUser();
$fbuser_id = $fbuser->getId();
$me = $response->getDecodedBody();

$template_info['slug'] = $this->getID(); $template_info['slug'] = $this->getID();
$template_info['action'] = $action; $template_info['action'] = $action;
$template_info['errors'] = $errors; $template_info['errors'] = $errors;
$template_info['data'] = $data; $template_info['data'] = $data;


$template_info['me'] = $me; $template_info['me'] = $me;
$template_info['fb_id'] = $fb_id; $template_info['fb_id'] = $fbuser_id;


return StartupAPI::$template->render("@startupapi/modules/facebook/edit_user_form.html.twig", $template_info); return StartupAPI::$template->render("@startupapi/modules/facebook/edit_user_form.html.twig", $template_info);
} }
Expand All @@ -277,6 +287,68 @@ public function getAutoLogoutURL($return) {
return UserConfig::$USERSROOTFULLURL . '/modules/facebook/logout.php'; return UserConfig::$USERSROOTFULLURL . '/modules/facebook/logout.php';
} }


function getAccessToken() {
$storage = new MrClay_CookieStorage(array(
'secret' => UserConfig::$SESSION_SECRET,
'mode' => MrClay_CookieStorage::MODE_ENCRYPT,
'path' => UserConfig::$SITEROOTURL,
'httponly' => true
));

$accessToken = $storage->fetch(UserConfig::$fb_access_token_key);

UserTools::debug('FB access token used: ' . var_export($accessToken, true));

if (!$accessToken) {
$accessToken = $this->retrieveAccessToken();
}

return $accessToken;
}

function retrieveAccessToken() {
try {
$accessToken = $this->sdk->getJavaScriptHelper()->getAccessToken();
} catch(Facebook\Exceptions\FacebookResponseException $e) {
// When Graph returns an error
UserTools::debug("Can't get Facebook user. Graph returned an error: " . $e->getMessage());
return null;
} catch(Facebook\Exceptions\FacebookSDKException $e) {
// When validation fails or other local issues
UserTools::debug("Can't get Facebook user. Facebook SDK returned an error: " . $e->getMessage());
return null;
}

if (!isset($accessToken)) {
UserTools::debug("Can't get Facebook user. No cookie set or no OAuth data could be obtained from cookie.");
return null;
}

$oAuth2Client = $this->sdk->getOAuth2Client();
if (!$accessToken->isLongLived()) {
// Exchanges a short-lived access token for a long-lived one
try {
$accessToken = $oAuth2Client->getLongLivedAccessToken($accessToken);
} catch (Facebook\Exceptions\FacebookSDKException $e) {
UserTools::debug("Error getting long-lived access token: " . $helper->getMessage());
return null;
}
}

$storage = new MrClay_CookieStorage(array(
'secret' => UserConfig::$SESSION_SECRET,
'mode' => MrClay_CookieStorage::MODE_ENCRYPT,
'path' => UserConfig::$SITEROOTURL,
'httponly' => true
));

if (!$storage->store(UserConfig::$fb_access_token_key, $accessToken->getValue())) {
throw new StartupAPIException(implode("\n", $storage->errors));
}

return $accessToken->getValue();
}

/** /**
* Processes user login form submission * Processes user login form submission
* *
Expand All @@ -295,15 +367,23 @@ public function processLogin($post_data, &$remember, $auto = false) {
$remember = $this->remember; $remember = $this->remember;


try { try {
$fbuser = $this->sdk->getUser(); $response = $this->sdk->get('/me', $this->getAccessToken());
} catch (FacebookApiException $e) { } catch(\Facebook\Exceptions\FacebookResponseException $e) {
UserTools::debug("Can't get Facebook user"); // When Graph returns an error
UserTools::debug("Can't get Facebook user. Graph returned an error: " . $e->getMessage());
return null;
} catch(\Facebook\Exceptions\FacebookSDKException $e) {
// When validation fails or other local issues
UserTools::debug("Can't get Facebook user. Facebook SDK returned an error: " . $e->getMessage());
return null; return null;
} }


UserTools::debug('Facebook user id: ' . $fbuser); $fbuser = $response->getGraphUser();
$fbuser_id = $fbuser->getId();

UserTools::debug('Facebook user id: ' . $fbuser_id);


if ($fbuser == 0) { if ($fbuser_id == 0) {
// if we're trying to auto-login, just return null // if we're trying to auto-login, just return null
if ($auto) { if ($auto) {
return null; return null;
Expand All @@ -314,10 +394,17 @@ public function processLogin($post_data, &$remember, $auto = false) {
} }


try { try {
$permissions = $this->api('/me/permissions'); $granted_permissions = $this->api('/me/permissions')->getDecodedBody();
UserTools::debug('User permissions: ' . var_export($permissions, true)); UserTools::debug('User permissions: ' . var_export($granted_permissions, true));

foreach ($granted_permissions['data'] as $perm) {
if ($perm['status'] == 'granted') {
$perm_lookup[$perm['permission']] = true;
}
}

foreach ($this->permissions as $perm) { foreach ($this->permissions as $perm) {
if (!array_key_exists($perm, $permissions['data'][0]) || $permissions['data'][0][$perm] !== 1) { if (!array_key_exists($perm, $perm_lookup)) {
// looks like not all required permissions were granted // looks like not all required permissions were granted
UserTools::debug("Can't login - not enough permissions granted"); UserTools::debug("Can't login - not enough permissions granted");
return null; return null;
Expand All @@ -328,7 +415,7 @@ public function processLogin($post_data, &$remember, $auto = false) {
return null; return null;
} }


$user = User::getUserByFacebookID($fbuser); $user = User::getUserByFacebookID($fbuser_id);


if (!is_null($user)) { if (!is_null($user)) {
$user->recordActivity(USERBASE_ACTIVITY_LOGIN_FB); $user->recordActivity(USERBASE_ACTIVITY_LOGIN_FB);
Expand All @@ -347,14 +434,23 @@ public function processRegistration($post_data, &$remember) {
$remember = $this->remember; $remember = $this->remember;


try { try {
$fbuser = $this->sdk->getUser(); $response = $this->sdk->get('/me?fields=id,name,email,link', $this->getAccessToken());
} catch (FacebookApiException $e) { } catch(\Facebook\Exceptions\FacebookResponseException $e) {
UserTools::debug("Can't get Facebook user"); // When Graph returns an error
UserTools::debug("Can't get Facebook user. Graph returned an error: " . $e->getMessage());
return null;
} catch(\Facebook\Exceptions\FacebookSDKException $e) {
// When validation fails or other local issues
UserTools::debug("Can't get Facebook user. Facebook SDK returned an error: " . $e->getMessage());
return null; return null;
} }


$fbuser = $response->getGraphUser();
$fbuser_id = $fbuser->getId();
$me = $response->getDecodedBody();

$errors = array(); $errors = array();
if ($fbuser == 0) { if (!$fbuser_id) {
$errors['fbuserid'][] = 'No Facebook id is passed'; $errors['fbuserid'][] = 'No Facebook id is passed';
throw new InputValidationException('No facebook user id', 0, $errors); throw new InputValidationException('No facebook user id', 0, $errors);
} }
Expand All @@ -367,13 +463,6 @@ public function processRegistration($post_data, &$remember) {
return $existing_user; return $existing_user;
} }


try {
$me = $this->api('/me?fields=id,name,email,link');
} catch (Exception $e) {
UserTools::debug("Can't get /me API data: " . $e);
return null;
}

if (array_key_exists('name', $me)) { if (array_key_exists('name', $me)) {
$name = $me['name']; $name = $me['name'];
} else { } else {
Expand All @@ -383,7 +472,7 @@ public function processRegistration($post_data, &$remember) {


// ok, let's create a user // ok, let's create a user
try { try {
$user = User::createNewFacebookUser($name, $fbuser, $me); $user = User::createNewFacebookUser($name, $fbuser_id, $me);
} catch (UserCreationException $e) { } catch (UserCreationException $e) {
$errors[$e->getField()][] = $e->getMessage(); $errors[$e->getField()][] = $e->getMessage();
} }
Expand All @@ -404,43 +493,64 @@ public function processEditUser($user, $data) {


$user->recordActivity(USERBASE_ACTIVITY_REMOVED_FB); $user->recordActivity(USERBASE_ACTIVITY_REMOVED_FB);


$this->api('/me/permissions', 'DELETE'); try {
$response = $this->sdk->delete('/me/permissions', [], $this->getAccessToken());
} catch(\Facebook\Exceptions\FacebookResponseException $e) {
// When Graph returns an error
UserTools::debug("Can't delete Facebook app. Graph returned an error: " . $e->getMessage());
return null;
} catch(\Facebook\Exceptions\FacebookSDKException $e) {
// When validation fails or other local issues
UserTools::debug("Can't delete Facebook app. Facebook SDK returned an error: " . $e->getMessage());
return null;
}

$storage = new MrClay_CookieStorage(array(
'secret' => UserConfig::$SESSION_SECRET,
'mode' => MrClay_CookieStorage::MODE_ENCRYPT,
'path' => UserConfig::$SITEROOTURL,
'httponly' => true
));

$storage->delete(UserConfig::$fb_access_token_key);


return true; return true;
} }


try { try {
$fbuser = $this->sdk->getUser(); $response = $this->sdk->get('/me?fields=id,name,email,link', $this->getAccessToken());
} catch (FacebookApiException $e) { } catch(\Facebook\Exceptions\FacebookResponseException $e) {
UserTools::debug("Can't get Facebook user"); // When Graph returns an error
UserTools::debug("Can't get Facebook user. Graph returned an error: " . $e->getMessage());
return null;
} catch(\Facebook\Exceptions\FacebookSDKException $e) {
// When validation fails or other local issues
UserTools::debug("Can't get Facebook user. Facebook SDK returned an error: " . $e->getMessage());
return null; return null;
} }


$fbuser = $response->getGraphUser();
$fbuser_id = $fbuser->getId();
$me = $response->getDecodedBody();

$errors = array(); $errors = array();
if ($fbuser == 0) { if (!$fbuser_id) {
$errors['fbuserid'][] = 'No Facebook id is passed'; $errors['fbuserid'][] = 'No Facebook id is passed';
throw new InputValidationException('No facebook user id', 0, $errors); throw new InputValidationException('No facebook user id', 0, $errors);
} }


if (!is_null(User::getUserByFacebookID($fbuser))) { if (!is_null(User::getUserByFacebookID($fbuser_id))) {
$errors['fbuserid'][] = 'Another user is already associated with your Facebook account.'; $errors['fbuserid'][] = 'Another user is already associated with your Facebook account.';
} }


if (count($errors) > 0) { if (count($errors) > 0) {
throw new InputValidationException('Validation failed', 0, $errors); throw new InputValidationException('Validation failed', 0, $errors);
} }


$user->setFacebookID($fbuser); $user->setFacebookID($fbuser_id);


// if user has email address and we required it for Facebook connection, let's save it // if user has email address and we required it for Facebook connection, let's save it
if (!$user->getEmail()) { if (!$user->getEmail()) {
try {
$me = $this->api('/me?fields=email');
} catch (Exception $e) {
UserTools::debug("Can't get /me API data: " . $e);
return null;
}

if (array_key_exists('email', $me)) { if (array_key_exists('email', $me)) {
try { try {
$user->setEmail($me['email']); $user->setEmail($me['email']);
Expand All @@ -464,30 +574,21 @@ public function processEditUser($user, $data) {
* *
* @throws FacebookApiException * @throws FacebookApiException
*/ */
public function api(/* polymorphic */) { public function api($query) {
$args = func_get_args(); $args = func_get_args();


$result = null;

try { try {
$result = call_user_func_array(array($this->sdk, 'api'), $args); $result = $this->sdk->get($query, $this->getAccessToken());
} catch (FacebookApiException $fb_ex) { } catch(\Facebook\Exceptions\FacebookResponseException $e) {
$message = $fb_ex->getMessage(); // When Graph returns an error

UserTools::debug("Graph returned an error: " . $e->getMessage());
if (strpos($message, 'An active access token must be used') !== false ||
strpos($message, 'Session has expired at unix time') !== false
) {
UserTools::debug('Facebook access token has expired, redirecting to login');

// looks like we have a problem with token, let's redirect to login
$url = $this->sdk->getLoginUrl(array(
'scope' => $this->permissions
));
header('Location: ' . $url);
exit;
}


throw $fb_ex; return null;
} catch(\Facebook\Exceptions\FacebookSDKException $e) {
// When validation fails or other local issues
UserTools::debug("Facebook SDK returned an error: " . $e->getMessage());

return null;
} }


return $result; return $result;
Expand Down
Loading

0 comments on commit facd658

Please sign in to comment.