This commit is contained in:
root
2025-11-13 19:04:05 +03:00
commit 240d0aba5f
75129 changed files with 11118122 additions and 0 deletions

View File

@@ -0,0 +1,328 @@
<?php
/**
* Bitrix Framework
* @package bitrix
* @subpackage rest
* @copyright 2001-2016 Bitrix
*/
namespace Bitrix\Rest\OAuth;
use Bitrix\Main\Localization\Loc;
use Bitrix\Rest\Application;
use Bitrix\Rest\AppTable;
use Bitrix\Rest\AuthStorageInterface;
use Bitrix\Rest\Engine\Access;
use Bitrix\Rest\Engine\Access\HoldEntity;
use Bitrix\Rest\Event\Session;
use Bitrix\Rest\Internal\Access\UserAccessChecker;
use Bitrix\Rest\OAuthService;
use Bitrix\Main\SystemException;
class Auth
{
const AUTH_TYPE = 'oauth';
const CACHE_TTL = 3600;
const CACHE_PREFIX = "oauth_";
const PARAM_LOCAL_USER = 'LOCAL_USER';
const PARAM_TZ_OFFSET = 'TZ_OFFSET';
/**
* @var AuthStorageInterface
*/
protected static $storage = null;
protected static $authQueryParams = array(
'auth', 'access_token'
);
protected static $authQueryAdditional = array(
'auth_connector'
);
/**
* @deprecated Use \Bitrix\Rest\Application::getAuthProvider()->authorizeClient()
*/
public static function authorizeClient($clientId, $userId, $state = '')
{
return Application::getAuthProvider()->authorizeClient($clientId, $userId, $state);
}
/**
* @deprecated Use \Bitrix\Rest\Application::getAuthProvider()->get()
*/
public static function get($clientId, $scope, $additionalParams, $userId)
{
return Application::getAuthProvider()->get($clientId, $scope, $additionalParams, $userId);
}
public static function storeRegisteredAuth(array $tokenInfo)
{
static::getStorage()->store($tokenInfo);
}
public static function onRestCheckAuth(array $query, $scope, &$res)
{
$authKey = static::getAuthKey($query);
if($authKey)
{
$tokenInfo = static::check($authKey);
if(is_array($tokenInfo))
{
$error = array_key_exists('error', $tokenInfo);
if(!$error && !array_key_exists('client_id', $tokenInfo))
{
$tokenInfo = array('error' => 'CONNECTION_ERROR', 'error_description' => 'Error connecting to authorization server');
$error = true;
}
if (!$error && HoldEntity::is(HoldEntity::TYPE_APP, $tokenInfo['client_id']))
{
$tokenInfo = [
'error' => 'OVERLOAD_LIMIT',
'error_description' => 'REST API is blocked due to overload.'
];
$error = true;
}
if (
!$error
&& (
!Access::isAvailable($tokenInfo['client_id'])
|| (
Access::needCheckCount()
&& !Access::isAvailableCount(Access::ENTITY_TYPE_APP, $tokenInfo['client_id'])
)
)
)
{
$tokenInfo = [
'error' => 'ACCESS_DENIED',
'error_description' => 'REST is available only on commercial plans.'
];
$error = true;
}
if(!$error)
{
$clientInfo = AppTable::getByClientId($tokenInfo['client_id']);
if(is_array($clientInfo))
{
\CRestUtil::updateAppStatus($tokenInfo);
}
if(!is_array($clientInfo) || $clientInfo['ACTIVE'] !== 'Y')
{
$tokenInfo = array('error' => 'APPLICATION_NOT_FOUND', 'error_description' => 'Application not found');
$error = true;
}
}
if(!$error && $tokenInfo['expires'] <= time())
{
$tokenInfo = array('error' => 'expired_token', 'error_description' => 'The access token provided has expired');
$error = true;
}
if(!$error && $scope !== \CRestUtil::GLOBAL_SCOPE && isset($tokenInfo['scope']))
{
$tokenScope = explode(',', $tokenInfo['scope']);
$tokenScope = \Bitrix\Rest\Engine\RestManager::fillAlternativeScope($scope, $tokenScope);
if(!in_array($scope, $tokenScope))
{
$tokenInfo = array('error' => 'insufficient_scope', 'error_description' => 'The request requires higher privileges than provided by the access token');
$error = true;
}
}
if(!$error && $tokenInfo['user_id'] > 0)
{
global $USER;
if ($USER instanceof \CUser && $USER->isAuthorized())
{
if ((int)$USER->getId() !== (int)$tokenInfo['user_id'])
{
$tokenInfo = [
'error' => 'authorization_error',
'error_description' => Loc::getMessage('REST_OAUTH_ERROR_LOGOUT_BEFORE'),
];
$error = true;
}
}
elseif (!\CRestUtil::makeAuth($tokenInfo))
{
$tokenInfo = array('error' => 'authorization_error', 'error_description' => 'Unable to authorize user');
$error = true;
}
elseif(!\CRestUtil::checkAppAccess($tokenInfo['client_id']))
{
$tokenInfo = array('error' => 'user_access_error', 'error_description' => 'The user does not have access to the application.');
$error = true;
}
}
$res = $tokenInfo;
$res['parameters_clear'] = static::$authQueryParams;
$res['auth_type'] = static::AUTH_TYPE;
$res['parameters_callback'] = array(__CLASS__, 'updateTokenParameters');
foreach(static::$authQueryAdditional as $key)
{
if(array_key_exists($key, $query))
{
$res[$key] = $query[$key];
$res['parameters_clear'][] = $key;
}
}
return !$error;
}
return false;
}
return null;
}
public static function getAuthKey(array $query)
{
$authKey = null;
$authHeader = \Bitrix\Main\Application::getInstance()->getContext()->getRequest()->getHeader('Authorization');
if($authHeader !== null)
{
if(preg_match('/^Bearer\s+/i', $authHeader))
{
$authKey = preg_replace('/^Bearer\s+/i', '', $authHeader);
}
}
if($authKey === null)
{
foreach(static::$authQueryParams as $key)
{
if(array_key_exists($key, $query) && !is_array($query[$key]))
{
$authKey = $query[$key];
break;
}
}
}
return $authKey;
}
public static function updateTokenParameters($tokenInfo)
{
$authResult = static::getStorage()->restore($tokenInfo['access_token']);
if(is_array($authResult))
{
if(!is_array($authResult['parameters']))
{
$authResult['parameters'] = array();
}
$authResult['parameters'] = array_replace_recursive($authResult['parameters'], $tokenInfo['parameters']);
static::getStorage()->rewrite($authResult);
}
}
protected static function check($accessToken)
{
$authResult = static::getStorage()->restore($accessToken);
if($authResult === false)
{
if (!OAuthService::getEngine()->isRegistered())
{
try
{
OAuthService::register();
}
catch(SystemException $e)
{
return ['error' => 'CONNECTION_ERROR', 'error_description' => 'Error connecting to authorization server'];
}
}
$tokenInfo = OAuthService::getEngine()->getClient()->checkAuth($accessToken);
if(is_array($tokenInfo))
{
if($tokenInfo['result'])
{
$authResult = $tokenInfo['result'];
$authResult['user_id'] = $authResult['parameters'][static::PARAM_LOCAL_USER];
unset($authResult['parameters'][static::PARAM_LOCAL_USER]);
$accessChecker = new UserAccessChecker((int)$authResult['user_id']);
if (!$accessChecker->canAuthorize())
{
return ['error' => 'ACCESS_DENIED', 'error_description' => "Current user can't be authorized in this context"];
}
// compatibility with old oauth response
if(!isset($authResult['expires']) && isset($authResult['expires_in']))
{
$authResult['expires'] = time() + $authResult['expires_in'];
}
}
else
{
$authResult = $tokenInfo;
$authResult['access_token'] = $accessToken;
}
static::getStorage()->store($authResult);
}
else
{
$authResult = ['access_token' => $accessToken];
}
}
return $authResult;
}
protected static function getTokenParams($additionalParams, $userId)
{
if(!is_array($additionalParams))
{
$additionalParams = array();
}
$additionalParams[static::PARAM_LOCAL_USER] = $userId;
$additionalParams[static::PARAM_TZ_OFFSET] = \CTimeZone::getOffset();
$additionalParams[Session::PARAM_SESSION] = Session::get();
return $additionalParams;
}
/**
* @return AuthStorageInterface
*/
public static function getStorage()
{
if(static::$storage === null)
{
static::setStorage(new StorageCache());
}
return static::$storage;
}
/**
* @param AuthStorageInterface $storage
*/
public static function setStorage(AuthStorageInterface $storage)
{
static::$storage = $storage;
}
}

View File

@@ -0,0 +1,318 @@
<?php
/**
* Bitrix Framework
* @package bitrix
* @subpackage rest
* @copyright 2001-2016 Bitrix
*/
namespace Bitrix\Rest\OAuth;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\SystemException;
use Bitrix\Main\Text\Encoding;
use Bitrix\Main\Web\HttpClient;
use Bitrix\Main\Web\Json;
use Bitrix\Rest\OAuthService;
use Bitrix\Rest\Public\Provider;
if(!defined("BITRIX_OAUTH_URL"))
{
// replaced \Bitrix\Main\Config\Option::get('rest', 'oauth_server', 'https://oauth.bitrix.info');
$defaultValue = (new Provider\OAuth\AuthorizationServerProvider())->getCurrentAuthorizationUrl();
define("BITRIX_OAUTH_URL", $defaultValue);
}
if(!defined('BITRIXREST_URL'))
{
define('BITRIXREST_URL', BITRIX_OAUTH_URL);
}
class Client
{
const SERVICE_URL = BITRIXREST_URL;
const SERVICE_PATH = "/rest/";
const METHOD_METHODS = 'methods';
const METHOD_BATCH = 'batch';
const METHOD_APPLICATION_ADD = 'application.add';
const METHOD_APPLICATION_UPDATE = 'application.update';
const METHOD_APPLICATION_DELETE = 'application.delete';
const METHOD_APPLICATION_INSTALL = 'application.install';
const METHOD_APPLICATION_INSTALL_SUBSCRIPTION = 'application.install.subscription';
const METHOD_APPLICATION_UNINSTALL = 'application.uninstall';
const METHOD_APPLICATION_STAT = 'application.stat';
const METHOD_APPLICATION_LIST = 'application.list';
const METHOD_APPLICATION_USAGE = 'application.usage.add';
const METHOD_APPLICATION_VERSION_UPDATE = 'application.version.update';
const METHOD_APPLICATION_VERSION_DELETE = 'application.version.delete';
const METHOD_REST_AUTHORIZE = 'rest.authorize';
const METHOD_REST_CHECK = 'rest.check';
const METHOD_REST_CODE = 'rest.code';
const METHOD_REST_EVENT_CALL = 'rest.event.call';
const HTTP_SOCKET_TIMEOUT = 15;
const HTTP_STREAM_TIMEOUT = 15;
private const NAME_IDENTIFIER_REQUEST = 'bx24_request_id';
protected $clientId;
protected $clientSecret;
protected $licenseKey;
public function __construct($clientId, $clientSecret, $licenseKey)
{
$this->clientId = $clientId;
$this->clientSecret = $clientSecret;
$this->licenseKey = $licenseKey;
}
protected function prepareRequestData($additionalParams)
{
if(!is_array($additionalParams))
{
$additionalParams = array();
}
return $additionalParams;
}
protected function prepareRequest($additionalParams, $licenseCheck = false)
{
$additionalParams = $this->prepareRequestData($additionalParams);
$additionalParams['client_id'] = $this->clientId;
$additionalParams['client_secret'] = $this->clientSecret;
$additionalParams['client_redirect_uri'] = OAuthService::getRedirectUri();
$additionalParams['member_id'] = \CRestUtil::getMemberId();
if($licenseCheck)
{
$additionalParams = \CRestUtil::signLicenseRequest($additionalParams, $this->licenseKey);
}
return $additionalParams;
}
protected function prepareResponse($result)
{
try
{
return Json::decode($result);
}
catch(ArgumentException $e)
{
return false;
}
}
protected function getHttpClient()
{
return new HttpClient(array(
'socketTimeout' => static::HTTP_SOCKET_TIMEOUT,
'streamTimeout' => static::HTTP_STREAM_TIMEOUT,
));
}
protected function getRequestId(string $methodName): string
{
$requestId = '';
if (isset($_SERVER['BX24_REQUEST_ID']))
{
if (str_contains($methodName, '?'))
{
$separator = '&';
}
else
{
$separator = '/?';
}
$requestId = $separator . http_build_query([
static::NAME_IDENTIFIER_REQUEST => urlencode($_SERVER['BX24_REQUEST_ID'])
]);
}
return $requestId;
}
protected function getRequestUrl($methodName): string
{
return static::SERVICE_URL . static::SERVICE_PATH . $methodName . $this->getRequestId($methodName);
}
/**
* Low-level function for REST method call. Returns method response.
*
* @param string $methodName Method name.
* @param array|null $additionalParams Method params.
* @param bool|false $licenseCheck Send license key in request (will be sent automatically on verification_needed error).
*
* @return bool|mixed
*
* @throws SystemException
*/
public function call($methodName, $additionalParams = null, $licenseCheck = false)
{
if ($this->clientId && $this->clientSecret)
{
$additionalParams = $this->prepareRequest($additionalParams, $licenseCheck);
$httpClient = $this->getHttpClient();
$httpResult = $httpClient->post(
$this->getRequestUrl($methodName),
$additionalParams
);
$response = $this->prepareResponse($httpResult);
if ($response)
{
if (!$licenseCheck && is_array($response) && isset($response['error']) && $response['error'] === 'verification_needed')
{
return $this->call($methodName, $additionalParams, true);
}
}
else
{
addMessage2Log(
'Strange answer from Bitrix Service! '
. static::SERVICE_URL
. static::SERVICE_PATH
. $methodName . ": "
. $httpClient->getStatus() . ' '
. implode(" ", $httpClient->getError())
. $httpResult
);
}
return $response;
}
else
{
throw new SystemException("No client credentials");
}
}
public function batch($actions)
{
$batch = array();
if(is_array($actions))
{
foreach($actions as $queryKey => $cmdData)
{
list($cmd, $cmdParams) = array_values($cmdData);
$batch['cmd'][$queryKey] = $cmd.(is_array($cmdParams) ? '?'.http_build_query($this->prepareRequestData($cmdParams)) : '');
}
}
return $this->call(static::METHOD_BATCH, $batch);
}
public function addApplication(array $applicationSettings)
{
return $this->call(static::METHOD_APPLICATION_ADD, array(
"TITLE" => $applicationSettings["TITLE"],
"REDIRECT_URI" => $applicationSettings["REDIRECT_URI"],
"SCOPE" => $applicationSettings["SCOPE"],
));
}
public function updateApplication(array $applicationSettings)
{
return $this->call(static::METHOD_APPLICATION_UPDATE, array(
"CLIENT_ID" => $applicationSettings["CLIENT_ID"],
"TITLE" => $applicationSettings["TITLE"],
"REDIRECT_URI" => $applicationSettings["REDIRECT_URI"],
"SCOPE" => $applicationSettings["SCOPE"],
));
}
public function deleteApplication(array $applicationSettings)
{
return $this->call(static::METHOD_APPLICATION_DELETE, array(
"CLIENT_ID" => $applicationSettings["CLIENT_ID"],
));
}
public function installApplication(array $applicationSettings)
{
$queryFields = array(
"CLIENT_ID" => $applicationSettings["CLIENT_ID"],
"VERSION" => $applicationSettings["VERSION"],
);
if(isset($applicationSettings["CHECK_HASH"]) && isset($applicationSettings["INSTALL_HASH"]))
{
$queryFields['CHECK_HASH'] = $applicationSettings["CHECK_HASH"];
$queryFields['INSTALL_HASH'] = $applicationSettings["INSTALL_HASH"];
}
if ($applicationSettings['BY_SUBSCRIPTION'] === 'Y')
{
$method = static::METHOD_APPLICATION_INSTALL_SUBSCRIPTION;
}
else
{
$method = static::METHOD_APPLICATION_INSTALL;
}
return $this->call($method, $queryFields);
}
public function unInstallApplication(array $applicationSettings)
{
return $this->call(static::METHOD_APPLICATION_UNINSTALL, array(
"CLIENT_ID" => $applicationSettings["CLIENT_ID"],
));
}
public function getAuth($clientId, $scope, array $additionalParams = array())
{
return $this->call(static::METHOD_REST_AUTHORIZE, array(
"CLIENT_ID" => $clientId,
"SCOPE" => $scope,
"PARAMS" => $additionalParams,
));
}
public function checkAuth($accessToken)
{
return $this->call(static::METHOD_REST_CHECK, array(
"TOKEN" => $accessToken,
));
}
public function getCode($clientId, $state, $additionalParams)
{
return $this->call(static::METHOD_REST_CODE, array(
"CLIENT_ID" => $clientId,
"STATE" => $state,
"PARAMS" => $additionalParams,
));
}
public function getApplicationList()
{
return $this->call(static::METHOD_APPLICATION_LIST);
}
public function sendApplicationUsage(array $usage)
{
return $this->call(static::METHOD_APPLICATION_USAGE, array(
"USAGE" => $usage,
));
}
public function sendEvent(array $eventItems)
{
return $this->call(static::METHOD_REST_EVENT_CALL, array(
"QUERY" => $eventItems,
));
}
}

View File

@@ -0,0 +1,92 @@
<?php
/**
* Bitrix Framework
* @package bitrix
* @subpackage rest
* @copyright 2001-2016 Bitrix
*/
namespace Bitrix\Rest\OAuth;
use Bitrix\Main\Config\Option;
use Bitrix\Main;
class Engine
{
protected $scope = array(
"rest", "application"
);
protected $client = null;
public function __construct()
{
}
/**
* @return \Bitrix\Rest\OAuth\Client
*/
public function getClient()
{
if(!$this->client)
{
$this->client = new Client(
$this->getClientId(),
$this->getClientSecret(),
$this->getLicense()
);
}
return $this->client;
}
public function isRegistered()
{
return $this->getClientId() !== false;
}
public function getClientId()
{
return Option::get("rest", "service_client_id", false);
}
public function getClientSecret()
{
return Option::get("rest", "service_client_secret", false);
}
public function setAccess(array $accessParams)
{
$connection = Main\Application::getInstance()->getConnection();
$connection->startTransaction();
try
{
Option::set("rest", "service_client_id", $accessParams["client_id"]);
Option::set("rest", "service_client_secret", $accessParams["client_secret"]);
$connection->commitTransaction();
}
catch (Main\ArgumentNullException $e)
{
$connection->rollbackTransaction();
}
$this->client = null;
}
public function clearAccess()
{
$this->setAccess(array(
"client_id" => false,
"client_secret" => false,
));
$this->client = null;
}
public function getLicense()
{
return LICENSE_KEY;
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace Bitrix\Rest\OAuth;
use Bitrix\Main\SystemException;
use Bitrix\Rest\AuthProviderInterface;
use Bitrix\Rest\Event\Session;
use Bitrix\Rest\OAuthService;
use Bitrix\Main\Type\DateTime;
class Provider implements AuthProviderInterface
{
/**
* @var Provider
*/
protected static $instance = null;
public static function instance()
{
if(static::$instance === null)
{
static::$instance = new static();
}
return static::$instance;
}
public function authorizeClient($clientId, $userId, $state = '')
{
if($userId > 0)
{
$additionalParams = $this->getTokenParams(array(), $userId);
$client = $this->getClient();
$codeInfo = $client->getCode($clientId, $state, $additionalParams);
if($codeInfo['result'])
{
return $codeInfo['result'];
}
else
{
return $codeInfo;
}
}
return false;
}
public function get($clientId, $scope, $additionalParams, $userId)
{
if($userId > 0)
{
$additionalParams = $this->getTokenParams($additionalParams, $userId);
$client = $this->getClient();
$authResult = $client->getAuth($clientId, $scope, $additionalParams);
if($authResult['result'])
{
if($authResult['result']['access_token'])
{
$authResult['result']['user_id'] = $userId;
$authResult['result']['client_id'] = $clientId;
Auth::storeRegisteredAuth($authResult['result']);
}
if (!empty($authResult['result']['date_finish_format']))
{
$dateFinish = new DateTime($authResult['result']['date_finish_format'], DATE_ATOM);
$authResult['result']['date_finish'] = $dateFinish->getTimestamp();
}
return $authResult['result'];
}
else
{
return $authResult;
}
}
return false;
}
protected function getClient()
{
if (!OAuthService::getEngine()->isRegistered())
{
try
{
OAuthService::register();
}
catch (SystemException)
{
}
}
return OAuthService::getEngine()->getClient();
}
protected function getTokenParams($additionalParams, $userId)
{
if(!is_array($additionalParams))
{
$additionalParams = array();
}
$additionalParams[Auth::PARAM_LOCAL_USER] = $userId;
$additionalParams[Auth::PARAM_TZ_OFFSET] = \CTimeZone::getOffset();
$additionalParams[Session::PARAM_SESSION] = Session::get();
return $additionalParams;
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Bitrix\Rest\OAuth;
use Bitrix\Main\Application;
use Bitrix\Rest\AuthStorageInterface;
class StorageCache implements AuthStorageInterface
{
const CACHE_TTL = 3600;
const CACHE_PREFIX = "oauth_";
public function store(array $authResult)
{
$cache = $this->getCache();
$cache->read(static::CACHE_TTL, $this->getCacheId($authResult["access_token"]));
$cache->set($this->getCacheId($authResult["access_token"]), $authResult);
}
public function rewrite(array $authResult)
{
$cache = $this->getCache();
$cache->clean($this->getCacheId($authResult["access_token"]));
$cache->read(static::CACHE_TTL, $this->getCacheId($authResult["access_token"]));
$cache->set($this->getCacheId($authResult["access_token"]), $authResult);
}
public function restore($accessToken)
{
$cache = $this->getCache();
$authResult = false;
if($readResult = $cache->read(static::CACHE_TTL, $this->getCacheId($accessToken)))
{
$authResult = $cache->get($this->getCacheId($accessToken));
}
return $authResult;
}
protected function getCacheId($accessToken)
{
return static::CACHE_PREFIX.$accessToken;
}
protected function getCache()
{
return Application::getInstance()->getManagedCache();
}
}