Open main menu

Extension:Discuz X Single Sign-On

MediaWiki extensions manual
OOjs UI icon advanced.svg
Discuz X Single Sign-On
Release status: unmaintained
Implementation User identity
Description Uses the discuz session to Automatically log users in out based on their discuz login status. - Based off of Extension:AuthUCenter and Extension:Phpbb Single Sign-On
Author(s) chaosconst, Nicholas Dunnaway, Hopesoft, outcrop
Latest version 0.1
MediaWiki ? Tested on 1.20
License GNU General Public License 2.0
Download https://skydrive.live.com/redir?resid=E0F633BD00131BE1!5346
Translate the Discuz X Single Sign-On extension if it is available at translatewiki.net
Check usage and version matrix.

This is tool for Chinese Forum: Discuz.

这是一个真正的discusX2和mediawiki的同步登陆插件,不是用户共享,也不是UCenter自带的样例程序,只用一套登陆系统,用户登陆的通道就能双向打通。我甚至直接用discuz登陆页替换了mediawiki的登陆页。

This plugin is a hacked version of Extension:AuthUCenter and Extension:Phpbb Single Sign-On - It was used as the base for this, I just replaced all needed code to be specific to discuz

This is very beta - Please make any edits to this code to make it better. I probably will not keep the code curent, so please, take control.

Contents

InstallationEdit

下载并修改mediawiki配置Edit

To install this extension, download the plugin, deccompress it and upload to the extensions directory, add the following to LocalSettings.php :

require_once("$IP/extensions/DiscuzXSSO/DiscuzXSSO.php");   
$wgAuth = new Auth_UCenter();

配置ucenterEdit

在UCenter中添加应用Edit

在UCenter中添加应用mediawiki;

应用类型选择:"其他";应用的主URL: "你的wiki地址/extensions/DiscuzXSSO";通信密钥随便写一个复杂点的,其他的不用填,直接点击确定。

更改配置文件config.inc.php让UCenter和DiscuzXSSO连通Edit

<?php
/******************************************/
define('UC_CONNECT', 'uc_api_post');
define('UC_DBHOST', 'localhost');
define('UC_DBUSER', 'root');
define('UC_DBPW', '');
define('UC_DBNAME', '');
define('UC_DBCHARSET', 'utf8');
define('UC_DBTABLEPRE', 'uc_');
define('UC_DBCONNECT', '0');
define('UC_KEY', '');
define('UC_API', 'http://example.com/uc');
define('UC_CHARSET', 'utf-8');
define('UC_IP', '127.0.0.1');
define('UC_APPID', '');
define('UC_PPP', '20');
/******************************************/

//用到的应用程序数据库连接参数
$dbhost = UC_DBHOST;			// 数据库服务器
$dbuser = UC_DBUSER;			// 数据库用户名
$dbpw = UC_DBPW;				// 数据库密码
$dbname = UC_DBNAME;			// 数据库名
$pconnect = UC_DBCONNECT;				// 数据库持久连接 0=关闭, 1=打开
$tablepre = UC_DBTABLEPRE;   		// 表名前缀, 同一数据库安装多个论坛请修改此处
$dbcharset = UC_CHARSET;			// MySQL 字符集, 可选 'gbk', 'big5', 'utf8', 'latin1', 留空为按照论坛字符集设定

修改插件程序里面的配置Edit

这个同步登陆没有可能一下子搞定,但是我尽量把相关注释写进去了,下面是DiscuzXSSO的代码,里面有需要添加的配置,请自己补全:

include './extensions/DiscuzXSSO/config.inc.php';
include './extensions/DiscuzXSSO/uc_client/client.php';
// First check if class has already been defined.
if (!class_exists('AuthPlugin'))
{
	/**
	 * Auth Plugin
	 *
	 */
	require_once './includes/AuthPlugin.php';
}

// 下面这个函数在Setup.php调用,在mediawiki渲染页面之前插入我们的uc_login_hook 
$wgExtensionFunctions[] = 'uc_login_hook';
function uc_login_hook() {
	global $wgUser, $wgRequest, $wgAuth;

	// 因为是hook调用,需要在这里重新输入一些配置,主要是discuz的cookie和数据库
	$_config = array();
	$_config['cookie']['cookiepre'] = '';
	$_config['cookie']['cookiedomain'] = '';
	$_config['cookie']['cookiepath'] = '';
	$_config['security']['authkey'] = '';

	$dbhost="";
	$dbuser="";
	$dbpw="";
	$dbname="";
	$tablepre="";

	// 登陆和登出页面不做同步登陆,For a few special pages, don't do anything.
	$title = $wgRequest->getVal( 'title' );
	if ( ( $title == Title::makeName( NS_SPECIAL, 'UserLogout' ) ) ||
			( $title == Title::makeName( NS_SPECIAL, 'UserLogin' ) ) ) {
		return;
	}

	// 第一步,获取当前用户的 UID 和 用户名,从discuz的cookie里面解码出来 
	if(substr($_config['cookie']['cookiepath'], 0, 1) != '/') {
		$_config['cookie']['cookiepath']= '/' . $_config['cookie']['cookiepath'];
	}
	$cookiepre =  $_config['cookie']['cookiepre'] . substr(md5($_config['cookie']['cookiepath'] . '|' .  $_config['cookie']['cookiedomain']), 0, 4) . '_';//COOKIE前缀

	$auth = $cookiepre.'auth';//存储用户信息的COOKIE名
	$saltkey = $_COOKIE[ $cookiepre . 'saltkey'];//解密auth用到的key

	$discuz_auth_key = md5($_config['security']['authkey'] . $saltkey);//x2的密钥
	$auth_value = uc_authcode($_COOKIE[$auth],'DECODE',$discuz_auth_key);
	
	// 调试:是否取得discuz的uid 
	//	   echo "<script>window.onunload=function();</script>";
	//	   echo "<script>alert('cookie=".$_COOKIE[$auth]."');</script>";
	//	   echo "<script>alert('auth_value=".$auth_value."');</script>";

	$user = User::newFromSession();
	if(!empty($auth_value)) { 
		list($ygclub_password,$ygclub_uid) = explode("\t", $auth_value); 
	} else {
		//无法从cookie取得uid, 猜测是没有登陆discuz, 维基百科同步登出
		$user->doLogout();
		return ;
	}

	// 第二步, 下面要继续登陆,从UID查询用户名

	// Connect to database.
	$connect = mysql_connect($dbhost, $dbuser, $dbpw, true);

	mysql_select_db($dbname,$connect);
	// 请根据数据库编码调整
        mysql_query("set names utf8");

	$result = mysql_query("SELECT username FROM ".$tablepre."members  WHERE uid = '".$ygclub_uid."'");
	if (!$result) {
		return ;
	}
	$row = mysql_fetch_row($result);

	// 得到用户名
	$ygclub_username= $row[0];

	// 验证用户名是否已经登陆,已经登陆则推出,否则登出,用discuz的用户替换之
	if ( !$user->isAnon() ) {
		if ( strtolower($user->getName()) == strtolower($wgAuth->getCanonicalName($ygclub_username)) ) {
			return; // Correct user is already logged in.
		} else {
			$user->doLogout(); // Logout mismatched user.
		}
	}

	// Copied from includes/SpecialUserlogin.php
	if ( !isset( $wgCommandLineMode ) && !isset( $_COOKIE[session_name()] ) ) {
		wfSetupSession();
	}


	// 第三步,发送用户名进行登陆
	// If the login form returns NEED_TOKEN try once more with the right token
	$trycount = 0;
	$token = '';
	$errormessage = '';
	do {
		$tryagain = false;
		// Submit a fake login form to authenticate the user.
		// 用户名必须encode,不然会500错误
                $params = new FauxRequest( array(
					'wpName' => urlencode($wgAuth->getCanonicalName($ygclub_username)),
					'wpPassword' => 'SUMMERBEGIN',
					'wpDomain' => '',
					'wpLoginToken' => $token,
					'wpRemember' => ''
					) );


		// Authenticate user data will automatically create new users.
		$loginForm = new LoginForm( $params );
		$result = $loginForm->authenticateUserData();
		//		echo "<script>alert('wpName=".urlencode($wgAuth->getCanonicalName($ygclub_username))."auth complete:".$result."');</script>";

		switch ( $result ) {
			case LoginForm :: SUCCESS :
				$wgUser->setOption( 'rememberpassword', 1 );
				$wgUser->setCookies();
				break;
			case LoginForm :: NEED_TOKEN:
				$token = $loginForm->getLoginToken();
				$tryagain = ( $trycount == 0 );
				break;
			case LoginForm :: WRONG_TOKEN:
				$errormessage = 'WrongToken';
				break;
			case LoginForm :: NO_NAME :
				$errormessage = 'NoName';
				break;
			case LoginForm :: ILLEGAL :
				$errormessage = 'Illegal';
				break;
			case LoginForm :: WRONG_PLUGIN_PASS :
				$errormessage = 'WrongPluginPass';
				break;
			case LoginForm :: NOT_EXISTS :
				$errormessage = 'NotExists|'.$ygclub_username."|";
				break;
			case LoginForm :: WRONG_PASS :
				$errormessage = 'WrongPass';
				break;
			case LoginForm :: EMPTY_PASS :
				$errormessage = 'EmptyPass';
				break;
			default:
				$errormessage = 'Unknown';
				break;
		}

		if ( $result != LoginForm::SUCCESS && $result != LoginForm::NEED_TOKEN ) {
			error_log( 'Unexpected REMOTE_USER authentication failure. Login Error was:' . $errormessage );
		}
		$trycount++;
	} while ( $tryagain );

	//验证完毕
        return;
}


//下面重载用来验证用户的类
/**
 * Handles the Authentication with the Discuz database.
 *
 * @package MediaWiki
 * @subpackage Auth_UCenter
 */
class Auth_UCenter extends AuthPlugin
{

	/**
	 * Add a user to the external authentication database.
	 * Return true if successful.
	 *
	 * NOTE: We are not allowed to add users to Discuz from the
	 * wiki so this always returns false.
	 *
	 * @param User $user
	 * @param string $password
	 * @return bool
	 * @access public
	 */
	function addUser( $user, $password )
	{
		return false;
	}

	/**
	 * Can users change their passwords?
	 *
	 * @return bool
	 */
	function allowPasswordChange()
	{
		return true;
	}

	/**
	 * Check if a username+password pair is a valid login.
	 * The name will be normalized to MediaWiki's requirements, so
	 * you might need to munge it (for instance, for lowercase initial
	 * letters).
	 *
	 * @param string $username
	 * @param string $password
	 * @return bool
	 * @access public
	 * @todo Check if the password is being changed when it contains a slash or an escape char.
	 */
	function authenticate($username, $password)
	{
		// Clean $username and force lowercase username.
		$username = htmlentities(strtolower($username), ENT_QUOTES, 'UTF-8');
		$username = str_replace('&#039;', '\\\'', $username); // Allow apostrophes (Escape them though)
		//调用client的uc_user_login判断用户密码,由于MediaWiki为utf8编码,请自行判断中文ID的编码

		//用户名打出来调试一下
                //echo "<script>alert('username(".$username.")auth passed!');</script>";

		//如果来自同步登陆则通过认证
		if ($password == "SUMMERBEGIN") return true;
		
		//如果来自其他方式则调用UCenter api验证登陆是否有效
		list($uid, $username1, $password, $email) = uc_user_login(iconv("UTF-8", "UTF-8", $username), $password);

		if ($uid > 0 ) {
			uc_user_synlogin($uid);
			return true;
		}

		return false;
	}

	/**
	 * Return true if the wiki should create a new local account automatically
	 * when asked to login a user who doesn't exist locally but does in the
	 * external auth database.
	 *
	 * If you don't automatically create accounts, you must still create
	 * accounts in some way. It's not possible to authenticate without
	 * a local account.
	 *
	 * This is just a question, and shouldn't perform any actions.
	 *
	 * NOTE: I have set this to true to allow the wiki to create accounts.
	 *       Without an accout in the wiki database a user will never be
	 *       able to login and use the wiki. I think the password does not
	 *       matter as long as authenticate() returns true.
	 *
	 * @return bool
	 * @access public
	 */
	function autoCreate()
	{
		return true;
	}

	/**
	 * Check to see if external accounts can be created.
	 * Return true if external accounts can be created.
	 *
	 * NOTE: We are not allowed to add users to Discuz from the
	 * wiki so this always returns false.
	 *
	 * @return bool
	 * @access public
	 */
	function canCreateAccounts()
	{
		return false;
	}

	/**
	 * If you want to munge the case of an account name before the final
	 * check, now is your chance.
	 */
	function getCanonicalName( $username )
	{
		return $username;
	}

	/**
	 * When creating a user account, optionally fill in preferences and such.
	 * For instance, you might pull the email address or real name from the
	 * external user database.
	 *
	 * The User object is passed by reference so it can be modified; don't
	 * forget the & on your function declaration.
	 *
	 * NOTE: This gets the email address from SMF for the wiki account.
	 *
	 * @param User $user
	 * @access public
	 */
	function initUser(&$user)
	{
		//当新建用户的时候从discuz读取电邮等信息
		
		$username = htmlentities(strtolower($user->mName), ENT_QUOTES, 'UTF-8');
		$username = str_replace('&#039;', '\\\'', $username); // Allow apostrophes (Escape them though)

		//调试:看下用户名是否正确
                //echo "<script>alert('username(".$username.")init started!');</script>";

		if($data = uc_get_user(iconv("UTF-8", "UTF-8", $username))) { //Get information from UC
			list($uid, $username1, $email) = $data;
			$user->mEmail=$email; //Get email from UC
			$user->mid=$uid; //Get address from UC
		}

		$this->updateUser($user);
		$user->setToken();

		$user->setOption( 'enotifwatchlistpages', 1 );
		$user->setOption( 'enotifusertalkpages', 1 );
		$user->setOption( 'enotifminoredits', 1 );
		$user->setOption( 'enotifrevealaddr', 1 );

		$user->saveSettings(); 
	}

	/**
	 * Modify options in the login template.
	 *
	 * NOTE: Turned off some Template stuff here. Anyone who knows where
	 * to find all the template options please let me know. I was only able
	 * to find a few.
	 *
	 * @param UserLoginTemplate $template
	 * @access public
	 */
	function modifyUITemplate( &$template )
	{

		$template->set('usedomain',   false); // We do not want a domain name.
		$template->set('create',      false); // Remove option to create new accounts from the wiki.
		$template->set('useemail',    false); // Disable the mail new password box.

	}

	/**
	 * This prints an error when a MySQL error is found.
	 *
	 * @param string $message
	 * @access public
	 */
	function mySQLError( $message )
	{
		exit;
	}

	/**
	 * Set the domain this plugin is supposed to use when authenticating.
	 *
	 * NOTE: We do not use this.
	 *
	 * @param string $domain
	 * @access public
	 */
	function setDomain( $domain )
	{
		$this->domain = $domain;
	}

	/**
	 * Set the given password in the authentication database.
	 * Return true if successful.
	 *
	 * NOTE: We only allow the user to change their password via phpBB.
	 *
	 * @param string $password
	 * @return bool
	 * @access public
	 */
	function setPassword( $password )
	{
		return true;
	}

	/**
	 * Return true to prevent logins that don't authenticate here from being
	 * checked against the local database's password fields.
	 *
	 * This is just a question, and shouldn't perform any actions.
	 *
	 * Note: This forces a user to pass Authentication with the above
	 *       function authenticate(). So if a user changes their SMF
	 *       password, their old one will not work to log into the wiki.
	 *       Wiki does not have a way to update it's password when SMF
	 *       does. This however does not matter.
	 *
	 * @return bool
	 * @access public
	 */
	function strict()
	{
		return true;
	}

	/**
	 * Update user information in the external authentication database.
	 * Return true if successful.
	 *
	 * @param $user User object.
	 * @return bool
	 * @public
	 */
	function updateExternalDB( $user )
	{
		return true;
	}

	/**
	 * When a user logs in, optionally fill in preferences and such.
	 * For instance, you might pull the email address or real name from the
	 * external user database.
	 *
	 * The User object is passed by reference so it can be modified; don't
	 * forget the & on your function declaration.
	 *
	 * NOTE: Not useing right now.
	 *
	 * @param User $user
	 * @access public
	 */
	function updateUser( &$user )
	{
		return true;
	}

	/**
	 * Check whether there exists a user account with the given name.
	 * The name will be normalized to MediaWiki's requirements, so
	 * you might need to munge it (for instance, for lowercase initial
	 * letters).
	 *
	 * NOTE: MediaWiki checks its database for the username. If it has
	 *       no record of the username it then asks. "Is this really a
	 *       valid username?" If not then MediaWiki fails Authentication.
	 *
	 * @param string $username
	 * @return bool
	 * @access public
	 * @todo write this function.
	 */
	function userExists($username)
	{
		//用uc的接口查询用户是否存在
		$username = htmlentities(strtolower($username), ENT_QUOTES, 'UTF-8');
		$username = str_replace('&#039;', '\\\'', $username); // Allow apostrophes (Escape them though)

		//---------------------------------------------------------------------------------------------------
		if($data = uc_get_user(iconv("UTF-8", "UTF-8", $username))) {
			list($uid, $username1, $email) = $data;	
			return true;
		} else {
			return false;
		}

	}

	/**
	 * Check to see if the specific domain is a valid domain.
	 *
	 * @param string $domain
	 * @return bool
	 * @access public
	 */
	function validDomain( $domain )
	{
		return true;
	}

}

?>

替换下载包中的mediawiki登陆程序Edit

主要是添加了对同步登陆中的用户名为中文的支持

cp SpecialUserlogin.php $MediaWikiHome/include/special/SpecialUserlogin.php

修改Mediawiki的默认登陆按钮Edit

You probably want to change the login/register / logout links to work with the system.

This is how I did this for my wiki.

修改SkinTemplate.php为如下的形式

                        $personal_urls['logout'] = array(
                                'text' => $this->msg( 'userlogout' )->text(),
                                'href' => 'http://$forumUrl/member.php?mod=logging&action=logout',
                                'active' => false
                        );

How It WorksEdit

So... This little guy, what he does is: When a user comes to the wiki, it checks to see if the user is currently logged into the forum. If not, nothing happens. If so, he then looks to see if the user is logged into the wiki. If the user is, then everything is good. If the user is not, he takes the session data from discuz and logs the user in automatically to the wiki. If there is no such user on the wiki, he creates the user.

TroubleshootingEdit

权限组管理Edit

还没有整合论坛的权限和mediawiki的权限,理想的情况应该类似: 这里

新建用户Edit

新用户第一次登陆需要在维基数据库创建用户,然后显示白板,我在新建用户之后强刷了一次页面,暂时隐藏了这个问题。

SafariEdit

用苹果浏览器直接从维基百科登陆后回退页面不会自动刷新,还是显示未登陆,但是刷新后正常。

CookiesEdit

If the user has cookies turned off, they will probably find themselves in an endless redirection loop.