snapshot of https://github.com/espocrm/espocrm.git (master, 2026-09-18; blobs > 131072B omitted)
Some checks failed
Test / Test on PHP 8.3 (push) Failing after 5s
Test / Test on PHP 8.4 (push) Failing after 4s
Test / Test on PHP 8.5 (push) Failing after 3s
TypeScript static analysis / TypeScript check (push) Failing after 14s
Test Integration on MySQL / Test on PHP 8.3 (push) Failing after 1s
Test Integration on MySQL / Test on PHP 8.5 (push) Failing after 1s
Test Integration on PostgreSQL / Test on PHP 8.4 fix (push) Failing after 1s
Test Integration on PostgreSQL / Test on PHP 8.4 master (push) Failing after 1s
Some checks failed
Test / Test on PHP 8.3 (push) Failing after 5s
Test / Test on PHP 8.4 (push) Failing after 4s
Test / Test on PHP 8.5 (push) Failing after 3s
TypeScript static analysis / TypeScript check (push) Failing after 14s
Test Integration on MySQL / Test on PHP 8.3 (push) Failing after 1s
Test Integration on MySQL / Test on PHP 8.5 (push) Failing after 1s
Test Integration on PostgreSQL / Test on PHP 8.4 fix (push) Failing after 1s
Test Integration on PostgreSQL / Test on PHP 8.4 master (push) Failing after 1s
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Api\AccessToken;
|
||||
|
||||
use Espo\Core\Api\Action;
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\Api\ResponseComposer;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Record\EntityProvider;
|
||||
use Espo\Tools\OAuthServer\Entities\AccessToken;
|
||||
use Espo\Tools\OAuthServer\Record\TokenService;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class PostRevoke implements Action
|
||||
{
|
||||
public function __construct(
|
||||
private EntityProvider $entityProvider,
|
||||
private TokenService $service,
|
||||
) {}
|
||||
|
||||
public function process(Request $request): Response
|
||||
{
|
||||
$id = $request->getRouteParam('id') ?? throw new BadRequest();
|
||||
|
||||
$entity = $this->entityProvider->getByClass(AccessToken::class, $id);
|
||||
|
||||
$this->service->revoke($entity);
|
||||
|
||||
return ResponseComposer::json(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Api\AuthorizationCode;
|
||||
|
||||
use Espo\Core\Api\Action;
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\Api\ResponseComposer;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Record\EntityProvider;
|
||||
use Espo\Tools\OAuthServer\Entities\AuthorizationCode;
|
||||
use Espo\Tools\OAuthServer\Record\TokenService;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class PostRevoke implements Action
|
||||
{
|
||||
public function __construct(
|
||||
private EntityProvider $entityProvider,
|
||||
private TokenService $service,
|
||||
) {}
|
||||
|
||||
public function process(Request $request): Response
|
||||
{
|
||||
$id = $request->getRouteParam('id') ?? throw new BadRequest();
|
||||
|
||||
$entity = $this->entityProvider->getByClass(AuthorizationCode::class, $id);
|
||||
|
||||
$this->service->revoke($entity);
|
||||
|
||||
return ResponseComposer::json(true);
|
||||
}
|
||||
}
|
||||
56
application/Espo/Tools/OAuthServer/Api/GetConsentData.php
Normal file
56
application/Espo/Tools/OAuthServer/Api/GetConsentData.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Api;
|
||||
|
||||
use Espo\Core\Api\Action;
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\Api\ResponseComposer;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Tools\OAuthServer\ConsentDataService;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class GetConsentData implements Action
|
||||
{
|
||||
public function __construct(
|
||||
private ConsentDataService $service,
|
||||
) {}
|
||||
|
||||
public function process(Request $request): Response
|
||||
{
|
||||
$clientId = $request->getQueryParam('clientId') ?? throw new BadRequest("No clientId.");
|
||||
|
||||
$data = $this->service->getData($clientId);
|
||||
|
||||
return ResponseComposer::json($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Api\RefreshToken;
|
||||
|
||||
use Espo\Core\Api\Action;
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\Api\ResponseComposer;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Record\EntityProvider;
|
||||
use Espo\Tools\OAuthServer\Entities\RefreshToken;
|
||||
use Espo\Tools\OAuthServer\Record\TokenService;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class PostRevoke implements Action
|
||||
{
|
||||
public function __construct(
|
||||
private EntityProvider $entityProvider,
|
||||
private TokenService $service,
|
||||
) {}
|
||||
|
||||
public function process(Request $request): Response
|
||||
{
|
||||
$id = $request->getRouteParam('id') ?? throw new BadRequest();
|
||||
|
||||
$entity = $this->entityProvider->getByClass(RefreshToken::class, $id);
|
||||
|
||||
$this->service->revoke($entity);
|
||||
|
||||
return ResponseComposer::json(true);
|
||||
}
|
||||
}
|
||||
185
application/Espo/Tools/OAuthServer/AuthorizationService.php
Normal file
185
application/Espo/Tools/OAuthServer/AuthorizationService.php
Normal file
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer;
|
||||
|
||||
use Espo\Core\ApplicationState;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Tools\OAuthServer\League\AuthorizationRequestStorage;
|
||||
use Espo\Tools\OAuthServer\League\AuthorizationServerFactory;
|
||||
use Espo\Tools\OAuthServer\League\Entities\ScopeEntity;
|
||||
use Espo\Tools\OAuthServer\League\Entities\UserEntity;
|
||||
use Espo\Tools\OAuthServer\Scope\ScopeSorter;
|
||||
use Espo\Tools\OAuthServer\Utils\UriUtil;
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class AuthorizationService
|
||||
{
|
||||
public function __construct(
|
||||
private AuthorizationServerFactory $authorizationServerFactory,
|
||||
private ApplicationState $applicationState,
|
||||
private AuthorizationRequestStorage $authorizationRequestStorage,
|
||||
private ScopeSorter $scopeSorter,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function authorizeStart(ServerRequestInterface $request, ResponseInterface $response): ?ResponseInterface
|
||||
{
|
||||
$server = $this->authorizationServerFactory->create();
|
||||
|
||||
try {
|
||||
$authRequest = $server->validateAuthorizationRequest($request);
|
||||
} catch (OAuthServerException $e) {
|
||||
return $e->generateHttpResponse($response);
|
||||
}
|
||||
|
||||
$clientId = $authRequest->getClient()->getIdentifier();
|
||||
|
||||
$this->authorizationRequestStorage->store($clientId, $authRequest);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string[] $scopes
|
||||
* @throws NotFound
|
||||
* @throws Error
|
||||
* @throws Forbidden
|
||||
* @throws BadRequest
|
||||
* @noinspection PhpRedundantCatchClauseInspection
|
||||
*/
|
||||
public function authorizeComplete(
|
||||
string $clientId,
|
||||
ResponseInterface $response,
|
||||
bool $approved,
|
||||
array $scopes,
|
||||
): ResponseInterface {
|
||||
|
||||
$scopes = $this->scopeSorter->sort($scopes);
|
||||
|
||||
$authorizationRequest = $this->getAuthRequestFromSession($clientId);
|
||||
|
||||
$this->validateScopesOnCompletion($authorizationRequest, $scopes);
|
||||
|
||||
$authorizationRequest->setScopes(array_map(fn ($it) => new ScopeEntity($it), $scopes));
|
||||
|
||||
$user = $this->applicationState->getUser();
|
||||
|
||||
try {
|
||||
$this->assertUser($user, $authorizationRequest);
|
||||
} catch (OAuthServerException $e) {
|
||||
return $e->generateHttpResponse($response);
|
||||
}
|
||||
|
||||
$authorizationRequest->setUser(new UserEntity($user));
|
||||
$authorizationRequest->setAuthorizationApproved($approved);
|
||||
|
||||
$server = $this->authorizationServerFactory->create();
|
||||
|
||||
try {
|
||||
return $server->completeAuthorizationRequest($authorizationRequest, $response);
|
||||
} catch (OAuthServerException $e) {
|
||||
return $e->generateHttpResponse($response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function token(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$server = $this->authorizationServerFactory->create();
|
||||
|
||||
try {
|
||||
return $server->respondToAccessTokenRequest($request, $response);
|
||||
} catch (OAuthServerException $e) {
|
||||
return $e->generateHttpResponse($response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFound
|
||||
*/
|
||||
private function getAuthRequestFromSession(string $clientId): AuthorizationRequest
|
||||
{
|
||||
$authRequest = $this->authorizationRequestStorage->get($clientId, clear: true);
|
||||
|
||||
if (!$authRequest) {
|
||||
throw new NotFound("Session not found.");
|
||||
}
|
||||
|
||||
return $authRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
* @throws OAuthServerException
|
||||
*/
|
||||
private function assertUser(User $user, AuthorizationRequest $authorizationRequest): void
|
||||
{
|
||||
if ($user->isRegular() || $user->getType() === User::TYPE_ADMIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
$redirectUri = $authorizationRequest->getRedirectUri();
|
||||
|
||||
if ($redirectUri && $authorizationRequest->getState()) {
|
||||
$redirectUri = UriUtil::makeRedirectUri($redirectUri, $authorizationRequest->getState());
|
||||
}
|
||||
|
||||
throw OAuthServerException::accessDenied("User is not allowed.", $redirectUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $scopes
|
||||
* @throws Forbidden
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function validateScopesOnCompletion(AuthorizationRequest $authorizationRequest, array $scopes): void
|
||||
{
|
||||
if (count(array_unique($scopes)) !== count($scopes)) {
|
||||
throw new BadRequest("Duplicate scopes.");
|
||||
}
|
||||
|
||||
$storedScopes = array_map(fn ($it) => $it->getIdentifier(), $authorizationRequest->getScopes());
|
||||
|
||||
if (array_diff($scopes, $storedScopes)) {
|
||||
throw new Forbidden("Scope mismatch.");
|
||||
}
|
||||
}
|
||||
}
|
||||
165
application/Espo/Tools/OAuthServer/Cleanup/GeneralCleanup.php
Normal file
165
application/Espo/Tools/OAuthServer/Cleanup/GeneralCleanup.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Cleanup;
|
||||
|
||||
use Espo\Core\Cleanup\Cleanup;
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\DateTime;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\Condition as Cond;
|
||||
use Espo\ORM\Query\Part\Expression as Expr;
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
use Espo\Tools\OAuthServer\Entities\AccessToken;
|
||||
use Espo\Tools\OAuthServer\Entities\AuthorizationCode;
|
||||
use Espo\Tools\OAuthServer\Entities\RefreshToken;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class GeneralCleanup implements Cleanup
|
||||
{
|
||||
private const string PERIOD_AUTHORIZATION_CODE = '15 days';
|
||||
private const string PERIOD_ACCESS_TOKEN = '15 days';
|
||||
private const string PERIOD_REFRESH_TOKEN = '15 days';
|
||||
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private EntityManager $entityManager,
|
||||
private DateTime $dateTime,
|
||||
) {}
|
||||
|
||||
public function process(): void
|
||||
{
|
||||
$this->processAuthorizationCodes();
|
||||
$this->processRefreshTokens();
|
||||
$this->processAccessTokens();
|
||||
}
|
||||
|
||||
private function getAuthorizationCodePeriod(): string
|
||||
{
|
||||
return $this->config->get('oAuthServer.cleanup.authorizationCodePeriod') ?? self::PERIOD_AUTHORIZATION_CODE;
|
||||
}
|
||||
|
||||
private function getAccessTokenPeriod(): string
|
||||
{
|
||||
return $this->config->get('oAuthServer.cleanup.accessTokenPeriod') ?? self::PERIOD_ACCESS_TOKEN;
|
||||
}
|
||||
|
||||
private function getRefreshTokenPeriod(): string
|
||||
{
|
||||
return $this->config->get('oAuthServer.cleanup.refreshTokenPeriod') ?? self::PERIOD_REFRESH_TOKEN;
|
||||
}
|
||||
|
||||
private function processAuthorizationCodes(): void
|
||||
{
|
||||
$time = $this->dateTime->getNow()
|
||||
->modify('-' . $this->getAuthorizationCodePeriod());
|
||||
|
||||
$entries = $this->entityManager
|
||||
->getRDBRepositoryByClass(AuthorizationCode::class)
|
||||
->sth()
|
||||
->where([
|
||||
AuthorizationCode::FIELD_EXPIRES_AT . '<=' => $time->toString(),
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$this->entityManager->removeEntity($entry);
|
||||
}
|
||||
}
|
||||
|
||||
private function processAccessTokens(): void
|
||||
{
|
||||
$time = $this->dateTime->getNow()
|
||||
->modify('-' . $this->getAccessTokenPeriod());
|
||||
|
||||
$entries = $this->entityManager
|
||||
->getRDBRepositoryByClass(AccessToken::class)
|
||||
->sth()
|
||||
->where([
|
||||
AccessToken::FIELD_EXPIRES_AT . '<=' => $time->toString(),
|
||||
])
|
||||
->where(
|
||||
Cond::not(
|
||||
Cond::exists(
|
||||
SelectBuilder::create()
|
||||
->from(RefreshToken::ENTITY_TYPE, 'r')
|
||||
->select(Attribute::ID)
|
||||
->where([
|
||||
'r.' . RefreshToken::FIELD_STATUS => RefreshToken::STATUS_ACTIVE,
|
||||
])
|
||||
->where(
|
||||
Cond::equal(
|
||||
Expr::column(lcfirst(AccessToken::ENTITY_TYPE) . '.' . Attribute::ID),
|
||||
Expr::column('r.' . RefreshToken::FIELD_ACCESS_TOKEN . 'Id'),
|
||||
)
|
||||
)
|
||||
->build()
|
||||
)
|
||||
)
|
||||
)
|
||||
->find();
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$this->entityManager->removeEntity($entry);
|
||||
}
|
||||
}
|
||||
|
||||
private function processRefreshTokens(): void
|
||||
{
|
||||
$time = $this->dateTime->getNow()
|
||||
->modify('-' . $this->getRefreshTokenPeriod());
|
||||
|
||||
$entries = $this->entityManager
|
||||
->getRDBRepositoryByClass(RefreshToken::class)
|
||||
->sth()
|
||||
->where([
|
||||
'OR' => [
|
||||
[
|
||||
RefreshToken::FIELD_EXPIRES_AT . '<=' => $time->toString(),
|
||||
],
|
||||
[
|
||||
RefreshToken::FIELD_STATUS => [
|
||||
RefreshToken::STATUS_REVOKED,
|
||||
RefreshToken::STATUS_EXPIRED,
|
||||
],
|
||||
Field::MODIFIED_AT . '<=' => $time->toString(),
|
||||
],
|
||||
],
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$this->entityManager->removeEntity($entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
application/Espo/Tools/OAuthServer/ClientType.php
Normal file
36
application/Espo/Tools/OAuthServer/ClientType.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer;
|
||||
|
||||
enum ClientType: string
|
||||
{
|
||||
case Confidential = 'Confidential';
|
||||
case Public = 'Public';
|
||||
}
|
||||
50
application/Espo/Tools/OAuthServer/ClientValidator.php
Normal file
50
application/Espo/Tools/OAuthServer/ClientValidator.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer;
|
||||
|
||||
use Espo\Tools\OAuthServer\Entities\Client;
|
||||
use Espo\Tools\OAuthServer\Secret\SecretValidator;
|
||||
use SensitiveParameter;
|
||||
|
||||
class ClientValidator
|
||||
{
|
||||
public function __construct(
|
||||
private SecretValidator $secretValidator,
|
||||
) {}
|
||||
|
||||
public function validate(Client $client, #[SensitiveParameter] ?string $secret): bool
|
||||
{
|
||||
if (!$secret) {
|
||||
return $client->getClientType() === ClientType::Public;
|
||||
}
|
||||
|
||||
return $this->secretValidator->validate($client, $secret);
|
||||
}
|
||||
}
|
||||
48
application/Espo/Tools/OAuthServer/ConnectedApp/AppData.php
Normal file
48
application/Espo/Tools/OAuthServer/ConnectedApp/AppData.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\ConnectedApp;
|
||||
|
||||
use stdClass;
|
||||
|
||||
readonly class AppData
|
||||
{
|
||||
public function __construct(
|
||||
public string $id,
|
||||
public ?string $name,
|
||||
) {}
|
||||
|
||||
public function toApiOutput(): stdClass
|
||||
{
|
||||
return (object) [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\ConnectedApp;
|
||||
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\OAuthServer\Entities\Client;
|
||||
use Espo\Tools\OAuthServer\Repository\AccessTokenRepository;
|
||||
use Espo\Tools\OAuthServer\Repository\ClientRepository;
|
||||
use Espo\Tools\OAuthServer\Repository\RefreshTokenRepository;
|
||||
use RuntimeException;
|
||||
|
||||
class ConnectedAppService
|
||||
{
|
||||
private const int LIMIT = 50;
|
||||
|
||||
public function __construct(
|
||||
private ClientRepository $clientRepository,
|
||||
private RefreshTokenRepository $refreshTokenRepository,
|
||||
private AccessTokenRepository $accessTokenRepository,
|
||||
private EntityManager $entityManager,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return AppData[]
|
||||
*/
|
||||
public function getList(User $user): array
|
||||
{
|
||||
$clients = $this->clientRepository->findConnectedForUser($user->getId(), self::LIMIT);
|
||||
|
||||
return array_map(function (Client $it) {
|
||||
$id = $it->getIdentifier() ?? throw new RuntimeException();
|
||||
|
||||
return new AppData(
|
||||
id: $id,
|
||||
name: $it->getName() ?? $id,
|
||||
);
|
||||
}, iterator_to_array($clients));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function disconnect(User $user, string $clientId): void
|
||||
{
|
||||
$client = $this->clientRepository->getByIdentifier($clientId);
|
||||
|
||||
if (!$client) {
|
||||
throw new NotFound("Client not found.");
|
||||
}
|
||||
|
||||
$refreshTokens = $this->refreshTokenRepository->findActiveForClientIdAndUser($client->getId(), $user->getId());
|
||||
|
||||
foreach ($refreshTokens as $refreshToken) {
|
||||
$refreshToken->setRevoked();
|
||||
|
||||
$this->entityManager->saveEntity($refreshToken);
|
||||
}
|
||||
|
||||
$accessTokens = $this->accessTokenRepository->findActiveForClientIdAndUser($client->getId(), $user->getId());
|
||||
|
||||
foreach ($accessTokens as $accessToken) {
|
||||
$accessToken->setRevoked();
|
||||
|
||||
$this->entityManager->saveEntity($accessToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\ConnectedApp;
|
||||
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Entities\User;
|
||||
|
||||
class UserCheck
|
||||
{
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function assert(User $user): void
|
||||
{
|
||||
if ($user->getType() !== User::TYPE_ADMIN && !$user->isRegular()) {
|
||||
throw new Forbidden("Only regular users and admins are allowed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
165
application/Espo/Tools/OAuthServer/ConsentDataService.php
Normal file
165
application/Espo/Tools/OAuthServer/ConsentDataService.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer;
|
||||
|
||||
use Espo\Core\Acl\Scope;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Utils\Config\ApplicationConfig;
|
||||
use Espo\Core\Utils\Language;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Tools\OAuthServer\Entities\Client;
|
||||
use Espo\Tools\OAuthServer\League\AuthorizationRequestStorage;
|
||||
use Espo\Tools\OAuthServer\Repository\ClientRepository;
|
||||
use Espo\Tools\OAuthServer\Scope\ScopeSorter;
|
||||
use Espo\Tools\OAuthServer\Scope\UserAvailableScopesFilter;
|
||||
use InvalidArgumentException;
|
||||
use stdClass;
|
||||
|
||||
class ConsentDataService
|
||||
{
|
||||
public function __construct(
|
||||
private ClientRepository $clientRepository,
|
||||
private User $user,
|
||||
private Language $language,
|
||||
private ApplicationConfig $applicationConfig,
|
||||
private AuthorizationRequestStorage $authorizationRequestStorage,
|
||||
private UserAvailableScopesFilter $userAvailableScopesFilter,
|
||||
private ScopeSorter $scopeSorter,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws NotFound
|
||||
* @throws Error
|
||||
*/
|
||||
public function getData(string $clientId): stdClass
|
||||
{
|
||||
$client = $this->getClient($clientId);
|
||||
|
||||
return (object) [
|
||||
'scopeDataList' => $this->getScopeDataList($client),
|
||||
'labels' => (object) [
|
||||
'allow' => $this->language->translateLabel('allow', 'strings', Client::ENTITY_TYPE),
|
||||
'cancel' => $this->language->translateLabel('Cancel'),
|
||||
'info' => $this->composeInfoText($client),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFound
|
||||
*/
|
||||
private function getClient(string $clientId): Entities\Client
|
||||
{
|
||||
$client = $this->clientRepository->getActiveByIdentifier($clientId);
|
||||
|
||||
if (!$client) {
|
||||
throw new NotFound("Client not found.");
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
private function translateScope(string $scope): string
|
||||
{
|
||||
$appScope = $scope;
|
||||
$actionLabel = null;
|
||||
|
||||
if (str_contains($scope, ':')) {
|
||||
[$appScope, $action] = explode(':', $appScope, 2);
|
||||
|
||||
$actionLabel = $this->language->translateOption($action, 'levelList', 'Role');
|
||||
}
|
||||
|
||||
$scopeLabel = $this->language->translateLabel($appScope, 'scopeNames');
|
||||
|
||||
$label = $scopeLabel;
|
||||
|
||||
if ($actionLabel) {
|
||||
$label .= ' . ' . $actionLabel;
|
||||
}
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
private function composeInfoText(Client $client): string
|
||||
{
|
||||
return strtr($this->language->translateLabel('allowAccessInfo', 'messages', Client::ENTITY_TYPE), [
|
||||
'{clientName}' => $client->getName(),
|
||||
'{applicationName}' => $this->applicationConfig->getApplicationName(),
|
||||
'{username}' => $this->user->getUserName(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return stdClass[]
|
||||
* @throws Error
|
||||
*/
|
||||
private function getScopeDataList(Client $client): array
|
||||
{
|
||||
$clientId = $client->getIdentifier() ?? throw new InvalidArgumentException("No client identifier.");
|
||||
|
||||
$request = $this->authorizationRequestStorage->get($clientId);
|
||||
|
||||
if (!$request) {
|
||||
throw new Error("No session data.");
|
||||
}
|
||||
|
||||
$scopes = array_map(fn ($it) => $it->getIdentifier(), $request->getScopes());
|
||||
|
||||
$scopes = $this->userAvailableScopesFilter->filter($this->user->getId(), $scopes);
|
||||
|
||||
/** @var (stdClass & object{name: string, label: string})[] $scopeDataList */
|
||||
$scopeDataList = array_map(function ($scope) {
|
||||
return (object) [
|
||||
'name' => $scope,
|
||||
'label' => $this->translateScope($scope),
|
||||
'description' => $this->getScopeDescription($scope),
|
||||
];
|
||||
}, $scopes);
|
||||
|
||||
return $this->scopeSorter->sortDataItems($scopeDataList);
|
||||
}
|
||||
|
||||
private function getScopeDescription(string $scope): ?string
|
||||
{
|
||||
if (!in_array($scope, [Scope::ADMIN, Scope::GLOBAL])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$key = $scope;
|
||||
|
||||
if ($scope === Scope::GLOBAL && $this->user->isAdmin()) {
|
||||
$key = 'GlobalAdmin';
|
||||
}
|
||||
|
||||
return $this->language->translateLabel($key, 'scopeDescriptions', Client::ENTITY_TYPE);
|
||||
}
|
||||
}
|
||||
163
application/Espo/Tools/OAuthServer/Entities/AccessToken.php
Normal file
163
application/Espo/Tools/OAuthServer/Entities/AccessToken.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Entities;
|
||||
|
||||
use Espo\Core\Field\DateTime;
|
||||
use Espo\Core\Field\Link;
|
||||
use Espo\Core\ORM\Entity;
|
||||
use Espo\Entities\User;
|
||||
use SensitiveParameter;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class AccessToken extends Entity
|
||||
{
|
||||
public const string ENTITY_TYPE = 'OAuthAccessToken';
|
||||
|
||||
public const string FIELD_STATUS = 'status';
|
||||
public const string FIELD_CLIENT = 'client';
|
||||
public const string FIELD_IDENTIFIER = 'identifier';
|
||||
public const string FIELD_EXPIRES_AT = 'expiresAt';
|
||||
public const string FIELD_SCOPES = 'scopes';
|
||||
public const string FIELD_USER = 'user';
|
||||
public const string FIELD_HASH = 'hash';
|
||||
|
||||
public const string STATUS_ACTIVE = 'Active';
|
||||
public const string STATUS_REVOKED = 'Revoked';
|
||||
public const string STATUS_EXPIRED = 'Expired';
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->get(self::FIELD_STATUS) === self::STATUS_ACTIVE;
|
||||
}
|
||||
|
||||
public function isRevoked(): bool
|
||||
{
|
||||
return $this->get(self::FIELD_STATUS) === self::STATUS_REVOKED;
|
||||
}
|
||||
|
||||
public function getClient(): Client
|
||||
{
|
||||
$client = $this->relations->getOne(self::FIELD_CLIENT);
|
||||
|
||||
if (!$client instanceof Client) {
|
||||
throw new UnexpectedValueException("No client.");
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
public function setClient(Client|Link $client): self
|
||||
{
|
||||
return $this->setRelatedLinkOrEntity(self::FIELD_CLIENT, $client);
|
||||
}
|
||||
|
||||
public function setRevoked(): self
|
||||
{
|
||||
return $this->set(self::FIELD_STATUS, self::STATUS_REVOKED);
|
||||
}
|
||||
|
||||
public function setExpired(): self
|
||||
{
|
||||
return $this->set(self::FIELD_STATUS, self::STATUS_EXPIRED);
|
||||
}
|
||||
|
||||
public function setHash(string $hash): self
|
||||
{
|
||||
return $this->set(self::FIELD_HASH, $hash);
|
||||
}
|
||||
|
||||
public function setIdentifier(#[SensitiveParameter] string $identifier): self
|
||||
{
|
||||
return $this->set(self::FIELD_IDENTIFIER, $identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
/** @var non-empty-string */
|
||||
return $this->get(self::FIELD_IDENTIFIER) ?? throw new UnexpectedValueException("No identifier.");
|
||||
}
|
||||
|
||||
public function getExpiresAt(): DateTime
|
||||
{
|
||||
$value = $this->getValueObject(self::FIELD_EXPIRES_AT);
|
||||
|
||||
if (!$value instanceof DateTime) {
|
||||
throw new UnexpectedValueException("No expiresAt.");
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function setExpiresAt(DateTime $expiresAt): self
|
||||
{
|
||||
return $this->setValueObject(self::FIELD_EXPIRES_AT, $expiresAt);
|
||||
}
|
||||
|
||||
public function isExpired(): bool
|
||||
{
|
||||
return $this->get(self::FIELD_STATUS) === self::STATUS_EXPIRED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string[]
|
||||
*/
|
||||
public function getScopes(): array
|
||||
{
|
||||
/** @var non-empty-string[] */
|
||||
return $this->get(self::FIELD_SCOPES) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $scopes
|
||||
*/
|
||||
public function setScopes(array $scopes): self
|
||||
{
|
||||
return $this->set(self::FIELD_SCOPES, $scopes);
|
||||
}
|
||||
|
||||
public function getUser(): Link
|
||||
{
|
||||
$user = $this->getValueObject(self::FIELD_USER);
|
||||
|
||||
if (!$user instanceof Link) {
|
||||
throw new UnexpectedValueException("No user.");
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function setUser(Link|User $user): self
|
||||
{
|
||||
return $this->setRelatedLinkOrEntity(self::FIELD_USER, $user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Entities;
|
||||
|
||||
use Espo\Core\Field\DateTime;
|
||||
use Espo\Core\Field\Link;
|
||||
use Espo\Core\ORM\Entity;
|
||||
use Espo\Entities\User;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class AuthorizationCode extends Entity
|
||||
{
|
||||
public const string ENTITY_TYPE = 'OAuthAuthorizationCode';
|
||||
|
||||
public const string FIELD_STATUS = 'status';
|
||||
public const string FILED_CLIENT = 'client';
|
||||
public const string FIELD_HASH = 'hash';
|
||||
public const string FIELD_EXPIRES_AT = 'expiresAt';
|
||||
public const string FIELD_REDIRECT_URI = 'redirectUri';
|
||||
public const string FIELD_SCOPES = 'scopes';
|
||||
public const string FIELD_USER = 'user';
|
||||
|
||||
public const string STATUS_ACTIVE = 'Active';
|
||||
public const string STATUS_REVOKED = 'Revoked';
|
||||
public const string STATUS_EXPIRED = 'Expired';
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->get(self::FIELD_STATUS) === self::STATUS_ACTIVE;
|
||||
}
|
||||
|
||||
public function isRevoked(): bool
|
||||
{
|
||||
return $this->get(self::FIELD_STATUS) === self::STATUS_REVOKED;
|
||||
}
|
||||
|
||||
public function getClient(): Client
|
||||
{
|
||||
$client = $this->relations->getOne(self::FILED_CLIENT);
|
||||
|
||||
if (!$client instanceof Client) {
|
||||
throw new UnexpectedValueException("No client.");
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
public function setClient(Client|Link $client): self
|
||||
{
|
||||
return $this->setRelatedLinkOrEntity(self::FILED_CLIENT, $client);
|
||||
}
|
||||
|
||||
public function setRevoked(): self
|
||||
{
|
||||
return $this->set(self::FIELD_STATUS, self::STATUS_REVOKED);
|
||||
}
|
||||
|
||||
public function setExpired(): self
|
||||
{
|
||||
return $this->set(self::FIELD_STATUS, self::STATUS_EXPIRED);
|
||||
}
|
||||
|
||||
public function setHash(string $hash): self
|
||||
{
|
||||
return $this->set(self::FIELD_HASH, $hash);
|
||||
}
|
||||
|
||||
public function getExpiresAt(): DateTime
|
||||
{
|
||||
$value = $this->getValueObject(self::FIELD_EXPIRES_AT);
|
||||
|
||||
if (!$value instanceof DateTime) {
|
||||
throw new UnexpectedValueException("No expiresAt.");
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function setExpiresAt(DateTime $expiresAt): self
|
||||
{
|
||||
return $this->setValueObject(self::FIELD_EXPIRES_AT, $expiresAt);
|
||||
}
|
||||
|
||||
public function getRedirectUri(): string
|
||||
{
|
||||
return $this->get(self::FIELD_REDIRECT_URI) ?? throw new UnexpectedValueException("No redirectUri.");
|
||||
}
|
||||
|
||||
public function setRedirectUri(string $redirectUri): self
|
||||
{
|
||||
return $this->set(self::FIELD_REDIRECT_URI, $redirectUri);
|
||||
}
|
||||
|
||||
public function isExpired(): bool
|
||||
{
|
||||
return $this->get(self::FIELD_STATUS) === self::STATUS_EXPIRED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getScopes(): array
|
||||
{
|
||||
return $this->get(self::FIELD_SCOPES);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $scopes
|
||||
*/
|
||||
public function setScopes(array $scopes): self
|
||||
{
|
||||
return $this->set(self::FIELD_SCOPES, $scopes);
|
||||
}
|
||||
|
||||
public function getUser(): Link
|
||||
{
|
||||
$user = $this->getValueObject(self::FIELD_USER);
|
||||
|
||||
if (!$user instanceof Link) {
|
||||
throw new UnexpectedValueException("No user.");
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function setUser(Link|User $user): self
|
||||
{
|
||||
return $this->setRelatedLinkOrEntity(self::FIELD_USER, $user);
|
||||
}
|
||||
}
|
||||
142
application/Espo/Tools/OAuthServer/Entities/Client.php
Normal file
142
application/Espo/Tools/OAuthServer/Entities/Client.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Entities;
|
||||
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\ORM\Entity;
|
||||
use Espo\Tools\OAuthServer\ClientType;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class Client extends Entity
|
||||
{
|
||||
public const string ENTITY_TYPE = 'OAuthClient';
|
||||
|
||||
public const string FIELD_STATUS = 'status';
|
||||
public const string FIELD_IDENTIFIER = 'identifier';
|
||||
public const string FIELD_CLIENT_TYPE = 'clientType';
|
||||
public const string FIELD_SCOPES = 'scopes';
|
||||
public const string FIELD_REDIRECT_URIS = 'redirectUris';
|
||||
|
||||
public const string LINK_SECRETS = 'secrets';
|
||||
|
||||
public const string STATUS_ACTIVE = 'Active';
|
||||
public const string STATUS_INACTIVE = 'Inactive';
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->get(Field::NAME);
|
||||
}
|
||||
|
||||
public function setName(string $name): self
|
||||
{
|
||||
return $this->set(Field::NAME, $name);
|
||||
}
|
||||
|
||||
public function getClientType(): ClientType
|
||||
{
|
||||
$raw = $this->get(self::FIELD_CLIENT_TYPE) ?? throw new UnexpectedValueException("No client type.");
|
||||
|
||||
return ClientType::tryFrom($raw) ?? throw new UnexpectedValueException("Bad client type.");
|
||||
}
|
||||
|
||||
public function setClientType(ClientType $clientType): self
|
||||
{
|
||||
return $this->set(self::FIELD_CLIENT_TYPE, $clientType->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?non-empty-string
|
||||
*/
|
||||
public function getIdentifier(): ?string
|
||||
{
|
||||
/** @var ?non-empty-string */
|
||||
return $this->get(self::FIELD_IDENTIFIER);
|
||||
}
|
||||
|
||||
public function setIdentifier(?string $clientId): self
|
||||
{
|
||||
return $this->set(self::FIELD_IDENTIFIER, $clientId);
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->get(self::FIELD_STATUS) === self::STATUS_ACTIVE;
|
||||
}
|
||||
|
||||
public function setActive(): self
|
||||
{
|
||||
return $this->set(self::FIELD_STATUS, self::STATUS_ACTIVE);
|
||||
}
|
||||
|
||||
public function setInactive(): self
|
||||
{
|
||||
return $this->set(self::FIELD_STATUS, self::STATUS_INACTIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string[]
|
||||
*/
|
||||
public function getScopes(): array
|
||||
{
|
||||
$scopes = $this->get(self::FIELD_SCOPES) ?? [];
|
||||
|
||||
foreach ($scopes as $scope) {
|
||||
if (!$scope) {
|
||||
throw new UnexpectedValueException("Empty scope.");
|
||||
}
|
||||
}
|
||||
|
||||
return $scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $scopes
|
||||
*/
|
||||
public function setScopes(array $scopes): self
|
||||
{
|
||||
return $this->set(self::FIELD_SCOPES, $scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getRedirectUris(): array
|
||||
{
|
||||
return $this->get(self::FIELD_REDIRECT_URIS) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $redirectUris
|
||||
*/
|
||||
public function setRedirectUris(array $redirectUris): self
|
||||
{
|
||||
return $this->set(self::FIELD_REDIRECT_URIS, $redirectUris);
|
||||
}
|
||||
}
|
||||
135
application/Espo/Tools/OAuthServer/Entities/ClientSecret.php
Normal file
135
application/Espo/Tools/OAuthServer/Entities/ClientSecret.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Entities;
|
||||
|
||||
use Espo\Core\Field\Date;
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\ORM\Entity;
|
||||
use SensitiveParameter;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class ClientSecret extends Entity
|
||||
{
|
||||
public const string ENTITY_TYPE = 'OAuthClientSecret';
|
||||
|
||||
public const string FIELD_STATUS = 'status';
|
||||
public const string FIELD_HASH = 'hash';
|
||||
public const string FIELD_CLIENT = 'client';
|
||||
public const string FIELD_EXPIRATION_DATE = 'expirationDate';
|
||||
public const string FIELD_VALUE = 'value';
|
||||
|
||||
public const string STATUS_ACTIVE = 'Active';
|
||||
public const string STATUS_REVOKED = 'Revoked';
|
||||
public const string STATUS_EXPIRED = 'Expired';
|
||||
|
||||
public const string ATTR_CLIENT_ID = 'clientId';
|
||||
|
||||
public function setName(string $name): self
|
||||
{
|
||||
return $this->set(Field::NAME, $name);
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->get(Field::NAME);
|
||||
}
|
||||
|
||||
public function getHash(): string
|
||||
{
|
||||
return $this->get(self::FIELD_HASH) ?? throw new UnexpectedValueException("No hash.");
|
||||
}
|
||||
|
||||
public function setHash(string $value): self
|
||||
{
|
||||
return $this->set(self::FIELD_HASH, $value);
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->get(self::FIELD_STATUS) === self::STATUS_ACTIVE;
|
||||
}
|
||||
|
||||
public function isExpired(): bool
|
||||
{
|
||||
return $this->get(self::FIELD_STATUS) === self::STATUS_EXPIRED;
|
||||
}
|
||||
|
||||
public function isRevoked(): bool
|
||||
{
|
||||
return $this->get(self::FIELD_STATUS) === self::STATUS_REVOKED;
|
||||
}
|
||||
|
||||
public function setRevoked(): self
|
||||
{
|
||||
return $this->set(self::FIELD_STATUS, self::STATUS_REVOKED);
|
||||
}
|
||||
|
||||
public function setExpired(): self
|
||||
{
|
||||
return $this->set(self::FIELD_STATUS, self::STATUS_EXPIRED);
|
||||
}
|
||||
|
||||
public function getExpirationDate(): ?Date
|
||||
{
|
||||
/** @var ?Date */
|
||||
return $this->getValueObject(self::FIELD_EXPIRATION_DATE);
|
||||
}
|
||||
|
||||
public function setExpirationDate(?Date $date): self
|
||||
{
|
||||
return $this->setValueObject(self::FIELD_EXPIRATION_DATE, $date);
|
||||
}
|
||||
|
||||
public function getClient(): Client
|
||||
{
|
||||
$client = $this->relations->getOne(self::FIELD_CLIENT);
|
||||
|
||||
if (!$client instanceof Client) {
|
||||
throw new UnexpectedValueException("No client.");
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
public function setClient(Client $client): self
|
||||
{
|
||||
return $this->setRelatedLinkOrEntity(self::FIELD_CLIENT, $client);
|
||||
}
|
||||
|
||||
public function setValue(#[SensitiveParameter] string $value): self
|
||||
{
|
||||
return $this->set(self::FIELD_VALUE, $value);
|
||||
}
|
||||
|
||||
public function getValue(): string
|
||||
{
|
||||
return $this->get(self::FIELD_VALUE) ?? throw new UnexpectedValueException("No value.");
|
||||
}
|
||||
}
|
||||
146
application/Espo/Tools/OAuthServer/Entities/RefreshToken.php
Normal file
146
application/Espo/Tools/OAuthServer/Entities/RefreshToken.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Entities;
|
||||
|
||||
use Espo\Core\Field\DateTime;
|
||||
use Espo\Core\Field\Link;
|
||||
use Espo\Core\ORM\Entity;
|
||||
use Espo\Entities\User;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class RefreshToken extends Entity
|
||||
{
|
||||
public const string ENTITY_TYPE = 'OAuthRefreshToken';
|
||||
|
||||
public const string FIELD_STATUS = 'status';
|
||||
public const string FIELD_CLIENT = 'client';
|
||||
public const string FIELD_EXPIRES_AT = 'expiresAt';
|
||||
public const string FIELD_USER = 'user';
|
||||
public const string FIELD_ACCESS_TOKEN = 'accessToken';
|
||||
public const string FIELD_HASH = 'hash';
|
||||
|
||||
public const string STATUS_ACTIVE = 'Active';
|
||||
public const string STATUS_REVOKED = 'Revoked';
|
||||
public const string STATUS_EXPIRED = 'Expired';
|
||||
|
||||
public function setHash(string $value): self
|
||||
{
|
||||
return $this->set(self::FIELD_HASH, $value);
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->get(self::FIELD_STATUS) === self::STATUS_ACTIVE;
|
||||
}
|
||||
|
||||
public function isRevoked(): bool
|
||||
{
|
||||
return $this->get(self::FIELD_STATUS) === self::STATUS_REVOKED;
|
||||
}
|
||||
|
||||
public function setStatus(string $status): self
|
||||
{
|
||||
return $this->set(self::FIELD_STATUS, $status);
|
||||
}
|
||||
|
||||
public function getClient(): Client
|
||||
{
|
||||
$client = $this->relations->getOne(self::FIELD_CLIENT);
|
||||
|
||||
if (!$client instanceof Client) {
|
||||
throw new UnexpectedValueException("No client.");
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
public function setAccessToken(AccessToken $accessToken): self
|
||||
{
|
||||
return $this->setRelatedLinkOrEntity(self::FIELD_ACCESS_TOKEN, $accessToken);
|
||||
}
|
||||
|
||||
public function setClient(Client $client): self
|
||||
{
|
||||
return $this->setRelatedLinkOrEntity(self::FIELD_CLIENT, $client);
|
||||
}
|
||||
|
||||
public function isExpired(): bool
|
||||
{
|
||||
return $this->get(self::FIELD_STATUS) === self::STATUS_EXPIRED;
|
||||
}
|
||||
|
||||
public function setRevoked(): self
|
||||
{
|
||||
return $this->set(self::FIELD_STATUS, self::STATUS_REVOKED);
|
||||
}
|
||||
|
||||
public function setExpired(): self
|
||||
{
|
||||
return $this->set(self::FIELD_STATUS, self::STATUS_EXPIRED);
|
||||
}
|
||||
|
||||
public function getExpiresAt(): DateTime
|
||||
{
|
||||
$value = $this->getValueObject(self::FIELD_EXPIRES_AT);
|
||||
|
||||
if (!$value instanceof DateTime) {
|
||||
throw new UnexpectedValueException("No expiresAt.");
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function setExpiresAt(DateTime $expiresAt): self
|
||||
{
|
||||
return $this->setValueObject(self::FIELD_EXPIRES_AT, $expiresAt);
|
||||
}
|
||||
|
||||
public function getUser(): Link
|
||||
{
|
||||
$user = $this->getValueObject(self::FIELD_USER);
|
||||
|
||||
if (!$user instanceof Link) {
|
||||
throw new UnexpectedValueException("No user.");
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function setUser(Link|User $user): self
|
||||
{
|
||||
return $this->setRelatedLinkOrEntity(self::FIELD_USER, $user);
|
||||
}
|
||||
|
||||
public function getAccessToken(): ?AccessToken
|
||||
{
|
||||
/** @var ?AccessToken */
|
||||
return $this->relations->getOne(self::FIELD_ACCESS_TOKEN);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\EntryPoints;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\EntryPoint\EntryPoint;
|
||||
use Espo\Core\Utils\Json;
|
||||
use Espo\Tools\OAuthServer\Metadata\AuthorizationServerMetadataProvider;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class AuthorizationServerMetadata implements EntryPoint
|
||||
{
|
||||
public function __construct(
|
||||
private AuthorizationServerMetadataProvider $provider,
|
||||
) {}
|
||||
|
||||
public function run(Request $request, Response $response): void
|
||||
{
|
||||
$data = $this->provider->get();
|
||||
|
||||
$response->writeBody(Json::encode($data, JSON_PRETTY_PRINT));
|
||||
}
|
||||
}
|
||||
74
application/Espo/Tools/OAuthServer/EntryPoints/Authorize.php
Normal file
74
application/Espo/Tools/OAuthServer/EntryPoints/Authorize.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\EntryPoints;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\EntryPoint\EntryPoint;
|
||||
use Espo\Core\Utils\Client\ActionRenderer;
|
||||
use Espo\Core\Utils\Client\SecurityParams;
|
||||
use Espo\Tools\OAuthServer\AuthorizationService;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class Authorize implements EntryPoint
|
||||
{
|
||||
public function __construct(
|
||||
private ActionRenderer $actionRenderer,
|
||||
private AuthorizationService $service,
|
||||
) {}
|
||||
|
||||
public function run(Request $request, Response $response): void
|
||||
{
|
||||
$psr7Response = $this->service->authorizeStart($request->toPsr7(), $response->toPsr7());
|
||||
|
||||
if ($psr7Response) {
|
||||
$response->applyPsr7($psr7Response);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$params = new ActionRenderer\Params(
|
||||
controller: 'controllers/o-auth-authorize',
|
||||
action: 'show',
|
||||
);
|
||||
|
||||
$params = $params
|
||||
->withLogin()
|
||||
->withSecurityParams(
|
||||
new SecurityParams(
|
||||
formAction: [],
|
||||
)
|
||||
);
|
||||
|
||||
$this->actionRenderer->write($response, $params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\EntryPoints;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\EntryPoint\EntryPoint;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Tools\OAuthServer\AuthorizationService;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class AuthorizeComplete implements EntryPoint
|
||||
{
|
||||
public function __construct(
|
||||
private AuthorizationService $service,
|
||||
) {}
|
||||
|
||||
public function run(Request $request, Response $response): void
|
||||
{
|
||||
$body = $request->getParsedBody();
|
||||
|
||||
$clientId = $body->clientId ?? throw new BadRequest("No 'clientId'.");
|
||||
$approved = ($body->approved ?? null) === 'true';
|
||||
|
||||
if (!is_string($clientId)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$scopes = $this->fetchScopes($request);
|
||||
|
||||
$psr7Response = $this->service->authorizeComplete(
|
||||
clientId: $clientId,
|
||||
response: $response->toPsr7(),
|
||||
approved: $approved,
|
||||
scopes: $scopes,
|
||||
);
|
||||
|
||||
$response->applyPsr7($psr7Response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string[]
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function fetchScopes(Request $request): array
|
||||
{
|
||||
$scopesRaw = $request->getParsedBody()->scopes ?? throw new BadRequest("No 'scopes'.");
|
||||
|
||||
$scopes = explode(' ', $scopesRaw);
|
||||
|
||||
foreach ($scopes as $scope) {
|
||||
if (!$scope) {
|
||||
throw new BadRequest("Bad 'scopes' item values.");
|
||||
}
|
||||
|
||||
if ($scope !== trim($scope)) {
|
||||
throw new BadRequest("Bad scope.");
|
||||
}
|
||||
}
|
||||
|
||||
/** @var non-empty-string[] */
|
||||
return $scopes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\EntryPoints;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\EntryPoint\EntryPoint;
|
||||
use Espo\Core\Utils\Json;
|
||||
use Espo\Tools\OAuthServer\Metadata\ProtectedResourceMetadataProvider;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class ProtectedResourceMetadata implements EntryPoint
|
||||
{
|
||||
public function __construct(
|
||||
private ProtectedResourceMetadataProvider $provider,
|
||||
) {}
|
||||
|
||||
public function run(Request $request, Response $response): void
|
||||
{
|
||||
$data = $this->provider->get();
|
||||
|
||||
$response->writeBody(Json::encode($data, JSON_PRETTY_PRINT));
|
||||
}
|
||||
}
|
||||
49
application/Espo/Tools/OAuthServer/EntryPoints/Token.php
Normal file
49
application/Espo/Tools/OAuthServer/EntryPoints/Token.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\EntryPoints;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\EntryPoint\EntryPoint;
|
||||
use Espo\Tools\OAuthServer\AuthorizationService;
|
||||
|
||||
class Token implements EntryPoint
|
||||
{
|
||||
public function __construct(
|
||||
private AuthorizationService $service,
|
||||
) {}
|
||||
|
||||
public function run(Request $request, Response $response): void
|
||||
{
|
||||
$psr7Response = $this->service->token($request->toPsr7(), $response->toPsr7());
|
||||
|
||||
$response->applyPsr7($psr7Response);
|
||||
}
|
||||
}
|
||||
173
application/Espo/Tools/OAuthServer/EntryPoints/TokenRevoke.php
Normal file
173
application/Espo/Tools/OAuthServer/EntryPoints/TokenRevoke.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\EntryPoints;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\EntryPoint\EntryPoint;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Unauthorized;
|
||||
use Espo\Tools\OAuthServer\Revoke\Data;
|
||||
use Espo\Tools\OAuthServer\Revoke\TokenRevokeService;
|
||||
use Espo\Tools\OAuthServer\Utils\ErrorResponseComposer;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class TokenRevoke implements EntryPoint
|
||||
{
|
||||
private const string HEADER_AUTHORIZATION = 'Authorization';
|
||||
|
||||
public function __construct(
|
||||
private TokenRevokeService $service,
|
||||
) {}
|
||||
|
||||
public function run(Request $request, Response $response): void
|
||||
{
|
||||
$token = $request->getParsedBody()->token ?? throw new BadRequest("No 'token'.");
|
||||
$tokenTypeHint = $request->getParsedBody()->token_type_hint ?? null;
|
||||
|
||||
[$clientId, $clientSecret] = $this->obtainAuthorizationFromHeader($request);
|
||||
|
||||
$clientId ??= $request->getParsedBody()->client_id ?? throw new BadRequest("No client ID.");
|
||||
$clientSecret ??= $request->getParsedBody()->client_secret ?? null;
|
||||
|
||||
if (!is_string($clientId)) {
|
||||
throw new BadRequest("Bad 'client_id' value");
|
||||
}
|
||||
|
||||
if ($clientSecret !== null && !is_string($clientSecret)) {
|
||||
throw new BadRequest("Bad 'client_secret' value");
|
||||
}
|
||||
|
||||
if (!is_string($token)) {
|
||||
throw new BadRequest("Bad 'token' value.");
|
||||
}
|
||||
|
||||
if ($tokenTypeHint !== null && !is_string($tokenTypeHint)) {
|
||||
throw new BadRequest("Bad 'token_type_hint' value.");
|
||||
}
|
||||
|
||||
if (!$this->isTokenTypeValid($tokenTypeHint)) {
|
||||
$response->applyPsr7(
|
||||
ErrorResponseComposer::compose(
|
||||
error: 'unsupported_token_type',
|
||||
errorDescription: 'Token type is not supported.',
|
||||
statusCode: 400,
|
||||
)->toPsr7()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$data = new Data(
|
||||
clientId: $clientId,
|
||||
clientSecret: $clientSecret,
|
||||
token: $token,
|
||||
tokenTypeHint: $tokenTypeHint,
|
||||
);
|
||||
|
||||
try {
|
||||
$this->service->revoke($data);
|
||||
} catch (Unauthorized) {
|
||||
$response->applyPsr7(
|
||||
ErrorResponseComposer::compose(
|
||||
error: 'invalid_client',
|
||||
errorDescription: 'Invalid client credentials.',
|
||||
statusCode: 401,
|
||||
)->toPsr7()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function isTokenTypeValid(?string $tokenTypeHint): bool
|
||||
{
|
||||
return
|
||||
$tokenTypeHint === null ||
|
||||
in_array($tokenTypeHint, [
|
||||
TokenRevokeService::TYPE_ACCESS_TOKEN,
|
||||
TokenRevokeService::TYPE_REFRESH_TOKEN,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{?string, ?string}
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function obtainAuthorizationFromHeader(Request $request): array
|
||||
{
|
||||
$headerValue = $request->getHeader(self::HEADER_AUTHORIZATION);
|
||||
|
||||
if (!$headerValue) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
$prefix = 'Basic ';
|
||||
|
||||
if (!str_starts_with($headerValue, $prefix)) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
$string = substr($headerValue, strlen($prefix));
|
||||
|
||||
[$clientId, $clientSecret] = self::decodeAuthorizationString($string);
|
||||
|
||||
if (!$clientId) {
|
||||
$clientId = null;
|
||||
}
|
||||
|
||||
if (!$clientSecret) {
|
||||
$clientSecret = null;
|
||||
}
|
||||
|
||||
return [$clientId, $clientSecret];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{string, string}
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function decodeAuthorizationString(string $string): array
|
||||
{
|
||||
/** @var string $stringDecoded */
|
||||
$stringDecoded = base64_decode($string);
|
||||
|
||||
if (!str_contains($stringDecoded, ':')) {
|
||||
throw new BadRequest("Bad authorization string.");
|
||||
}
|
||||
|
||||
[$username, $password] = explode(':', $stringDecoded, 2);
|
||||
|
||||
$username = trim($username);
|
||||
$password = trim($password);
|
||||
|
||||
return [$username, $password];
|
||||
}
|
||||
}
|
||||
115
application/Espo/Tools/OAuthServer/ExpireService.php
Normal file
115
application/Espo/Tools/OAuthServer/ExpireService.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer;
|
||||
|
||||
use Espo\Core\Utils\DateTime;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Query\UpdateBuilder;
|
||||
use Espo\Tools\OAuthServer\Entities\AccessToken;
|
||||
use Espo\Tools\OAuthServer\Entities\AuthorizationCode;
|
||||
use Espo\Tools\OAuthServer\Entities\ClientSecret;
|
||||
use Espo\Tools\OAuthServer\Entities\RefreshToken;
|
||||
|
||||
/**
|
||||
* Sets status Expired. Note that even Active status with the expiration date in past
|
||||
* renders the token/secret inactive. Expired status serves an information purpose.
|
||||
*/
|
||||
class ExpireService
|
||||
{
|
||||
public function __construct(
|
||||
private DateTime $dateTime,
|
||||
private EntityManager $entityManager,
|
||||
) {}
|
||||
|
||||
public function expireClientSecrets(): void
|
||||
{
|
||||
$secrets = $this->entityManager
|
||||
->getRDBRepositoryByClass(ClientSecret::class)
|
||||
->where([
|
||||
ClientSecret::FIELD_STATUS => ClientSecret::STATUS_ACTIVE,
|
||||
ClientSecret::FIELD_EXPIRATION_DATE . '<=' => $this->dateTime->getToday()->toString(),
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($secrets as $secret) {
|
||||
$secret->setExpired();
|
||||
|
||||
$this->entityManager->saveEntity($secret);
|
||||
}
|
||||
}
|
||||
|
||||
public function expireAccessTokens(): void
|
||||
{
|
||||
$query = UpdateBuilder::create()
|
||||
->in(AccessToken::ENTITY_TYPE)
|
||||
->set([
|
||||
AccessToken::FIELD_STATUS => AccessToken::STATUS_EXPIRED,
|
||||
])
|
||||
->where([
|
||||
AccessToken::FIELD_STATUS => AccessToken::STATUS_ACTIVE,
|
||||
AccessToken::FIELD_EXPIRES_AT . '<=' => $this->dateTime->getNow()->toString(),
|
||||
])
|
||||
->build();
|
||||
|
||||
$this->entityManager->getQueryExecutor()->execute($query);
|
||||
}
|
||||
|
||||
public function expireRefreshTokens(): void
|
||||
{
|
||||
$query = UpdateBuilder::create()
|
||||
->in(RefreshToken::ENTITY_TYPE)
|
||||
->set([
|
||||
RefreshToken::FIELD_STATUS => RefreshToken::STATUS_EXPIRED,
|
||||
])
|
||||
->where([
|
||||
RefreshToken::FIELD_STATUS => RefreshToken::STATUS_ACTIVE,
|
||||
RefreshToken::FIELD_EXPIRES_AT . '<=' => $this->dateTime->getNow()->toString(),
|
||||
])
|
||||
->build();
|
||||
|
||||
$this->entityManager->getQueryExecutor()->execute($query);
|
||||
}
|
||||
|
||||
public function expireAuthorizationCodes(): void
|
||||
{
|
||||
$query = UpdateBuilder::create()
|
||||
->in(AuthorizationCode::ENTITY_TYPE)
|
||||
->set([
|
||||
AuthorizationCode::FIELD_STATUS => AuthorizationCode::STATUS_EXPIRED,
|
||||
])
|
||||
->where([
|
||||
AuthorizationCode::FIELD_STATUS => AuthorizationCode::STATUS_ACTIVE,
|
||||
AuthorizationCode::FIELD_EXPIRES_AT . '<=' => $this->dateTime->getNow()->toString(),
|
||||
])
|
||||
->build();
|
||||
|
||||
$this->entityManager->getQueryExecutor()->execute($query);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Jobs;
|
||||
|
||||
use Espo\Core\Job\JobDataLess;
|
||||
use Espo\Tools\OAuthServer\ExpireService;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class OAuthServerControl implements JobDataLess
|
||||
{
|
||||
public function __construct(
|
||||
private ExpireService $clientSecretExpireService,
|
||||
) {}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$this->clientSecretExpireService->expireClientSecrets();
|
||||
$this->clientSecretExpireService->expireAccessTokens();
|
||||
$this->clientSecretExpireService->expireRefreshTokens();
|
||||
$this->clientSecretExpireService->expireAuthorizationCodes();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\League;
|
||||
|
||||
use Espo\Core\Session\Session;
|
||||
use Espo\Tools\OAuthServer\League\Entities\AuthCodeEntity;
|
||||
use Espo\Tools\OAuthServer\League\Entities\ClientEntity;
|
||||
use Espo\Tools\OAuthServer\League\Entities\ScopeEntity;
|
||||
use InvalidArgumentException;
|
||||
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
|
||||
use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface;
|
||||
use RuntimeException;
|
||||
|
||||
class AuthorizationRequestStorage
|
||||
{
|
||||
public function __construct(
|
||||
private Session $session,
|
||||
) {}
|
||||
|
||||
public function store(string $clientId, AuthorizationRequestInterface $request): void
|
||||
{
|
||||
/** @noinspection PhpConditionAlreadyCheckedInspection */
|
||||
if (!$request instanceof AuthorizationRequest) {
|
||||
throw new InvalidArgumentException("Unsupported authorization request instance.");
|
||||
}
|
||||
|
||||
$this->session->set(self::composeSessionKey($clientId), serialize($request));
|
||||
}
|
||||
|
||||
public function get(string $clientId, bool $clear = false): ?AuthorizationRequest
|
||||
{
|
||||
$key = self::composeSessionKey($clientId);
|
||||
|
||||
$raw = $this->session->get($key);
|
||||
|
||||
if (!$raw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($clear) {
|
||||
$this->session->clear($key);
|
||||
}
|
||||
|
||||
$request = unserialize($raw, [
|
||||
'allowed_classes' => [
|
||||
AuthorizationRequest::class,
|
||||
ClientEntity::class,
|
||||
ScopeEntity::class,
|
||||
AuthCodeEntity::class,
|
||||
]
|
||||
]);
|
||||
|
||||
if (!$request instanceof AuthorizationRequest) {
|
||||
throw new RuntimeException("Unserialization error.");
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
public static function composeSessionKey(string $clientId): string
|
||||
{
|
||||
return "oAuthServerAuthorizeRequest_" . $clientId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\League;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Tools\OAuthServer\League\Repositories\AccessTokenRepository;
|
||||
use Espo\Tools\OAuthServer\League\Repositories\AuthCodeRepository;
|
||||
use Espo\Tools\OAuthServer\League\Repositories\ClientRepository;
|
||||
use Espo\Tools\OAuthServer\League\Repositories\RefreshTokenRepository;
|
||||
use Espo\Tools\OAuthServer\League\Repositories\ScopeRepository;
|
||||
use Espo\Tools\OAuthServer\Utils\CryptKeyProvider;
|
||||
use Exception;
|
||||
|
||||
use League\OAuth2\Server\AuthorizationServer;
|
||||
use League\OAuth2\Server\CryptKey;
|
||||
use League\OAuth2\Server\Grant\AuthCodeGrant;
|
||||
use League\OAuth2\Server\Grant\RefreshTokenGrant;
|
||||
|
||||
class AuthorizationServerFactory
|
||||
{
|
||||
public function __construct(
|
||||
private ClientRepository $clientRepository,
|
||||
private AccessTokenRepository $accessTokenRepository,
|
||||
private ScopeRepository $scopeRepository,
|
||||
private AuthCodeRepository $authCodeRepository,
|
||||
private RefreshTokenRepository $refreshTokenRepository,
|
||||
private CryptKeyProvider $cryptKeyProvider,
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function create(): AuthorizationServer
|
||||
{
|
||||
$server = new AuthorizationServer(
|
||||
clientRepository: $this->clientRepository,
|
||||
accessTokenRepository: $this->accessTokenRepository,
|
||||
scopeRepository: $this->scopeRepository,
|
||||
privateKey: $this->createPrivateKeyStub(),
|
||||
encryptionKey: $this->cryptKeyProvider->getCryptKey(),
|
||||
);
|
||||
|
||||
$server->revokeRefreshTokens(true);
|
||||
|
||||
$this->enableAuthorizationCodeGrant($server);
|
||||
$this->enableRefreshTokenGrant($server);
|
||||
|
||||
return $server;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
private function enableAuthorizationCodeGrant(AuthorizationServer $server): void
|
||||
{
|
||||
try {
|
||||
$grant = new AuthCodeGrant(
|
||||
authCodeRepository: $this->authCodeRepository,
|
||||
refreshTokenRepository: $this->refreshTokenRepository,
|
||||
authCodeTTL: $this->configDataProvider->getAuthorizationCodeTtl(),
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
throw new Error("Error occurred.", previous: $e);
|
||||
}
|
||||
|
||||
$server->enableGrantType(
|
||||
grantType: $grant,
|
||||
accessTokenTTL: $this->configDataProvider->getAccessTokenTtl(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
private function enableRefreshTokenGrant(AuthorizationServer $server): void
|
||||
{
|
||||
try {
|
||||
$grant = new RefreshTokenGrant($this->refreshTokenRepository);
|
||||
} catch (Exception $e) {
|
||||
throw new Error("Error occurred.", previous: $e);
|
||||
}
|
||||
|
||||
$grant->setRefreshTokenTTL($this->configDataProvider->getRefreshTokenTtl());
|
||||
|
||||
$server->enableGrantType(
|
||||
grantType: $grant,
|
||||
accessTokenTTL: $this->configDataProvider->getAccessTokenTtl(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Access token encryption is not used. Opaque tokens are used.
|
||||
*/
|
||||
private function createPrivateKeyStub(): CryptKey
|
||||
{
|
||||
return new class() extends CryptKey {
|
||||
|
||||
/**
|
||||
* @noinspection PhpMissingParentConstructorInspection
|
||||
*/
|
||||
public function __construct() {}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\League;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use DateInterval;
|
||||
use DateMalformedIntervalStringException;
|
||||
use RuntimeException;
|
||||
|
||||
class ConfigDataProvider
|
||||
{
|
||||
private const string AUTHORIZATION_CODE_TTL = 'PT10M';
|
||||
private const string ACCESS_TOKEN_TTL = 'PT1H';
|
||||
private const string REFRESH_TOKEN_TTL = 'P1M';
|
||||
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
) {}
|
||||
|
||||
public function getAuthorizationCodeTtl(): DateInterval
|
||||
{
|
||||
$interval = $this->config->get('oAuthServer.ttl.authorizationCode') ?? self::AUTHORIZATION_CODE_TTL;
|
||||
|
||||
return self::convertIntervalToDateInterval($interval);
|
||||
}
|
||||
|
||||
public function getAccessTokenTtl(): DateInterval
|
||||
{
|
||||
$interval = $this->config->get('oAuthServer.ttl.accessToken') ?? self::ACCESS_TOKEN_TTL;
|
||||
|
||||
return self::convertIntervalToDateInterval($interval);
|
||||
}
|
||||
|
||||
public function getRefreshTokenTtl(): DateInterval
|
||||
{
|
||||
$interval = $this->config->get('oAuthServer.ttl.refreshToken') ?? self::REFRESH_TOKEN_TTL;
|
||||
|
||||
return self::convertIntervalToDateInterval($interval);
|
||||
}
|
||||
|
||||
private static function convertIntervalToDateInterval(mixed $interval): DateInterval
|
||||
{
|
||||
try {
|
||||
$value = new DateInterval($interval);
|
||||
} catch (DateMalformedIntervalStringException $e) {
|
||||
throw new RuntimeException("Bad interval '$interval'.", previous: $e);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\League\Entities;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use Espo\Core\Field\DateTime;
|
||||
use Espo\Core\Field\Link;
|
||||
use Espo\Tools\OAuthServer\Entities\AccessToken;
|
||||
use Espo\Tools\OAuthServer\Utils\IdentifierHasher;
|
||||
use InvalidArgumentException;
|
||||
use League\OAuth2\Server\CryptKeyInterface;
|
||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
||||
|
||||
class AccessTokenEntity implements AccessTokenEntityInterface
|
||||
{
|
||||
public function __construct(
|
||||
private AccessToken $entity,
|
||||
private IdentifierHasher $hasher,
|
||||
) {}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->entity->getIdentifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return void
|
||||
*/
|
||||
public function setPrivateKey(CryptKeyInterface $privateKey): void
|
||||
{}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->entity->getIdentifier();
|
||||
}
|
||||
|
||||
public function setIdentifier(string $identifier): void
|
||||
{
|
||||
$this->entity->setIdentifier($identifier);
|
||||
|
||||
$hash = $this->hasher->hash($identifier);
|
||||
|
||||
$this->entity->setHash($hash);
|
||||
}
|
||||
|
||||
public function getExpiryDateTime(): DateTimeImmutable
|
||||
{
|
||||
return $this->entity->getExpiresAt()->toDateTime();
|
||||
}
|
||||
|
||||
public function setExpiryDateTime(DateTimeImmutable $dateTime): void
|
||||
{
|
||||
$this->entity->setExpiresAt(DateTime::fromDateTime($dateTime));
|
||||
}
|
||||
|
||||
public function setUserIdentifier(string $identifier): void
|
||||
{
|
||||
$link = Link::create($identifier);
|
||||
|
||||
$this->entity->setUser($link);
|
||||
}
|
||||
|
||||
public function getUserIdentifier(): ?string
|
||||
{
|
||||
/** @var non-empty-string */
|
||||
return $this->entity->getUser()->getId();
|
||||
}
|
||||
|
||||
public function getClient(): ClientEntityInterface
|
||||
{
|
||||
return ClientEntity::fromEntity($this->entity->getClient());
|
||||
}
|
||||
|
||||
|
||||
public function setClient(ClientEntityInterface $client): void
|
||||
{
|
||||
/** @noinspection PhpConditionAlreadyCheckedInspection */
|
||||
if (!$client instanceof ClientEntity) {
|
||||
throw new InvalidArgumentException("Not supported client entity.");
|
||||
}
|
||||
|
||||
$this->entity->setClient(Link::create($client->entityId));
|
||||
}
|
||||
|
||||
|
||||
public function addScope(ScopeEntityInterface $scope): void
|
||||
{
|
||||
$scopes = $this->entity->getScopes();
|
||||
|
||||
if (!in_array($scope->getIdentifier(), $scopes)) {
|
||||
$scopes[] = $scope->getIdentifier();
|
||||
}
|
||||
|
||||
$this->entity->setScopes($scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ScopeEntityInterface[]
|
||||
*/
|
||||
public function getScopes(): array
|
||||
{
|
||||
$scopes = $this->entity->getScopes();
|
||||
|
||||
return array_map(fn ($scope) => new ScopeEntity($scope), $scopes);
|
||||
}
|
||||
|
||||
public function getEntity(): AccessToken
|
||||
{
|
||||
return $this->entity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\League\Entities;
|
||||
|
||||
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
|
||||
use League\OAuth2\Server\Entities\Traits\AuthCodeTrait;
|
||||
use League\OAuth2\Server\Entities\Traits\EntityTrait;
|
||||
use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;
|
||||
|
||||
class AuthCodeEntity implements AuthCodeEntityInterface
|
||||
{
|
||||
use EntityTrait;
|
||||
use AuthCodeTrait;
|
||||
use TokenEntityTrait;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\League\Entities;
|
||||
|
||||
use Espo\Tools\OAuthServer\ClientType;
|
||||
use Espo\Tools\OAuthServer\Entities\Client;
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class ClientEntity implements ClientEntityInterface
|
||||
{
|
||||
/**
|
||||
* @param non-empty-string $identifier
|
||||
* @param string[] $redirectUri
|
||||
*/
|
||||
public function __construct(
|
||||
public string $entityId,
|
||||
private string $identifier,
|
||||
private string $name,
|
||||
private array $redirectUri,
|
||||
private bool $isConfidential,
|
||||
) {}
|
||||
|
||||
public static function fromEntity(Client $entity): self
|
||||
{
|
||||
return new self(
|
||||
entityId: $entity->getId(),
|
||||
identifier: $entity->getIdentifier() ?? throw new UnexpectedValueException("No client identifier."),
|
||||
name: $entity->getName() ?? $entity->getId(),
|
||||
redirectUri: $entity->getRedirectUris(),
|
||||
isConfidential: $entity->getClientType() === ClientType::Confidential,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getRedirectUri(): array
|
||||
{
|
||||
return $this->redirectUri;
|
||||
}
|
||||
|
||||
public function isConfidential(): bool
|
||||
{
|
||||
return $this->isConfidential;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\League\Entities;
|
||||
|
||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
||||
use League\OAuth2\Server\Entities\Traits\EntityTrait;
|
||||
use League\OAuth2\Server\Entities\Traits\RefreshTokenTrait;
|
||||
|
||||
class RefreshTokenEntity implements RefreshTokenEntityInterface
|
||||
{
|
||||
use RefreshTokenTrait;
|
||||
use EntityTrait;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\League\Entities;
|
||||
|
||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
||||
use League\OAuth2\Server\Entities\Traits\ScopeTrait;
|
||||
|
||||
class ScopeEntity implements ScopeEntityInterface
|
||||
{
|
||||
use ScopeTrait;
|
||||
|
||||
/**
|
||||
* @param non-empty-string $identifier
|
||||
*/
|
||||
public function __construct(
|
||||
private string $identifier,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\League\Entities;
|
||||
|
||||
use Espo\Entities\User;
|
||||
use League\OAuth2\Server\Entities\UserEntityInterface;
|
||||
use LogicException;
|
||||
|
||||
class UserEntity implements UserEntityInterface
|
||||
{
|
||||
public function __construct(
|
||||
private User $user,
|
||||
) {
|
||||
if ($user->isSystem()) {
|
||||
// Extra safety measure.
|
||||
throw new LogicException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
/** @var non-empty-string */
|
||||
return $this->user->getId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\League\Repositories;
|
||||
|
||||
use Espo\Core\Field\Link;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\OAuthServer\Entities\AccessToken;
|
||||
use Espo\Tools\OAuthServer\League\Entities\AccessTokenEntity;
|
||||
use Espo\Tools\OAuthServer\League\Entities\ClientEntity;
|
||||
use Espo\Tools\OAuthServer\Repository\AccessTokenRepository as Repository;
|
||||
use Espo\Tools\OAuthServer\Utils\IdentifierHasher;
|
||||
use InvalidArgumentException;
|
||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
||||
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
|
||||
use SensitiveParameter;
|
||||
|
||||
class AccessTokenRepository implements AccessTokenRepositoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private Repository $repository,
|
||||
private IdentifierHasher $hasher,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param ScopeEntityInterface[] $scopes
|
||||
*/
|
||||
public function getNewToken(
|
||||
ClientEntityInterface $clientEntity,
|
||||
array $scopes,
|
||||
?string $userIdentifier = null,
|
||||
): AccessTokenEntityInterface {
|
||||
|
||||
$entity = $this->entityManager->getRDBRepositoryByClass(AccessToken::class)->getNew();
|
||||
|
||||
/** @noinspection PhpConditionAlreadyCheckedInspection */
|
||||
if (!$clientEntity instanceof ClientEntity) {
|
||||
throw new InvalidArgumentException("Bad client.");
|
||||
}
|
||||
|
||||
if (!$userIdentifier) {
|
||||
throw new InvalidArgumentException('No user ID.');
|
||||
}
|
||||
|
||||
$entity->setClient(Link::create($clientEntity->entityId));
|
||||
|
||||
$accessToken = new AccessTokenEntity($entity, $this->hasher);
|
||||
|
||||
foreach ($scopes as $scope) {
|
||||
$accessToken->addScope($scope);
|
||||
}
|
||||
|
||||
$accessToken->setUserIdentifier($userIdentifier);
|
||||
|
||||
return $accessToken;
|
||||
}
|
||||
|
||||
public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity): void
|
||||
{
|
||||
/** @noinspection PhpConditionAlreadyCheckedInspection */
|
||||
if (!$accessTokenEntity instanceof AccessTokenEntity) {
|
||||
throw new InvalidArgumentException("Not supported access token entity.");
|
||||
}
|
||||
|
||||
$this->entityManager->saveEntity($accessTokenEntity->getEntity());
|
||||
}
|
||||
|
||||
public function revokeAccessToken(#[SensitiveParameter] string $tokenId): void
|
||||
{
|
||||
$entity = $this->repository->getActiveByIdentifier($tokenId);
|
||||
|
||||
if (!$entity || $entity->isRevoked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity->setRevoked();
|
||||
|
||||
$this->entityManager->saveEntity($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Not existent or inactive token is treated as revoked intentionally.
|
||||
*/
|
||||
public function isAccessTokenRevoked(#[SensitiveParameter] string $tokenId): bool
|
||||
{
|
||||
$entity = $this->repository->getActiveByIdentifier($tokenId);
|
||||
|
||||
if (!$entity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Intentionally.
|
||||
return !$entity->isActive();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\League\Repositories;
|
||||
|
||||
use Espo\Core\Field\DateTime;
|
||||
use Espo\Core\Field\Link;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\OAuthServer\Entities\AuthorizationCode;
|
||||
use Espo\Tools\OAuthServer\League\Entities\AuthCodeEntity;
|
||||
use Espo\Tools\OAuthServer\Repository\AuthorizationCodeRepository;
|
||||
use Espo\Tools\OAuthServer\Repository\ClientRepository as ClientRepository;
|
||||
use Espo\Tools\OAuthServer\Utils\IdentifierHasher;
|
||||
use InvalidArgumentException;
|
||||
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
|
||||
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
|
||||
use RuntimeException;
|
||||
use SensitiveParameter;
|
||||
|
||||
class AuthCodeRepository implements AuthCodeRepositoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private AuthorizationCodeRepository $repository,
|
||||
private ClientRepository $clientRepository,
|
||||
private IdentifierHasher $hasher,
|
||||
) {}
|
||||
|
||||
public function getNewAuthCode(): AuthCodeEntity
|
||||
{
|
||||
return new AuthCodeEntity();
|
||||
}
|
||||
|
||||
public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity): void
|
||||
{
|
||||
$entity = $this->entityManager->getRDBRepositoryByClass(AuthorizationCode::class)->getNew();
|
||||
|
||||
$userId = $authCodeEntity->getUserIdentifier();
|
||||
|
||||
if (!is_string($userId)) {
|
||||
throw new InvalidArgumentException("User ID must be string.");
|
||||
}
|
||||
|
||||
$scopes = array_map(fn ($scope) => $scope->getIdentifier(), $authCodeEntity->getScopes());
|
||||
|
||||
$redirectUri = $authCodeEntity->getRedirectUri() ?? throw new InvalidArgumentException("No redirect URI");
|
||||
|
||||
$clientId = $authCodeEntity->getClient()->getIdentifier();
|
||||
|
||||
$client = $this->clientRepository->getActiveByIdentifier($clientId);
|
||||
|
||||
if (!$client) {
|
||||
throw new RuntimeException("Client not found.");
|
||||
}
|
||||
|
||||
$hash = $this->hasher->hash($authCodeEntity->getIdentifier());
|
||||
|
||||
$entity
|
||||
->setClient($client)
|
||||
->setHash($hash)
|
||||
->setUser(Link::create($userId))
|
||||
->setScopes($scopes)
|
||||
->setRedirectUri($redirectUri)
|
||||
->setExpiresAt(DateTime::fromDateTime($authCodeEntity->getExpiryDateTime()));
|
||||
|
||||
$this->entityManager->saveEntity($entity);
|
||||
}
|
||||
|
||||
public function revokeAuthCode(#[SensitiveParameter] string $codeId): void
|
||||
{
|
||||
$code = $this->repository->getActiveByIdentifier($codeId);
|
||||
|
||||
if (!$code || $code->isRevoked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$code->setRevoked();
|
||||
|
||||
$this->entityManager->saveEntity($code);
|
||||
}
|
||||
|
||||
public function isAuthCodeRevoked(#[SensitiveParameter] string $codeId): bool
|
||||
{
|
||||
$entity = $this->repository->getActiveByIdentifier($codeId);
|
||||
|
||||
if (!$entity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Intentionally.
|
||||
return !$entity->isActive();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\League\Repositories;
|
||||
|
||||
use Espo\Tools\OAuthServer\ClientValidator;
|
||||
use Espo\Tools\OAuthServer\League\Entities\ClientEntity;
|
||||
use Espo\Tools\OAuthServer\Repository\ClientRepository as Repository;
|
||||
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
|
||||
use SensitiveParameter;
|
||||
|
||||
class ClientRepository implements ClientRepositoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private Repository $repository,
|
||||
private ClientValidator $clientValidator,
|
||||
) {}
|
||||
|
||||
public function getClientEntity(string $clientIdentifier): ?ClientEntity
|
||||
{
|
||||
$client = $this->repository->getActiveByIdentifier($clientIdentifier);
|
||||
|
||||
if (!$client) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ClientEntity::fromEntity($client);
|
||||
}
|
||||
|
||||
public function validateClient(
|
||||
string $clientIdentifier,
|
||||
#[SensitiveParameter] ?string $clientSecret,
|
||||
?string $grantType,
|
||||
): bool {
|
||||
|
||||
$client = $this->repository->getActiveByIdentifier($clientIdentifier);
|
||||
|
||||
if (!$client) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->clientValidator->validate($client, $clientSecret);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\League\Repositories;
|
||||
|
||||
use Espo\Core\Field\DateTime;
|
||||
use Espo\Core\Field\Link;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\OAuthServer\Entities\RefreshToken;
|
||||
use Espo\Tools\OAuthServer\League\Entities\AccessTokenEntity;
|
||||
use Espo\Tools\OAuthServer\League\Entities\RefreshTokenEntity;
|
||||
use Espo\Tools\OAuthServer\Repository\ClientRepository as ClientRepositoryInternal;
|
||||
use Espo\Tools\OAuthServer\Repository\RefreshTokenRepository as RefreshTokenRepositoryInternal;
|
||||
use Espo\Tools\OAuthServer\Utils\IdentifierHasher;
|
||||
use InvalidArgumentException;
|
||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
||||
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
|
||||
use RuntimeException;
|
||||
use SensitiveParameter;
|
||||
|
||||
class RefreshTokenRepository implements RefreshTokenRepositoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private RefreshTokenRepositoryInternal $repository,
|
||||
private ClientRepositoryInternal $clientRepository,
|
||||
private IdentifierHasher $hasher,
|
||||
) {}
|
||||
|
||||
public function getNewRefreshToken(): RefreshTokenEntity
|
||||
{
|
||||
return new RefreshTokenEntity();
|
||||
}
|
||||
|
||||
public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity): void
|
||||
{
|
||||
$entity = $this->entityManager->getRDBRepositoryByClass(RefreshToken::class)->getNew();
|
||||
|
||||
$userId = $refreshTokenEntity->getAccessToken()->getUserIdentifier();
|
||||
|
||||
if (!is_string($userId)) {
|
||||
throw new InvalidArgumentException("User ID must be string.");
|
||||
}
|
||||
|
||||
$clientId = $refreshTokenEntity->getAccessToken()->getClient()->getIdentifier();
|
||||
|
||||
$client = $this->clientRepository->getActiveByIdentifier($clientId) ??
|
||||
throw new RuntimeException("Client not found.");
|
||||
|
||||
$accessToken = $refreshTokenEntity->getAccessToken();
|
||||
|
||||
/** @noinspection PhpConditionAlreadyCheckedInspection */
|
||||
if (!$accessToken instanceof AccessTokenEntity) {
|
||||
throw new InvalidArgumentException("Not supported access token implementation.");
|
||||
}
|
||||
|
||||
$hash = $this->hasher->hash($refreshTokenEntity->getIdentifier());
|
||||
|
||||
$entity
|
||||
->setClient($client)
|
||||
->setAccessToken($accessToken->getEntity())
|
||||
->setHash($hash)
|
||||
->setUser(Link::create($userId))
|
||||
->setExpiresAt(DateTime::fromDateTime($refreshTokenEntity->getExpiryDateTime()));
|
||||
|
||||
$this->entityManager->saveEntity($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Delay?
|
||||
*/
|
||||
public function revokeRefreshToken(#[SensitiveParameter] string $tokenId): void
|
||||
{
|
||||
$token = $this->repository->getActiveByIdentifier($tokenId);
|
||||
|
||||
if (!$token || $token->isRevoked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$token->setRevoked();
|
||||
|
||||
$this->entityManager->saveEntity($token);
|
||||
}
|
||||
|
||||
public function isRefreshTokenRevoked(#[SensitiveParameter] string $tokenId): bool
|
||||
{
|
||||
$entity = $this->repository->getActiveByIdentifier($tokenId);
|
||||
|
||||
if (!$entity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Intentionally.
|
||||
return !$entity->isActive();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\League\Repositories;
|
||||
|
||||
use Espo\Tools\OAuthServer\League\Entities\ScopeEntity;
|
||||
use Espo\Tools\OAuthServer\Scope\UserAvailableScopesFilter;
|
||||
use Espo\Tools\OAuthServer\Scope\ScopeValidator;
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
|
||||
|
||||
class ScopeRepository implements ScopeRepositoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ScopeValidator $scopeValidator,
|
||||
private UserAvailableScopesFilter $userAvailableScopesProvider,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $identifier
|
||||
*/
|
||||
public function getScopeEntityByIdentifier(string $identifier): ?ScopeEntity
|
||||
{
|
||||
if (!$this->scopeValidator->validate($identifier)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ScopeEntity($identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function finalizeScopes(
|
||||
array $scopes,
|
||||
?string $grantType,
|
||||
ClientEntityInterface $clientEntity,
|
||||
?string $userIdentifier = null,
|
||||
?string $authCodeId = null,
|
||||
): array {
|
||||
|
||||
if (!$userIdentifier) {
|
||||
return $scopes;
|
||||
}
|
||||
|
||||
$ids = array_map(fn ($it) => $it->getIdentifier(), $scopes);
|
||||
|
||||
$availableIds = $this->userAvailableScopesProvider->filter($userIdentifier, $ids);
|
||||
|
||||
$scopes = array_filter($scopes, function ($scope) use ($availableIds) {
|
||||
return in_array($scope->getIdentifier(), $availableIds);
|
||||
});
|
||||
|
||||
return array_values($scopes);
|
||||
}
|
||||
}
|
||||
116
application/Espo/Tools/OAuthServer/Login/OAuthLogin.php
Normal file
116
application/Espo/Tools/OAuthServer/Login/OAuthLogin.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Login;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Authentication\Login;
|
||||
use Espo\Core\Authentication\Login\Data;
|
||||
use Espo\Core\Authentication\Repository\UserRepository;
|
||||
use Espo\Core\Authentication\Result;
|
||||
use Espo\Core\Field\DateTime;
|
||||
use Espo\Core\Utils\DateTime\Clock;
|
||||
use Espo\Tools\OAuthServer\Repository\AccessTokenRepository;
|
||||
use Espo\Tools\OAuthServer\Utils\ErrorResponseComposer;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class OAuthLogin implements Login
|
||||
{
|
||||
private const string HEADER_AUTHORIZATION = 'Authorization';
|
||||
private const string SCHEME = 'Bearer';
|
||||
|
||||
public function __construct(
|
||||
private AccessTokenRepository $repository,
|
||||
private Clock $clock,
|
||||
private UserRepository $userRepository,
|
||||
) {}
|
||||
|
||||
public function login(Data $data, Request $request): Result
|
||||
{
|
||||
$header = $request->getHeader(self::HEADER_AUTHORIZATION);
|
||||
|
||||
$prefix = self::SCHEME . ' ';
|
||||
|
||||
if (!$header || !str_starts_with($header, $prefix)) {
|
||||
return Result::fail(Result\FailReason::ERROR);
|
||||
}
|
||||
|
||||
$opaqueToken = substr($header, strlen($prefix));
|
||||
|
||||
$accessToken = $this->repository->getActiveByIdentifier($opaqueToken);
|
||||
|
||||
if (!$accessToken) {
|
||||
$response = ErrorResponseComposer::compose(
|
||||
error: 'access_denied',
|
||||
errorDescription: 'Invalid access token.',
|
||||
);
|
||||
|
||||
return Result::fail(Result\FailReason::WRONG_CREDENTIALS, $response);
|
||||
}
|
||||
|
||||
$now = DateTime::fromDateTime($this->clock->now());
|
||||
|
||||
if ($accessToken->getExpiresAt()->isLessThanOrEqualTo($now)) {
|
||||
$response = ErrorResponseComposer::compose(
|
||||
error: 'access_denied',
|
||||
errorDescription: 'The access token expired.',
|
||||
);
|
||||
|
||||
return Result::fail(Result\FailReason::DENIED, $response);
|
||||
}
|
||||
|
||||
if (!$accessToken->getClient()->isActive()) {
|
||||
$response = ErrorResponseComposer::compose(
|
||||
error: 'access_denied',
|
||||
errorDescription: 'The client is not active.',
|
||||
);
|
||||
|
||||
return Result::fail(Result\FailReason::DENIED, $response);
|
||||
}
|
||||
|
||||
$userLink = $accessToken->getUser();
|
||||
|
||||
$user = $this->userRepository->findOneById($userLink->getId());
|
||||
|
||||
if (!$user) {
|
||||
$response = ErrorResponseComposer::compose(
|
||||
error: 'access_denied',
|
||||
errorDescription: 'User not found.',
|
||||
);
|
||||
|
||||
return Result::fail(Result\FailReason::DENIED, $response);
|
||||
}
|
||||
|
||||
$user->setScopes($accessToken->getScopes());
|
||||
|
||||
return Result::success($user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Metadata;
|
||||
|
||||
use Espo\Core\Utils\Cache\DataCacheAccess;
|
||||
use Espo\Core\Utils\Config\ApplicationConfig;
|
||||
use stdClass;
|
||||
|
||||
class AuthorizationServerMetadataProvider
|
||||
{
|
||||
private const string CACHE_KEY = 'oAuthServer/authorizationServerMetadata';
|
||||
|
||||
/**
|
||||
* @param DataCacheAccess<stdClass> $dataCacheAccess
|
||||
*/
|
||||
public function __construct(
|
||||
private DataCacheAccess $dataCacheAccess,
|
||||
private ApplicationConfig $applicationConfig,
|
||||
) {
|
||||
$this->dataCacheAccess->init(
|
||||
key: self::CACHE_KEY,
|
||||
loader: fn () => $this->buildData(),
|
||||
);
|
||||
}
|
||||
|
||||
public function get(): stdClass
|
||||
{
|
||||
return $this->dataCacheAccess->get();
|
||||
}
|
||||
|
||||
private function buildData(): stdClass
|
||||
{
|
||||
return (object) [
|
||||
'issuer' => self::serverIdentifierFromUrl($this->applicationConfig->getSiteUrl()),
|
||||
'authorization_endpoint' => $this->applicationConfig->getSiteUrl() . '/oauth/authorize',
|
||||
'token_endpoint' => $this->applicationConfig->getSiteUrl() . '/oauth/token',
|
||||
'revocation_endpoint' => $this->applicationConfig->getSiteUrl() . '/oauth/token/revoke',
|
||||
];
|
||||
}
|
||||
|
||||
private static function serverIdentifierFromUrl(string $url): string
|
||||
{
|
||||
$parsed = parse_url($url);
|
||||
|
||||
$scheme = isset($parsed['scheme']) ? $parsed['scheme'] . '://' : '';
|
||||
$host = $parsed['host'] ?? '';
|
||||
$port = isset($parsed['port']) ? ':' . $parsed['port'] : '';
|
||||
|
||||
return $scheme . $host . $port;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Metadata;
|
||||
|
||||
use Espo\Core\Utils\Cache\DataCacheAccess;
|
||||
use Espo\Core\Utils\Config\ApplicationConfig;
|
||||
use stdClass;
|
||||
|
||||
class ProtectedResourceMetadataProvider
|
||||
{
|
||||
private const string CACHE_KEY = 'oAuthServer/protectedResourceMetadata';
|
||||
|
||||
/**
|
||||
* @param DataCacheAccess<stdClass> $dataCacheAccess
|
||||
*/
|
||||
public function __construct(
|
||||
private DataCacheAccess $dataCacheAccess,
|
||||
private ApplicationConfig $applicationConfig,
|
||||
) {
|
||||
$this->dataCacheAccess->init(
|
||||
key: self::CACHE_KEY,
|
||||
loader: fn () => $this->buildData(),
|
||||
);
|
||||
}
|
||||
|
||||
public function get(): stdClass
|
||||
{
|
||||
return $this->dataCacheAccess->get();
|
||||
}
|
||||
|
||||
private function buildData(): stdClass
|
||||
{
|
||||
return (object) [
|
||||
'resource' => $this->applicationConfig->getSiteUrl(),
|
||||
'authorization_servers' => [
|
||||
self::serverIdentifierFromUrl($this->applicationConfig->getSiteUrl()),
|
||||
],
|
||||
'bearer_methods_supported' => [
|
||||
'header',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private static function serverIdentifierFromUrl(string $url): string
|
||||
{
|
||||
$parsed = parse_url($url);
|
||||
|
||||
$scheme = isset($parsed['scheme']) ? $parsed['scheme'] . '://' : '';
|
||||
$host = $parsed['host'] ?? '';
|
||||
$port = isset($parsed['port']) ? ':' . $parsed['port'] : '';
|
||||
|
||||
return $scheme . $host . $port;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Record\MassActions;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Conflict;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\MassAction\Data;
|
||||
use Espo\Core\MassAction\MassAction;
|
||||
use Espo\Core\MassAction\Params;
|
||||
use Espo\Core\MassAction\QueryBuilder;
|
||||
use Espo\Core\MassAction\Result;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\OAuthServer\Entities\AccessToken;
|
||||
use Espo\Tools\OAuthServer\Entities\AuthorizationCode;
|
||||
use Espo\Tools\OAuthServer\Entities\RefreshToken;
|
||||
use Espo\Tools\OAuthServer\Record\TokenService;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class RevokeMassAction implements MassAction
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $entityTypes = [
|
||||
AccessToken::ENTITY_TYPE,
|
||||
RefreshToken::ENTITY_TYPE,
|
||||
AuthorizationCode::ENTITY_TYPE,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private TokenService $service,
|
||||
private User $user,
|
||||
private QueryBuilder $queryBuilder,
|
||||
private Acl $acl,
|
||||
private EntityManager $entityManager,
|
||||
) {}
|
||||
|
||||
public function process(Params $params, Data $data): Result
|
||||
{
|
||||
$entityType = $params->getEntityType();
|
||||
|
||||
if (!in_array($entityType, $this->entityTypes)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$this->assertAccess($entityType);
|
||||
|
||||
$query = $this->queryBuilder->build($params);
|
||||
|
||||
$collection = $this->entityManager->getRDBRepository($entityType)
|
||||
->clone($query)
|
||||
->sth()
|
||||
->find();
|
||||
|
||||
$ids = [];
|
||||
$count = 0;
|
||||
|
||||
foreach ($collection as $entity) {
|
||||
try {
|
||||
$this->assertEntity($entity);
|
||||
|
||||
/** @noinspection PhpParamsInspection */
|
||||
$this->service->revoke($entity);
|
||||
|
||||
$ids[] = $entity->getId();
|
||||
$count ++;
|
||||
} catch (Conflict|Forbidden) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return new Result($count, $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function assertAccess(string $entityType): void
|
||||
{
|
||||
if (!$this->user->isEffectiveAdmin()) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
if (!$this->acl->check($entityType, Acl\Table::ACTION_EDIT)) {
|
||||
throw new Forbidden("No edit access.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-assert AccessToken|RefreshToken|AuthorizationCode $entity
|
||||
*/
|
||||
private function assertEntity(Entity $entity): void
|
||||
{
|
||||
if (
|
||||
!$entity instanceof AccessToken &&
|
||||
!$entity instanceof RefreshToken &&
|
||||
!$entity instanceof AuthorizationCode
|
||||
) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
65
application/Espo/Tools/OAuthServer/Record/TokenService.php
Normal file
65
application/Espo/Tools/OAuthServer/Record/TokenService.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Record;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Exceptions\Conflict;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\OAuthServer\Entities\AccessToken;
|
||||
use Espo\Tools\OAuthServer\Entities\AuthorizationCode;
|
||||
use Espo\Tools\OAuthServer\Entities\RefreshToken;
|
||||
|
||||
class TokenService
|
||||
{
|
||||
public function __construct(
|
||||
private Acl $acl,
|
||||
private EntityManager $entityManager,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws Conflict
|
||||
*/
|
||||
public function revoke(AccessToken|RefreshToken|AuthorizationCode $token): void
|
||||
{
|
||||
if (!$this->acl->checkEntityEdit($token)) {
|
||||
throw new Forbidden("No edit access.");
|
||||
}
|
||||
|
||||
if (!$token->isActive()) {
|
||||
throw new Conflict("Token is not active.");
|
||||
}
|
||||
|
||||
$token->setRevoked();
|
||||
|
||||
$this->entityManager->saveEntity($token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Repository;
|
||||
|
||||
use Espo\ORM\SthCollection;
|
||||
use Espo\Tools\OAuthServer\Utils\IdentifierHasher;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\OAuthServer\Entities\AccessToken;
|
||||
use SensitiveParameter;
|
||||
|
||||
class AccessTokenRepository
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private IdentifierHasher $hasher,
|
||||
) {}
|
||||
|
||||
public function getActiveByIdentifier(#[SensitiveParameter] string $identifier): ?AccessToken
|
||||
{
|
||||
$hash = $this->hasher->hash($identifier);
|
||||
|
||||
return $this->entityManager
|
||||
->getRDBRepositoryByClass(AccessToken::class)
|
||||
->where([
|
||||
AccessToken::FIELD_HASH => $hash,
|
||||
AccessToken::FIELD_STATUS => AccessToken::STATUS_ACTIVE,
|
||||
])
|
||||
->findOne();
|
||||
}
|
||||
|
||||
public function getByIdentifier(#[SensitiveParameter] string $identifier): ?AccessToken
|
||||
{
|
||||
$hash = $this->hasher->hash($identifier);
|
||||
|
||||
return $this->entityManager
|
||||
->getRDBRepositoryByClass(AccessToken::class)
|
||||
->where([
|
||||
AccessToken::FIELD_HASH => $hash,
|
||||
])
|
||||
->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SthCollection<AccessToken>
|
||||
*/
|
||||
public function findActiveForClientIdAndUser(string $clientId, string $userId): SthCollection
|
||||
{
|
||||
return $this->entityManager->getRDBRepositoryByClass(AccessToken::class)
|
||||
->where([
|
||||
AccessToken::FIELD_STATUS => AccessToken::STATUS_ACTIVE,
|
||||
AccessToken::FIELD_CLIENT . 'Id' => $clientId,
|
||||
AccessToken::FIELD_USER . 'Id' => $userId,
|
||||
])
|
||||
->sth()
|
||||
->find();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Repository;
|
||||
|
||||
use Espo\Tools\OAuthServer\Utils\IdentifierHasher;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\OAuthServer\Entities\AuthorizationCode;
|
||||
use SensitiveParameter;
|
||||
|
||||
class AuthorizationCodeRepository
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private IdentifierHasher $hasher,
|
||||
) {}
|
||||
|
||||
public function getActiveByIdentifier(#[SensitiveParameter] string $identifier): ?AuthorizationCode
|
||||
{
|
||||
$hash = $this->hasher->hash($identifier);
|
||||
|
||||
return $this->entityManager
|
||||
->getRDBRepositoryByClass(AuthorizationCode::class)
|
||||
->where([
|
||||
AuthorizationCode::FIELD_HASH => $hash,
|
||||
AuthorizationCode::FIELD_STATUS => AuthorizationCode::STATUS_ACTIVE,
|
||||
])
|
||||
->findOne();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Repository;
|
||||
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\ORM\EntityCollection;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\Condition as Cond;
|
||||
use Espo\ORM\Query\Part\Expression as Expr;
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
use Espo\Tools\OAuthServer\Entities\AccessToken;
|
||||
use Espo\Tools\OAuthServer\Entities\Client;
|
||||
use Espo\Tools\OAuthServer\Entities\RefreshToken;
|
||||
|
||||
class ClientRepository
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
) {}
|
||||
|
||||
public function getActiveByIdentifier(string $identifier): ?Client
|
||||
{
|
||||
return $this->entityManager
|
||||
->getRDBRepositoryByClass(Client::class)
|
||||
->where([
|
||||
Client::FIELD_IDENTIFIER => $identifier,
|
||||
Client::FIELD_STATUS => Client::STATUS_ACTIVE,
|
||||
])
|
||||
->findOne();
|
||||
}
|
||||
|
||||
public function getByIdentifier(string $identifier): ?Client
|
||||
{
|
||||
return $this->entityManager
|
||||
->getRDBRepositoryByClass(Client::class)
|
||||
->where([
|
||||
Client::FIELD_IDENTIFIER => $identifier,
|
||||
])
|
||||
->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EntityCollection<Client>
|
||||
*/
|
||||
public function findConnectedForUser(string $userId, int $limit): EntityCollection
|
||||
{
|
||||
return $this->entityManager->getRDBRepositoryByClass(Client::class)
|
||||
->where(
|
||||
Cond::or(
|
||||
Cond::in(
|
||||
Expr::column(Attribute::ID),
|
||||
SelectBuilder::create()
|
||||
->from(RefreshToken::ENTITY_TYPE)
|
||||
->select([RefreshToken::FIELD_CLIENT . 'Id'])
|
||||
->where([
|
||||
RefreshToken::FIELD_STATUS => RefreshToken::STATUS_ACTIVE,
|
||||
RefreshToken::FIELD_USER . 'Id' => $userId,
|
||||
])
|
||||
->build()
|
||||
),
|
||||
Cond::in(
|
||||
Expr::column(Attribute::ID),
|
||||
SelectBuilder::create()
|
||||
->from(AccessToken::ENTITY_TYPE)
|
||||
->select([RefreshToken::FIELD_CLIENT . 'Id'])
|
||||
->where([
|
||||
AccessToken::FIELD_STATUS => RefreshToken::STATUS_ACTIVE,
|
||||
AccessToken::FIELD_USER . 'Id' => $userId,
|
||||
])
|
||||
->build()
|
||||
),
|
||||
)
|
||||
)
|
||||
->where([
|
||||
Client::FIELD_IDENTIFIER . '!=' => null,
|
||||
])
|
||||
->order(Field::NAME)
|
||||
->limit(0, $limit)
|
||||
->find();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Repository;
|
||||
|
||||
use Espo\ORM\SthCollection;
|
||||
use Espo\Tools\OAuthServer\Utils\IdentifierHasher;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\OAuthServer\Entities\RefreshToken;
|
||||
use SensitiveParameter;
|
||||
|
||||
class RefreshTokenRepository
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private IdentifierHasher $hasher,
|
||||
) {}
|
||||
|
||||
public function getActiveByIdentifier(#[SensitiveParameter] string $identifier): ?RefreshToken
|
||||
{
|
||||
$hash = $this->hasher->hash($identifier);
|
||||
|
||||
return $this->entityManager
|
||||
->getRDBRepositoryByClass(RefreshToken::class)
|
||||
->where([
|
||||
RefreshToken::FIELD_HASH => $hash,
|
||||
RefreshToken::FIELD_STATUS => RefreshToken::STATUS_ACTIVE,
|
||||
])
|
||||
->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SthCollection<RefreshToken>
|
||||
*/
|
||||
public function findActiveForClientIdAndUser(string $clientId, string $userId): SthCollection
|
||||
{
|
||||
return $this->entityManager->getRDBRepositoryByClass(RefreshToken::class)
|
||||
->where([
|
||||
RefreshToken::FIELD_STATUS => RefreshToken::STATUS_ACTIVE,
|
||||
RefreshToken::FIELD_CLIENT . 'Id' => $clientId,
|
||||
RefreshToken::FIELD_USER . 'Id' => $userId,
|
||||
])
|
||||
->sth()
|
||||
->find();
|
||||
}
|
||||
}
|
||||
43
application/Espo/Tools/OAuthServer/Revoke/Data.php
Normal file
43
application/Espo/Tools/OAuthServer/Revoke/Data.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Revoke;
|
||||
|
||||
use SensitiveParameter;
|
||||
|
||||
readonly class Data
|
||||
{
|
||||
public function __construct(
|
||||
public string $clientId,
|
||||
#[SensitiveParameter]
|
||||
public ?string $clientSecret,
|
||||
public string $token,
|
||||
public ?string $tokenTypeHint,
|
||||
) {}
|
||||
}
|
||||
125
application/Espo/Tools/OAuthServer/Revoke/TokenRevokeService.php
Normal file
125
application/Espo/Tools/OAuthServer/Revoke/TokenRevokeService.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Revoke;
|
||||
|
||||
use Espo\Core\Exceptions\Unauthorized;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\OAuthServer\ClientValidator;
|
||||
use Espo\Tools\OAuthServer\Entities\Client;
|
||||
use Espo\Tools\OAuthServer\Repository\AccessTokenRepository;
|
||||
use Espo\Tools\OAuthServer\Repository\ClientRepository;
|
||||
use Espo\Tools\OAuthServer\Repository\RefreshTokenRepository;
|
||||
use Espo\Tools\OAuthServer\Utils\RefreshTokenDataExtractor;
|
||||
use Exception;
|
||||
use SensitiveParameter;
|
||||
|
||||
class TokenRevokeService
|
||||
{
|
||||
public const string TYPE_ACCESS_TOKEN = 'access_token';
|
||||
public const string TYPE_REFRESH_TOKEN = 'refresh_token';
|
||||
|
||||
public function __construct(
|
||||
private AccessTokenRepository $accessTokenRepository,
|
||||
private RefreshTokenRepository $refreshTokenRepository,
|
||||
private ClientRepository $clientRepository,
|
||||
private ClientValidator $clientValidator,
|
||||
private EntityManager $entityManager,
|
||||
private RefreshTokenDataExtractor $refreshTokenDataExtractor,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Unauthorized
|
||||
*/
|
||||
public function revoke(#[SensitiveParameter] Data $data): void
|
||||
{
|
||||
$tokenTypeHint = $data->tokenTypeHint;
|
||||
$token = $data->token;
|
||||
|
||||
$client = $this->clientRepository->getActiveByIdentifier($data->clientId);
|
||||
|
||||
if (!$client || !$this->clientValidator->validate($client, $data->clientSecret)) {
|
||||
throw new Unauthorized();
|
||||
}
|
||||
|
||||
if (!$tokenTypeHint || $tokenTypeHint === self::TYPE_ACCESS_TOKEN) {
|
||||
$this->revokeAccessToken($token, $client);
|
||||
}
|
||||
|
||||
if (!$tokenTypeHint || $tokenTypeHint === self::TYPE_REFRESH_TOKEN) {
|
||||
$this->revokeRefreshToken($token, $client);
|
||||
}
|
||||
}
|
||||
|
||||
private function revokeAccessToken(#[SensitiveParameter] string $token, Client $client): void
|
||||
{
|
||||
$accessToken = $this->accessTokenRepository->getActiveByIdentifier($token);
|
||||
|
||||
if (
|
||||
!$accessToken ||
|
||||
$accessToken->getClient()->getId() !== $client->getId()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$accessToken->setRevoked();
|
||||
|
||||
$this->entityManager->saveEntity($accessToken);
|
||||
}
|
||||
|
||||
private function revokeRefreshToken(#[SensitiveParameter] string $token, Client $client): void
|
||||
{
|
||||
try {
|
||||
$identifier = $this->refreshTokenDataExtractor->extractIdentifier($token);
|
||||
} catch (Exception) {
|
||||
return;
|
||||
}
|
||||
|
||||
$refreshToken = $this->refreshTokenRepository->getActiveByIdentifier($identifier);
|
||||
|
||||
if (
|
||||
!$refreshToken ||
|
||||
$refreshToken->getClient()->getId() !== $client->getId()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$accessToken = $refreshToken->getAccessToken();
|
||||
|
||||
$refreshToken->setRevoked();
|
||||
|
||||
$this->entityManager->saveEntity($refreshToken);
|
||||
|
||||
if ($accessToken) {
|
||||
$accessToken->setRevoked();
|
||||
|
||||
$this->entityManager->saveEntity($accessToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
76
application/Espo/Tools/OAuthServer/Scope/ScopeSorter.php
Normal file
76
application/Espo/Tools/OAuthServer/Scope/ScopeSorter.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Scope;
|
||||
|
||||
use Espo\Core\Acl\Scope;
|
||||
use stdClass;
|
||||
|
||||
class ScopeSorter
|
||||
{
|
||||
/**
|
||||
* @var array<string, int>
|
||||
*/
|
||||
private array $priority = [
|
||||
Scope::GLOBAL => 0,
|
||||
Scope::ADMIN => 1,
|
||||
];
|
||||
|
||||
/**
|
||||
* @param (stdClass & object{name: string, label: string})[] $items
|
||||
* @return (stdClass & object{name: string, label: string})[]
|
||||
*/
|
||||
public function sortDataItems(array $items): array
|
||||
{
|
||||
usort($items, function ($a, $b) {
|
||||
$aPriority = $this->priority[$a->name] ?? PHP_INT_MAX;
|
||||
$bPriority = $this->priority[$b->name] ?? PHP_INT_MAX;
|
||||
|
||||
return $aPriority <=> $bPriority ?: strcasecmp($a->label, $b->label);
|
||||
});
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string[] $scopes
|
||||
* @return non-empty-string[]
|
||||
*/
|
||||
public function sort(array $scopes): array
|
||||
{
|
||||
usort($scopes, function ($a, $b) {
|
||||
$aPriority = $this->priority[$a] ?? PHP_INT_MAX;
|
||||
$bPriority = $this->priority[$b] ?? PHP_INT_MAX;
|
||||
|
||||
return $aPriority <=> $bPriority ?: strcmp($a, $b);
|
||||
});
|
||||
|
||||
return $scopes;
|
||||
}
|
||||
}
|
||||
42
application/Espo/Tools/OAuthServer/Scope/ScopeValidator.php
Normal file
42
application/Espo/Tools/OAuthServer/Scope/ScopeValidator.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Scope;
|
||||
|
||||
class ScopeValidator
|
||||
{
|
||||
public function __construct(
|
||||
private ScopesProvider $scopesProvider,
|
||||
) {}
|
||||
|
||||
public function validate(string $scope): bool
|
||||
{
|
||||
return in_array($scope, $this->scopesProvider->get());
|
||||
}
|
||||
}
|
||||
94
application/Espo/Tools/OAuthServer/Scope/ScopesProvider.php
Normal file
94
application/Espo/Tools/OAuthServer/Scope/ScopesProvider.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Scope;
|
||||
|
||||
use Espo\Core\Acl\Scope;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
/**
|
||||
* @since 10.1.0
|
||||
*/
|
||||
class ScopesProvider
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $commonScopes = [
|
||||
Scope::ADMIN,
|
||||
Scope::GLOBAL,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function get(): array
|
||||
{
|
||||
return [
|
||||
...$this->commonScopes,
|
||||
...$this->getAclScopes(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getAclScopes(): array
|
||||
{
|
||||
/**
|
||||
* @var array<string, array{
|
||||
* disabled?: bool,
|
||||
* acl?: mixed,
|
||||
* }> $scopesDefs
|
||||
*/
|
||||
$scopesDefs = $this->metadata->get("scopes", []);
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach ($scopesDefs as $scope => $defs) {
|
||||
if (
|
||||
($defs['disabled'] ?? false) ||
|
||||
!($defs['acl'] ?? false) ||
|
||||
in_array($scope, $this->commonScopes)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[] = $scope;
|
||||
}
|
||||
|
||||
sort($list);
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Scope;
|
||||
|
||||
use Espo\Core\Acl\Scope;
|
||||
use Espo\Core\Utils\Acl\UserAclManagerProvider;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
class UserAvailableScopesFilter
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private UserAclManagerProvider $aclManagerProvider,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param string[] $scopes
|
||||
* @return string[]
|
||||
*/
|
||||
public function filter(string $userId, array $scopes): array
|
||||
{
|
||||
$user = $this->entityManager->getRDBRepositoryByClass(User::class)->getById($userId);
|
||||
|
||||
if (!$user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$aclManager = $this->aclManagerProvider->get($user);
|
||||
|
||||
$scopes = array_filter($scopes, function ($scope) use ($aclManager, $user) {
|
||||
if ($scope === Scope::GLOBAL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $aclManager->tryCheck($user, $scope);
|
||||
});
|
||||
|
||||
return array_values($scopes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Secret;
|
||||
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\Utils\DateTime;
|
||||
use Espo\Core\Utils\PasswordHash;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
use Espo\Tools\OAuthServer\Entities\Client;
|
||||
use Espo\Tools\OAuthServer\Entities\ClientSecret;
|
||||
use SensitiveParameter;
|
||||
|
||||
class SecretValidator
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private DateTime $dateTime,
|
||||
private PasswordHash $passwordHash,
|
||||
) {}
|
||||
|
||||
public function validate(Client $client, #[SensitiveParameter] string $secret): bool
|
||||
{
|
||||
$secrets = $this->entityManager
|
||||
->getRDBRepositoryByClass(ClientSecret::class)
|
||||
->sth()
|
||||
->where([
|
||||
ClientSecret::ATTR_CLIENT_ID => $client->getId(),
|
||||
ClientSecret::FIELD_STATUS => ClientSecret::STATUS_ACTIVE,
|
||||
])
|
||||
->order(Field::CREATED_AT, Order::DESC)
|
||||
->find();
|
||||
|
||||
foreach ($secrets as $entry) {
|
||||
/** @noinspection PhpParamsInspection */
|
||||
if ($this->match($entry, $secret)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function match(ClientSecret $entry, #[SensitiveParameter] string $secret): bool
|
||||
{
|
||||
if ($entry->getExpirationDate()?->isLessThanOrEqualTo($this->dateTime->getToday())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->passwordHash->verify($secret, $entry->getHash());
|
||||
}
|
||||
}
|
||||
54
application/Espo/Tools/OAuthServer/Utils/Crypt.php
Normal file
54
application/Espo/Tools/OAuthServer/Utils/Crypt.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Utils;
|
||||
|
||||
use Defuse\Crypto\Crypto;
|
||||
use Defuse\Crypto\Exception\EnvironmentIsBrokenException;
|
||||
use Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException;
|
||||
use RuntimeException;
|
||||
use SensitiveParameter;
|
||||
|
||||
class Crypt
|
||||
{
|
||||
public function __construct(
|
||||
private CryptKeyProvider $keyProvider,
|
||||
) {}
|
||||
|
||||
public function decrypt(#[SensitiveParameter] string $value): string
|
||||
{
|
||||
$key = $this->keyProvider->getCryptKey();
|
||||
|
||||
try {
|
||||
return Crypto::decryptWithPassword($value, $key);
|
||||
} catch (EnvironmentIsBrokenException|WrongKeyOrModifiedCiphertextException $e) {
|
||||
throw new RuntimeException(previous: $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Utils;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Random\RandomException;
|
||||
use RuntimeException;
|
||||
|
||||
class CryptKeyProvider
|
||||
{
|
||||
private const string PARAM_CRYPT_KEY = 'oAuthServerCryptKey';
|
||||
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private Config\ConfigWriter $configWriter,
|
||||
) {}
|
||||
|
||||
public function getCryptKey(): string
|
||||
{
|
||||
if (!$this->config->has(self::PARAM_CRYPT_KEY)) {
|
||||
$this->configWriter->set(self::PARAM_CRYPT_KEY, $this->generateKey());
|
||||
$this->configWriter->save();
|
||||
}
|
||||
|
||||
return $this->config->get(self::PARAM_CRYPT_KEY) ?? throw new RuntimeException("No crypt key.");
|
||||
}
|
||||
|
||||
private function generateKey(): string
|
||||
{
|
||||
try {
|
||||
$key = bin2hex(random_bytes(32));
|
||||
} catch (RandomException $e) {
|
||||
throw new RuntimeException(previous: $e);
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Utils;
|
||||
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\Api\ResponseComposer;
|
||||
|
||||
class ErrorResponseComposer
|
||||
{
|
||||
/**
|
||||
* @param ?int<400, 499> $statusCode
|
||||
*/
|
||||
public static function compose(
|
||||
string $error,
|
||||
string $errorDescription,
|
||||
?int $statusCode = null,
|
||||
): Response {
|
||||
|
||||
$header = "Bearer error=\"$error\", error_description=\"$errorDescription\"";
|
||||
|
||||
$response = ResponseComposer::json([
|
||||
'error' => $error,
|
||||
'error_description' => $errorDescription,
|
||||
]);
|
||||
|
||||
if ($statusCode) {
|
||||
$response->setStatus($statusCode);
|
||||
}
|
||||
|
||||
$response->setHeader('WWW-Authenticate', $header);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Utils;
|
||||
|
||||
use SensitiveParameter;
|
||||
|
||||
class IdentifierHasher
|
||||
{
|
||||
public function hash(#[SensitiveParameter] string $value): string
|
||||
{
|
||||
return hash('sha256', $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Utils;
|
||||
|
||||
use Espo\Core\Utils\Json;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use JsonException;
|
||||
use SensitiveParameter;
|
||||
|
||||
class RefreshTokenDataExtractor
|
||||
{
|
||||
public function __construct(
|
||||
private Crypt $crypt,
|
||||
) {}
|
||||
|
||||
public function extractIdentifier(#[SensitiveParameter] string $token): string
|
||||
{
|
||||
try {
|
||||
$decrypted = $this->crypt->decrypt($token);
|
||||
} catch (Exception) {
|
||||
throw new InvalidArgumentException("Could not decrypt.");
|
||||
}
|
||||
|
||||
try {
|
||||
$tokenData = Json::decode($decrypted, true);
|
||||
} catch (JsonException) {
|
||||
throw new InvalidArgumentException("Could not JSON decode.");
|
||||
}
|
||||
|
||||
$identifier = $tokenData['refresh_token_id'] ?? null;
|
||||
|
||||
if (!is_string($identifier)) {
|
||||
throw new InvalidArgumentException("No identifier.");
|
||||
}
|
||||
|
||||
return $identifier;
|
||||
}
|
||||
}
|
||||
43
application/Espo/Tools/OAuthServer/Utils/UriUtil.php
Normal file
43
application/Espo/Tools/OAuthServer/Utils/UriUtil.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\OAuthServer\Utils;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class UriUtil
|
||||
{
|
||||
public static function makeRedirectUri(string $uri, string $state): string
|
||||
{
|
||||
$uri .= !str_contains($uri, '?') ? '?' : '&';
|
||||
|
||||
return $uri . http_build_query(['state' => $state]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user