PhpBB3 Api Bridge
Hi everyone,
I recently needed to bridge between my Cake app and a PhpBB3 forum. When I realized the old PhpBB2 Bridge didn't work anymore, I decided to write a new one.
Public functions included are: login, logout, changePassword
I recently needed to bridge between my Cake app and a PhpBB3 forum. When I realized the old PhpBB2 Bridge didn't work anymore, I decided to write a new one.
Public functions included are: login, logout, changePassword
INTRODUCTION
Hi everyone,
I recently needed to bridge between my Cake app and a PhpBB3 forum. When I realized the old PhpBB2 Bridge didn't work anymore, I decided to write a new one.
Public functions included are: login, logout, changePassword
In addition, the login function will check if a user current exists, and if not, will add them.
As much as possible, I used the native PhpBB3 'API'. However, for changePassword(), a custom query was required as well as a few password generation functions which were adapted from Jonathan Gibb's (http://www.polr.co.uk) PHPBB3Integration class.
Small caveat, to workaround the 'cache' classname collision, you must change four lines of code in PhpBB3. If someone knows of another workaround, please let me know.
In phpBB3/includes/cache.php, rename the 'cache' class.
Download code
class phpbb3_cache extends acm
In these three files, 'phpBB3/common.php', 'phpBB3/style.php', and 'phpBB3/download/file.php' change:
Download code
$cache = new cache()
to
Download code
$cache = new phpbb3_cache()
Perhaps, you could store the four changes in an SVN branch and merge when you update your forum.
USAGE
To use the component, simply include it in your controller and call as you normally would.
Download code
var $components = array('PhpBB3');
Download code
//login to the forum as well
$this->PhpBB3->login($this->data['Member']['username'], $textPassword, $this->data['Member']['email']);
CODE
Component file name will be: app/controllers/components/php_b_b3.php
Component Class:
Download code
<?php
class PhpBB3Component extends Object {
var $controller;
var $model;
function startup(&$controller) {
$this->controller = &$controller;
define('IN_PHPBB', true);
global $phpbb_root_path, $phpEx, $db, $config, $user, $auth, $cache, $template;
$phpbb_root_path = WWW_ROOT . 'phpBB3/';
$phpEx = substr(strrchr(__FILE__, '.'), 1);
require_once($phpbb_root_path . 'common.' . $phpEx);
$this->table_prefix = $table_prefix;
$this->auth = $auth;
$this->user = $user;
// Start session management
$this->user->session_begin();
$this->auth->acl($user->data);
$this->user->setup();
require_once($phpbb_root_path .'includes/functions_user.php');
}
private function checkUserExists($username, $isFalse = false) {
if (user_get_id_name($isFalse, $username) == 'NO_USERS') {
return false;
} else {
return true;
}
}
// Helper Methods
/**
* Generate salt for hash generation
*/
private function _hash_gensalt_private($input,&$itoa64,$iteration_count_log2 = 6) {
if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) {
$iteration_count_log2 = 8;
}
$output = '$H$';
$output .= $itoa64 [min($iteration_count_log2 + ((PHP_VERSION >= 5) ? 5 : 3), 30)];
$output .= $this->_hash_encode64($input, 6, $itoa64);
return $output;
}
/**
* Encode hash
*/
private function _hash_encode64($input,$count,&$itoa64) {
$output = '';
$i = 0;
do {
$value = ord ( $input [$i ++] );
$output .= $itoa64 [$value & 0x3f];
if ($i < $count) {
$value |= ord ( $input [$i] ) << 8;
}
$output .= $itoa64 [($value >> 6) & 0x3f];
if ($i ++ >= $count) {
break;
}
if ($i < $count) {
$value |= ord ( $input [$i] ) << 16;
}
$output .= $itoa64 [($value >> 12) & 0x3f];
if ($i ++ >= $count) {
break;
}
$output .= $itoa64 [($value >> 18) & 0x3f];
} while ( $i < $count );
return $output;
}
/**
* The crypt function/replacement
*/
private function _hash_crypt_private($password,$setting,&$itoa64) {
$output = '*';
// Check for correct hash
if (substr ( $setting, 0, 3 ) != '$H$') {
return $output;
}
$count_log2 = strpos ( $itoa64, $setting [3] );
if ($count_log2 < 7 || $count_log2 > 30) {
return $output;
}
$count = 1 << $count_log2;
$salt = substr ( $setting, 4, 8 );
if (strlen ( $salt ) != 8) {
return $output;
}
/**
* We're kind of forced to use MD5 here since it's the only
* cryptographic primitive available in all versions of PHP
* currently in use. To implement our own low-level crypto
* in PHP would result in much worse performance and
* consequently in lower iteration counts and hashes that are
* quicker to crack (by non-PHP code).
*/
if (PHP_VERSION >= 5) {
$hash = md5 ( $salt . $password, true );
do {
$hash = md5 ( $hash . $password, true );
} while ( -- $count );
} else {
$hash = pack ( 'H*', md5 ( $salt . $password ) );
do {
$hash = pack ( 'H*', md5 ( $hash . $password ) );
} while ( -- $count );
}
$output = substr ( $setting, 0, 12 );
$output .= $this->_hash_encode64 ( $hash, 16, $itoa64 );
return $output;
}
private function unique_id($extra = 'c') {
static $dss_seeded = false;
global $config;
$val = $config ['rand_seed'] . microtime ();
$val = md5 ( $val );
$config ['rand_seed'] = md5 ( $config ['rand_seed'] . $val . $extra );
$dss_seeded = true;
return substr ( $val, 4, 16 );
}
private function phpbb_hash($password) {
$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
$random_state = $this->unique_id();
$random = '';
$count = 6;
if (($fh = @fopen ( '/dev/urandom', 'rb' ))) {
$random = fread ($fh, $count);
fclose ($fh);
}
if (strlen($random) < $count) {
$random = '';
for($i=0;$i<$count;$i+=16) {
$random_state = md5($this->unique_id () . $random_state);
$random .= pack('H*', md5($random_state));
}
$random = substr($random, 0, $count);
}
$hash = $this->_hash_crypt_private($password, $this->_hash_gensalt_private($random, $itoa64 ), $itoa64);
if (strlen($hash) == 34) {
return $hash;
}
return md5($password);
}
public function login($username, $password, $email) {
if ($this->checkUserExists($username) == false) {
$user_row = array(
'username' => $username,
'user_password' => md5($password),
'user_email' => $email,
'group_id' => 2, //Registered users group
'user_timezone' => '1.00',
'user_dst' => 0,
'user_lang' => 'en',
'user_type' => '0',
'user_actkey' => '',
'user_dateformat' => 'd M Y H:i',
'user_style' => 1,
'user_regdate' => time(),
);
user_add($user_row);
}
$this->auth->login($username, $password);
}
public function changePassword($username, $password) {
if ($this->checkUserExists($username) == true) {
global $db;
$sql = "UPDATE `" . $this->table_prefix . "users` SET user_password = '" . $this->phpbb_hash($password) . "' WHERE username = '" . $username . "'";
$db->sql_query($sql);
$this->logout();
}
}
public function logout() {
$this->user->session_kill();
$this->user->session_begin();
}
}
?>
Hope this can be of some help to others in similar situations. Looking forward to your thoughts and/or improvements.
Wilson
Comments
Comment
1 Fatal error: Cannot redeclare class user in /www/htdocs/allesova/new/forum/includes/session.php on line 1971
tnx for this component, I was waiting for it :)
Unfortunately after implementing, i came up with this error:
Fatal error: Cannot redeclare class user in /www/htdocs/allesova/new/forum/includes/session.php on line 1971
I have a UserController is that the problem?
Thank you in advantage!
Reijer
Comment
2 User model
Yes, it was a near miss :)
You've got a User model, that name is same that phpBB user class. For example if change your model to Member and controller to MembersController the tutorial works fine.
Bug
3 Including the component breaks everything
var $components = array('PhpBB3');
this breaks the whole controller. I did some digging, and it happens in the PhpBB3 component's startup() function, on the line where it calls $this->user->setup(). After that the browser renders a broken (without CSS etc.) version of the phpBB3 forum front page. I assume it shouldn't render anything at all? Or did I misunderstand how to use the component? I'm planning to log in to the forum in my members_controller's login() function, using $this->PhpBB3->login(...).
Maybe this is a Windows issue? Something to do with the pathnames \ vs / ? Just wondering... And yes, help would be appreciated! :)
Comment
4 Working
My issues lay in my custom login function being called in the beforeFilter() of my AppController (cookies related).
Since your components uses startup(), which is called AFTER the beforeFilter() of the controller it did not require the specified forum files. Thus it could not find the needed functions.
After changing startup() to initialize() it works like a charm.
Thanks a bunch.
EDIT: However, it does not seem to work quite well due to the hashed password in the cookie. I removed it when trying to login with cookie.
Another issue I am having is that users are not remembered for the forum. Am trying to find a solution and was wondering whether you are facing the same?
Question
5 Permissions Data
including permissions etc
Comment
6 compatible with Auth?
I am trying to have users log in at /members/login in the normal Auth way. Then if they manage that I want to use this PHPBB api login method to pass along their details and authenticate them there, so if they visit the forum they will be logged in as well. (as I will have all users registered from a exsiting list, I do not require registering on php bb side)
can anyone explain a bit the way this component is set up - are the login functions meant to act as a replacement for Auth on the Cake side of things, as well as handle the login to PHP BB?
thanks!
Comment
7 Has anyone got a solid working example of this?
It only needs a little more explanation and I'm sure we'd be up and running! Anyone?
Comment
8 Huge Thanks!