Create a Bridge

From EQdkp Plus
Jump to navigation Jump to search


What is a Bridge?

A bridge uses the login credentials of other CMS/Boards to log user into EQdkp Plus. Of course you can enhance your bridge to support Single Sign-On, Synchronisation of Profilfields and much more.

Creating a new Bridge

In this tutorial, we will create a bridge for phpbb3, a very popular Board. The bridge needs an ID used for the filename and the classname. The Bridge-ID must be lowercase and mustn't contain any blank spaces. In our example, we will use phpbb3 as our Bridge-ID, so please replace xxx or phpbb3 with your own Bridge-ID.

The frame

First, I have to create a new file, called "xxx.bridge.class.php". xxx stands here for phpbb3. This file must be in the folder "eqdkp_root/core/bridges". Now, I create the frame of my class.

<?php

if ( !defined('EQDKP_INC') ){
	header('HTTP/1.0 404 Not Found');exit;
}

class phpbb3_bridge extends bridge_generic {
	
	public function __construct(){
		parent::__construct();
	}	

}
?>

Please make sure that you name the class "xxx_bridge", in this case "phpbb3_bridge".

Name of the Bridge

To better identify the Bridge, you can give the Bridge an name who will be used instead of the Bridge-ID.

$this->name = "phpBB3";

Data-Array

The Data-Array contains all needed information about the User-, Group- and Usergroup-Table. The Bridge-Handler will use this information to Build the Querys needed to get the data. Needed indexes: user, groups and user_group. Empty Data-Array:

$this->data = array(
	'user'	=> array(
		'table'	=> '',
		'id'	=> '',
		'name'	=> '',
		'where'	=> '',
		'password' => '',
		'email'	=> '',
		'salt'	=> '',
		'QUERY'	=> '',
	),
	'groups' => array( 
		'table'	=> '',
		'id'	=> '',
		'name'	=> '',
		'QUERY'	=> '',
	),
	'user_group' => array(
		'table'	=> '',
		'group'	=> '',
		'user'	=> '',
		'QUERY'	=> '',
		'FUNCTION' => '',
	),
);

User-Table

The User-Table contains all informationen about the User from the CMS/Board.

Index Description
table The Name of the user-table, but without the prefix. If the table is names phpbb_users, insert users.
id Name of the column containing the user-id
name Name of the column containing the username
where Name of the column the given Username should be compared with
password Name of the column containing the password
email Name of the column containing the email
salt (optional) Name of the column containing the Salt used for hashing the password
QUERY (optional) The given query will be executed instead of the default one from the Bridge-Handler. The Username will replaced if you use _USERNAME_ String. Example-Query: "SELECT user_id as id, username as name, user_password as password, user_email as email FROM ___users WHERE username = '_USERNAME_'"

___ will be replaced automatically through the right table-prefix choosen by the Administrator from the EQdkp Plus. Dev bridge usertable.png

Group-Table

The Group-Table contains all informationen of the usergroups, for example the ID and the Name of the group

Index Description
table The Name of the Group-Table, but without the prefix. If the table is names phpbb_groups, insert groups.
id Name of the column containing the group-id
name Name of the column containing the group-name
QUERY (optional) The given query will be executed instead of the default one from the Bridge-Handler. Example-Query: "SELECT group_id as id, group_name as name FROM ___groups"

Dev bridge grouptable.png

User-Group-Table

The User-Group-Table contains the connection between user and the groups.

Index Description
table The Name of the Group-Table, but without the prefix. If the table is names phpbb_groups, insert groups.
group Name of the column containing the group-id
user Name of the column containing the user-id
QUERY (optional) The given query will be executed instead of the default one from the Bridge-Handler. You can access the UserID by using the _USERID_ String. Example-Query: "SELECT group_id as group, user_id as user FROM ___user_group WHERE user_id = '_USERID_'".
FUNCTION (optional) Add here the name of the function that should be called to get the Usergroups.
Params
$intUserID int User-ID from CMS/Forum
$arrGroups array The array contains the Group-ID selected by the EQdkp-Admin in his Bridge-Settings
Returns
boolean if user is in one of the selected groups

Dev bridge usergrouptable.png

Functions-Array

In the Functions-Array you can define functions called by the Bridge-Handler. You must define this functions in your bridge-class, but the name can be choosen freely. Empty Functions-Array:

$this->functions = array(
	'login'	=> array(
		'callbefore'	=> '',
		'function' 	=> '',
		'callafter'	=> '',
	),
	'logout' 	=> '',
	'autologin' 	=> '',	
	'sync'		=> '',
);
Index Description
login => callbefore Function will be called before Login
login => function This function will be executed instead of the Bridge-Handler login-function
login => callafter Function will be called, if login was successfull
logout Will be called when user logges out
autologin Used for autologin a user
sync Used for syncing Userdata

For the phpbb3-Bridge, we need an "login => callafter" - function to check if the user is active and for initiating the Single Sign-On. The "logout"-function will be used to destroy the Single Sign-On. Also, we will use a "sync"-function to sync profiledata from the Board with EQdkp Plus.

$this->functions = array(
	'login'	=> array(
		'callbefore'	=> '',
		'function' 	=> '',
		'callafter'	=> 'phpbb3_callafter',
	),
	'logout'    => 'phpbb3_logout',
	'autologin' => '',	
	'sync'	    => 'phpbb3_sync',
);

As you can see, we prefixed our functions with "phpbb3_".

Settings-Array

The Settings-Array contains bridge-specific settings displayed at the administration-page of the bridge.

$this->settings = array(
	'cmsbridge_disable_sso'	=> array(
		'fieldtype'	=> 'checkbox',
		'name'		=> 'Disable Single Sign-On',
		'help'		=> 'User will not be logged in automatically at phpbb3 when he logges into EQdkp Plus',
	),
	'cmsbridge_disable_sync' => array(
		'fieldtype'	=> 'checkbox',
		'name'		=> 'Disable Profildata-Sync',
		'help'		=> 'Disables Sync of Profildata between phpbb3 and EQdkp Plus',
	),
);
Index Description
Index The Index will be used for the ID of the Setting, you can access it through $core->config('your_index')
fieldtype Fieldtype, e.g. checkbox, text, ...
name Description of the Setting
help Help-Message

Sync-fields

If your Bridge syncs profilefields, the EQdkp User should change this fields in his CMS/Forum and not anymore at the EQdkp Plus. Therefore, you can set the fields that should be disabled at the usersettings.

$this->sync_fields = array(
			'icq',
			'town',
			'interests',
			'birthday',
			'msn',
			'user_email',
			'username',
		);

Needed functions

There is one function you have to define in your bridge-class, called "check_password"

check_password

This function is used for comparing if the given Password is correct

Params
$password string The Password the user entered on login
$hash string The Password-Hash saved at the CMS/Forum-Database
$strSalt string The Salt used for hashing the Password, may be empty
$boolUseHash boolean If $password is a hash
Returns
boolean if password is correct

Optinal functions

convert_username

If this function is defined, it will be used to convert the username

Params
$strUsername string The Username entered on login
Returns
string converted username

Finished Bridge

Here's the finished Bridge (we have removed some code of functions owning to phpbb3 to reduce the length.)

<?php

if ( !defined('EQDKP_INC') ){
	header('HTTP/1.0 404 Not Found');exit;
}

class phpbb3_bridge extends bridge_generic {
	
	public function __construct(){
		parent::__construct();
		
		$this->name = "phpBB3";
		
		$this->data = array(
			'user'	=> array(
				'table'	=> 'users',
				'id'	=> 'user_id',
				'name'	=> 'username',
				'where'	=> 'username',
				'password' => 'user_password',
				'email'	=> 'user_email',
				'salt'	=> '',
				'QUERY'	=> '',
			),
			'groups' => array(
				'table'	=> 'groups', //without prefix
				'id'	=> 'group_id',
				'name'	=> 'group_name',
				'QUERY'	=> '',
			),
			'user_group' => array(
				'table'	=> 'user_group',
				'group'	=> 'group_id',
				'user'	=> 'user_id',
				'QUERY'	=> '',
			),
			
		);
		
		$this->functions = array(
			'login'	=> array(
				'callbefore'	=> '',
				'function' 	=> '',
				'callafter'	=> 'phpbb3_callafter',
			),
			'logout' 	=> 'phpbb3_logout',
			'autologin'     => '',	
			'sync'		=> 'phpbb3_sync',
		);
		
		$this->settings = array(
			'cmsbridge_disable_sso'	=> array(
				'fieldtype'	=> 'checkbox',
				'name'		=> 'cmsbridge_disable_sso',
			),
			'cmsbridge_disable_sync' => array(
				'fieldtype'	=> 'checkbox',
				'name'		=> 'cmsbridge_disable_sync',
			),
		);
		
		$this->sync_fields = array(
			'icq',
			'town',
			'interests',
			'birthday',
			'msn',
			'user_email',
			'username',
		);
	}
	
	//Needed function
	public function check_password($password, $hash, $strSalt = '', $boolUseHash){
		
		$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
		if (strlen($hash) == 34)
		{
			return ($this->_hash_crypt_private($password, $hash, $itoa64) === $hash) ? true : false;
		}
		return (md5($password) === $hash) ? true : false;
	}
	
	
	public function phpbb3_callafter($strUsername, $strPassword, $arrUserdata, $boolLoginResult, $boolUseHash){
		global $core;
		//Is user active?
		if ($boolLoginResult){
			if ($arrUserdata['user_inactive_reason'] != '0') {
				return false;
			}
			//Single Sign On
			if ($core->config['cmsbridge_disable_sso'] != '1'){
				$this->phpbb3_sso($arrUserdata);
			}
		}
		return true;
	}
	
	public function phpbb3_sso($arrUserdata){
		global $env;
		$user_id = $arrUserdata['id'];
		$strSessionID = md5(rand().rand());
		$this->db->query("DELETE FROM ".$this->prefix."sessions WHERE session_user_id='".$this->db->escape($user_id)."'");
		
		$query = $this->db->query("SELECT * FROM ".$this->prefix."config");
		$result = $this->db->fetch_rowset($query);
		if (is_array($result)){
			foreach ($result as $row){
				$arrConfig[$row['config_name']] = $row['config_value'];
			}
		}
		
		//PW is true, logg the user into our Forum
		$arrSet = array(
			'session_user_id'			=> (int) $user_id,
			'session_start'				=> (int) time(),
			'session_last_visit'		=> (int) time(),
			'session_time'				=> (int) time(),
			'session_browser'			=> (string) trim(substr($env->useragent, 0, 149)),
			'session_forwarded_for'		=> '',
			'session_ip'				=> $env->ip,
			'session_autologin'			=> 0,
			'session_admin'				=> 0,
			'session_viewonline'		=> 1,
			'session_id'				=> $strSessionID,
			'session_page'				=> '',
			'session_forum_id'			=> 0,
		);
		
		$this->db->query("INSERT INTO ".$this->prefix."sessions :params", $arrSet);
				
		// Set cookie
		$expire = time() + 31536000;
		//SID Cookie
		setcookie($arrConfig['cookie_name'].'_sid', $strSessionID, $expire, $arrConfig['cookie_path'], $arrConfig['cookie_domain'], $arrConfig['cookie_secure']);
		//User-Cookie
		setcookie($arrConfig['cookie_name'].'_u', $user_id, $expire, $arrConfig['cookie_path'], $arrConfig['cookie_domain'], $arrConfig['cookie_secure']);
		setcookie($arrConfig['cookie_name'].'_k', '', $expire, $arrConfig['cookie_path'], $arrConfig['cookie_domain'], $arrConfig['cookie_secure']);
		
		return true;
	}
	
	public function phpbb3_logout(){
		global $user;
		$arrUserdata = $this->get_userdata($user->data['username']);
		if (isset($arrUserdata['id'])){
			$this->db->query("DELETE FROM ".$this->prefix."sessions WHERE session_user_id='".$this->db->escape($arrUserdata['id'])."'");
		}
		setcookie($arrConfig['cookie_name'].'_sid', '', $expire, $arrConfig['cookie_path'], $arrConfig['cookie_domain'], $arrConfig['cookie_secure']);
		//User-Cookie
		setcookie($arrConfig['cookie_name'].'_u', '', $expire, $arrConfig['cookie_path'], $arrConfig['cookie_domain'], $arrConfig['cookie_secure']);
		setcookie($arrConfig['cookie_name'].'_k', '', $expire, $arrConfig['cookie_path'], $arrConfig['cookie_domain'], $arrConfig['cookie_secure']);
	}
	
	public function phpbb3_sync($arrUserdata){
		global $core;
		if ($core->config['cmsbridge_disable_sync'] == '1'){
			return false;
		}
		$sync_array = array(
			'icq' 		=> $arrUserdata['user_icq'],
			'town'		=> $arrUserdata['user_from'],
			'interests'	=> $arrUserdata['user_interests'],
			'birthday'	=> $this->_handle_birthday($arrUserdata['user_birthday']),
			'msn'		=> $arrUserdata['user_msnm'],
		);
		return $sync_array;
	}
	
	private function _handle_birthday($date){
		list($d, $m, $y) = explode('-', $date);
		if ($y != ''){
			return str_replace('-','.', $date);
		}
		return false;
	}
	
	
	//...
	//removed some function from phpbb3
	//...
}
?>

Params/Returns for function defined at Function-Array

login => callbefore

Params
$strUsername string The Username entered on login
$strPassword string The Password entered on login
$boolAutoLogin boolean If user wants an Autologin
$boolUseHash boolean If password is a hash

login => function

Params
$strUsername string The Username entered on login
$strPassword string The Password entered on login
$boolAutoLogin boolean If user wants an Autologin
$boolUseHash boolean If Password is a hash
Returns
array
array (
	'status' => 1, //Loginresult, 1 = login successful
	'user_id'	=> ...
	....
	.... //The whole userdata, like id, name, email, ...
	....
)

login => callafter

Params
$strUsername string The Username entered on login
$strPassword string The Password entered on login
$boolAutoLogin boolean If user wants an Autologin
$arrUserdata array Array containing the Userdata (from CMS/Board)
$boolLoginResult boolean If Login from login => function or Bridge-Handler-Login was successful
$boolUseHash boolean If Password is a hash
Returns
boolean login successful?

logout

- no params/returns

autologin

Params
$arrCookieData array Cookie-Data, contains Session-ID and Password-Hash for Autologin
Returns
boolean autologin successful?

sync

Params
$arrUserdata array Array containing the Userdata (from CMS/Board)
Returns
array The index is always the Name of the EQdkp Column at the usertable e.g.
$sync_array = array(
	'icq' 			=> $arrUserdata['user_icq'],
	'town'			=> $arrUserdata['user_from'],
	'interests'		=> $arrUserdata['user_interests'],
	'birthday'		=> $this->_handle_birthday($arrUserdata['user_birthday']),
	'msn'			=> $arrUserdata['user_msnm'],
);

Bridge-Handler-Functions

get_user_groups

Return all Usergroups of the Forum/CMS

Returns
array Key: Group-ID, Value: Group-Name

get_userdata

Returns Forum/CMS-Userdata of given User

Params
$name string Username
Returns
array Array containing the Userdata from the CMS/Forum

check_user_group

Checks if the CMS/Forum-User is in the given Usergroup

Params
$intUserID int User-ID from CMS/Forum
Returns
boolean User Member of given Group?