We have a lot of nice tools and tricks for user login/authentications in Rails so far, such as the LoginGenerator and the SaltedHashLoginGenerator.
Unfortunately, Ruby does not yet have a killer BB solution (though rForum looks promising). So, for many, phpBB is still the way to go. Unfortunately, if you want to make your site a single login site, we need a way to integrate our sessions with phpBB (after all, it sucks having 1 login for a site, and another for the forums for that same site).
The following describes what I’ve done so far in integrating my Rails site with my phpBB2 forums (work in progress, as I am in the process of making this integration now). Don’t worry, it’s not as hard as you think it might be :). Since we are using phpBB as our model for user management, we’ll have to ditch LoginGenerator and SaltedHashLoginGenerator.
First of all, the following assumes that you installed you phpBB2 forums in the same database as your Rails application, and that you use the default “phpbb_” prefix for your phpBB tables.
The two tables of most interest to us in the phpBB2 structure is phpbb_users and phpbb_sessions.
First, create a model for your User:
/path/to/rails/app> rails script/generate model User
And, in app/model/user.rb, place the following:
class User < ActiveRecord::Base
set_primary_key "user_id"
set_table_name "phpbb_users"
def is_online?
tmp = User.find_by_sql( "SELECT session_user_id, session_logged_in FROM phpbb_sessions WHERE session_user_id=#{self.user_id}" )[0]
if self.user_allow_viewonline == 1 && self.user_id != -1 && !tmp.nil?
return true
else
return false
end
end
end
This is pretty basic right now (suggestions welcome!). Basically, we just tell ActiveRecord that our model is based on the table phpbb_users with a primary key of user_id (instead of the normal id). The method is_online?() is just a basic query for asking if a specific user is currently logged in (and returns true only if the user wants to be visible. Also, in phpBB the user with id == -1 is the anonymous user).
Now, phpBB stores cookies on your machine whenever you login. Fortunately, the Rails “cookies” hash can read this just as easy as it would a Rails cookie.
With that in mind, we can detect which phpBB user, if any, is logged in (you may need to modify the cookies[“phpbb2mysql_sid”] if you customized your cookie naming at all in phpbb).
In my app/controllers/application.rb, I put:
class ApplicationController < ActionController::Base
def get_user_by_sid( )
sid = cookies["phpbb2mysql_sid"]
return nil if sid.nil?
User.find_by_sql( [ "SELECT phpbb_users.* FROM phpbb_sessions,phpbb_users WHERE session_id = ? AND phpbb_sessions.session_user_id = phpbb_users.user_id", sid ] )[0]
end
end
What this method does is return a User object based on the phpbb session data. If the user logs off of phpBB, they are logged off the Rails app.
So, any controller and any action in which you are wanting to verify which (if any) user is logged in, you can simply call get_user_by_sid() and use the returned User object.
With this, you are now ready to create any relationships between the phpbb user and the rest of your rails app. The user only has to login once, and you have access to all their phpBB information (email, AIM, avatar, etc), and any of your Rails User specific data.
If you want to allow a user to login without making them go to the forums, then you can use the following php script. I have not figured out how to do this in Ruby yet, so I put this So it seems all the php gibberish I had in there wasn’t necessary. All you need is the following form. Pay particular note to the .php in my public/
<input type=hidden name="redirect" value="..">, as this tells phpBB where to redirect after the login (so they can login in your app and never even have to see the forums) note, this path is relative to the location of your forums.
<table border=0>
<tr><td colspan=2 align=left>
<B>Please Login</b>
</td>
</tr>
<tr>
<td>
Username:<form action="/forums/login.php" method="post" name="login_form" target=_top>
</td>
<td valign=top>
<input type="text" name="username">
</td>
</tr>
<tr>
<td>Password:</td>
<td>
<input type="password" name="password">
</td>
<tr>
<td></td>
<td align=center>
<input type=hidden name="redirect" value=".."><input type="submit" value="Login" name="login"></form>
</td>
</tr>
<tr>
<td></td>
<td align=center>
<a href="http://www.yourdomain.com/forums/profile.php?mode=register" target=_top><span style="font-size: 0.8em">Register</span></a>
</td>
</tr>
</table>
To log out of the system without a trip to the forums, you would use:
<form action="/forums/login.php?logout=true" method="post" name="login_form" target=_top>
<input type=hidden name="redirect" value="..">(<a href="#" onClick="javascript:document.login_form.submit()">logout</a>
</form>
That’s all for now. Obviously, the User model is fairly bare. It should be enough to get you going though. And I’m not sure I followed Rails “best practices” (I’m still fairly new to the game). Suggestions are welcome.
The following is how I am able to post to the forums from within the rails app. I use this for autoposting to the forums whenever I have a news item on the Rails app.
First of all, we need to add a few models:
# app/model/phpbb_forum.rb
class PhpbbForum < ActiveRecord::Base
set_primary_key :forum_id
end
# app/model/phpbb_post.rb
class PhpbbPost< ActiveRecord::Base
set_primary_key :post_id
end
# app/model/phpbb_posts_text.rb
class PhpbbPostsText < ActiveRecord::Base
set_primary_key :posts_text_id
set_table_name :phpbb_posts_text
end
# app/model/phpbb_topic.rb
class PhpbbTopic < ActiveRecord::Base
set_primary_key :topic_id
belongs_to :user, :foreign_key => 'topic_poster'
end
Next, add the following function to your app (I put it in app/controllers/application.rb so I can use it in all controllers):
def post_topic( forum_id, user_id, subject="test subject", content="test content" )
forum = PhpbbForum.find( forum_id )
return if( forum.nil? || user_id.nil? || forum_id.nil? || subject.nil? || content.nil? )
return unless validate_admin_with_hidden_action()
# Apply post
# Update forums table with new posts count, new topic count, and forum_last_post_id
forum.forum_posts += 1
forum.forum_topics += 1
# Update user post count
user.user_posts += 1
user.save
# Update posts with new topic_id,
topic = PhpbbTopic.create
post = PhpbbPost.create
post.topic_id = topic.id
post.forum_id = forum.id
post.poster_id = user.id
post.post_time = Time.now.to_f.to_i
post.poster_id = user_id
post.save
# Create new topic
topic.forum_id = forum.id
topic.topic_title = subject
topic.topic_poster = user_id
topic.topic_time = Time.now.to_f.to_i
topic.topic_first_post_id = post.id
topic.topic_last_post_id = post.id
topic.save
forum.forum_last_post_id = post.id
forum.save
# Create new posts text
post_text = PhpbbPostsText.new
post_text.post_id = post.id
post_text.bbcode_uid = "3edc9030fc"
post_text.post_subject = subject
post_text.post_text = content
post_text.save
return topic.id
end
end
Yes, I was very verbose above, and one could easily shorten this by passing a hash on the Model.new, but I wanted to make it clear what is going on here.
So far, the above solution has worked for me … but be warned, this has not been thoroughly tested and may throw your forums out of whack.
Extras
Here is some additional functionality that might be useful, such as getting the last post from a user, etc.
First, the model code (in their respective files):
class PhpbbForum < ActiveRecord::Base
set_primary_key :forum_id
has_many :phpbb_topics, :foreign_key => 'forum_id'
end
class PhpbbPost< ActiveRecord::Base
set_primary_key :post_id
belongs_to :phpbb_topic, :foreign_key => 'topic_id'
belongs_to :phpbb_forum, :foreign_key => 'forum_id'
belongs_to :user, :foreign_key => 'poster_id'
has_one :phpbb_posts_text, :foreign_key => 'post_id'
def text
self.phpbb_posts_text.text
end
end
class PhpbbPostsText < ActiveRecord::Base
set_primary_key :post_id
set_table_name :phpbb_posts_text
belongs_to :phpbb_posts, :foreign_key=>'post_id'
def text
self[:post_text]
end
end
class PhpbbSession < ActiveRecord::Base
set_primary_key :session_id
belongs_to :user, :foreign_key => 'session_user_id'
end
class PhpbbTopic < ActiveRecord::Base
set_primary_key :topic_id
belongs_to :user, :foreign_key => 'topic_poster'
has_many :phpbb_posts
def last_post
PhpbbPost.find( self.topic_last_post_id )
end
end
With a user model that looks like this:
class User < ActiveRecord::Base
set_primary_key "user_id"
set_table_name :phpbb_users
has_many :phpbb_posts, :foreign_key => 'poster_id', :order=>'post_time DESC'
has_one :phpbb_session, :foreign_key=>'session_user_id'
def is_online?
tmp = User.find_by_sql( "SELECT session_user_id, session_logged_in FROM phpbb_sessions WHERE session_user_id=#{self.user_id}" )[0]
if self.user_allow_viewonline == 1 && self.user_id != -1 && !tmp.nil?
return true
else
return false
end
end
def sid
return self.phpbb_session.nil? ? -1 : self.phpbb_session.session_id
end
# Grab latest post for this user
def latest_post
self.phpbb_posts.first
end
end
Now you can do things like:
Post.find( 1 ).text
Post.find( 1 ).user
Post.find( 1 ).topic
Post.find( 1 ).forum
User.find( 2 ).last_post.text
etc.