Using Wikka PHPBB 3.x

Works with versions: 1.1.6.5 through 1.2
 

Contributed by Paul Young, using the example code by JeremyCoates .

phpBB3 changed its user authentication mechanism. phpBB3 no longer stores MD5 hashes of passwords; instead it does a "true" one-way, one-time, non-recoverable hash of the password using phpass. The output of the hash is different each time, so you can't just select the password in the phpBB database and do a compare against a MD5 of the submitted password anymore - you have to algorithmically compare them using phpass. They also changed how and where they indicate if a user is active or inactive.

It's not difficult, but you will need to bring in code from phpBB to your Wikka files.

One final note, this code works for me on my server, but could stand to be further tested. Feel free to correct if you find a bug.

Wakka.class.php

Find:
function LoadUser($name, $password = 0) { return $this->LoadSingle("select * from ".$this->config['table_prefix']."users where name = '".mysql_real_escape_string($name)."' ".($password === 0 ? "" : "and password = '".mysql_real_escape_string($password)."'")." limit 1"); }


Replace With:

/**
    * PHPBB Integration
    *
    * function LoadUser($name, $password = 0) { return $this->LoadSingle("select * from ".$this->config['table_prefix']."users where name = '".mysql_real_escape_string($name)."' ".($password === 0 ? "" : "and password = '".mysql_real_escape_string($password)."'")." limit 1"); }
    */

    function LoadUser($name, $password = 0) {
           
            $user = $this->LoadSingle("select
         p.username as name
        ,p.user_password as password
        ,p.user_email as email
        ,p.user_regdate as signuptime
        ,w.revisioncount
        ,w.changescount
        ,w.doubleclickedit
        ,w.show_comments
        from "
.phpbb3_."users p
        left join "
. $this->config['table_prefix'] . "users w ON p.username = w.name
        where p.username = '"
.mysql_real_escape_string($name)."' and p.user_type != 1 limit 1");        
       
        if (isset($user['signuptime'])) {
            $user['signuptime'] = date('Y-m-d H:i:s', $user['signuptime']);
        }              
        return $user;


Find:
function LoadUsers() { return $this->LoadAll("select * from ".$this->config['table_prefix']."users order by name"); }


Replace With:
/**
    * PHPBB Integration
    *
    * function LoadUsers() { return $this->LoadAll("select * from ".$this->config['table_prefix']."users order by name"); }
    */

    function LoadUsers() { $users = $this->LoadAll("select
        p.username as name
        ,p.user_password as password
        ,p.user_email as email
        ,p.user_regdate as signuptime
        ,w.revisioncount
        ,w.changescount
        ,w.doubleclickedit
        ,w.show_comments
        from "
.phpbb3_."users p
        left join "
. $this->config['table_prefix'] . "users w ON p.username = w.name
        where p.user_type != 1
        order by username"
);

        foreach ($users as $key => $user) {

            if (isset($user['signuptime'])) {
                $user['signuptime'] = date('Y-m-d H:i:s', $user['signuptime']);
            }
            $users[$key] = $user;
        }

        return $users;
    }


Finally, add this block from phpass (via phpBB) right before the
?>
at the end of the file.
/**
*
* @version Version 0.1 / $Id: functions.php 8491 2008-04-04 11:41:58Z acydburn $
*
* Portable PHP password hashing framework.
*
* Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
* the public domain.
*
* There's absolutely no warranty.
*
* The homepage URL for this framework is:
*
*   http://www.openwall.com/phpass/
*
* Please be sure to update the Version line if you edit this file in any way.
* It is suggested that you leave the main version number intact, but indicate
* your project name (after the slash) and add your own revision information.
*
* Please do not change the "private" password hashing method implemented in
* here, thereby making your hashes incompatible.  However, if you must, please
* change the hash type identifier (the "$P$") to something different.
*
* Obviously, since this code is in the public domain, the above are not
* requirements (there can be none), but merely suggestions.
*
*
* Hash the password
*/

function phpbb_hash($password)
{
    $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

    $random_state = 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(unique_id() . $random_state);
            $random .= pack('H*', md5($random_state));
        }
        $random = substr($random, 0, $count);
    }

    $hash = _hash_crypt_private($password, _hash_gensalt_private($random, $itoa64), $itoa64);

    if (strlen($hash) == 34)
    {
        return $hash;
    }

    return md5($password);
}

/**
* Check for correct password
*/

function phpbb_check_hash($password, $hash)
{
    $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
    if (strlen($hash) == 34)
    {
        return (_hash_crypt_private($password, $hash, $itoa64) === $hash) ? true : false;
    }

    return (md5($password) === $hash) ? true : false;
}

/**
* Generate salt for hash generation
*/

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 .= _hash_encode64($input, 6, $itoa64);

    return $output;
}

/**
* Encode hash
*/

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
*/

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 .= _hash_encode64($hash, 16, $itoa64);

    return $output;
}

/**
* Return unique id
* @param string $extra additional entropy
*/

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);

    if ($dss_seeded !== true && ($config['rand_seed_last_update'] < time() - rand(1,10)))
    {
        set_config('rand_seed', $config['rand_seed'], true);
        set_config('rand_seed_last_update', time(), true);
        $dss_seeded = true;
    }
    */

    return substr($val, 4, 16);
}


/actions/usersettings.php

Find:
            default: // input is valid
                $this->Query('UPDATE '.$this->config['table_prefix'].'users SET '.
                    "email = '".mysql_real_escape_string($email)."', ".
                    "doubleclickedit = '".mysql_real_escape_string($doubleclickedit)."', ".
                    "show_comments = '".mysql_real_escape_string($show_comments)."', ".
                    "revisioncount = '".mysql_real_escape_string($revisioncount)."', ".
                    "changescount = '".mysql_real_escape_string($changescount)."' ".
                    "WHERE name = '".$user['name']."' LIMIT 1");


Replace With:
  default: // input is valid
                /**
                * PHPBB Integration
                *
                * Insert the user into the Wakka users table
                */

                $tmpUser = $this->LoadUser($user['name']);
                if (is_null($tmpUser['show_comments'])) {
                    $this->Query("INSERT INTO ".$this->config['table_prefix']."users SET ".
                        "signuptime = '".mysql_real_escape_string($user['signuptime'])."',".
                        "name = '".mysql_real_escape_string($user['name'])."', ".
                        "email = '".mysql_real_escape_string($user['email'])."'");
                }
                /**
                * End PHPBB Integration
                */


Find:
case (md5($_POST['password']) != $existingUser['password']):                   
                    $error = ERROR_WRONG_PASSWORD;
                    $password_highlight = INPUT_ERROR_STYLE;
                    break;


Replace With:
                case (!phpbb_check_hash($_POST['password'],$existingUser['password'])):                
                    $error = ERROR_WRONG_PASSWORD;
                    $password_highlight = INPUT_ERROR_STYLE;
                    break;


/actions/highscores.php

Find:
    switch($rank)
{
    case 'edits':  
    $label= 'edits';
    $query = 'SELECT COUNT(*) AS cnt, `name`  FROM '.$this->GetConfigValue('table_prefix').'users, '.$this->GetConfigValue('table_prefix').'pages WHERE `name` = `user` GROUP BY name ORDER BY cnt DESC LIMIT '.$limit;
    $total = $this->getCount('pages');
    break;
       
    case 'comments':
    $label= 'comments';
    $query = 'SELECT COUNT(*) AS cnt, `name`  FROM '.$this->GetConfigValue('table_prefix').'users, '.$this->GetConfigValue('table_prefix').'comments WHERE `name` = `user` GROUP BY name ORDER BY cnt DESC LIMIT '.$limit; 
    $total = $this->getCount('comments');
    break; 

    default:
    case 'pages':
    $label= 'pages owned';
    $query = 'SELECT COUNT(*) AS cnt, `name`  FROM '.$this->GetConfigValue('table_prefix').'users, '.$this->GetConfigValue('table_prefix').'pages WHERE `name` = `owner` AND `latest` = "Y" GROUP BY name ORDER BY cnt DESC LIMIT '.$limit;
    $total = $this->getCount('pages', "`latest` = 'Y'");
    break;
}


Replace with:
    switch($rank)
{
    case 'edits':  
    $label= 'edits';
    $query = 'SELECT COUNT(*) AS cnt, `username` AS name FROM phpbb3_users '.$this->GetConfigValue('table_prefix').'pages WHERE `username` = `user` GROUP BY username ORDER BY cnt DESC LIMIT '.$limit;
    $total = $this->getCount('pages');
    break;
       
    case 'comments':
    $label= 'comments';
    $query = 'SELECT COUNT(*) AS cnt, `username` AS name FROM phpbb3_users '.$this->GetConfigValue('table_prefix').'comments WHERE `username` = `user` GROUP BY username ORDER BY cnt DESC LIMIT '.$limit; 
    $total = $this->getCount('comments');
    break; 

    default:
    case 'pages':
    $label= 'pages owned';
    $query = 'SELECT COUNT(*) AS cnt, `username` AS name FROM phpbb3_users '.$this->GetConfigValue('table_prefix').'pages WHERE `username` = `owner` AND `latest` = "Y" GROUP BY username ORDER BY cnt DESC LIMIT '.$limit;
    $total = $this->getCount('pages', "`latest` = 'Y'");
    break;
}


Configuration to disable UserRegistration is still required, either patch for 1.1.6.2 or update config setting in 1.1.6.3 (or later) see UserRegistration for more details. See the phpBB 2.x integration page for info about how to edit phpBB to force Wiki-style usernames. On my install this works fine with usernames containing spaces, no caps, etc.

EDIT AlexRust:
I did this today and found two problems:

  1. There is the ending } missing in function LoadUser.
  1. After I made these changes I can't logon as admin. Thus I opened a group in phpbb called WIKKA and modified isAdmin as followed:

file: Wakka.class.php

    //returns true if user is listed in configuration list as admin
    function IsAdmin($user='') {
        // $adminstring = $this->config["admin_users"];  // <---
        // $adminarray = explode(',' , $adminstring);  // <---
        // das $adminarray kann für alle User aufgebaut werden,  // <---
        // die zur phpbb-Gruppe Wikka gehören.  // <---
        $adminarray = array();  // <---
        $query = "SELECT
                      username,
                      group_name
                    FROM
                      phpbb.phpbb_users u
                      join phpbb.phpbb_user_group ug on (u.user_id=ug.user_id)
                      join phpbb.phpbb_groups g on (ug.group_id=g.group_id)
                    where
                      group_name='WIKKA'
                    "
;  // <---
        if ($r = $this->Query($query))  // <---
        {  // <---
            while ($row = mysql_fetch_assoc($r)) $adminarray[] = $row['username'];  // <---
            mysql_free_result($r);  // <---
        }  // <---

        if(TRUE===empty($user))
        {
            $user = $this->GetUserName();
        }
        else if(is_array($user))
        {
            $user = $user['name'];
        }
        foreach ($adminarray as $admin) {
            if (trim($admin) == $user) return true;
        }
    }


You may change the phpbb database name "phpbb" and the phpbb prefix "phpbb_" into yours.

Comments
Comment by DarTar
2008-05-13 08:12:03
Paul, thanks for sharing this. It'd be great if you could implement this via the new User Registration framework introduced in 1.1.6.5. I'm sure Brian could point you in the right direction should you need any help, in the meantime you can check this page out for an introduction: http://docs.wikkawiki.org/UserRegistrationValidationFramework
Comment by BrianKoontz
2008-05-17 22:40:36
Paul, here's a short tutorial that covers creation of new UR modules:

http://www.wikkawiki.org/URModuleHowTo

Also, if you click the CategoryURModules link at the bottom of that page, you'll find additional code examples in addition to the modules in DarTar's link above.
Comment by AlexRust
2010-01-24 07:20:09
Today I updated to 1.2 and reentered this plugin. It's still working with just some line numbers changes. Again thanks for this great plugin.
Comment by ScottPeterson
2010-06-22 15:17:26
I just used this plugin and it still works great! The only question I have is once the user has signed into phpbb, how can you have them automatically signed into wikkawiki as well. Any help? Thanks!
Comment by BloodshotEye
2010-08-28 15:27:10
I can confirm the above is working using fresh installs of Wikka 1.2-p1 and phpBB3.
I had to edit Wakka.class.php as per AlexRust's comments above.

I can login to the wiki with the forum admin's credentials and administrate the wiki - very neat!! I added the original wiki admin to a group called WIKKA in the forum, made the wiki admin an administrator of the forum and he too can continue to administer the wiki - double cool!!

Now to see how I can get our senior school involved :-) I'm just thinking aloud here, but I currently have phpBB as the landing page of a sub directory and so the wiki is not immediately visible. Perhaps the wiki should be the "main" sub directory with a link to the forum, but new users must go to the forum to register - mmmm.

I have saved wiki syntax files of all the pages from a previous Wikka install which I will use to populate this new arrangement - fortunately it is a brand new site only with some additional administrative pages (howtos and such),

A thought: Will this "bridge" still work when the new version of Wikka hits the shelves, I wonder?
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki