. * * 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); } }