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) {
$fuser = new Foreign_user();
$fuser->whereAdd("service = $service");
$fuser->whereAdd("id = $id");
$fuser->id = $id;
$fuser->service = $service;
$fuser->limit(1);
if ($fuser->find()) {
$fuser->fetch();
return $fuser;
}
$result = $fuser->find(true);
return null;
return empty($result) ? null : $fuser;
}
static function getByNickname($nickname, $service)

View File

@ -103,8 +103,6 @@ class FacebookBridgePlugin extends Plugin
{
$dir = dirname(__FILE__);
//common_debug("class = " . $cls);
switch ($cls)
{
case 'Facebook': // Facebook PHP SDK
@ -352,6 +350,13 @@ class FacebookBridgePlugin extends Plugin
$action->script('https://connect.facebook.net/en_US/all.js');
$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});
$('#facebook_button').bind('click', function(event) {
@ -360,6 +365,8 @@ $('#facebook_button').bind('click', function(event) {
FB.login(function(response) {
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';
} else {
// NOP (user cancelled login)
@ -383,24 +390,30 @@ ENDOFSCRIPT;
*
* @param Action action the current action
*/
function onEndLogout($action)
function onStartLogout($action)
{
if ($this->hasApplication()) {
//$session = $this->facebook->getSession();
$fbuser = null;
$fbuid = null;
try {
$fbuid = $this->facebook->getUser();
$fbuser = $this->facebook->api('/me');
} catch (FacebookApiException $e) {
common_log(LOG_ERROR, $e, __FILE__);
$cur = common_current_user();
$flink = Foreign_link::getByUserID($cur->id, FACEBOOK_SERVICE);
if (!empty($flink)) {
$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(
array('next' => common_local_url('public'))
array('next' => $destination)
);
common_log(
@ -411,9 +424,14 @@ ENDOFSCRIPT;
),
__FILE__
);
common_debug("LOGOUT URL = $logoutUrl");
$action->logout();
common_redirect($logoutUrl, 303);
return false; // probably never get here, but hey
}
return true;
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* 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
*
@ -23,7 +23,7 @@
* @category Plugin
* @package StatusNet
* @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
* @link http://status.net/
*/

View File

@ -42,37 +42,24 @@ class FacebookfinishloginAction extends Action
// Check cookie for a valid access_token
$cookie = $this->get_facebook_cookie(
common_config('facebook', 'appid'),
common_config('facebook', 'secret')
);
if (isset($_COOKIE['fb_access_token'])) {
$this->accessToken = $_COOKIE['fb_access_token'];
if (empty($this->accessToken)) {
$this->clientError(_m("Unable to authenticate you with Facebook."));
return false;
}
}
$this->accessToken = $cookie['access_token'];
common_debug("cookie = " . var_export($cookie, true));
$this->fbuser = json_decode(
file_get_contents(
'https://graph.facebook.com/me?access_token='
. $this->accessToken
)
);
$graphUrl = 'https://graph.facebook.com/me?access_token=' . urlencode($this->accessToken);
$this->fbuser = json_decode(file_get_contents($graphUrl));
if (!empty($this->fbuser)) {
$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
common_debug("Found a valid Facebook user.", __FILE__);
return true;
} else {
// This shouldn't happen in the regular course of things
// log badness
list($proxy, $ip) = common_client_ip();
@ -95,27 +82,6 @@ class FacebookfinishloginAction extends Action
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)
{
parent::handle($args);
@ -148,14 +114,6 @@ class FacebookfinishloginAction extends Action
// 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(
// TRANS: Client error displayed when trying to connect to a Facebook account that is already linked
// 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.
common_debug(
sprintf(
'There\'s already a local user %d linked with Facebook user %s.',
$cur->id,
$this->fbuid
)
);
$this->clientError(
// TRANS: Client error displayed when trying to connect to a Facebook account that is already linked
// TRANS: in the same StatusNet site.
@ -194,6 +144,7 @@ class FacebookfinishloginAction extends Action
{
$token = $this->trimmed('token');
// CSRF protection
if (!$token || $token != common_session_token()) {
$this->showForm(
// 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)
{
try {
$picUrl = sprintf(
'http://graph.facebook.com/%s/picture?type=large',
$this->fbuid
'http://graph.facebook.com/%d/picture?type=large',
$this->fbuser->id
);
// fetch the picture from Facebook
@ -494,36 +446,21 @@ class FacebookfinishloginAction extends Action
if ($response->isOk()) {
$finalUrl = $client->getUrl();
// 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);
// seems to always be jpeg, but not sure
$tmpname = "facebook-avatar-tmp-" . common_good_rand(4);
$ok = file_put_contents(
Avatar::path($filename),
Avatar::path($tmpname),
$response->getBody()
);
if (!$ok) {
common_log(
LOG_WARNING,
sprintf(
'Couldn\'t save Facebook avatar %s',
$tmp
),
__FILE__
);
common_log(LOG_WARNING, 'Couldn\'t save tmp Facebook avatar: ' . $tmpname, __FILE__);
} else {
// save it as an avatar
$profile = $user->getProfile();
if ($profile->setOriginal($filename)) {
if ($profile->setOriginal($tmpname)) {
common_log(
LOG_INFO,
sprintf(
@ -537,8 +474,16 @@ class FacebookfinishloginAction extends Action
),
__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);
if (!empty($user)) {
common_debug(
sprintf(
'Found a legit user to connect to Facebook: %s (%d)',
$user->nickname,
$user->id
),
__FILE__
);
}
$this->tryLinkUser($user);
common_set_user($user);
common_real_login(true);
// clear out the stupid cookie
setcookie('fb_access_token', '', time() - 3600); // one hour ago
$this->goHome($user->nickname);
}
@ -578,6 +515,9 @@ class FacebookfinishloginAction extends Action
{
$user = common_current_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);
}
@ -590,29 +530,10 @@ class FacebookfinishloginAction extends Action
$this->serverError(_m('Error connecting user to Facebook.'));
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()
{
common_debug(
sprintf(
'Trying login for Facebook user %s',
$this->fbuid
),
__FILE__
);
$flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE);
if (!empty($flink)) {
@ -633,19 +554,14 @@ class FacebookfinishloginAction extends Action
common_set_user($user);
common_real_login(true);
// clear out the stupid cookie
setcookie('fb_access_token', '', time() - 3600); // one hour ago
$this->goHome($user->nickname);
}
} else {
common_debug(
sprintf(
'No flink found for fbuid: %s - new user',
$this->fbuid
),
__FILE__
);
$this->showForm(null, $this->bestNewNickname());
}
}
@ -668,13 +584,11 @@ class FacebookfinishloginAction extends Action
function flinkUser($user_id, $fbuid)
{
$flink = new Foreign_link();
$flink->user_id = $user_id;
$flink->foreign_id = $fbuid;
$flink->service = FACEBOOK_SERVICE;
// Pull the access token from the Facebook cookies
$flink->credentials = $this->accessToken;
$flink->created = common_sql_now();
$flink_id = $flink->insert();

View File

@ -23,7 +23,7 @@
* @category Plugin
* @package StatusNet
* @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
* @link http://status.net/
*/
@ -98,16 +98,6 @@ class FacebookloginAction extends Action
$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');
}

View File

@ -23,7 +23,7 @@
* @package StatusNet
* @author Craig Andrews <candrews@integralblue.com>
* @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
* @link http://status.net/
*/
@ -927,8 +927,8 @@ class Facebookclient
LOG_INFO,
sprintf(
'Removed old Facebook user: %s, fbuid %d',
$fbuid['name'],
$fbuid['id']
$fbuid->name,
$fbuid->id
),
__FILE__
);
@ -938,7 +938,7 @@ class Facebookclient
$fuser = new Foreign_user();
$fuser->nickname = $fbuser->username;
$fuser->uri = $fbuser->url;
$fuser->uri = $fbuser->link;
$fuser->id = $fbuser->id;
$fuser->service = FACEBOOK_SERVICE;
$fuser->created = common_sql_now();