Saturday, May 30th, 2009...10:32 am

How to integrate punBB into an existing Ruby on Rails application

Jump to Comments

I released launched a rails side project, XBoxMMA.com, and one of the most requested features by the initial users was the addition of forums to the site.

Thinking it over, I clearly had two options: 1) code my own custom forum system for the site, or 2) use an existing forum system.

While I don’t mind coding forums for the site, I really didn’t want to set aside the time needed for coding when I could have been working on plenty of other projects. So, I set out researching existing forum software that I could utilize.

In searching for a third-party forum system, I really only had one major requirement – users needed to be able to login to the main website and be authenticated at the same time on the forums. I didn’t want my users to have to maintain separate accounts and separate logins for the forums and the main website. If I did, there would be no need for this tutorial as implementing standalone forums is a basic task.

First off, I researched existing forum systems that were already rails based, hoping there was something I could quickly integrate into my app. I checked out all the regular suspects like Beast, Altered Beast, Savage Beast 2, and the new rBoard.

Of the existing rails forums, rBoard showed the most promise but none of them really work well for integrating into an existing, established rails app. All of the rails options are really intended for standalone use as a forum only.

Next I proceeded to research non-Rails based forums like bbPress, vBulletin, phpBB and a handful of others.

I ended up settling on the punBB forum software, written in PHP. I chose punBB for a few reasons: 1) it was a lightweight forum system, 2) it seemed to get good reviews online, and 3) I was able to find some existing information online on integrating punBB with Rails.

Terrell Russell wrote two blog posts (post one, post two) regarding running PHP from within a Rails environment and how to get punBB (and DokuWiki) running within a Rails app using single sign-on. I used Terrell’s posts as a starting point for my project as some of his information is now outdated for both Rails 2.3x and punBB 1.3x.

Now, on to the tutorial.

My next decision was to choose how exactly to integrate the PHP code into my Rails app. I could put the forum’s PHP code into my application’s

RAILS_ROOT/public/forums/

directory, or I could install the forums somewhere else on my servers file system.

Since the forums really are their own separate application, and the code is independent of my Rails code, I decided to install punBB on its own and leave it out of my source repository. This will allow me to maintain/upgrade/modify the forum software without touching my Rails repository.

As a side note, your server must be able to run PHP. My app server is a Debian Lenny system and I installed PHP via the normal

$ sudo aptitude install

routine. (Installing PHP on your server is beyond the scope of this tutorial.)

I went ahead and setup the punBB files into

/var/www/punbb

on my server. I want users to be able to access the forums by visiting http://xboxmma.com/forums, so there were a few additional steps I needed to take.

First, when I deploy my app via capistrano, I setup a symlink in the public directory to point to my forum installation. I also copy the forum config file into my Rails app config directory (I’ll explain why later). Add the following code to your capistrano deploy file.

# RAILS_ROOT/config/deploy.rb
desc "Setup settings for punBB forums"
task :after_symlink, :roles => [:app] do
  run "ln -nfs /var/www/punbb #{release_path}/public/forums"
  run "cp /var/www/punbb/config.php #{release_path}/config"
end

My app server uses Nginx as the web front end, so I also needed to update my nginx configuration to properly route requests for the new forums.

My existing nginx configuration file looked like this

server {
        listen  80;
        server_name xboxmma.com;
        root /home/deploy/xboxmma.com/current/public;
        passenger_enabled on;
        access_log off;
        client_max_body_size 20M;

        location ~* \.(ico|css|js|gif|jp?g|png)(\?[0-9]+)?$ {
                expires max;
                break;
        }

        if (-f $request_filename) {
                break;
        if (-f $document_root/system/maintenance.html) {
                rewrite ^(.*)$ /system/maintenance.html break;
        }

        error_page 500 502 503 504 /500.html;
        location = /500.html {
                root /home/deploy/xboxmma.com/current/public;
        }
}

I updated it with the following sections

        location /forums {
                root /var/www/punbb;
                index index.php;

                if (-f $request_filename) {
                        expires 30d;
                        break;
                }

                if (!-e $request_filename) {
                        rewrite ^(.+)$ /forums/index.php?q=$1 last;
                }
        }

        location ~ \.php$ {
                fastcgi_pass 127.0.0.1:9000;
                fastcgi_index index.php;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include /opt/nginx/conf/fastcgi_params;
        }

        location ~ /\.ht {
                deny all;
        }

The

location /forums {}

block will process all requests that are made to the /forums path on my site. I set the document root to

/var/www/punbb

where I installed the punBB software and set the index page to index.php.

The

location ~ \.php$ {}

block catches any request for a php page and passes the request along to the php5-cgi processor to handle the request.

The end result config file looks like this:

server {
        listen  80;
        server_name xboxmma.com;
        root /home/deploy/xboxmma.com/current/public;
        passenger_enabled on;
        access_log off;
        client_max_body_size 20M;

        location ~* \.(ico|css|js|gif|jp?g|png)(\?[0-9]+)?$ {
                expires max;
                break;
        }

        location /forums {
                root /var/www/punbb;
                index index.php;

                if (-f $request_filename) {
                        expires 30d;
                        break;
                }

                if (!-e $request_filename) {
                        rewrite ^(.+)$ /forums/index.php?q=$1 last;
                }
        }

        location ~ \.php$ {
                fastcgi_pass 127.0.0.1:9000;
                fastcgi_index index.php;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include /opt/nginx/conf/fastcgi_params;
        }

        location ~ /\.ht {
                deny all;
        }

        if (-f $request_filename) {
                break;
        if (-f $document_root/system/maintenance.html) {
                rewrite ^(.*)$ /system/maintenance.html break;
        }

        error_page 500 502 503 504 /500.html;
        location = /500.html {
                root /home/deploy/xboxmma.com/current/public;
        }
}

Now that the boring php processing and routing stuff is out of the way, lets get into the important stuff and look how to integrate the authentication system of punBB and the Rails application.

The “trick” we will be using, is to manually set the login cookie for punBB whenever a user logs into the main website (we’ll delete the cookie as well when the user logs out). The login cookie format has changed a bit since Terrell’s writeup, so I had to get my hands dirty and dig into the punBB PHP codebase to see exactly how they are setting their cookies.

First, add the following code to your application controller (or another controller if you don’t want/need the methods to be available everywhere).

# RAILS_ROOT/app/controllers/application_controller.rb
  def set_shared_cookie
    return true unless Rails.env.production?
    require 'base64'
    expires = Time.now.to_i + 1209600

    begin
      # this sets the punbb cookie
      # it should be called on login from main site to allow unified login
      forumconfig = get_forum_config_data() # private method at bottom
      forumuser = ForumAccount.find_by_username( current_user.username )

      forum_hash = Digest::SHA1.hexdigest( forumuser.salt + Digest::SHA1.hexdigest( expires.to_s ) )
      cookie_value = "#{forumuser.id}|#{forumuser.password}|#{expires.to_s}|"
      cookie_value << Digest::SHA1.hexdigest( "#{forumuser.salt}#{forumuser.password}#{forum_hash}" )

      cookies[forumconfig[:cookie_name]] = {
        :value => Base64.encode64( cookie_value ),
        :expires => 14.days.from_now
      }
    rescue => e
      Rails.logger.warn "Problem setting punBB cookie #{e.message}"
    end
  end

  def clear_shared_cookie
    return true unless Rails.env.production?
    begin
      # should be called on logout from main site
      forumconfig = get_forum_config_data() # private method at bottom
      cookies[forumconfig[:cookie_name]] = nil
    rescue => e
      Rails.logger.warn "Problem clearing punBB cookie #{e.message}"
    end
  end

  private

  # Uses regex to parse the php punbb config file
  # taken from: ahgsoftware.com/punbb_sdk/
  def get_forum_config_data
    config_hash = Hash.new
    c = File.read( File.join( RAILS_ROOT, 'config', 'config.php' ) )
    c.scan(/\$(\w*)\s*=\s*['"](.*)['"];/).each do |pair|
      config_hash[pair[0].to_sym] = pair[1]
    end
    config_hash
  end

The

#get_forum_config_data

private method simply reads the punBB config.php file to get the forum configuration info. (Remember we copied the config.php file over in our capistrano deploy addition.)

Both the

#set_shared_cookie

method and the

#clear_shared_cookie

method will only run during production mode. I don’t have punBB setup on my dev system. If you run a staging server you probably want to change it to check

Rails.env.development?

so that the method will also be processed on your staging server.

You’ll also notice I wrap everything in both methods in a

begin ... rescue

block. I don’t want errors in the cookie creation/deletion to throw errors to the users, so I just log then gracefully for now.

Let’s do a quick run through on the #set_shared_cookie method. First we set an integer expiration date to 14 days in the future (the default punBB cookie length). Next we read the forum config data and find the existing user record from the punBB database via the ForumAccount model. We’ll look at the ForumAccount model in a bit.

Next we set the cookie value so it appears in the same format the punBB will expect when it reads the cookie back in and write the cookie to the session.

For XBoxMMA.com I use Authlogic as the authentication system, so I call the set_shared_cookie method from my user_sessions controller after a new session is created. I also call it from my users_controller when a new user is created. I call the clear_shared_cookie method when the user logs out.

Now that we’ve seen how to make the shared cookie, lets look at the models used to access the punBB database tables.

I use a separate database for punBB instead of dumping the tables into my rails app DB, so I added an extra section to my

RAILS_ROOT/config/database.yml

file.

forums_production:
  adapter: mysql
  database: xboxmma_forum
  username: xboxmma
  password: password
  host: localhost
  encoding: utf8
  socket: /var/run/mysqld/mysqld.sock

If you are using a staging environment, be sure to add a “forums_staging” section as well.

Next, I added two new models to access the punBB DB.

# app/models/forum.rb
class Forum < ActiveRecord::Base
  self.abstract_class = true
  establish_connection "forums_#{Rails.env}"
end

# app/models/forum_account.rb
class ForumAccount < Forum
  set_table_name 'users' # add the table prefix if you set one in punBB

  def encrypt_and_save_new_password( _password )
    write_attribute( "password", forum_hash( _password ) )
    save
  end

  private

  def forum_hash( _str )
    Digest::SHA1.hexdigest( self.salt + Digest::SHA1.hexdigest( _str ) )
  end

end

The

#encrypt_and_save_new_password

method should be called anytime your user updates their password in your Rails app. This method will update their forum password at the same time and keep the two in sync.

Now you have Rails models setup that will be able to access your punBB DB (you will remember the ForumAccount model we referenced earlier in the set_shared_cookie method).

Everything should now be working for you. Don’t forget to run the

admin/install.php

script as well to setup your initial punBB configuration.

1 Comment

© 2017 Craig P Jolicoeur.