Skip to content

Commit

Permalink
Added tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
laurentmuller committed Oct 25, 2024
1 parent 49ebb96 commit 4af9e81
Show file tree
Hide file tree
Showing 18 changed files with 107 additions and 149 deletions.
3 changes: 3 additions & 0 deletions src/Entity/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
#[UniqueEntity(fields: 'code', message: 'group.unique_code')]
class Group extends AbstractCodeEntity
{
/**
* @use ValidateMarginsTrait<int, GroupMargin>
*/
use ValidateMarginsTrait;

/**
Expand Down
3 changes: 3 additions & 0 deletions src/Entity/TaskItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
class TaskItem extends AbstractEntity implements \Countable, ComparableInterface, ParentTimestampableInterface, PositionInterface
{
use PositionTrait;
/**
* @use ValidateMarginsTrait<int, TaskItemMargin>
*/
use ValidateMarginsTrait;

/**
Expand Down
12 changes: 6 additions & 6 deletions src/Form/User/ProfileChangePasswordType.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,7 @@ public function configureOptions(OptionsResolver $resolver): void
{
parent::configureOptions($resolver);
$resolver->setDefaults([
'constraints' => [
new Callback(function (User $user, ExecutionContextInterface $context): void {
$this->validate($context);
}),
],
'constraints' => [new Callback($this->validate(...))],
]);
}

Expand Down Expand Up @@ -76,8 +72,12 @@ protected function getLabelPrefix(): ?string
/**
* Conditional validation depending on the check password checkbox.
*/
private function validate(ExecutionContextInterface $context): void
private function validate(?User $user, ExecutionContextInterface $context): void
{
if (!$user instanceof User) {
return;
}

/** @psalm-var FormInterface<mixed> $root */
$root = $context->getRoot();

Expand Down
1 change: 0 additions & 1 deletion src/Interfaces/PropertyServiceInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,6 @@ interface PropertyServiceInterface
'security_special_char',
'security_case_diff',
'security_email',
'security_compromised',
];

/**
Expand Down
3 changes: 3 additions & 0 deletions src/Model/GlobalMargins.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
*/
class GlobalMargins implements \Countable
{
/**
* @use ValidateMarginsTrait<int, GlobalMargin>
*/
use ValidateMarginsTrait;

/**
Expand Down
26 changes: 18 additions & 8 deletions src/Pdf/Colors/AbstractPdfColor.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ public static function create(array|int|string|null $rgb): ?static
$rgb = (string) \preg_replace('/[^0-9A-F]/i', '', $rgb);

return match (\strlen($rgb)) {
6 => self::createFrom6Chars($rgb),
3 => self::createFrom3Chars($rgb),
6 => self::createFrom6Chars($rgb),
default => null,
};
}
Expand Down Expand Up @@ -238,19 +238,20 @@ public static function white(): self

private static function createFrom3Chars(string $rgb): static
{
/** @psalm-var int<0, 255> $r */
$r = \hexdec(\str_repeat(\substr($rgb, 0, 1), 2));
/** @psalm-var int<0, 255> $g */
$g = \hexdec(\str_repeat(\substr($rgb, 1, 1), 2));
/** @psalm-var int<0, 255> $b */
$b = \hexdec(\str_repeat(\substr($rgb, 2, 1), 2));
$r = self::hexdec(\str_repeat(\substr($rgb, 0, 1), 2));
$b = self::hexdec(\str_repeat(\substr($rgb, 1, 1), 2));
$g = self::hexdec(\str_repeat(\substr($rgb, 2, 1), 2));

return new static($r, $g, $b);
}

private static function createFrom6Chars(string $rgb): static
{
return self::createFromInt((int) \hexdec($rgb));
$r = self::hexdec(\substr($rgb, 0, 2));
$g = self::hexdec(\substr($rgb, 2, 2));
$b = self::hexdec(\substr($rgb, 4, 2));

return new static($r, $g, $b);
}

private static function createFromInt(int $rgb): static
Expand All @@ -264,4 +265,13 @@ private static function createFromInt(int $rgb): static

return new static($r, $g, $b);
}

/**
* @psalm-return int<0, 255>
*/
private static function hexdec(string $value): int
{
/** @psalm-var int<0, 255> */
return (int) \hexdec($value);
}
}
2 changes: 1 addition & 1 deletion src/Repository/CalculationRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,7 @@ private function getOrder(string $column): string
'date',
'customer',
'description' => "e.$column",
'state' => 's.code',
'stateCode' => 's.code',
default => 'e.id',
};
}
Expand Down
5 changes: 5 additions & 0 deletions src/Traits/ValidateMarginsTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@

/**
* Trait to validate margins within a callback.
*
* @psalm-template TMarginKey of array-key
* @psalm-template TMarginValue of MarginInterface
*/
trait ValidateMarginsTrait
{
/**
* Get margins.
*
* @psalm-return Collection<TMarginKey, TMarginValue>
*/
abstract public function getMargins(): Collection;

Expand Down
15 changes: 0 additions & 15 deletions src/Validator/Password.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ class Password extends Constraint
{
final public const CASE_DIFF_ERROR = '4c725240-da48-42df-ba9a-ce09a16ab1b5';

final public const COMPROMISED_ERROR = 'd042c39d-b2d3-4ef3-97b8-10948aed2988';

final public const EMAIL_ERROR = '85386dde-1b29-42d4-9b7c-de03693fb963';

final public const LETTERS_ERROR = 'cc369ec9-ea3d-4d27-8f96-6e03bfb63323';
Expand All @@ -38,7 +36,6 @@ class Password extends Constraint
self::NUMBERS_ERROR => 'NUMBERS_ERROR',
self::SPECIAL_CHAR_ERROR => 'SPECIAL_CHAR_ERROR',
self::EMAIL_ERROR => 'EMAIL_ERROR',
self::COMPROMISED_ERROR => 'COMPROMISED_ERROR',
];

/**
Expand All @@ -56,16 +53,6 @@ class Password extends Constraint
*/
public string $case_diff_message = 'password.case_diff';

/**
* Checks if the password is compromised.
*/
public bool $compromised = false;

/**
* Password comprise error message.
*/
public string $compromised_message = 'password.compromised';

/**
* Checks if the password is an e-mail.
*/
Expand Down Expand Up @@ -116,7 +103,6 @@ public function __construct(
?bool $numbers = null,
?bool $special_char = null,
?bool $email = null,
?bool $compromised = null,
?array $options = null,
?array $groups = null,
mixed $payload = null
Expand All @@ -129,6 +115,5 @@ public function __construct(
$this->numbers = $numbers ?? $this->numbers;
$this->special_char = $special_char ?? $this->special_char;
$this->email = $email ?? $this->email;
$this->compromised = $compromised ?? $this->compromised;
}
}
72 changes: 7 additions & 65 deletions src/Validator/PasswordValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

namespace App\Validator;

use App\Utils\FormatUtils;
use Symfony\Component\Validator\Constraint;

/**
Expand All @@ -22,8 +21,6 @@
*/
class PasswordValidator extends AbstractConstraintValidator
{
private const API_ENDPOINT = 'https://api.pwnedpasswords.com/range/%s';

public function __construct()
{
parent::__construct(Password::class);
Expand All @@ -44,10 +41,9 @@ protected function doValidate(string $value, Constraint $constraint): void
/**
* Adds a violation.
*/
private function addViolation(string $message, string $value, string $code, array $parameters = []): true
private function addViolation(string $message, string $value, string $code): true
{
$this->context->buildViolation($message)
->setParameters($parameters)
->setInvalidValue($value)
->setCode($code)
->addViolation();
Expand All @@ -65,7 +61,6 @@ private function checkAll(string $value, Password $constraint): void
$this->checkNumber($constraint, $value);
$this->checkSpecialChar($constraint, $value);
$this->checkEmail($constraint, $value);
$this->checkCompromised($constraint, $value);
}

/**
Expand All @@ -74,11 +69,10 @@ private function checkAll(string $value, Password $constraint): void
private function checkAny(string $value, Password $constraint): void
{
if (!($this->checkLetters($constraint, $value)
|| $this->checkCaseDiff($constraint, $value)
|| $this->checkNumber($constraint, $value)
|| $this->checkSpecialChar($constraint, $value)
|| $this->checkEmail($constraint, $value))) {
$this->checkCompromised($constraint, $value);
|| $this->checkCaseDiff($constraint, $value)
|| $this->checkNumber($constraint, $value)
|| $this->checkSpecialChar($constraint, $value))) {
$this->checkEmail($constraint, $value);
}
}

Expand All @@ -96,35 +90,14 @@ private function checkCaseDiff(Password $constraint, string $value): bool
);
}

/**
* Check if the password is compromised.
*/
private function checkCompromised(Password $constraint, string $value): void
{
if (!$constraint->compromised) {
return;
}

$count = $this->getPasswordCount($value);
if (0 === $count) {
return;
}

$parameters = ['{{count}}' => FormatUtils::formatInt($count)];

$this->addViolation($constraint->compromised_message, $value, Password::COMPROMISED_ERROR, $parameters);
}

/**
* Checks if the value is an e-mail.
*/
private function checkEmail(Password $constraint, string $value): bool
private function checkEmail(Password $constraint, string $value): void
{
if ($constraint->email && false !== \filter_var($value, \FILTER_VALIDATE_EMAIL)) {
return $this->addViolation($constraint->email_message, $value, Password::EMAIL_ERROR);
$this->addViolation($constraint->email_message, $value, Password::EMAIL_ERROR);
}

return false;
}

/**
Expand Down Expand Up @@ -169,37 +142,6 @@ private function checkSpecialChar(Password $constraint, string $value): bool
);
}

/**
* Check if the password has been compromised in a data breach.
*/
private function getPasswordCount(string $password): int
{
// hash
$hash = \strtoupper(\sha1($password));
$hashPrefix = \substr($hash, 0, 5);

// load
$url = \sprintf(self::API_ENDPOINT, $hashPrefix);
$lines = \file($url, \FILE_IGNORE_NEW_LINES | \FILE_SKIP_EMPTY_LINES);
if (false === $lines || [] === $lines) {
return 0;
}

// search
foreach ($lines as $line) {
if (!\str_contains($line, ':')) {
continue;
}

[$hashSuffix, $count] = \explode(':', $line);
if ($hashPrefix . $hashSuffix === $hash) {
return (int) $count;
}
}

return 0;
}

/**
* @psalm-param non-empty-string $pattern
*/
Expand Down
3 changes: 3 additions & 0 deletions tests/Controller/UserControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public static function getRoutes(): \Iterator
yield ['/user/edit/1', self::ROLE_ADMIN];
yield ['/user/edit/1', self::ROLE_SUPER_ADMIN];

// for last login
yield ['/user/edit/2', self::ROLE_ADMIN];

yield ['/user/delete/1', self::ROLE_USER, Response::HTTP_FORBIDDEN];
yield ['/user/delete/1', self::ROLE_ADMIN];

Expand Down
Loading

0 comments on commit 4af9e81

Please sign in to comment.