Facebook bridge back in business with new JS-SDK and OAuth 2.0 flow.

Might be better to rewrite the login mechanism to use server side flow
now that Facebook provides it.
This commit is contained in:
Zach Copley 2011-09-27 04:09:47 +00:00
parent 634fb64c11
commit 3b28f226c7
6 changed files with 116 additions and 196 deletions

View File

@ -44,20 +44,18 @@ class Foreign_user extends Managed_DataObject
); );
} }
// XXX: This only returns a 1->1 single obj mapping. Change? Or make
// a getForeignUsers() that returns more than one? --Zach
static function getForeignUser($id, $service) { static function getForeignUser($id, $service) {
$fuser = new Foreign_user(); $fuser = new Foreign_user();
$fuser->whereAdd("service = $service");
$fuser->whereAdd("id = $id"); $fuser->id = $id;
$fuser->service = $service;
$fuser->limit(1); $fuser->limit(1);
if ($fuser->find()) { $result = $fuser->find(true);
$fuser->fetch();
return $fuser;
}
return null; return empty($result) ? null : $fuser;
} }
static function getByNickname($nickname, $service) static function getByNickname($nickname, $service)

View File

@ -103,8 +103,6 @@ class FacebookBridgePlugin extends Plugin
{ {
$dir = dirname(__FILE__); $dir = dirname(__FILE__);
//common_debug("class = " . $cls);
switch ($cls) switch ($cls)
{ {
case 'Facebook': // Facebook PHP SDK case 'Facebook': // Facebook PHP SDK
@ -352,6 +350,13 @@ class FacebookBridgePlugin extends Plugin
$action->script('https://connect.facebook.net/en_US/all.js'); $action->script('https://connect.facebook.net/en_US/all.js');
$script = <<<ENDOFSCRIPT $script = <<<ENDOFSCRIPT
function setCookie(name, value) {
var date = new Date();
date.setTime(date.getTime() + (5 * 60 * 1000)); // 5 mins
var expires = "; expires=" + date.toGMTString();
document.cookie = name + "=" + value + expires + "; path=/";
}
FB.init({appId: %1\$s, status: true, cookie: true, xfbml: true, oauth: true}); FB.init({appId: %1\$s, status: true, cookie: true, xfbml: true, oauth: true});
$('#facebook_button').bind('click', function(event) { $('#facebook_button').bind('click', function(event) {
@ -360,6 +365,8 @@ $('#facebook_button').bind('click', function(event) {
FB.login(function(response) { FB.login(function(response) {
if (response.authResponse) { if (response.authResponse) {
// put the access token in a cookie for the next step
setCookie('fb_access_token', response.authResponse.accessToken);
window.location.href = '%2\$s'; window.location.href = '%2\$s';
} else { } else {
// NOP (user cancelled login) // NOP (user cancelled login)
@ -383,24 +390,30 @@ ENDOFSCRIPT;
* *
* @param Action action the current action * @param Action action the current action
*/ */
function onEndLogout($action) function onStartLogout($action)
{ {
if ($this->hasApplication()) { if ($this->hasApplication()) {
//$session = $this->facebook->getSession();
$fbuser = null;
$fbuid = null;
try { $cur = common_current_user();
$fbuid = $this->facebook->getUser(); $flink = Foreign_link::getByUserID($cur->id, FACEBOOK_SERVICE);
$fbuser = $this->facebook->api('/me');
} catch (FacebookApiException $e) { if (!empty($flink)) {
common_log(LOG_ERROR, $e, __FILE__);
$this->facebook->setAccessToken($flink->credentials);
if (common_config('singleuser', 'enabled')) {
$user = User::singleUser();
$destination = common_local_url(
'showstream',
array('nickname' => $user->nickname)
);
} else {
$destination = common_local_url('public');
} }
if (!empty($fbuser)) {
$logoutUrl = $this->facebook->getLogoutUrl( $logoutUrl = $this->facebook->getLogoutUrl(
array('next' => common_local_url('public')) array('next' => $destination)
); );
common_log( common_log(
@ -411,9 +424,14 @@ ENDOFSCRIPT;
), ),
__FILE__ __FILE__
); );
common_debug("LOGOUT URL = $logoutUrl");
$action->logout();
common_redirect($logoutUrl, 303); common_redirect($logoutUrl, 303);
return false; // probably never get here, but hey
} }
return true;
} }
} }

View File

@ -1,7 +1,7 @@
<?php <?php
/** /**
* StatusNet - the distributed open-source microblogging tool * StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc. * Copyright (C) 2010-2011, StatusNet, Inc.
* *
* An action that handles deauthorize callbacks from Facebook * An action that handles deauthorize callbacks from Facebook
* *
@ -23,7 +23,7 @@
* @category Plugin * @category Plugin
* @package StatusNet * @package StatusNet
* @author Zach Copley <zach@status.net> * @author Zach Copley <zach@status.net>
* @copyright 2010 StatusNet, Inc. * @copyright 2010-2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/ * @link http://status.net/
*/ */

View File

@ -42,37 +42,24 @@ class FacebookfinishloginAction extends Action
// Check cookie for a valid access_token // Check cookie for a valid access_token
$cookie = $this->get_facebook_cookie( if (isset($_COOKIE['fb_access_token'])) {
common_config('facebook', 'appid'), $this->accessToken = $_COOKIE['fb_access_token'];
common_config('facebook', 'secret') if (empty($this->accessToken)) {
); $this->clientError(_m("Unable to authenticate you with Facebook."));
return false;
}
}
$this->accessToken = $cookie['access_token']; $graphUrl = 'https://graph.facebook.com/me?access_token=' . urlencode($this->accessToken);
$this->fbuser = json_decode(file_get_contents($graphUrl));
common_debug("cookie = " . var_export($cookie, true));
$this->fbuser = json_decode(
file_get_contents(
'https://graph.facebook.com/me?access_token='
. $this->accessToken
)
);
if (!empty($this->fbuser)) { if (!empty($this->fbuser)) {
$this->fbuid = $this->fbuser->id; $this->fbuid = $this->fbuser->id;
common_debug("fbuser = " . var_export($this->fbuser, true));
common_debug("fbuid = " . $this->fbuid);
// OKAY, all is well... proceed to register // OKAY, all is well... proceed to register
common_debug("Found a valid Facebook user.", __FILE__);
return true; return true;
} else { } else {
// This shouldn't happen in the regular course of things // log badness
list($proxy, $ip) = common_client_ip(); list($proxy, $ip) = common_client_ip();
@ -95,27 +82,6 @@ class FacebookfinishloginAction extends Action
return false; return false;
} }
function get_facebook_cookie($app_id, $app_secret) {
$args = array();
parse_str(trim($_COOKIE['fbs_' . $app_id], '\\"'), $args);
ksort($args);
$payload = '';
foreach ($args as $key => $value) {
if ($key != 'sig') {
$payload .= $key . '=' . $value;
}
}
if (md5($payload . $app_secret) != $args['sig']) {
return null;
}
return $args;
}
function handle($args) function handle($args)
{ {
parent::handle($args); parent::handle($args);
@ -148,14 +114,6 @@ class FacebookfinishloginAction extends Action
// User already has a linked Facebook account and shouldn't be here! // User already has a linked Facebook account and shouldn't be here!
common_debug(
sprintf(
'There\'s already a local user %d linked with Facebook user %s.',
$flink->user_id,
$this->fbuid
)
);
$this->clientError( $this->clientError(
// TRANS: Client error displayed when trying to connect to a Facebook account that is already linked // TRANS: Client error displayed when trying to connect to a Facebook account that is already linked
// TRANS: in the same StatusNet site. // TRANS: in the same StatusNet site.
@ -172,14 +130,6 @@ class FacebookfinishloginAction extends Action
// There's already a local user linked to this Facebook account. // There's already a local user linked to this Facebook account.
common_debug(
sprintf(
'There\'s already a local user %d linked with Facebook user %s.',
$cur->id,
$this->fbuid
)
);
$this->clientError( $this->clientError(
// TRANS: Client error displayed when trying to connect to a Facebook account that is already linked // TRANS: Client error displayed when trying to connect to a Facebook account that is already linked
// TRANS: in the same StatusNet site. // TRANS: in the same StatusNet site.
@ -194,6 +144,7 @@ class FacebookfinishloginAction extends Action
{ {
$token = $this->trimmed('token'); $token = $this->trimmed('token');
// CSRF protection
if (!$token || $token != common_session_token()) { if (!$token || $token != common_session_token()) {
$this->showForm( $this->showForm(
// TRANS: Client error displayed when the session token does not match or is not given. // TRANS: Client error displayed when the session token does not match or is not given.
@ -481,9 +432,10 @@ class FacebookfinishloginAction extends Action
*/ */
function setAvatar($user) function setAvatar($user)
{ {
try {
$picUrl = sprintf( $picUrl = sprintf(
'http://graph.facebook.com/%s/picture?type=large', 'http://graph.facebook.com/%d/picture?type=large',
$this->fbuid $this->fbuser->id
); );
// fetch the picture from Facebook // fetch the picture from Facebook
@ -494,36 +446,21 @@ class FacebookfinishloginAction extends Action
if ($response->isOk()) { if ($response->isOk()) {
$finalUrl = $client->getUrl(); // seems to always be jpeg, but not sure
$tmpname = "facebook-avatar-tmp-" . common_good_rand(4);
// Make sure the filename is unique becuase it's possible for a user
// to deauthorize our app, and then come back in as a new user but
// have the same Facebook picture (avatar URLs have a unique index
// and their URLs are based on the filenames).
$filename = 'facebook-' . common_good_rand(4) . '-'
. substr(strrchr($finalUrl, '/'), 1);
$ok = file_put_contents( $ok = file_put_contents(
Avatar::path($filename), Avatar::path($tmpname),
$response->getBody() $response->getBody()
); );
if (!$ok) { if (!$ok) {
common_log( common_log(LOG_WARNING, 'Couldn\'t save tmp Facebook avatar: ' . $tmpname, __FILE__);
LOG_WARNING,
sprintf(
'Couldn\'t save Facebook avatar %s',
$tmp
),
__FILE__
);
} else { } else {
// save it as an avatar // save it as an avatar
$profile = $user->getProfile(); $profile = $user->getProfile();
if ($profile->setOriginal($filename)) { if ($profile->setOriginal($tmpname)) {
common_log( common_log(
LOG_INFO, LOG_INFO,
sprintf( sprintf(
@ -537,8 +474,16 @@ class FacebookfinishloginAction extends Action
), ),
__FILE__ __FILE__
); );
// clean up
@unlink(Avatar::path($tmpname));
}
} }
} }
} catch (Exception $e) {
common_log(LOG_WARNING, 'Couldn\'t save Facebook avatar: ' . $e->getMessage(), __FILE__);
// error isn't fatal, continue
} }
} }
@ -555,22 +500,14 @@ class FacebookfinishloginAction extends Action
$user = User::staticGet('nickname', $nickname); $user = User::staticGet('nickname', $nickname);
if (!empty($user)) {
common_debug(
sprintf(
'Found a legit user to connect to Facebook: %s (%d)',
$user->nickname,
$user->id
),
__FILE__
);
}
$this->tryLinkUser($user); $this->tryLinkUser($user);
common_set_user($user); common_set_user($user);
common_real_login(true); common_real_login(true);
// clear out the stupid cookie
setcookie('fb_access_token', '', time() - 3600); // one hour ago
$this->goHome($user->nickname); $this->goHome($user->nickname);
} }
@ -578,6 +515,9 @@ class FacebookfinishloginAction extends Action
{ {
$user = common_current_user(); $user = common_current_user();
$this->tryLinkUser($user); $this->tryLinkUser($user);
// clear out the stupid cookie
setcookie('fb_access_token', '', time() - 3600); // one hour ago
common_redirect(common_local_url('facebookfinishlogin'), 303); common_redirect(common_local_url('facebookfinishlogin'), 303);
} }
@ -590,29 +530,10 @@ class FacebookfinishloginAction extends Action
$this->serverError(_m('Error connecting user to Facebook.')); $this->serverError(_m('Error connecting user to Facebook.'));
return; return;
} }
common_debug(
sprintf(
'Connected Facebook user %s (fbuid %d) to local user %s (%d)',
$this->fbuser->name,
$this->fbuid,
$user->nickname,
$user->id
),
__FILE__
);
} }
function tryLogin() function tryLogin()
{ {
common_debug(
sprintf(
'Trying login for Facebook user %s',
$this->fbuid
),
__FILE__
);
$flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE); $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE);
if (!empty($flink)) { if (!empty($flink)) {
@ -633,19 +554,14 @@ class FacebookfinishloginAction extends Action
common_set_user($user); common_set_user($user);
common_real_login(true); common_real_login(true);
// clear out the stupid cookie
setcookie('fb_access_token', '', time() - 3600); // one hour ago
$this->goHome($user->nickname); $this->goHome($user->nickname);
} }
} else { } else {
common_debug(
sprintf(
'No flink found for fbuid: %s - new user',
$this->fbuid
),
__FILE__
);
$this->showForm(null, $this->bestNewNickname()); $this->showForm(null, $this->bestNewNickname());
} }
} }
@ -668,13 +584,11 @@ class FacebookfinishloginAction extends Action
function flinkUser($user_id, $fbuid) function flinkUser($user_id, $fbuid)
{ {
$flink = new Foreign_link(); $flink = new Foreign_link();
$flink->user_id = $user_id; $flink->user_id = $user_id;
$flink->foreign_id = $fbuid; $flink->foreign_id = $fbuid;
$flink->service = FACEBOOK_SERVICE; $flink->service = FACEBOOK_SERVICE;
// Pull the access token from the Facebook cookies
$flink->credentials = $this->accessToken; $flink->credentials = $this->accessToken;
$flink->created = common_sql_now(); $flink->created = common_sql_now();
$flink_id = $flink->insert(); $flink_id = $flink->insert();

View File

@ -23,7 +23,7 @@
* @category Plugin * @category Plugin
* @package StatusNet * @package StatusNet
* @author Zach Copley <zach@status.net> * @author Zach Copley <zach@status.net>
* @copyright 2010 StatusNet, Inc. * @copyright 2010-2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
@ -98,16 +98,6 @@ class FacebookloginAction extends Action
$this->elementEnd('a'); $this->elementEnd('a');
/*
$this->element('div', array('id' => 'fb-root'));
$this->script(
sprintf(
'http://connect.facebook.net/en_US/all.js#appId=%s&xfbml=1',
common_config('facebook', 'appid')
)
);
$this->element('fb:facepile', array('max-rows' => '2', 'width' =>'300'));
*/
$this->elementEnd('fieldset'); $this->elementEnd('fieldset');
} }

View File

@ -23,7 +23,7 @@
* @package StatusNet * @package StatusNet
* @author Craig Andrews <candrews@integralblue.com> * @author Craig Andrews <candrews@integralblue.com>
* @author Zach Copley <zach@status.net> * @author Zach Copley <zach@status.net>
* @copyright 2009-2010 StatusNet, Inc. * @copyright 2009-2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
@ -927,8 +927,8 @@ class Facebookclient
LOG_INFO, LOG_INFO,
sprintf( sprintf(
'Removed old Facebook user: %s, fbuid %d', 'Removed old Facebook user: %s, fbuid %d',
$fbuid['name'], $fbuid->name,
$fbuid['id'] $fbuid->id
), ),
__FILE__ __FILE__
); );
@ -938,7 +938,7 @@ class Facebookclient
$fuser = new Foreign_user(); $fuser = new Foreign_user();
$fuser->nickname = $fbuser->username; $fuser->nickname = $fbuser->username;
$fuser->uri = $fbuser->url; $fuser->uri = $fbuser->link;
$fuser->id = $fbuser->id; $fuser->id = $fbuser->id;
$fuser->service = FACEBOOK_SERVICE; $fuser->service = FACEBOOK_SERVICE;
$fuser->created = common_sql_now(); $fuser->created = common_sql_now();