Views: 16633
Last Modified: 22.09.2014

Sometimes it may be necessary to use special checking algorithms and/or external user storage databases for the user’s authorization (verification of login and password). E.g., there is a user database and they must have the possibility to undergo authorization on a CMS managed site. In these cases, all users sometimes may be moved to the CMS database using API functions, but often it is impossible because of the following two reasons:

  • First. Users’ passwords in the external database are not stored in the open form, only their hash is stored; that is why after migration the users will not be able to undergo authorization due to password inconsistency.
  • Second. Sometimes a common user database is necessary, with users’ passwords stored on the same remote server and used for verification in several places, including CMS.

In order to resolve such tasks, Bitrix Framework provides for an option to add own external authorization to the standard incorporated authorization system. It takes several steps that we will cover in detail taking an external authorization as an example and using users’ database of a popular forum PHP BB.

For a start, let us create a file, for example, /bitrix/php_interface/scripts/phpbb.php. A class with an external handler will be located in this class, let us call it __PHPBB2Auth:

class __PHPBB2Auth
{
}

In order to have our function invoked during an authorization attempt it is necessary to set the OnUserLoginExternal event handler that will be invoked automatically each time a user enters their login and password, before the built-in check. For this, let us use the AddEventHandler function in the file /bitrix/php_interface/init.php:

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

We have specified our class method __PHPBB2Auth::OnUserLoginExternal as a handler. The OnUserLoginExternal event handler accepts as a parameter a link to an array with fields for the check:

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')
        {
            // user’s name and password are correct
        }
    }
}

Having verified the login and password according to the PHPBB algorithm, an external user must be created in the internal database so that internal objects (news, votes, etc.) could be linked to it. To do so, let us use the method CUser::GetList() with a filter by login and external source code. If there is no such user we will create it, and if such a user exists we will update information about it.

$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)
{
   // authorization is permitted
   return $ID;
}

Now we have a user ID in our database and it can be recovered from the handler function to make this user authorized by the system, but a new user will be anonymous, because it is not bound to any group. Let us use the binding in the PHPBB database to transfer it to our database before 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);
}

That is all. Now the local user account is consistent with the remote one, a user’s code may be recovered, and it will be authorized. Let us tentatively switch off the function "remember me on this computer” if the user has enabled the checkbox, because in this case we will not be able to correctly check the access rights:

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

In order to register a new external system authorization check, the event OnExternalAuthList must be handled. Let us add a relevant invocation in the file /bitrix/php_interface/init.php:

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

Handler function must return the array from a set of handlers with the fields ID and NAME.

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

Now on the user editing page a drop-down list indicating external authorization sources appears. We provide the entire text of the file /bitrix/php_interface/scripts/phpbb.php below. A reverse mechanism is additionally implemented in it: once authorized in our system, a user is automatically authorized in the forum.

<?
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 must be added to /bitrix/php_interface/init.php:

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

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

AddEventHandler(
    "main", 
    "OnAfterUserAuthorize", 
    Array("__PHPBB2Auth", "OnAuthorize"),
    100
    );
?>

As an example, here is another script for external authorization – for 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")
            );
    }
}
?>

This script must be connected in /bitrix/php_interface/init.php to become operable.



Courses developed by Bitrix24