Skip to content

Commit

Permalink
Add ForAuthorizedUserOnlyValidator validation constraint
Browse files Browse the repository at this point in the history
  • Loading branch information
coldic3 committed Oct 28, 2024
1 parent 4edfd06 commit 90838d8
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 11 deletions.
1 change: 0 additions & 1 deletion config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

return static function(ContainerConfigurator $container): void {
$container->import('config/**/*.php');
$container->import('config/**/*.yaml');

$parameters = $container->parameters();
$parameters
Expand Down
23 changes: 23 additions & 0 deletions config/services/validator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use CommerceWeavers\SyliusTpayPlugin\Validator\Constraint\EncodedGooglePayTokenValidator;
use CommerceWeavers\SyliusTpayPlugin\Validator\Constraint\ForAuthorizedUserOnlyValidator;

return static function(ContainerConfigurator $container): void {
$services = $container->services();

$services->set('commerce_weavers_sylius_tpay.validator.constraint.encoded_google_pay_token', EncodedGooglePayTokenValidator::class)
->tag('validator.constraint_validator')
;

$services->set('commerce_weavers_sylius_tpay.validator.constraint.for_authorized_user_only', ForAuthorizedUserOnlyValidator::class)
->args([
service('security.helper'),
])
->tag('validator.constraint_validator')
;
};
7 changes: 7 additions & 0 deletions config/validation/Pay.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@
</option>
</constraint>
</property>
<property name="blikAliasAction">
<constraint name="CommerceWeavers\SyliusTpayPlugin\Validator\Constraint\ForAuthorizedUserOnly">
<option name="groups">
<value>commerce_weavers_sylius_tpay:shop:order:pay</value>
</option>
</constraint>
</property>
<property name="googlePayToken">
<constraint name="CommerceWeavers\SyliusTpayPlugin\Api\Validator\Constraint\NotBlankIfGatewayConfigTypeEquals">
<option name="paymentMethodType">google_pay</option>
Expand Down
23 changes: 23 additions & 0 deletions src/Validator/Constraint/ForAuthorizedUserOnly.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Validator\Constraint;

use Symfony\Component\Validator\Constraint;

final class ForAuthorizedUserOnly extends Constraint
{
public const USER_NOT_AUTHORIZED_ERROR = 'c146928c-f22b-4802-ba90-5fb9952e7ee8';

public string $userNotAuthorizedErrorMessage = 'commerce_weavers_sylius_tpay.shop.pay.field.user_not_authorized';

protected static $errorNames = [
self::USER_NOT_AUTHORIZED_ERROR => 'USER_NOT_AUTHORIZED_ERROR',
];

public function getTargets(): string
{
return self::PROPERTY_CONSTRAINT;
}
}
34 changes: 34 additions & 0 deletions src/Validator/Constraint/ForAuthorizedUserOnlyValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Validator\Constraint;

use Symfony\Component\Security\Core\Security;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;

final class ForAuthorizedUserOnlyValidator extends ConstraintValidator
{
public function __construct(private readonly Security $security)
{
}

public function validate(mixed $value, Constraint $constraint): void
{
if (!$constraint instanceof ForAuthorizedUserOnly) {
throw new UnexpectedTypeException($constraint, ForAuthorizedUserOnly::class);
}

if (null === $value || null !== $this->security->getUser()) {
return;
}

$this->context
->buildViolation($constraint->userNotAuthorizedErrorMessage)
->setCode($constraint::USER_NOT_AUTHORIZED_ERROR)
->addViolation()
;
}
}
9 changes: 9 additions & 0 deletions tests/Api/DataFixtures/shop/common/shop_user.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Sylius\Component\Core\Model\ShopUser:
shop_user_john_doe:
plainPassword: "sylius"
roles: [ROLE_USER]
enabled: true
verifiedAt: "<(new \\DateTime())>"
customer: "@customer_john_doe"
username: "[email protected]"
usernameCanonical: "[email protected]"
38 changes: 28 additions & 10 deletions tests/Api/Shop/PayingForOrdersByBlikTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
namespace Tests\CommerceWeavers\SyliusTpayPlugin\Api\Shop;

use CommerceWeavers\SyliusTpayPlugin\Api\Enum\BlikAliasAction;
use CommerceWeavers\SyliusTpayPlugin\Api\Validator\Constraint\NotBlankIfBlikAliasActionIsRegister;
use CommerceWeavers\SyliusTpayPlugin\Api\Validator\Constraint\OneOfPropertiesRequiredIfGatewayConfigTypeEquals;
use Sylius\Component\Core\Model\ShopUserInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Constraints\Length;
use Tests\CommerceWeavers\SyliusTpayPlugin\Api\JsonApiTestCase;
use Tests\CommerceWeavers\SyliusTpayPlugin\Api\Utils\OrderPlacerTrait;

Expand Down Expand Up @@ -46,14 +50,16 @@ public function test_paying_with_a_valid_blik_token_for_an_order(): void

public function test_paying_with_a_valid_blik_token_and_saving_alias(): void
{
$this->loadFixturesFromFile('shop/blik_payment_method.yml');
$fixtures = $this->loadFixturesFromFiles(['shop/blik_payment_method.yml', 'shop/common/shop_user.yml']);

/** @var ShopUserInterface $shopUser */
$shopUser = $fixtures['shop_user_john_doe'];
$order = $this->doPlaceOrder('t0k3n', paymentMethodCode: 'tpay_blik');

$this->client->request(
Request::METHOD_POST,
sprintf('/api/v2/shop/orders/%s/pay', $order->getTokenValue()),
server: self::CONTENT_TYPE_HEADER,
server: self::CONTENT_TYPE_HEADER + $this->generateAuthorizationHeader($shopUser),
content: json_encode([
'successUrl' => 'https://example.com/success',
'failureUrl' => 'https://example.com/failure',
Expand All @@ -70,14 +76,16 @@ public function test_paying_with_a_valid_blik_token_and_saving_alias(): void

public function test_paying_and_saving_alias_without_a_blik_token(): void
{
$this->loadFixturesFromFile('shop/blik_payment_method.yml');
$fixtures = $this->loadFixturesFromFiles(['shop/blik_payment_method.yml', 'shop/common/shop_user.yml']);

/** @var ShopUserInterface $shopUser */
$shopUser = $fixtures['shop_user_john_doe'];
$order = $this->doPlaceOrder('t0k3n', paymentMethodCode: 'tpay_blik');

$this->client->request(
Request::METHOD_POST,
sprintf('/api/v2/shop/orders/%s/pay', $order->getTokenValue()),
server: self::CONTENT_TYPE_HEADER,
server: self::CONTENT_TYPE_HEADER + $this->generateAuthorizationHeader($shopUser),
content: json_encode([
'successUrl' => 'https://example.com/success',
'failureUrl' => 'https://example.com/failure',
Expand All @@ -90,21 +98,24 @@ public function test_paying_and_saving_alias_without_a_blik_token(): void
$this->assertResponseViolations($response, [
[
'propertyPath' => 'blikToken',
'code' => NotBlankIfBlikAliasActionIsRegister::FIELD_REQUIRED_ERROR,
'message' => 'The BLIK token is required with an alias register action.',
]
]);
}

public function test_paying_using_a_valid_blik_alias(): void
{
$this->loadFixturesFromFiles(['shop/blik_payment_method.yml', 'shop/blik_alias.yml']);
$fixtures = $this->loadFixturesFromFiles(['shop/blik_payment_method.yml', 'shop/blik_alias.yml', 'shop/common/shop_user.yml']);

/** @var ShopUserInterface $shopUser */
$shopUser = $fixtures['shop_user_john_doe'];
$order = $this->doPlaceOrder('t0k3n', paymentMethodCode: 'tpay_blik');

$this->client->request(
Request::METHOD_POST,
sprintf('/api/v2/shop/orders/%s/pay', $order->getTokenValue()),
server: self::CONTENT_TYPE_HEADER,
server: self::CONTENT_TYPE_HEADER + $this->generateAuthorizationHeader($shopUser),
content: json_encode([
'successUrl' => 'https://example.com/success',
'failureUrl' => 'https://example.com/failure',
Expand All @@ -120,14 +131,16 @@ public function test_paying_using_a_valid_blik_alias(): void

public function test_paying_using_a_valid_blik_alias_but_registered_in_more_than_one_bank_app(): void
{
$this->loadFixturesFromFiles(['shop/blik_payment_method.yml', 'shop/blik_ambiguous_alias.yml']);
$fixtures = $this->loadFixturesFromFiles(['shop/blik_payment_method.yml', 'shop/blik_ambiguous_alias.yml', 'shop/common/shop_user.yml']);

/** @var ShopUserInterface $shopUser */
$shopUser = $fixtures['shop_user_john_doe'];
$order = $this->doPlaceOrder('t0k3n', paymentMethodCode: 'tpay_blik');

$this->client->request(
Request::METHOD_POST,
sprintf('/api/v2/shop/orders/%s/pay', $order->getTokenValue()),
server: self::CONTENT_TYPE_HEADER,
server: self::CONTENT_TYPE_HEADER + $this->generateAuthorizationHeader($shopUser),
content: json_encode([
'successUrl' => 'https://example.com/success',
'failureUrl' => 'https://example.com/failure',
Expand All @@ -142,14 +155,16 @@ public function test_paying_using_a_valid_blik_alias_but_registered_in_more_than

public function test_paying_using_a_valid_blik_alias_registered_in_different_banks(): void
{
$this->loadFixturesFromFiles(['shop/blik_payment_method.yml', 'shop/blik_ambiguous_alias.yml']);
$fixtures = $this->loadFixturesFromFiles(['shop/blik_payment_method.yml', 'shop/blik_ambiguous_alias.yml', 'shop/common/shop_user.yml']);

/** @var ShopUserInterface $shopUser */
$shopUser = $fixtures['shop_user_john_doe'];
$order = $this->doPlaceOrder('t0k3n', paymentMethodCode: 'tpay_blik');

$this->client->request(
Request::METHOD_POST,
sprintf('/api/v2/shop/orders/%s/pay', $order->getTokenValue()),
server: self::CONTENT_TYPE_HEADER,
server: self::CONTENT_TYPE_HEADER + $this->generateAuthorizationHeader($shopUser),
content: json_encode([
'successUrl' => 'https://example.com/success',
'failureUrl' => 'https://example.com/failure',
Expand Down Expand Up @@ -186,6 +201,7 @@ public function test_paying_with_a_too_short_blik_token(): void
$this->assertResponseViolations($response, [
[
'propertyPath' => 'blikToken',
'code' => Length::NOT_EQUAL_LENGTH_ERROR,
'message' => 'The BLIK token must have exactly 6 characters.',
]
]);
Expand Down Expand Up @@ -213,6 +229,7 @@ public function test_paying_with_a_too_long_blik_token(): void
$this->assertResponseViolations($response, [
[
'propertyPath' => 'blikToken',
'code' => Length::NOT_EQUAL_LENGTH_ERROR,
'message' => 'The BLIK token must have exactly 6 characters.',
]
]);
Expand All @@ -239,6 +256,7 @@ public function test_paying_without_providing_a_blik_token_or_using_an_alias():
$this->assertResponseViolations($response, [
[
'propertyPath' => '',
'code' => OneOfPropertiesRequiredIfGatewayConfigTypeEquals::ALL_FIELDS_ARE_BLANK_ERROR,
'message' => 'You must provide a BLIK token or use an alias to pay with BLIK.',
]
]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace Tests\CommerceWeavers\SyliusTpayPlugin\Unit\Validator\Constraint;

use CommerceWeavers\SyliusTpayPlugin\Validator\Constraint\ForAuthorizedUserOnly;
use CommerceWeavers\SyliusTpayPlugin\Validator\Constraint\ForAuthorizedUserOnlyValidator;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;

final class ForAuthorizedUserOnlyValidatorTest extends ConstraintValidatorTestCase
{
use ProphecyTrait;

private Security|ObjectProphecy $security;

protected function setUp(): void
{
$this->security = $this->prophesize(Security::class);

parent::setUp();
}

public function test_it_throws_an_exception_if_a_constraint_has_an_invalid_type(): void
{
$this->expectException(UnexpectedTypeException::class);

$this->validator->validate(
'hello',
$this->prophesize(Constraint::class)->reveal(),
);
}

public function test_it_does_not_build_violation_if_value_is_null(): void
{
$this->validator->validate(null, new ForAuthorizedUserOnly());

$this->assertNoViolation();
}

public function test_it_does_not_build_violation_if_user_is_authorized(): void
{
$user = $this->prophesize(UserInterface::class);
$this->security->getUser()->willReturn($user);

$this->validator->validate('hello', new ForAuthorizedUserOnly());

$this->assertNoViolation();
}

public function test_it_builds_violation_if_user_is_not_authorized(): void
{
$this->security->getUser()->willReturn(null);

$this->validator->validate('hello', new ForAuthorizedUserOnly());

$this
->buildViolation('commerce_weavers_sylius_tpay.shop.pay.field.user_not_authorized')
->setCode(ForAuthorizedUserOnly::USER_NOT_AUTHORIZED_ERROR)
->assertRaised()
;
}

protected function createValidator(): ForAuthorizedUserOnlyValidator
{
return new ForAuthorizedUserOnlyValidator($this->security->reveal());
}
}

0 comments on commit 90838d8

Please sign in to comment.