diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2cf50fb..f954f79 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,13 +14,21 @@ jobs: strategy: fail-fast: true matrix: - php: [7.1, 7.2, 7.3, 7.4, 8.0] - symfony: [2.8, 3.4, 4.4, 5.2] + php: [7.1, 7.2, 7.3, 7.4, 8.0, 8.1] + symfony: [2.8, 3.4, 4.4, 5.2, 6.0] exclude: - php: 7.1 symfony: 5.2 - php: 8.0 symfony: 3.4 + - php: 7.1 + symfony: 6.0 + - php: 7.2 + symfony: 6.0 + - php: 7.3 + symfony: 6.0 + - php: 7.4 + symfony: 6.0 steps: - name: Checkout uses: actions/checkout@v2 diff --git a/composer.json b/composer.json index 8017aad..a45a77e 100644 --- a/composer.json +++ b/composer.json @@ -17,15 +17,15 @@ "require": { "php": "^7.1 || ^8.0", "google/recaptcha": "^1.1", - "symfony/form": "^2.8 || ^3.0 || ^4.0 || ^5.0", - "symfony/framework-bundle": "^2.8 || ^3.0 || ^4.0 || ^5.0", - "symfony/security-bundle": "^2.8 || ^3.0 || ^4.0 || ^5.0", - "symfony/validator": "^2.8 || ^3.0 || ^4.0 || ^5.0", - "symfony/yaml": "^2.8 || ^3.0 || ^4.0 || ^5.0", + "symfony/form": "^2.8 || ^3.0 || ^4.0 || ^5.0 || ^6.0", + "symfony/framework-bundle": "^2.8 || ^3.0 || ^4.0 || ^5.0 || ^6.0", + "symfony/security-bundle": "^2.8 || ^3.0 || ^4.0 || ^5.0 || ^6.0", + "symfony/validator": "^2.8 || ^3.0 || ^4.0 || ^5.0 || ^6.0", + "symfony/yaml": "^2.8 || ^3.0 || ^4.0 || ^5.0 || ^6.0", "twig/twig": "^1.40 || ^2.9 || ^3.0" }, "require-dev": { - "phpunit/phpunit": "^7 || ^8" + "phpunit/phpunit": "^7 || ^8 || ^9.5" }, "autoload": { "psr-4": { diff --git a/tests/Validator/Constraints/IsTrueValidatorTest.php b/tests/Validator/Constraints/IsTrueValidatorTest.php new file mode 100644 index 0000000..38fd86e --- /dev/null +++ b/tests/Validator/Constraints/IsTrueValidatorTest.php @@ -0,0 +1,308 @@ +createMock(ReCaptcha::class); + $reCaptcha->expects(self::never()) + ->method('verify'); + $requestStack = $this->createMock(RequestStack::class); + $authorizationChecker = $this->createMock(AuthorizationCheckerInterface::class); + $context = $this->createMock(ExecutionContextInterface::class); + $context->expects(self::never()) + ->method('addViolation'); + $context->expects(self::never()) + ->method('buildViolation'); + + $authorizationChecker->expects(self::never()) + ->method('isGranted'); + + $validator = new IsTrueValidator(false, $reCaptcha, $requestStack, true, $authorizationChecker, []); + $validator->initialize($context); + $validator->validate('', new IsTrue()); + } + + public function testTrustedRolesAreNotValidated(): void + { + $trustedRoles = ['ROLE_TEST']; + $reCaptcha = $this->createMock(ReCaptcha::class); + $reCaptcha->expects(self::never()) + ->method('verify'); + $requestStack = $this->createMock(RequestStack::class); + $authorizationChecker = $this->createMock(AuthorizationCheckerInterface::class); + $context = $this->createMock(ExecutionContextInterface::class); + $context->expects(self::never()) + ->method('addViolation'); + $context->expects(self::never()) + ->method('buildViolation'); + + $authorizationChecker->expects(self::once()) + ->method('isGranted') + ->with($trustedRoles) + ->willReturn(true); + + if (\is_callable([$requestStack, 'getMainRequest'])) { + $requestStack->expects(self::never()) + ->method('getMainRequest'); + } else { + $requestStack->expects(self::never()) + ->method('getMasterRequest'); + } + + $validator = new IsTrueValidator(true, $reCaptcha, $requestStack, true, $authorizationChecker, $trustedRoles); + $validator->validate('', new IsTrue()); + } + + public function testResponseNotSuccess(): void + { + $trustedRoles = ['ROLE_TEST']; + $clientIp = '127.0.0.1'; + $recaptchaAnswer = 'encoded response'; + $constraint = new IsTrue(); + $reCaptcha = $this->createMock(ReCaptcha::class); + $requestStack = $this->createMock(RequestStack::class); + $authorizationChecker = $this->createMock(AuthorizationCheckerInterface::class); + $context = $this->createMock(ExecutionContextInterface::class); + $context->expects(self::never()) + ->method('buildViolation'); + + $authorizationChecker->expects(self::once()) + ->method('isGranted') + ->with($trustedRoles) + ->willReturn(false); + + $request = $this->createMock(Request::class); + $request->expects(self::once()) + ->method('getClientIp') + ->willReturn($clientIp); + $request->expects(self::once()) + ->method('get') + ->with('g-recaptcha-response') + ->willReturn($recaptchaAnswer); + + if (\is_callable([$requestStack, 'getMainRequest'])) { + $requestStack->expects(self::once()) + ->method('getMainRequest') + ->willReturn($request); + } else { + $requestStack->expects(self::once()) + ->method('getMasterRequest') + ->willReturn($request); + } + + $response = $this->createMock(Response::class); + $response->expects(self::once()) + ->method('isSuccess') + ->willReturn(false); + + $reCaptcha->expects(self::once()) + ->method('verify') + ->with($recaptchaAnswer, $clientIp) + ->willReturn($response); + + $context->expects(self::once()) + ->method('addViolation') + ->with($constraint->message); + + $validator = new IsTrueValidator(true, $reCaptcha, $requestStack, true, $authorizationChecker, $trustedRoles); + $validator->initialize($context); + $validator->validate('', $constraint); + } + + public function testInvalidHostWithVerifyHost(): void + { + $trustedRoles = ['ROLE_TEST']; + $clientIp = '127.0.0.1'; + $recaptchaAnswer = 'encoded response'; + $constraint = new IsTrue(); + $reCaptcha = $this->createMock(ReCaptcha::class); + $requestStack = $this->createMock(RequestStack::class); + $authorizationChecker = $this->createMock(AuthorizationCheckerInterface::class); + $context = $this->createMock(ExecutionContextInterface::class); + $context->expects(self::never()) + ->method('buildViolation'); + + $authorizationChecker->expects(self::once()) + ->method('isGranted') + ->with($trustedRoles) + ->willReturn(false); + + $request = $this->createMock(Request::class); + $request->expects(self::once()) + ->method('getClientIp') + ->willReturn($clientIp); + $request->expects(self::once()) + ->method('get') + ->with('g-recaptcha-response') + ->willReturn($recaptchaAnswer); + $request->expects(self::once()) + ->method('getHost') + ->willReturn('host1'); + + if (\is_callable([$requestStack, 'getMainRequest'])) { + $requestStack->expects(self::once()) + ->method('getMainRequest') + ->willReturn($request); + } else { + $requestStack->expects(self::once()) + ->method('getMasterRequest') + ->willReturn($request); + } + + $response = $this->createMock(Response::class); + $response->expects(self::once()) + ->method('isSuccess') + ->willReturn(true); + $response->expects(self::once()) + ->method('getHostname') + ->willReturn('host2'); + + $reCaptcha->expects(self::once()) + ->method('verify') + ->with($recaptchaAnswer, $clientIp) + ->willReturn($response); + + $context->expects(self::once()) + ->method('addViolation') + ->with($constraint->invalidHostMessage); + + $validator = new IsTrueValidator(true, $reCaptcha, $requestStack, true, $authorizationChecker, $trustedRoles); + $validator->initialize($context); + $validator->validate('', $constraint); + } + + public function testInvalidHostWithoutVerifyHost(): void + { + $trustedRoles = ['ROLE_TEST']; + $clientIp = '127.0.0.1'; + $recaptchaAnswer = 'encoded response'; + $constraint = new IsTrue(); + $reCaptcha = $this->createMock(ReCaptcha::class); + $requestStack = $this->createMock(RequestStack::class); + $authorizationChecker = $this->createMock(AuthorizationCheckerInterface::class); + $context = $this->createMock(ExecutionContextInterface::class); + $context->expects(self::never()) + ->method('buildViolation'); + + $authorizationChecker->expects(self::once()) + ->method('isGranted') + ->with($trustedRoles) + ->willReturn(false); + + $request = $this->createMock(Request::class); + $request->expects(self::once()) + ->method('getClientIp') + ->willReturn($clientIp); + $request->expects(self::once()) + ->method('get') + ->with('g-recaptcha-response') + ->willReturn($recaptchaAnswer); + $request->expects(self::never()) + ->method('getHost'); + + if (\is_callable([$requestStack, 'getMainRequest'])) { + $requestStack->expects(self::once()) + ->method('getMainRequest') + ->willReturn($request); + } else { + $requestStack->expects(self::once()) + ->method('getMasterRequest') + ->willReturn($request); + } + + $response = $this->createMock(Response::class); + $response->expects(self::once()) + ->method('isSuccess') + ->willReturn(true); + $response->expects(self::never()) + ->method('getHostname'); + + $reCaptcha->expects(self::once()) + ->method('verify') + ->with($recaptchaAnswer, $clientIp) + ->willReturn($response); + + $context->expects(self::never()) + ->method('addViolation'); + + $validator = new IsTrueValidator(true, $reCaptcha, $requestStack, false, $authorizationChecker, $trustedRoles); + $validator->initialize($context); + $validator->validate('', $constraint); + } + + public function testValidWithVerifyHost(): void + { + $trustedRoles = ['ROLE_TEST']; + $clientIp = '127.0.0.1'; + $recaptchaAnswer = 'encoded response'; + $host = 'host'; + $constraint = new IsTrue(); + $reCaptcha = $this->createMock(ReCaptcha::class); + $requestStack = $this->createMock(RequestStack::class); + $authorizationChecker = $this->createMock(AuthorizationCheckerInterface::class); + $context = $this->createMock(ExecutionContextInterface::class); + $context->expects(self::never()) + ->method('buildViolation'); + $context->expects(self::never()) + ->method('addViolation'); + + $authorizationChecker->expects(self::once()) + ->method('isGranted') + ->with($trustedRoles) + ->willReturn(false); + + $request = $this->createMock(Request::class); + $request->expects(self::once()) + ->method('getClientIp') + ->willReturn($clientIp); + $request->expects(self::once()) + ->method('get') + ->with('g-recaptcha-response') + ->willReturn($recaptchaAnswer); + $request->expects(self::once()) + ->method('getHost') + ->willReturn($host); + + if (\is_callable([$requestStack, 'getMainRequest'])) { + $requestStack->expects(self::once()) + ->method('getMainRequest') + ->willReturn($request); + } else { + $requestStack->expects(self::once()) + ->method('getMasterRequest') + ->willReturn($request); + } + + $response = $this->createMock(Response::class); + $response->expects(self::once()) + ->method('isSuccess') + ->willReturn(true); + $response->expects(self::once()) + ->method('getHostname') + ->willReturn($host); + + $reCaptcha->expects(self::once()) + ->method('verify') + ->with($recaptchaAnswer, $clientIp) + ->willReturn($response); + + $validator = new IsTrueValidator(true, $reCaptcha, $requestStack, true, $authorizationChecker, $trustedRoles); + $validator->initialize($context); + $validator->validate('', $constraint); + } + +} diff --git a/tests/Validator/Constraints/IsTrueValidatorV3Test.php b/tests/Validator/Constraints/IsTrueValidatorV3Test.php new file mode 100644 index 0000000..b4b8ad3 --- /dev/null +++ b/tests/Validator/Constraints/IsTrueValidatorV3Test.php @@ -0,0 +1,70 @@ +createMock(RequestStack::class); + $logger = $this->createMock(LoggerInterface::class); + $context = $this->createMock(ExecutionContextInterface::class); + $context->expects(self::never()) + ->method('addViolation'); + $context->expects(self::never()) + ->method('buildViolation'); + + $validator = new IsTrueValidatorV3(false, 'secret', 0.1, $requestStack, $logger); + $validator->initialize($context); + $validator->validate('', $this->createMock(Constraint::class)); + } + + public function testRequiresV3(): void + { + $requestStack = $this->createMock(RequestStack::class); + $logger = $this->createMock(LoggerInterface::class); + $context = $this->createMock(ExecutionContextInterface::class); + $context->expects(self::never()) + ->method('addViolation'); + $context->expects(self::never()) + ->method('buildViolation'); + + $this->expectException(UnexpectedTypeException::class); + $this->expectExceptionMessage('Expected argument of type "EWZ\Bundle\RecaptchaBundle\Validator\Constraints\IsTrueV3",'); + + $validator = new IsTrueValidatorV3(true, 'secret', 0.1, $requestStack, $logger); + $validator->initialize($context); + $validator->validate('', $this->createMock(IsTrue::class)); + } + + public function testRequiresValueNotNullButNotString(): void + { + $requestStack = $this->createMock(RequestStack::class); + $logger = $this->createMock(LoggerInterface::class); + $context = $this->createMock(ExecutionContextInterface::class); + $context->expects(self::never()) + ->method('addViolation'); + $context->expects(self::never()) + ->method('buildViolation'); + + $this->expectException(UnexpectedTypeException::class); + $this->expectExceptionMessage('Expected argument of type "string", "stdClass" given'); + + $validator = new IsTrueValidatorV3(true, 'secret', 0.1, $requestStack, $logger); + $validator->initialize($context); + $validator->validate(new stdClass(), new IsTrueV3()); + } + +}