From a821a5bf018385ac9c5d4642826236c728abae4f Mon Sep 17 00:00:00 2001 From: Alex Zamponi <562324+alexz707@users.noreply.github.com> Date: Fri, 26 Jan 2024 11:16:26 +0100 Subject: [PATCH] Add POC elements --- config/api_platform/resources/asset.yaml | 31 +++++ config/api_platform/resources/user.yaml | 19 +++ config/pimcore/routing.yaml | 4 + config/serialization/asset.yaml | 64 ++++++++++ config/serialization/user.yaml | 4 + config/services.yaml | 9 +- .../PimcoreStudioApiExtension.php | 69 +++++++++++ src/Dto/ResetPasswordRequest.php | 17 +++ src/Serializer/AssetNormalizer.php | 53 ++++++++ src/State/AssetProvider.php | 33 +++++ src/State/ResetPasswordProcessor.php | 114 ++++++++++++++++++ 11 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 config/api_platform/resources/asset.yaml create mode 100644 config/api_platform/resources/user.yaml create mode 100644 config/pimcore/routing.yaml create mode 100644 config/serialization/asset.yaml create mode 100644 config/serialization/user.yaml create mode 100644 src/DependencyInjection/PimcoreStudioApiExtension.php create mode 100644 src/Dto/ResetPasswordRequest.php create mode 100644 src/Serializer/AssetNormalizer.php create mode 100644 src/State/AssetProvider.php create mode 100644 src/State/ResetPasswordProcessor.php diff --git a/config/api_platform/resources/asset.yaml b/config/api_platform/resources/asset.yaml new file mode 100644 index 000000000..77d1edbd4 --- /dev/null +++ b/config/api_platform/resources/asset.yaml @@ -0,0 +1,31 @@ +resources: + Pimcore\Model\Asset: + provider: Pimcore\Bundle\StudioApiBundle\State\AssetProvider + normalizationContext: + groups: ['get'] + denormalizationContext: + groups: ['set'] + properties: + id: + identifier: true + parentId: ~ + type: ~ + data: ~ + fullPath: ~ + + Pimcore\Model\Asset\Image: + provider: Pimcore\Bundle\StudioApiBundle\State\AssetProvider + properties: + id: + identifier: true + normalizationContext: + groups: ['get'] + denormalizationContext: + groups: ['set'] + + Pimcore\Model\Asset\Image\Thumbnail: + provider: Pimcore\Bundle\StudioApiBundle\State\AssetProvider + normalizationContext: + groups: ['get'] + denormalizationContext: + groups: ['set'] diff --git a/config/api_platform/resources/user.yaml b/config/api_platform/resources/user.yaml new file mode 100644 index 000000000..a78d2be1e --- /dev/null +++ b/config/api_platform/resources/user.yaml @@ -0,0 +1,19 @@ +resources: + Pimcore\Model\User: + operations: + ApiPlatform\Metadata\Post: + status: 202 + processor: Pimcore\Bundle\StudioApiBundle\State\ResetPasswordProcessor + input: Pimcore\Bundle\StudioApiBundle\Dto\ResetPasswordRequest + output: false + uriTemplate: '/users/reset-password' +# ApiPlatform\Metadata\Get: +# controller: ApiPlatform\Action\NotFoundAction +# read: false +# output: false + + + normalizationContext: + groups: [ 'get' ] + denormalizationContext: + groups: [ 'set' ] \ No newline at end of file diff --git a/config/pimcore/routing.yaml b/config/pimcore/routing.yaml new file mode 100644 index 000000000..f6b934190 --- /dev/null +++ b/config/pimcore/routing.yaml @@ -0,0 +1,4 @@ +api_platform: + resource: . + type: api_platform + prefix: /api \ No newline at end of file diff --git a/config/serialization/asset.yaml b/config/serialization/asset.yaml new file mode 100644 index 000000000..8cf84b184 --- /dev/null +++ b/config/serialization/asset.yaml @@ -0,0 +1,64 @@ +Pimcore\Model\Asset: + attributes: + id: + groups: ['get', 'bla:read'] + parentId: + groups: ['get'] + type: + groups: ['get'] + filename: + groups: ['get'] + path: + groups: ['get'] + mimetype: + groups: ['get'] + creationDate: + groups: ['get'] + modificationDate: + groups: ['get'] + userOwner: + groups: ['get'] + userModification: + groups: ['get'] + properties: + groups: ['get'] + versions: + groups: ['get'] + metadata: + groups: ['get'] + locked: + groups: ['get'] + customSettings: + groups: ['get'] + hasMetaData: + groups: ['get'] + dependencies: + groups: ['get'] + scheduledTasks: + groups: ['get'] + versionCount: + groups: ['get'] + fullPath: + groups: ['get'] + +Pimcore\Model\Asset\Image: + attributes: + thumbnail: + groups: ['get'] + format: + groups: ['get'] + dimensions: + groups: ['get'] + width: + groups: [ 'get' ] + height: + groups: [ 'get' ] + +Pimcore\Model\Asset\Image\Thumbnail: + attributes: + path: + groups: ['get'] + imageTag: + groups: ['get'] + media: + groups: ['get'] \ No newline at end of file diff --git a/config/serialization/user.yaml b/config/serialization/user.yaml new file mode 100644 index 000000000..4346a156a --- /dev/null +++ b/config/serialization/user.yaml @@ -0,0 +1,4 @@ +Pimcore\Bundle\StudioApiBundle\Dto\ResetPasswordRequest: + attributes: + username: + groups: ['get', 'set'] \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index 8c98cca77..6561874e7 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -17,4 +17,11 @@ services: Pimcore\Bundle\StudioApiBundle\Controller\: resource: '../src/Controller' public: true - tags: [ 'controller.service_arguments' ] \ No newline at end of file + tags: [ 'controller.service_arguments' ] + + Pimcore\Bundle\StudioApiBundle\State\AssetProvider: ~ + Pimcore\Bundle\StudioApiBundle\State\ResetPasswordProcessor: ~ + + Pimcore\Bundle\StudioApiBundle\Serializer\AssetNormalizer: + tags: + - { name: 'serializer.normalizer' } \ No newline at end of file diff --git a/src/DependencyInjection/PimcoreStudioApiExtension.php b/src/DependencyInjection/PimcoreStudioApiExtension.php new file mode 100644 index 000000000..bf642d874 --- /dev/null +++ b/src/DependencyInjection/PimcoreStudioApiExtension.php @@ -0,0 +1,69 @@ +processConfiguration($configuration, $configs); + + // Load services and configuration + $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../../config')); + $loader->load('services.yaml'); + + // Set default serializer mapping if not provided in the app's config + if (!isset($config['serializer']['mapping']['paths'])) { + $config['serializer']['mapping']['paths'] = [__DIR__ . '/../../config/serialization']; + } + + // Pass the configuration to the custom normalizer + $container->setParameter('pimcore_studio_api.serializer.mapping.paths', $config['serializer']['mapping']['paths']); + } + + public function prepend(ContainerBuilder $container): void + { + $apiPlatformConfig = [ + "mapping"=>[ + "paths"=> [ + __DIR__ . '/../../config/api_platform/' + ] + ] + ]; + $container->prependExtensionConfig('api_platform', $apiPlatformConfig); + } +} \ No newline at end of file diff --git a/src/Dto/ResetPasswordRequest.php b/src/Dto/ResetPasswordRequest.php new file mode 100644 index 000000000..9683b67d4 --- /dev/null +++ b/src/Dto/ResetPasswordRequest.php @@ -0,0 +1,17 @@ +username; + } +} diff --git a/src/Serializer/AssetNormalizer.php b/src/Serializer/AssetNormalizer.php new file mode 100644 index 000000000..88c83b1cd --- /dev/null +++ b/src/Serializer/AssetNormalizer.php @@ -0,0 +1,53 @@ +normalizer->normalize($object, $format, $context); + + if (isset($data['data']) && $data['data']) { + $data['data'] = base64_encode($data['data']); + } + + if ($object instanceof Asset\Image) { + $data['thumbnail'] = $object->getThumbnail()->getPath(['frontend' => true]); + } + + return $data; + } + + public function supportsNormalization($data, $format = null, array $context = []): bool + { + if (isset($context[self::ALREADY_CALLED])) { + return false; + } + + return $data instanceof Asset; + } + + public function getSupportedTypes(?string $format): array + { + return [ + Asset::class => false, + ]; + } +} diff --git a/src/State/AssetProvider.php b/src/State/AssetProvider.php new file mode 100644 index 000000000..c27f1fec7 --- /dev/null +++ b/src/State/AssetProvider.php @@ -0,0 +1,33 @@ +setLimit(10); + + if (isset($context['filters']['page'])) { + $assetListing->setOffset(10 * $context['filters']['page']); + } + return $assetListing; + } + // getting a single asset by id + $test = Asset::getById($uriVariables['id']); + + //$tag = Tag::getTagsForElement('asset', $test->getId()); + return $test; + } +} diff --git a/src/State/ResetPasswordProcessor.php b/src/State/ResetPasswordProcessor.php new file mode 100644 index 000000000..192f3635b --- /dev/null +++ b/src/State/ResetPasswordProcessor.php @@ -0,0 +1,114 @@ +getUriTemplate() !== '/users/reset-password' || !$data instanceof ResetPasswordRequest) { + // wrong operation + throw new OperationNotFoundException(); + } + + $user = User::getByName($data->getUsername()); + if (!$user instanceof User) { + return $data; + } + + $currentRequest = $this->requestStack->getCurrentRequest(); + $limiter = $this->resetPasswordLimiter->create($currentRequest->getClientIp()); + + if (false === $limiter->consume(1)->isAccepted()) { + throw new InvalidValueException('Rate limit exceeded'); + } + + if (!$user->isActive()) { + throw new InvalidValueException('User is inactive'); + } + + if (!$user->getEmail()) { + throw new InvalidValueException('User has no email address'); + } + + if (!$user->getPassword()) { + throw new InvalidValueException('User has no password'); + } + + $error = null; + $token = Authentication::generateTokenByUser($user); + + try { + $domain = SystemSettingsConfig::get()['general']['domain']; + if (!$domain) { + throw new Exception('No main domain set in system settings, unable to generate reset password link'); + } + + $routerContext = $this->router->getContext(); + $routerContext->setHost($domain); + + $loginUrl = $this->router->generate( + 'pimcore_admin_login_check', + [ + 'token' => $token, + 'reset' => 'true', + ], + UrlGeneratorInterface::ABSOLUTE_URL + ); + + //@TODO: get rid off admin ui dependencies -> move to core (?) + $event = new LostPasswordEvent($user, $loginUrl); + $this->eventDispatcher->dispatch($event, AdminEvents::LOGIN_LOSTPASSWORD); + + // only send mail if it wasn't prevented in event + if ($event->getSendMail()) { + $mail = Tool::getMail([$user->getEmail()], 'Pimcore lost password service'); + $mail->setIgnoreDebugMode(true); + $mail->text("Login to pimcore and change your password using the following link. This temporary login link will expire in 24 hours: \r\n\r\n" . $loginUrl); + $mail->send(); + } + + // directly return event response + if ($event->hasResponse()) { + return $event->getResponse(); + } + } catch (Exception $e) { + Logger::error('Error sending password recovery email: ' . $e->getMessage()); + $error = 'lost_password_email_error'; + } + + if ($error) { + Logger::error('Lost password service: ' . $error); + } + return $data; + } +}