warzone2100/tools/trac/plugins/TracPhpBBCookieAuth.py

149 lines
6.5 KiB
Python

# vim: set et sts=4 sw=4 encoding=utf8:
#
# Copyright (c) 2008-2009, Giel van Schijndel
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Warzone 2100 Project nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import with_statement
from contextlib import contextmanager
import re, md5
from trac.config import Option, BoolOption
from trac.core import *
from trac.web.api import IAuthenticator, IRequestFilter
from phpbbauth.main import PhpDatabaseManager
__all__ = ['TracPhpBBCookieAuth']
@contextmanager
def openPhpBBDatabase(env):
cnx = PhpDatabaseManager(env).get_connection()
try:
yield cnx
finally:
cnx.close()
class TracPhpBBCookieAuth(Component):
"""Uses PhpBB cookies for authentication
This enables a Single Sign On system between a PHPBB3 Forum and Trac."""
ignore_case = BoolOption('trac', 'ignore_auth_case', 'false',
"""Whether login names should be converted to lower case
(''since 0.9'').""")
cookie_domain_option = Option('account-manager', 'phpbb_cookie_domain', None,
"""Domain that's used for phpBB's cookies""")
cookie_domain = property(fget=lambda self: str(self.cookie_domain_option))
cookie_name = Option('account-manager', 'phpbb_cookie_name', None,
"""Prefix name that's used for phpBB's cookies""")
sid_cookie = property(fget=lambda self: '%s_sid' % str(self.cookie_name))
u_cookie = property(fget=lambda self: '%s_u' % str(self.cookie_name))
k_cookie = property(fget=lambda self: '%s_k' % str(self.cookie_name))
implements(IAuthenticator, IRequestFilter)
# IAuthenticator methods
def authenticate(self, req):
authname = None
# Use existing phpBB3 sessions if there is one
if self.sid_cookie in req.incookie:
with openPhpBBDatabase(self.env) as cnx:
cur = cnx.cursor()
cur.execute('SELECT u.username'
' FROM phpbb3_sessions s '
' INNER JOIN phpbb3_users u '
' ON s.session_user_id = u.user_id'
' WHERE user_type <> 2 '
' AND s.session_id = %s', (req.incookie[self.sid_cookie].value,))
result = cur.fetchone()
authname = result and result[0] or None
if self.k_cookie in req.incookie and self.u_cookie in req.incookie \
and not authname:
key_id = md5.new(req.incookie[self.k_cookie].value).hexdigest()
with openPhpBBDatabase(self.env) as cnx:
cur = cnx.cursor()
cur.execute('SELECT u.username'
' FROM phpbb3_sessions_keys k '
' INNER JOIN phpbb3_users u '
' ON k.user_id = u.user_id'
' WHERE u.user_id = %s'
' AND u.user_type <> 2 '
' AND k.key_id = %s', (req.incookie[self.u_cookie].value, key_id))
result = cur.fetchone()
authname = result and result[0] or None
if not authname:
return None
if self.ignore_case:
authname = authname.lower()
return authname
# IRequestFilter methods
def pre_process_request(self, req, handler):
# Destroy existing phpBB3 sessions when logging out
if re.match('/logout/?$', req.path_info) \
and self.sid_cookie in req.incookie and self.u_cookie in req.incookie:
with openPhpBBDatabase(self.env) as cnx:
cur = cnx.cursor()
# Clean up the session itself
cur.execute('DELETE FROM phpbb3_sessions '
' WHERE session_id = %s AND session_user_id = %s',
(req.incookie[self.sid_cookie].value,
req.incookie[self.u_cookie].value))
# Remove the session key to prevent it from being recreated
if self.k_cookie in req.incookie:
cur.execute('DELETE FROM phpbb3_sessions_keys '
' WHERE user_id = %s AND key_id = %s',
(req.incookie[self.u_cookie].value,
req.incookie[self.k_cookie].value))
# Destroy all phpBB3 session-related cookies
req.outcookie[self.k_cookie] = ''
req.outcookie[self.k_cookie]['domain'] = self.cookie_domain
req.outcookie[self.k_cookie]['expires'] = -10000
req.outcookie[self.sid_cookie] = ''
req.outcookie[self.sid_cookie]['domain'] = self.cookie_domain
req.outcookie[self.sid_cookie]['expires'] = -10000
req.outcookie[self.u_cookie] = ''
req.outcookie[self.u_cookie]['domain'] = self.cookie_domain
req.outcookie[self.u_cookie]['expires'] = -10000
return handler
# for Genshi templates
def post_process_request(self, req, template, data, content_type):
return template, data, content_type