Bitrix Site Manager

External authorization

Sometimes, developers want to use their own user authorization algorithms or store users' credentials in external databases. For example, you may have an existing user database and want to allow those users to authorize at the site. In rare cases, it is possible to simply transfer user accounts to the CMS database using the CMS API. Nevertheless, in most cases this approach is not applicable due to the following reasons. The first reason is that user passwords are usually stored as unrecoverable hash values, so users will not be able to authorize after the transfer because of password failure. The second reason is that some system architectures require users' credentials to be stored in a centralized remote storage server for distributed authorization in different locations, including the CMS.

The Bitrix Site Manager provides for implementing custom external authorization systems, in addition to standard built-in authorization system. This section describes steps required for proper developing of such system. The PHP BB forum is used as an example to demonstrate basic guidelines that a developer should consider. 

As a start-off, create a file /bitrix/php_interface/scripts/phpbb.php. This file will contain a class with the external authorization processor implementation: __PHPBB2Auth:

class __PHPBB2Auth
{
}

We have to implement and register a handler of the OnUserLoginExternal event that will be called upon each authorization attempt, immediately after a user enters the login and password and immediately before the built-in authorization routine. To do so, add a call to AddEventHandler to the file /bitrix/php_interface/init.php:

AddEventHandler(
     "main", 
     "OnUserLoginExternal", 
     Array("__PHPBB2Auth", "OnUserLoginExternal"), 
     100, 
     $_SERVER['DOCUMENT_ROOT'].'/bitrix/php_interface/scripts/phpbb.php'
);

The registered handler is a member of our class: __PHPBB2Auth::OnUserLoginExternal. As a parameter, the OnUserLoginExternal event handler accepts a reference to the array with fields to be verified:

define("PHPBB2_TABLE_PREFIX", "phpbb_");
function OnUserLoginExternal(&$arArgs)
{
    $table_user = PHPBB2_TABLE_PREFIX."users";
    $table_user_group = PHPBB2_TABLE_PREFIX."user_group";
    extract($arArgs);

    global $DB, $USER, $APPLICATION;

    $strSql = "SELECT * FROM ".$table_user.
              " WHERE username='".$DB->ForSQL($login).
              "' AND user_password='".
              $DB->ForSql(md5($password))."'";
    $dbRes = $DB->Query($strSql);
    if ($arRes = $dbRes->Fetch())
    {
        if ($arRes['user_active']!='0')
        {
            // both login and password match
        }
    }
}

Now that the login and password are verified using the PHPBB algorithm, we have to create an external user in the domestic database so that native objects (news, polls etc.) can be used with it. To perform this, a method CUser::GetList is called with a filter by login name and the external authorization source ID. If a user cannot be found, we have to create it. Otherwise, just refresh the users information in the database.

$arFields = Array(
    "LOGIN" => $login,
    "NAME" => $login,
    "PASSWORD" => $password,
    "EMAIL" => $arRes['user_email'],
    "ACTIVE" => "Y",
    "EXTERNAL_AUTH_ID"=>"PHPBB2",
    "LID" => SITE_ID
    );
$oUser = new CUser;
$res = CUser::GetList($O, 
                      $B,
                      Array("LOGIN_EQUAL_EXACT" => $login, 
                            "EXTERNAL_AUTH_ID"  => "PHPBB2"));
if (!($ar_res = $res->Fetch()))
    $ID = $oUser->Add($arFields);
else
{
    $ID = $ar_res["ID"];
    $oUser->Update($ID, $arFields);
}

if ($ID>0)
{
   // can be authorized
   return $ID;
}

Now we have the user ID valid in the scope of our database. At this stage, we can return it from the event handler. But remember that the user is still anonymous as it is not bound to any user group. We can use the existing PHPBB binding information and pass it to our database prior to authorization.

$USER->SetParam("PHPBB2_USER_ID", $arRes['user_id']);
$groups_map = Array(
    /*'PhpBB2 Group ID' => 'Local Group ID',*/
     '2' => '1'
    );

$user_groups = Array();
$dbUserGroup = $DB->Query('SELECT * FROM '.
                          $table_user_group.
                          ' WHERE user_id='.
                          $arRes['user_id']);
while ($arUserGroup = $dbUserGroup->Fetch())
    $user_groups[] = $arUserGroup['group_id'];

if (count($user_groups)>0)
{
    $arUserGroups = CUser::GetUserGroup($ID);
    foreach ($groups_map as $ext_group_id => $group_id)
    {
        if (in_array($ext_group_id, $user_groups))
            $arUserGroups[] = $group_id;
        else
        {
            $arUserGroupsTmp = Array();
            foreach($arUserGroups as $grid)
                if($grid != $group_id)
                    $arUserGroupsTmp[] = $grid;
            $arUserGroups = $arUserGroupsTmp;
        }
    }
    CUser::SetUserGroup($ID, $arUserGroups);
}

Now, the local user account corresponds to the remote one and we can return the user ID to finish authorization. The final touch is to made; clear the "remember me" flag just in case the user checked it as we cannot verify the permissions correctly:

$arArgs["store_password"] = "N";
return $ID;

To register the just created authorization algorithm in the system, we have to process the OnExternalAuthList event. Add the corresponding call to the file /bitrix/php_interface/init.php:

AddEventHandler(
     "main", 
     "OnExternalAuthList", 
     Array("__PHPBB2Auth", "OnExternalAuthList"), 
     100, 
     $_SERVER['DOCUMENT_ROOT'].'/bitrix/php_interface/scripts/phpbb.php'
     );

The event handler should return an array containing information on the provided authorization handlers: the ID and the name of the handler.

function OnExternalAuthList()
{
    return Array(
        Array("ID"=>"PHPBB2", "NAME"=>"PhpBB2")
        );
}

Now the user editing page displays a combo box containing a list of external authorization sources. The code below show a complete file /bitrix/php_interface/scripts/phpbb.php. In addition to the above described algorithm, it implements the reverse scheme: a user is automatically authorized in the forum upon authorization in the system.

<?
define("PHPBB2_TABLE_PREFIX", "phpbb_");

class __PHPBB2Auth
{
    function OnUserLoginExternal(&$arArgs)
    {
        ////////// <settings> ////////////
        $table_user = PHPBB2_TABLE_PREFIX."users";
        $table_user_group = PHPBB2_TABLE_PREFIX."user_group";
        $groups_map = Array(
            /*'PhpBB2 Group ID' => 'Local Group ID',*/
            '2' => '1'
            );
        ////////// </settings> ////////////
        extract($arArgs);

        global $DB, $USER, $APPLICATION;

        $strSql = "SELECT * FROM ".$table_user.
                  " WHERE username='".$DB->ForSQL($login).
                  "' AND user_password='".
                  $DB->ForSql(md5($password))."'";
        $dbRes = $DB->Query($strSql);
        if ($arRes = $dbRes->Fetch())
        {
            if ($arRes['user_active']!='0')
            {
                $arFields = Array(
                    "LOGIN" => $login,
                    "NAME" => $login,
                    "PASSWORD" => $password,
                    "EMAIL" => $arRes['user_email'],
                    "ACTIVE" => "Y",
                    "EXTERNAL_AUTH_ID"=>"PHPBB2",
                    "LID" => SITE_ID
                    );
                $oUser = new CUser;
                $res = CUser::GetList($O, 
                                      $B,
                                      Array("LOGIN_EQUAL_EXACT" => $login, 
                                            "EXTERNAL_AUTH_ID"  => "PHPBB2"));
                if (!($ar_res = $res->Fetch()))
                    $ID = $oUser->Add($arFields);
                else
                {
                    $ID = $ar_res["ID"];
                    $oUser->Update($ID, $arFields);
                }

                if($ID>0)
                {
                    $USER->SetParam("PHPBB2_USER_ID", $arRes['user_id']);

                    $user_groups = Array();
                    $dbUserGroup = $DB->Query('SELECT * FROM '.
                                              $table_user_group.
                                              ' WHERE user_id='.
                                              $arRes['user_id']);
                    while($arUserGroup = $dbUserGroup->Fetch())
                        $user_groups[] = $arUserGroup['group_id'];

                    if(count($user_groups)>0)
                    {
                        $arUserGroups = CUser::GetUserGroup($ID);
                        foreach($groups_map as $ext_group_id => $group_id)
                        {
                            if (in_array($ext_group_id, $user_groups))
                                $arUserGroups[] = $group_id;
                            else
                            {
                                $arUserGroupsTmp = Array();
                                foreach ($arUserGroups as $grid)
                                    if ($grid != $group_id)
                                        $arUserGroupsTmp[] = $grid;
                                $arUserGroups = $arUserGroupsTmp;
                            }
                        }
                        CUser::SetUserGroup($ID, $arUserGroups);
                    }
                    $arArgs["store_password"] = "N";

                    return $ID;
                }
            }
        }
    }

    function OnExternalAuthList()
    {
        return Array(
            Array("ID"=>"PHPBB2", "NAME"=>"PhpBB2 Board")
            );
    }

    function OnAuthorize(&$arArgs)
    {
        extract($arArgs);

        global $DB, $APPLICATION, $USER;
        $user_id = $USER->GetParam("PHPBB2_USER_ID");
        if($user_id<=0)
            return;
        $table_user = PHPBB2_TABLE_PREFIX."users";
        $table_sessions = PHPBB2_TABLE_PREFIX."sessions";
        $table_config = PHPBB2_TABLE_PREFIX."config";

        $dbConfig = $DB->Query("SELECT * FROM ".
                               $table_config.
                               " WHERE config_name IN ('cookie_name', 
                                                       'cookie_path', 
                                                       'cookie_domain', 
                                                       'cookie_secure')");
        while($arConfig = $dbConfig->Fetch())
            ${$arConfig['config_name']} = $arConfig['config_value'];

        if(isset($HTTP_COOKIE_VARS[$cookie_name . '_sid']) || 
           isset($HTTP_COOKIE_VARS[$cookie_name . '_data']))
        {
            $session_id = isset($HTTP_COOKIE_VARS[$cookie_name . '_sid']) ? 
                            $HTTP_COOKIE_VARS[$cookie_name . '_sid'] : '';
        }

        $ip_sep = explode('.', $_SERVER['REMOTE_ADDR']);
        $user_ip = sprintf('%02x%02x%02x%02x', 
                           $ip_sep[0], $ip_sep[1], $ip_sep[2], $ip_sep[3]);
        $current_time = time();
        $sql =
            "UPDATE ".$table_sessions." SET ".
            "    session_user_id = ".$user_id.", ".
            "    session_start = ".$current_time.", ".
            "    session_time = ".$current_time.", ".
            "    session_page = 0, ".
            "    session_logged_in = 1 ".
            "WHERE session_id = '".$DB->ForSQL($session_id)."' ".
            "    AND session_ip = '".$user_ip."'";

        $r = $DB->Query($sql);
        if($r->AffectedRowsCount()<=0)
        {
            $session_id = md5(uniqid($user_ip));
            $sql =
                "INSERT INTO ".$table_sessions.
                "(session_id, session_user_id,".
                " session_start, session_time, session_ip,".
                " session_page, session_logged_in)".
                "VALUES ('".$session_id."', ".
                $user_id.", ".$current_time.", ".
                $current_time.", '".$user_ip."', 0, 1)";
            $DB->Query($sql);
        }

        $sql =
            "UPDATE ".$table_user." SET ".
            "    user_session_time = ".$current_time.", ".
            "    user_session_page = 0, ".
            "    user_lastvisit = ".$current_time." ".
            "WHERE user_id = ".$user_id;

        $DB->Query($sql);

        $sessiondata = Array('userid' => $user_id);

        setcookie($cookie_name.'_data', 
                  serialize($sessiondata), 
                  $current_time + 31536000, 
                  $cookie_path, $cookie_domain, 
                  $cookie_secure);
        setcookie($cookie_name.'_sid', 
                  $session_id, 0, 
                  $cookie_path, $cookie_domain, 
                  $cookie_secure);
    }
}
?>

The following lines should be added to /bitrix/php_interface/init.php:

<?
AddEventHandler(
    "main", 
    "OnUserLoginExternal", 
    Array("__PHPBB2Auth", "OnUserLoginExternal"), 
    $_SERVER['DOCUMENT_ROOT'].
    '/bitrix/php_interface/scripts/phpbb.php'
    );
AddEventHandler(
    "main", 
    "OnExternalAuthList", 
    Array("__PHPBB2Auth", "OnExternalAuthList"),
    $_SERVER['DOCUMENT_ROOT'].
    '/bitrix/php_interface/scripts/phpbb.php'
    );
AddEventHandler(
    "main", 
    "OnAfterUserAuthorize", 
    Array("__PHPBB2Auth", "OnAuthorize")
    );
?>

The following example shows a script for use with the Invision Power Board forum.

<?
define("IPB_TABLE_PREFIX", "ibf_");
define("IPB_VERSION", "2");

AddEventHandler("main", 
                "OnUserLoginExternal", 
                Array("__IPBAuth", "OnUserLoginExternal"));
AddEventHandler("main", 
                "OnExternalAuthList", 
                Array("__IPBAuth", "OnExternalAuthList"));

class __IPBAuth
{
    function OnUserLoginExternal(&$arArgs)
    {
        extract($arArgs);

        ////////// <settings> ////////////
        $table_user = IPB_TABLE_PREFIX."members";
        $table_converge = IPB_TABLE_PREFIX."members_converge";
        $groups_map = Array(
            /*'IPB Group ID' => 'Local Group ID',*/
            '4' => '1'
            );
        ////////// </settings> ////////////

        global $DB, $USER, $APPLICATION;

        if(IPB_VERSION == '1')
        {
            $strSql = "SELECT * FROM ".$table_user.
                      " WHERE name='".$DB->ForSql($login).
                      "' AND password='".md5($password)."'";
        }
        else
        {
            $strSql =
                "SELECT t1.* ".
                "FROM ".$table_user." t1, ".$table_converge." t2 ".
                "WHERE t1.name='".$DB->ForSql($login)."' ".
                "    AND t1.email = t2.converge_email ".
                "    AND t2.converge_pass_hash = ".
                "MD5(CONCAT(MD5(t2.converge_pass_salt), '".
                md5($password)."'))";
        }

        $dbAuthRes = $DB->Query($strSql);
        if($arAuthRes = $dbAuthRes->Fetch())
        {
            $arFields = Array(
                "LOGIN" => $login,
                "NAME" => $arAuthRes['title'],
                "PASSWORD" => $password,
                "EMAIL" => $arAuthRes['email'],
                "ACTIVE" => "Y",
                "EXTERNAL_AUTH_ID"=>"IPB",
                "LID" => SITE_ID
                );

            $oUser = new CUser;
            $res = CUser::GetList($O, 
                                  $B, 
                                  Array("LOGIN_EQUAL_EXACT"=>$login, 
                                        "EXTERNAL_AUTH_ID" =>"IPB"));
            if(!($ar_res = $res->Fetch()))
                $ID = $oUser->Add($arFields);
            else
            {
                $ID = $ar_res["ID"];
                $oUser->Update($ID, $arFields);
            }

            if($ID>0)
            {
                $USER->SetParam("IPB_USER_ID", $arAuthRes['id']);

                $user_group = $arAuthRes['mgroup'];
                $arUserGroups = CUser::GetUserGroup($ID);
                foreach($groups_map as $ext_group_id => $group_id)
                {
                    if($ext_group_id==$user_group)
                        $arUserGroups[] = $group_id;
                    else
                    {
                        $arUserGroupsTmp = Array();
                        foreach($arUserGroups as $grid)
                            if($grid != $group_id)
                                $arUserGroupsTmp[] = $grid;
                        $arUserGroups = $arUserGroupsTmp;
                    }
                }
                CUser::SetUserGroup($ID, $arUserGroups);
                $arArgs["store_password"] = "N";

                return $ID;
            }
        }
    }

    function OnExternalAuthList()
    {
        return Array(
            Array("ID"=>"IPB", "NAME"=>"Invision Power Board")
            );
    }
}
?>

The above script file should be registered in /bitrix/php_interface/init.php for it to become effective.

See Also