Merge branch '0.8.x' of git@gitorious.org:~evan/laconica/dev into 0.8.x

This commit is contained in:
Robin Millette 2009-05-17 00:05:53 -04:00
commit a325144fa0
10 changed files with 1717 additions and 159 deletions

View File

@ -103,3 +103,9 @@ EndPublicGroupNav: At the end of the public group nav menu
RouterInitialized: After the router instance has been initialized
- $m: the Net_URL_Mapper that has just been set up
StartLogout: Before logging out
- $action: the logout action
EndLogout: After logging out
- $action: the logout action

View File

@ -70,10 +70,20 @@ class LogoutAction extends Action
if (!common_logged_in()) {
$this->clientError(_('Not logged in.'));
} else {
common_set_user(null);
common_real_login(false); // not logged in
common_forgetme(); // don't log back in!
if (Event::handle('StartLogout', array($this))) {
$this->logout();
}
Event::handle('EndLogout', array($this));
common_redirect(common_local_url('public'), 303);
}
}
function logout()
{
common_set_user(null);
common_real_login(false); // not logged in
common_forgetme(); // don't log back in!
}
}

View File

@ -172,6 +172,10 @@ $config['sphinx']['port'] = 3312;
#$config['facebook']['apikey'] = 'APIKEY';
#$config['facebook']['secret'] = 'SECRET';
# Facebook Connect plugin (Needs valid APIKEY above)
#require_once(INSTALLDIR.'/plugins/FBConnect/FBConnectPlugin.php');
#$fbc = new FBConnectPlugin();
# Add Google Analytics
# require_once('plugins/GoogleAnalyticsPlugin.php');
# $ga = new GoogleAnalyticsPlugin('your secret code');

View File

@ -1,5 +1,5 @@
<?php
// Copyright 2004-2008 Facebook. All Rights Reserved.
// Copyright 2004-2009 Facebook. All Rights Reserved.
//
// +---------------------------------------------------------------------------+
// | Facebook Platform PHP5 client |
@ -30,13 +30,12 @@
// +---------------------------------------------------------------------------+
// | For help with this library, contact developers-help@facebook.com |
// +---------------------------------------------------------------------------+
//
include_once 'facebookapi_php5_restlib.php';
define('FACEBOOK_API_VALIDATION_ERROR', 1);
class Facebook {
public $api_client;
public $api_key;
public $secret;
public $generate_session_secret;
@ -213,9 +212,40 @@ class Facebook {
}
}
// Invalidate the session currently being used, and clear any state associated with it
// Invalidate the session currently being used, and clear any state associated
// with it. Note that the user will still remain logged into Facebook.
public function expire_session() {
if ($this->api_client->auth_expireSession()) {
$this->clear_cookie_state();
return true;
} else {
return false;
}
}
/** Logs the user out of all temporary application sessions as well as their
* Facebook session. Note this will only work if the user has a valid current
* session with the application.
*
* @param string $next URL to redirect to upon logging out
*
*/
public function logout($next) {
$logout_url = $this->get_logout_url($next);
// Clear any stored state
$this->clear_cookie_state();
$this->redirect($logout_url);
}
/**
* Clears any persistent state stored about the user, including
* cookies and information related to the current session in the
* client.
*
*/
public function clear_cookie_state() {
if (!$this->in_fb_canvas() && isset($_COOKIE[$this->api_key . '_user'])) {
$cookies = array('user', 'session_key', 'expires', 'ss');
foreach ($cookies as $name) {
@ -229,10 +259,6 @@ class Facebook {
// now, clear the rest of the stored state
$this->user = 0;
$this->api_client->session_key = 0;
return true;
} else {
return false;
}
}
public function redirect($url) {
@ -249,7 +275,8 @@ class Facebook {
}
public function in_frame() {
return isset($this->fb_params['in_canvas']) || isset($this->fb_params['in_iframe']);
return isset($this->fb_params['in_canvas'])
|| isset($this->fb_params['in_iframe']);
}
public function in_fb_canvas() {
return isset($this->fb_params['in_canvas']);
@ -296,14 +323,42 @@ class Facebook {
}
public function get_add_url($next=null) {
return self::get_facebook_url().'/add.php?api_key='.$this->api_key .
($next ? '&next=' . urlencode($next) : '');
$page = self::get_facebook_url().'/add.php';
$params = array('api_key' => $this->api_key);
if ($next) {
$params['next'] = $next;
}
return $page . '?' . http_build_query($params);
}
public function get_login_url($next, $canvas) {
return self::get_facebook_url().'/login.php?v=1.0&api_key=' . $this->api_key .
($next ? '&next=' . urlencode($next) : '') .
($canvas ? '&canvas' : '');
$page = self::get_facebook_url().'/login.php';
$params = array('api_key' => $this->api_key,
'v' => '1.0');
if ($next) {
$params['next'] = $next;
}
if ($canvas) {
$params['canvas'] = '1';
}
return $page . '?' . http_build_query($params);
}
public function get_logout_url($next) {
$page = self::get_facebook_url().'/logout.php';
$params = array('app_key' => $this->api_key,
'session_key' => $this->api_client->session_key);
if ($next) {
$params['connect_next'] = 1;
$params['next'] = $next;
}
return $page . '?' . http_build_query($params);
}
public function set_user($user, $session_key, $expires=null, $session_secret=null) {
@ -410,7 +465,20 @@ class Facebook {
return $fb_params;
}
/*
/**
* Validates the account that a user was trying to set up an
* independent account through Facebook Connect.
*
* @param user The user attempting to set up an independent account.
* @param hash The hash passed to the reclamation URL used.
* @return bool True if the user is the one that selected the
* reclamation link.
*/
public function verify_account_reclamation($user, $hash) {
return $hash == md5($user . $this->secret);
}
/**
* Validates that a given set of parameters match their signature.
* Parameters all match a given input prefix, such as "fb_sig".
*
@ -422,6 +490,37 @@ class Facebook {
return self::generate_sig($fb_params, $this->secret) == $expected_sig;
}
/**
* Validate the given signed public session data structure with
* public key of the app that
* the session proof belongs to.
*
* @param $signed_data the session info that is passed by another app
* @param string $public_key Optional public key of the app. If this
* is not passed, function will make an API call to get it.
* return true if the session proof passed verification.
*/
public function verify_signed_public_session_data($signed_data,
$public_key = null) {
// If public key is not already provided, we need to get it through API
if (!$public_key) {
$public_key = $this->api_client->auth_getAppPublicKey(
$signed_data['api_key']);
}
// Create data to verify
$data_to_serialize = $signed_data;
unset($data_to_serialize['sig']);
$serialized_data = implode('_', $data_to_serialize);
// Decode signature
$signature = base64_decode($signed_data['sig']);
$result = openssl_verify($serialized_data, $signature, $public_key,
OPENSSL_ALGO_SHA1);
return $result == 1;
}
/*
* Generate a signature using the application secret key.
*

View File

@ -1,5 +1,5 @@
<?php
// Copyright 2004-2008 Facebook. All Rights Reserved.
// Copyright 2004-2009 Facebook. All Rights Reserved.
//
// +---------------------------------------------------------------------------+
// | Facebook Platform PHP5 client |

1012
extlib/facebook/facebookapi_php5_restlib.php Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@ -27,9 +27,21 @@ define("FACEBOOK_PROMPTED_UPDATE_PREF", 2);
function getFacebook()
{
static $facebook = null;
$apikey = common_config('facebook', 'apikey');
$secret = common_config('facebook', 'secret');
return new Facebook($apikey, $secret);
if ($facebook === null) {
$facebook = new Facebook($apikey, $secret);
}
if (!$facebook) {
common_log(LOG_ERR, 'Could not make new Facebook client obj!',
__FILE__);
}
return $facebook;
}
function updateProfileBox($facebook, $flink, $notice) {
@ -92,7 +104,6 @@ function isFacebookBound($notice, $flink) {
}
function facebookBroadcastNotice($notice)
{
$facebook = getFacebook();

View File

@ -0,0 +1,372 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Plugin to enable Facebook Connect
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package Laconica
* @author Zach Copley <zach@controlyourself.ca>
* @copyright 2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php';
require_once INSTALLDIR . '/lib/facebookutil.php';
class FBConnectloginAction extends Action
{
var $fbuid = null;
var $fb_fields = null;
function prepare($args) {
parent::prepare($args);
$this->fbuid = getFacebook()->get_loggedin_user();
$this->fb_fields = $this->getFacebookFields($this->fbuid,
array('first_name', 'last_name', 'name'));
return true;
}
function handle($args)
{
parent::handle($args);
if (common_is_real_login()) {
$this->clientError(_('Already logged in.'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->showForm(_('There was a problem with your session token. Try again, please.'));
return;
}
if ($this->arg('create')) {
if (!$this->boolean('license')) {
$this->showForm(_('You can\'t register if you don\'t agree to the license.'),
$this->trimmed('newname'));
return;
}
$this->createNewUser();
} else if ($this->arg('connect')) {
$this->connectUser();
} else {
common_debug(print_r($this->args, true), __FILE__);
$this->showForm(_('Something weird happened.'),
$this->trimmed('newname'));
}
} else {
$this->tryLogin();
}
}
function showPageNotice()
{
if ($this->error) {
$this->element('div', array('class' => 'error'), $this->error);
} else {
$this->element('div', 'instructions',
sprintf(_('This is the first time you\'ve logged into %s so we must connect your Facebook to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name')));
}
}
function title()
{
return _('Facebook Account Setup');
}
function showForm($error=null, $username=null)
{
$this->error = $error;
$this->username = $username;
$this->showPage();
}
function showPage()
{
parent::showPage();
}
function showContent()
{
if (!empty($this->message_text)) {
$this->element('p', null, $this->message);
return;
}
$this->elementStart('form', array('method' => 'post',
'id' => 'account_connect',
'action' => common_local_url('fbconnectlogin')));
$this->hidden('token', common_session_token());
$this->element('h2', null,
_('Create new account'));
$this->element('p', null,
_('Create a new user with this nickname.'));
$this->input('newname', _('New nickname'),
($this->username) ? $this->username : '',
_('1-64 lowercase letters or numbers, no punctuation or spaces'));
$this->elementStart('p');
$this->element('input', array('type' => 'checkbox',
'id' => 'license',
'name' => 'license',
'value' => 'true'));
$this->text(_('My text and files are available under '));
$this->element('a', array('href' => common_config('license', 'url')),
common_config('license', 'title'));
$this->text(_(' except this private data: password, email address, IM address, phone number.'));
$this->elementEnd('p');
$this->submit('create', _('Create'));
$this->element('h2', null,
_('Connect existing account'));
$this->element('p', null,
_('If you already have an account, login with your username and password to connect it to your Facebook.'));
$this->input('nickname', _('Existing nickname'));
$this->password('password', _('Password'));
$this->submit('connect', _('Connect'));
$this->elementEnd('form');
}
function message($msg)
{
$this->message_text = $msg;
$this->showPage();
}
function createNewUser()
{
if (common_config('site', 'closed')) {
$this->clientError(_('Registration not allowed.'));
return;
}
$invite = null;
if (common_config('site', 'inviteonly')) {
$code = $_SESSION['invitecode'];
if (empty($code)) {
$this->clientError(_('Registration not allowed.'));
return;
}
$invite = Invitation::staticGet($code);
if (empty($invite)) {
$this->clientError(_('Not a valid invitation code.'));
return;
}
}
$nickname = $this->trimmed('newname');
if (!Validate::string($nickname, array('min_length' => 1,
'max_length' => 64,
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
$this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
return;
}
if (!User::allowed_nickname($nickname)) {
$this->showForm(_('Nickname not allowed.'));
return;
}
if (User::staticGet('nickname', $nickname)) {
$this->showForm(_('Nickname already in use. Try another one.'));
return;
}
$fullname = trim($this->fb_fields['firstname'] .
' ' . $this->fb_fields['lastname']);
$args = array('nickname' => $nickname, 'fullname' => $fullname);
if (!empty($invite)) {
$args['code'] = $invite->code;
}
$user = User::register($args);
$result = $this->flinkUser($user->id, $this->fbuid);
if (!$result) {
$this->serverError(_('Error connecting user to Facebook.'));
return;
}
common_set_user($user);
common_real_login(true);
common_debug("Registered new user $user->id from Facebook user $this->fbuid");
common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)),
303);
}
function connectUser()
{
$nickname = $this->trimmed('nickname');
$password = $this->trimmed('password');
if (!common_check_user($nickname, $password)) {
$this->showForm(_('Invalid username or password.'));
return;
}
$user = User::staticGet('nickname', $nickname);
if ($user) {
common_debug("Legit user to connect to Facebook: $nickname");
}
$result = $this->flinkUser($user->id, $this->fbuid);
if (!$result) {
$this->serverError(_('Error connecting user to Facebook.'));
return;
}
common_debug("Connected Facebook user $this->fbuid to local user $user->id");
common_set_user($user);
common_real_login(true);
$this->goHome($user->nickname);
}
function tryLogin()
{
$flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE);
if ($flink) {
$user = $flink->getUser();
if ($user) {
common_debug("Logged in Facebook user $flink->foreign_id as user $user->id");
common_set_user($user);
common_real_login(true);
$this->goHome($user->nickname);
}
} else {
$this->showForm(null, $this->bestNewNickname());
}
}
function goHome($nickname)
{
$url = common_get_returnto();
if ($url) {
// We don't have to return to it again
common_set_returnto(null);
} else {
$url = common_local_url('all',
array('nickname' =>
$nickname));
}
common_redirect($url, 303);
}
function flinkUser($user_id, $fbuid)
{
$flink = new Foreign_link();
$flink->user_id = $user_id;
$flink->foreign_id = $fbuid;
$flink->service = FACEBOOK_SERVICE;
$flink->created = common_sql_now();
$flink_id = $flink->insert();
return $flink_id;
}
function bestNewNickname()
{
common_debug("bestNewNickname()");
common_debug(print_r($this->fb_fields, true));
if (!empty($this->fb_fields['name'])) {
$nickname = $this->nicknamize($this->fb_fields['name']);
if ($this->isNewNickname($nickname)) {
return $nickname;
}
}
// Try the full name
$fullname = trim($this->fb_fields['firstname'] .
' ' . $this->fb_fields['lastname']);
if (!empty($fullname)) {
$fullname = $this->nicknamize($fullname);
if ($this->isNewNickname($fullname)) {
return $fullname;
}
}
return null;
}
// Given a string, try to make it work as a nickname
function nicknamize($str)
{
$str = preg_replace('/\W/', '', $str);
return strtolower($str);
}
function isNewNickname($str)
{
if (!Validate::string($str, array('min_length' => 1,
'max_length' => 64,
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
return false;
}
if (!User::allowed_nickname($str)) {
return false;
}
if (User::staticGet('nickname', $str)) {
return false;
}
return true;
}
// XXX: Consider moving this to lib/facebookutil.php
function getFacebookFields($fb_uid, $fields) {
try {
$infos = getFacebook()->api_client->users_getInfo($fb_uid, $fields);
if (empty($infos)) {
return null;
}
return reset($infos);
} catch (Exception $e) {
error_log("Failure in the api when requesting " . join(",", $fields)
." on uid " . $fb_uid . " : ". $e->getMessage());
return null;
}
}
}

View File

@ -0,0 +1,278 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Plugin to enable Facebook Connect
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package Laconica
* @author Zach Copley <zach@controlyourself.ca>
* @copyright 2009 Control Yourself, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
if (!defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php';
require_once INSTALLDIR . '/lib/facebookutil.php';
/**
* Plugin to enable Facebook Connect
*
* @category Plugin
* @package Laconica
* @author Zach Copley <zach@controlyourself.ca>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://laconi.ca/
*/
class FBConnectPlugin extends Plugin
{
function __construct()
{
parent::__construct();
}
// Hook in new actions
function onRouterInitialized(&$m) {
$m->connect('main/facebookconnect', array('action' => 'fbconnectlogin'));
}
// Add in xmlns:fb
function onStartShowHTML($action)
{
// XXX: This is probably a bad place to do general processing
// so maybe I need to make some new events? Maybe in
// Action::prepare?
$name = get_class($action);
common_debug("action: $name");
// Avoid a redirect loop
if ($name != 'FBConnectloginAction') {
$this->checkFacebookUser($action);
}
$httpaccept = isset($_SERVER['HTTP_ACCEPT']) ?
$_SERVER['HTTP_ACCEPT'] : null;
// XXX: allow content negotiation for RDF, RSS, or XRDS
$cp = common_accept_to_prefs($httpaccept);
$sp = common_accept_to_prefs(PAGE_TYPE_PREFS);
$type = common_negotiate_type($cp, $sp);
if (!$type) {
throw new ClientException(_('This page is not available in a '.
'media type you accept'), 406);
}
header('Content-Type: '.$type);
$action->extraHeaders();
$action->startXML('html',
'-//W3C//DTD XHTML 1.0 Strict//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
$language = $action->getLanguage();
$action->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml',
'xmlns:fb' => 'http://www.facebook.com/2008/fbml',
'xml:lang' => $language,
'lang' => $language));
return false;
}
function onEndShowLaconicaScripts($action)
{
$action->element('script',
array('type' => 'text/javascript',
'src' => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'),
' ');
$apikey = common_config('facebook', 'apikey');
$plugin_path = common_path('plugins/FBConnect');
$login_url = common_get_returnto() || common_local_url('public');
$html = sprintf('<script type="text/javascript">FB.init("%s", "%s/xd_receiver.htm");
function refresh_page() {
window.location = "%s";
}
</script>', $apikey, $plugin_path, $login_url);
$action->raw($html);
}
function onStartPrimaryNav($action)
{
$user = common_current_user();
if ($user) {
$action->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
_('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
$action->menuItem(common_local_url('profilesettings'),
_('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
if (common_config('xmpp', 'enabled')) {
$action->menuItem(common_local_url('imsettings'),
_('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
} else {
$action->menuItem(common_local_url('smssettings'),
_('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
}
$action->menuItem(common_local_url('invite'),
_('Invite'),
sprintf(_('Invite friends and colleagues to join you on %s'),
common_config('site', 'name')),
false, 'nav_invitecontact');
// Need to override the Logout link to make it do FB stuff
$logout_url = common_local_url('logout');
$title = _('Logout from the site');
$text = _('Logout');
$html = sprintf('<li id="nav_logout"><a href="%s" title="%s" ' .
'onclick="FB.Connect.logoutAndRedirect(\'%s\')">%s</a></li>',
$logout_url, $title, $logout_url, $text);
$action->raw($html);
}
else {
if (!common_config('site', 'closed')) {
$action->menuItem(common_local_url('register'),
_('Register'), _('Create an account'), false, 'nav_register');
}
$action->menuItem(common_local_url('openidlogin'),
_('OpenID'), _('Login with OpenID'), false, 'nav_openid');
$action->menuItem(common_local_url('login'),
_('Login'), _('Login to the site'), false, 'nav_login');
}
$action->menuItem(common_local_url('doc', array('title' => 'help')),
_('Help'), _('Help me!'), false, 'nav_help');
$action->menuItem(common_local_url('peoplesearch'),
_('Search'), _('Search for people or text'), false, 'nav_search');
// Tack on "Connect with Facebook" button
// XXX: Maybe this looks bad and should not go here. Where should it go?
if (!$user) {
$action->elementStart('li');
$action->element('fb:login-button', array('onlogin' => 'refresh_page()',
'length' => 'long'));
$action->elementEnd('li');
}
return false;
}
function checkFacebookUser() {
try {
$facebook = getFacebook();
$fbuid = $facebook->get_loggedin_user();
$user = common_current_user();
// If you're a Facebook user and you're logged in do nothing
// If you're a Facebook user and you're not logged in
// redirect to Facebook connect login page because that means you have clicked
// the 'connect with Facebook' button and have cookies
if ($fbuid > 0) {
if ($facebook->api_client->users_isAppUser($fbuid) ||
$facebook->api_client->added) {
// user should be connected...
common_debug("Facebook user found: $fbuid");
if ($user) {
common_debug("Facebook user is logged in.");
return;
} else {
common_debug("Facebook user is NOT logged in.");
common_redirect(common_local_url('fbconnectlogin'), 303);
}
} else {
common_debug("No Facebook connect user found.");
}
}
} catch (Exception $e) {
common_debug('Expired FB session.');
}
}
function onStartLogout($action)
{
common_debug("onEndLogout()");
common_set_user(null);
common_real_login(false); // not logged in
common_forgetme(); // don't log back in!
try {
$facebook = getFacebook();
$fbuid = $facebook->get_loggedin_user();
// XXX: ARGGGH this doesn't work right!
if ($fbuid) {
$facebook->expire_session();
$facebook->logout(common_local_url('public'));
}
} catch (Exception $e) {
common_debug('Problem expiring FB session');
}
common_debug("logged out.");
return false;
}
}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>cross domain receiver page</title>
</head>
<body>
<script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js" type="text/javascript"></script>
</body>
</html>