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