| Current Path : /var/www/homesaver/www/bitrix/modules/main/classes/general/ |
| Current File : /var/www/homesaver/www/bitrix/modules/main/classes/general/access.php |
<?php
/**
* Bitrix Framework
* @package bitrix
* @subpackage main
* @copyright 2001-2023 Bitrix
*/
use Bitrix\Main;
use Bitrix\Main\Type\DateTime;
IncludeModuleLangFile(__FILE__);
class CAccess
{
const CHECK_CACHE_DIR = "access_check";
const CACHE_DIR = "access_codes";
protected static $arAuthProviders = [];
protected static $arChecked = [];
protected static $userCodes = [];
protected $arParams = false;
public function __construct($arParams = false)
{
$this->arParams = $arParams;
if (empty(static::$arAuthProviders))
{
foreach (GetModuleEvents("main", "OnAuthProvidersBuildList", true) as $arEvent)
{
$res = ExecuteModuleEventEx($arEvent);
if (is_array($res))
{
if (!is_array($res[0]))
{
$res = [$res];
}
foreach ($res as $provider)
{
static::$arAuthProviders[$provider["ID"]] = $provider;
}
}
}
sortByColumn(static::$arAuthProviders, "SORT");
}
}
protected static function NeedToRecalculate($provider, $USER_ID, DateTime $now)
{
global $DB, $CACHE_MANAGER;
$USER_ID = intval($USER_ID);
if (!isset(static::$arChecked[$provider][$USER_ID]))
{
$cacheId = static::GetCheckCacheId($provider, $USER_ID);
if (CACHED_b_user_access_check !== false && $CACHE_MANAGER->Read(CACHED_b_user_access_check, $cacheId, static::CHECK_CACHE_DIR))
{
static::$arChecked[$provider][$USER_ID] = $CACHE_MANAGER->Get($cacheId);
}
else
{
$res = $DB->Query("
select DATE_CHECK
from b_user_access_check
where USER_ID = " . $USER_ID . "
and PROVIDER_ID = '" . $DB->ForSql($provider) . "'
");
static::$arChecked[$provider][$USER_ID] = [];
while ($check = $res->Fetch())
{
static::$arChecked[$provider][$USER_ID][] = $check['DATE_CHECK'];
}
if (CACHED_b_user_access_check !== false)
{
$CACHE_MANAGER->Set($cacheId, static::$arChecked[$provider][$USER_ID]);
}
}
}
$helper = Main\Application::getConnection()->getSqlHelper();
foreach (static::$arChecked[$provider][$USER_ID] as $dateCheck)
{
$date = $helper->convertFromDbDateTime($dateCheck);
if ($date && $date->getTimestamp() <= $now->getTimestamp())
{
return true;
}
}
return false;
}
public function UpdateCodes($arParams = false)
{
global $USER;
$USER_ID = 0;
if (is_array($arParams) && isset($arParams["USER_ID"]))
{
$USER_ID = intval($arParams["USER_ID"]);
}
elseif (is_object($USER) && $USER->IsAuthorized())
{
$USER_ID = intval($USER->GetID());
}
if ($USER_ID > 0)
{
$connection = Main\Application::getConnection();
$clearCache = false;
$now = new DateTime();
foreach (static::$arAuthProviders as $providerId => $providerDescription)
{
/** @var CGroupAuthProvider $provider For example */
$provider = new $providerDescription["CLASS"];
if (is_callable([$provider, "UpdateCodes"]))
{
//do we need to recalculate codes for the user?
if (static::NeedToRecalculate($providerId, $USER_ID, $now))
{
$lockName = "update_codes.{$providerId}.{$USER_ID}";
// we don't want to do the same job twice
if ($connection->lock($lockName))
{
$connection->startTransaction();
//should clear codes cache for the user
$clearCache = true;
//remove old codes
static::DeleteCodes($providerId, $USER_ID);
//call provider to insert access codes
$provider->UpdateCodes($USER_ID);
//update cache for checking
static::UpdateStat($providerId, $USER_ID, $now);
$connection->commitTransaction();
$connection->unlock($lockName);
}
}
}
}
if ($clearCache)
{
static::ClearCache($USER_ID);
}
}
}
/**
* @param int $userId
* @param string $provider
* @param string $code
*/
public static function AddCode($userId, $provider, $code)
{
$userId = (int)$userId;
$connection = Main\Application::getConnection();
$helper = $connection->getSqlHelper();
$sql = $helper->getInsertIgnore(
'b_user_access',
'(USER_ID, PROVIDER_ID, ACCESS_CODE)',
"VALUES ({$userId}, '{$helper->forSql($provider)}', '{$helper->forSql($code)}')"
);
$connection->query($sql);
static::ClearCache($userId);
}
/**
* @param int $userId
* @param string $provider
* @param string $code
*/
public static function RemoveCode($userId, $provider, $code)
{
$userId = (int)$userId;
$connection = Main\Application::getConnection();
$helper = $connection->getSqlHelper();
$connection->query("
DELETE FROM b_user_access
WHERE USER_ID = {$userId}
AND PROVIDER_ID = '{$helper->forSql($provider)}'
AND ACCESS_CODE = '{$helper->forSql($code)}'
");
static::ClearCache($userId);
}
/**
* @param int $userId User ID.
*/
public static function ClearCache($userId)
{
global $CACHE_MANAGER;
$CACHE_MANAGER->Clean(static::GetCodesCacheId($userId), static::CACHE_DIR);
unset(static::$userCodes[$userId]);
}
public static function RecalculateForUser($userId, $provider, DateTime $dateCheck = null)
{
global $DB;
$connection = Main\Application::getConnection();
$helper = $connection->getSqlHelper();
$userId = intval($userId);
if ($dateCheck === null)
{
$dateCheck = new DateTime('1974-12-07', 'Y-m-d'); // somewhen in the past
}
$sql = $helper->getInsertIgnore('b_user_access_check', '(USER_ID, PROVIDER_ID, DATE_CHECK)', "
SELECT ID, '{$DB->ForSQL($provider)}', {$helper->convertToDbDateTime($dateCheck)}
FROM b_user
WHERE ID = {$userId}"
);
$connection->query($sql);
static::ClearCheckCache($provider, $userId);
}
public static function RecalculateForProvider($provider)
{
global $CACHE_MANAGER;
$connection = Main\Application::getConnection();
$helper = $connection->getSqlHelper();
$dateCheck = new DateTime('1974-12-07', 'Y-m-d'); // somewhen in the past
$sql = $helper->getInsertIgnore('b_user_access_check', '(USER_ID, PROVIDER_ID, DATE_CHECK)', "
SELECT USER_ID, PROVIDER_ID, {$helper->convertToDbDateTime($dateCheck)}
FROM b_user_access
WHERE PROVIDER_ID = '" . $helper->forSQL($provider) . "'
AND USER_ID > 0
GROUP BY USER_ID, PROVIDER_ID
");
$connection->query($sql);
$CACHE_MANAGER->CleanDir(static::CHECK_CACHE_DIR);
unset(static::$arChecked[$provider]);
}
protected static function GetCheckCacheId($provider, $userId)
{
return "access_check_v2_" . $provider . "_" . $userId;
}
protected static function GetCodesCacheId($userId)
{
return "access_codes" . $userId;
}
protected static function DeleteCodes($providerId, $userId)
{
$userId = (int)$userId;
$connection = Main\Application::getConnection();
$helper = $connection->getSqlHelper();
$connection->query("
delete from b_user_access
where user_id = {$userId}
and provider_id = '{$helper->forSql($providerId)}'
");
}
protected static function UpdateStat($provider, $userId, DateTime $now)
{
global $DB;
$userId = intval($userId);
$helper = Main\Application::getConnection()->getSqlHelper();
$DB->Query("
delete from b_user_access_check
where user_id = {$userId}
and provider_id = '{$DB->ForSql($provider)}'
and date_check <= {$helper->convertToDbDateTime($now)}
");
static::ClearCheckCache($provider, $userId);
}
protected static function ClearCheckCache($provider, $userId)
{
global $CACHE_MANAGER;
$CACHE_MANAGER->Clean(static::GetCheckCacheId($provider, $userId), static::CHECK_CACHE_DIR);
unset(static::$arChecked[$provider][$userId]);
}
public static function GetUserCodes($USER_ID, $arFilter = [], bool $updateCodes = true)
{
global $DB;
if ($updateCodes)
{
$access = new CAccess();
$access->UpdateCodes(['USER_ID' => $USER_ID]);
}
$arWhere = [];
foreach ($arFilter as $key => $val)
{
$key = strtoupper($key);
switch ($key)
{
case "ACCESS_CODE":
if (!is_array($val))
{
$val = [$val];
}
$arIn = [];
foreach ($val as $code)
{
if (trim($code) <> '')
{
$arIn[] = "'" . $DB->ForSQL(trim($code)) . "'";
}
}
if (!empty($arIn))
{
$arWhere[] = "access_code in(" . implode(",", $arIn) . ")";
}
break;
case "PROVIDER_ID":
$arWhere[] = "provider_id='" . $DB->ForSQL($val) . "'";
break;
}
}
$sWhere = '';
if (!empty($arWhere))
{
$sWhere = " and " . implode(" and ", $arWhere);
}
return $DB->Query("select * from b_user_access where user_id=" . intval($USER_ID) . $sWhere);
}
public static function GetUserCodesArray($USER_ID, $arFilter = [])
{
global $CACHE_MANAGER;
$USER_ID = intval($USER_ID);
$access = new CAccess();
$access->UpdateCodes(['USER_ID' => $USER_ID]);
if (empty($arFilter) && isset(static::$userCodes[$USER_ID]))
{
return static::$userCodes[$USER_ID];
}
$useCache = (empty($arFilter) && CACHED_b_user_access_check !== false);
$cacheId = static::GetCodesCacheId($USER_ID);
if ($useCache && $CACHE_MANAGER->Read(CACHED_b_user_access_check, $cacheId, static::CACHE_DIR))
{
$arCodes = $CACHE_MANAGER->Get($cacheId);
}
else
{
$arCodes = [];
$res = CAccess::GetUserCodes($USER_ID, $arFilter, false);
while ($arRes = $res->Fetch())
{
$arCodes[] = $arRes["ACCESS_CODE"];
}
if ($useCache)
{
$CACHE_MANAGER->Set($cacheId, $arCodes);
}
}
if (empty($arFilter))
{
static::$userCodes[$USER_ID] = $arCodes;
}
return $arCodes;
}
public function GetFormHtml()
{
$arHtml = [];
foreach (static::$arAuthProviders as $provider)
{
$cl = new $provider["CLASS"];
if (is_callable([$cl, "GetFormHtml"]))
{
$res = call_user_func_array([$cl, "GetFormHtml"], [$this->arParams]);
if ($res !== false)
{
$arHtml[$provider["ID"]] = [
"NAME" => $provider["NAME"],
"HTML" => $res["HTML"],
"SELECTED" => $res["SELECTED"] ?? false,
];
}
}
}
return $arHtml;
}
public function AjaxRequest($arParams)
{
if (isset(static::$arAuthProviders[$arParams["provider"]]))
{
$cl = new static::$arAuthProviders[$arParams["provider"]]["CLASS"];
if (is_callable([$cl, "AjaxRequest"]))
{
return call_user_func_array([$cl, "AjaxRequest"], [$this->arParams]);
}
}
return false;
}
public function GetNames($arCodes, $bSort = false)
{
$arResult = [];
if (!is_array($arCodes) || empty($arCodes))
{
return $arResult;
}
foreach (static::$arAuthProviders as $provider)
{
$cl = new $provider["CLASS"];
if (is_callable([$cl, "GetNames"]))
{
$res = call_user_func_array([$cl, "GetNames"], [$arCodes]);
if (is_array($res))
{
foreach ($res as $codeId => $codeValues)
{
$codeValues['provider_id'] = $provider['ID'];
$arResult[$codeId] = $codeValues;
}
}
}
}
if ($bSort)
{
uasort($arResult, ['CAccess', 'CmpNames']);
}
return $arResult;
}
public static function CmpNames($a, $b)
{
$c = strcmp($a["provider"], $b["provider"]);
if ($c <> 0)
{
return $c;
}
return strcmp($a["name"], $b["name"]);
}
public function GetProviderNames()
{
$arResult = [];
foreach (static::$arAuthProviders as $ID => $provider)
{
$arResult[$ID] = [
"name" => ($provider["PROVIDER_NAME"] ?? $ID),
"prefixes" => ($provider["PREFIXES"] ?? []),
];
}
return $arResult;
}
public static function GetProviders()
{
return [
[
"ID" => "group",
"NAME" => GetMessage("access_groups"),
"PROVIDER_NAME" => GetMessage("access_group"),
"SORT" => 100,
"CLASS" => "CGroupAuthProvider",
],
[
"ID" => "user",
"NAME" => GetMessage("access_users"),
"PROVIDER_NAME" => GetMessage("access_user"),
"SORT" => 200,
"CLASS" => "CUserAuthProvider",
],
[
"ID" => "other",
"NAME" => GetMessage("access_other"),
"PROVIDER_NAME" => "",
"SORT" => 1000,
"CLASS" => "COtherAuthProvider",
],
];
}
public static function OnUserDelete($USER_ID)
{
global $DB;
$USER_ID = intval($USER_ID);
$DB->Query("delete from b_user_access where user_id=" . $USER_ID);
$DB->Query("delete from b_user_access_check where user_id=" . $USER_ID);
//all providers for one user
foreach (static::$arChecked as $provider => $dummy)
{
static::ClearCheckCache($provider, $USER_ID);
}
static::ClearCache($USER_ID);
return true;
}
public static function SaveLastRecentlyUsed($arLRU)
{
foreach ($arLRU as $provider => $arRecent)
{
if (is_array($arRecent))
{
$arLastRecent = CUserOptions::GetOption("access_dialog_recent", $provider, []);
$arItems = array_keys($arRecent);
$arItems = array_unique(array_merge($arItems, $arLastRecent));
$arItems = array_slice($arItems, 0, 20);
CUserOptions::SetOption("access_dialog_recent", $provider, $arItems);
}
}
}
public static function GetLastRecentlyUsed($provider)
{
$res = CUserOptions::GetOption("access_dialog_recent", $provider, []);
if (!is_array($res))
{
$res = [];
}
return $res;
}
/**
* @internal
* @param int $lastId
* @return string
*/
public static function deleteDuplicatesAgent(int $lastId = 0)
{
$connection = Main\Application::getConnection();
if (!($connection instanceof Main\DB\MysqlCommonConnection))
{
return '';
}
if ($connection->getIndexName('b_user_access', ['USER_ID', 'ACCESS_CODE'], true) === 'ux_ua_user_access')
{
return '';
}
$codes = $connection->query("
select ID, USER_ID, ACCESS_CODE
from b_user_access
where ID > {$lastId}
order by ID
limit 10000
");
$id = null;
while ($code = $codes->fetch())
{
$id = $code['ID'];
$connection->query("
delete from b_user_access
where ID > {$id}
and USER_ID = {$code['USER_ID']}
and ACCESS_CODE = '{$code['ACCESS_CODE']}'
");
}
if ($id !== null)
{
return "CAccess::deleteDuplicatesAgent({$id});";
}
try
{
$connection->query("
alter table b_user_access
drop index ix_ua_user_access,
add UNIQUE INDEX ux_ua_user_access (USER_ID, ACCESS_CODE)
");
}
catch (Main\DB\SqlQueryException)
{
return "CAccess::deleteDuplicatesAgent();";
}
return '';
}
}
AddEventHandler("main", "OnAuthProvidersBuildList", ["CAccess", "GetProviders"]);