Skip to content
Code-Schnipsel Gruppen Projekte
Commit 1b373fc5 erstellt von Patrick Müller's avatar Patrick Müller
Dateien durchsuchen

Merge branch 'next-3.x' into 'main'

Next 3.x

See merge request !16
Übergeordnete f7665e17 3e22ce9e
Keine zugehörigen Branchen gefunden
Tags 3.2.0
2 Merge Requests!16Next 3.x,!13Update 'next-4.x' with latest changes from 'main'
Pipeline-Nr. 13961 mit Warnungen bestanden
werden angezeigt mit 364 Ergänzungen und 18 Löschungen
...@@ -13,15 +13,5 @@ phpunit-php8.2: ...@@ -13,15 +13,5 @@ phpunit-php8.2:
# Remove the entire phpunit-php8.3 block, to allow PHPUnit to run on PHP 8.3 in your pipeline # Remove the entire phpunit-php8.3 block, to allow PHPUnit to run on PHP 8.3 in your pipeline
phpunit-php8.3: phpunit-php8.3:
rules:
- when: never
# Disable PHP 8.1 syntax check, as PHP 8.3 is minimum requirement
php-syntax-check-8.1:
rules:
- when: never
# Disable PHP 8.2 syntax check, as PHP 8.3 is minimum requirement
php-syntax-check-8.2:
rules: rules:
- when: never - when: never
\ No newline at end of file
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
"source": "https://dev.quiqqer.com/quiqqer/verification" "source": "https://dev.quiqqer.com/quiqqer/verification"
}, },
"require": { "require": {
"php" : "^8.1",
"quiqqer/core": "^2" "quiqqer/core": "^2"
}, },
"autoload": { "autoload": {
......
...@@ -59,6 +59,22 @@ ...@@ -59,6 +59,22 @@
<de><![CDATA[Maximale Anzahl an Versuchen für die Bestätigung eines Verifizierungs-Prozesses mittels Verifizierungs-Code. Wird diese Anzahl überschritten, wird der Verifizierungs-Prozess automatisch auf "abgelaufen" gesetzt.]]></de> <de><![CDATA[Maximale Anzahl an Versuchen für die Bestätigung eines Verifizierungs-Prozesses mittels Verifizierungs-Code. Wird diese Anzahl überschritten, wird der Verifizierungs-Prozess automatisch auf "abgelaufen" gesetzt.]]></de>
<en><![CDATA[Maximum number of attempts to confirm a verification process via verification code. If this number is exceeded, the verification process is automatically set to "expired".]]></en> <en><![CDATA[Maximum number of attempts to confirm a verification process via verification code. If this number is exceeded, the verification process is automatically set to "expired".]]></en>
</locale> </locale>
<locale name="settings.cooldownPhoneVerification.title">
<de><![CDATA[Minimale Wartezeit Telefonnummer-Verifizierung]]></de>
<en><![CDATA[Minimum waiting time for phone number verification]]></en>
</locale>
<locale name="settings.cooldownPhoneVerification.description" html="true">
<de><![CDATA[Minimale Wartezeit in <b>Minuten</b>, die zwischen zwei identischen Telefonnummer-Verifizierungen liegen muss.]]></de>
<en><![CDATA[Minimum waiting time in <b>minutes</b> between two identical phone number verifications.]]></en>
</locale>
<locale name="settings.cooldownAddressVerification.title">
<de><![CDATA[Minimale Wartezeit Adress-Verifizierung]]></de>
<en><![CDATA[Minimum waiting time for address verification]]></en>
</locale>
<locale name="settings.cooldownAddressVerification.description" html="true">
<de><![CDATA[Minimale Wartezeit in <b>Minuten</b>, die zwischen zwei identischen Address-Verifizierungen liegen muss.]]></de>
<en><![CDATA[Minimum waiting time in <b>minutes</b> between two identical address verifications.]]></en>
</locale>
<!-- Cron --> <!-- Cron -->
<locale name="cron.deleteVerified.title"> <locale name="cron.deleteVerified.title">
......
parameters: parameters:
ignoreErrors: ignoreErrors:
-
message: "#^Method QUI\\\\Verification\\\\Entity\\\\AddressVerification\\:\\:fromArray\\(\\) should return static\\(QUI\\\\Verification\\\\Entity\\\\AddressVerification\\) but returns QUI\\\\Verification\\\\Entity\\\\AddressVerification\\.$#"
count: 1
path: src/QUI/Verification/Entity/AddressVerification.php
- -
message: "#^Method QUI\\\\Verification\\\\Entity\\\\LinkVerification\\:\\:fromArray\\(\\) should return static\\(QUI\\\\Verification\\\\Entity\\\\LinkVerification\\) but returns QUI\\\\Verification\\\\Entity\\\\LinkVerification\\.$#" message: "#^Method QUI\\\\Verification\\\\Entity\\\\LinkVerification\\:\\:fromArray\\(\\) should return static\\(QUI\\\\Verification\\\\Entity\\\\LinkVerification\\) but returns QUI\\\\Verification\\\\Entity\\\\LinkVerification\\.$#"
count: 1 count: 1
...@@ -16,10 +21,15 @@ parameters: ...@@ -16,10 +21,15 @@ parameters:
path: src/QUI/Verification/VerificationFactory.php path: src/QUI/Verification/VerificationFactory.php
- -
message: "#^Cannot call method createRandomDigitsCode\\(\\) on QUI\\\\Verification\\\\Interface\\\\VerificationCodeFactoryInterface\\|null\\.$#" message: "#^Cannot access property \\$createDate on QUI\\\\Verification\\\\Entity\\\\AbstractVerification\\|null\\.$#"
count: 1 count: 1
path: src/QUI/Verification/VerificationFactory.php path: src/QUI/Verification/VerificationFactory.php
-
message: "#^Cannot call method createRandomDigitsCode\\(\\) on QUI\\\\Verification\\\\Interface\\\\VerificationCodeFactoryInterface\\|null\\.$#"
count: 2
path: src/QUI/Verification/VerificationFactory.php
- -
message: "#^Cannot call method createRandomHashCode\\(\\) on QUI\\\\Verification\\\\Interface\\\\VerificationCodeFactoryInterface\\|null\\.$#" message: "#^Cannot call method createRandomHashCode\\(\\) on QUI\\\\Verification\\\\Interface\\\\VerificationCodeFactoryInterface\\|null\\.$#"
count: 1 count: 1
...@@ -27,17 +37,17 @@ parameters: ...@@ -27,17 +37,17 @@ parameters:
- -
message: "#^Cannot call method delete\\(\\) on QUI\\\\Verification\\\\Interface\\\\VerificationRepositoryInterface\\|null\\.$#" message: "#^Cannot call method delete\\(\\) on QUI\\\\Verification\\\\Interface\\\\VerificationRepositoryInterface\\|null\\.$#"
count: 2 count: 3
path: src/QUI/Verification/VerificationFactory.php path: src/QUI/Verification/VerificationFactory.php
- -
message: "#^Cannot call method findByIdentifier\\(\\) on QUI\\\\Verification\\\\Interface\\\\VerificationRepositoryInterface\\|null\\.$#" message: "#^Cannot call method findByIdentifier\\(\\) on QUI\\\\Verification\\\\Interface\\\\VerificationRepositoryInterface\\|null\\.$#"
count: 2 count: 4
path: src/QUI/Verification/VerificationFactory.php path: src/QUI/Verification/VerificationFactory.php
- -
message: "#^Cannot call method insert\\(\\) on QUI\\\\Verification\\\\Interface\\\\VerificationRepositoryInterface\\|null\\.$#" message: "#^Cannot call method insert\\(\\) on QUI\\\\Verification\\\\Interface\\\\VerificationRepositoryInterface\\|null\\.$#"
count: 2 count: 3
path: src/QUI/Verification/VerificationFactory.php path: src/QUI/Verification/VerificationFactory.php
- -
......
...@@ -17,6 +17,14 @@ ...@@ -17,6 +17,14 @@
<type><![CDATA[integer]]></type> <type><![CDATA[integer]]></type>
<defaultvalue>5</defaultvalue> <defaultvalue>5</defaultvalue>
</conf> </conf>
<conf name="cooldownPhoneVerification">
<type><![CDATA[integer]]></type>
<defaultvalue>3</defaultvalue>
</conf>
<conf name="cooldownAddressVerification">
<type><![CDATA[integer]]></type>
<defaultvalue>4320</defaultvalue>
</conf>
</section> </section>
</config> </config>
...@@ -68,6 +76,24 @@ ...@@ -68,6 +76,24 @@
</description> </description>
</input> </input>
<input conf="settings.cooldownPhoneVerification" type="number">
<text>
<locale group="quiqqer/verification" var="settings.cooldownPhoneVerification.title"/>
</text>
<description>
<locale group="quiqqer/verification" var="settings.cooldownPhoneVerification.description"/>
</description>
</input>
<input conf="settings.cooldownAddressVerification" type="number">
<text>
<locale group="quiqqer/verification" var="settings.cooldownAddressVerification.title"/>
</text>
<description>
<locale group="quiqqer/verification" var="settings.cooldownAddressVerification.description"/>
</description>
</input>
</settings> </settings>
</category> </category>
......
<?php
namespace QUI\Verification\Entity;
final class Address
{
public function __construct(
public string $name,
public string $street,
public string $houseNumber,
public string $zipCode,
public string $city,
public string $countryCode
) {
}
/**
* @return array<string,string>
*/
public function toArray(): array
{
return [
'name' => $this->name,
'street' => $this->street,
'houseNumber' => $this->houseNumber,
'zipCode' => $this->zipCode,
'city' => $this->city,
'countryCode' => $this->countryCode,
];
}
/**
* @param array<string,string> $data
* @return Address
*/
public static function fromArray(array $data): Address
{
return new Address(
$data['name'],
$data['street'],
$data['houseNumber'],
$data['zipCode'],
$data['city'],
$data['countryCode']
);
}
}
<?php
namespace QUI\Verification\Entity;
use DateTimeImmutable;
use QUI\Verification\Enum\VerificationStatus;
class AddressVerification extends AbstractVerification
{
/**
* @param string $uuid
* @param string $identifier
* @param string $verificationCode
* @param DateTimeImmutable $createDate
* @param DateTimeImmutable $updateDate
* @param int $tries
* @param Address $address
* @param VerificationStatus $status
* @param array<string|int,mixed> $customData
* @param DateTimeImmutable|null $validUntilDate
*/
public function __construct(
string $uuid,
string $identifier,
string $verificationCode,
DateTimeImmutable $createDate,
DateTimeImmutable $updateDate,
int $tries,
public readonly Address $address,
VerificationStatus $status = VerificationStatus::PENDING,
array $customData = [],
?DateTimeImmutable $validUntilDate = null
) {
$customData['address'] = $address->toArray();
parent::__construct(
$uuid,
$identifier,
$verificationCode,
$createDate,
$updateDate,
$tries,
$status,
$customData,
$validUntilDate
);
}
/**
* @param array<string,mixed> $data
* @return static
*
* @throws \DateMalformedStringException
*/
public static function fromArray(array $data): static
{
$data['customData']['strategy'] = 'sms';
return new AddressVerification(
$data['uuid'],
$data['identifier'],
$data['verificationCode'],
new DateTimeImmutable($data['createDate']),
new DateTimeImmutable($data['updateDate']),
(int)$data['tries'],
Address::fromArray($data['customData']['address']),
VerificationStatus::from($data['status']),
$data['customData'],
!empty($data['validUntilDate']) ? new DateTimeImmutable($data['validUntilDate']) : null
);
}
}
<?php
namespace QUI\Verification\Exception;
use DateTimeImmutable;
use QUI\Verification\Exception;
/**
* Thrown if an expired verification cannot be created due to cooldown restrictions.
*/
class VerificationCooldownActiveException extends Exception
{
/**
* @param DateTimeImmutable $cooldownExpirationDate
* @param string|null $message
* @param int $code
* @param array<string,mixed> $context
*/
public function __construct(
public readonly DateTimeImmutable $cooldownExpirationDate,
?string $message = null,
int $code = 0,
array $context = []
) {
parent::__construct($message, $code, $context);
}
}
<?php
namespace QUI\Verification\Interface;
use QUI\Verification\Entity\AddressVerification;
use QUI\Verification\Enum\VerificationErrorReason;
interface AddressVerificationHandlerInterface extends VerificationHandlerInterface
{
/**
* Execute this method on successful verification
*
* @param AddressVerification $verification
* @return void
*/
public function onSuccess(AddressVerification $verification): void;
/**
* Execute this method on unsuccessful verification
*
* @param AddressVerification $verification
* @param VerificationErrorReason $reason
* @return void
*/
public function onError(AddressVerification $verification, VerificationErrorReason $reason): void;
}
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
namespace QUI\Verification\Interface; namespace QUI\Verification\Interface;
use QUI\PhoneApi\Entity\PhoneNumber; use QUI\PhoneApi\Entity\PhoneNumber;
use QUI\Verification\Entity\Address;
use QUI\Verification\Entity\AddressVerification;
use QUI\Verification\Entity\LinkVerification; use QUI\Verification\Entity\LinkVerification;
use QUI\Verification\Entity\PhoneNumberVerification; use QUI\Verification\Entity\PhoneNumberVerification;
use QUI\Verification\Enum\PhoneNumberVerificationStrategy; use QUI\Verification\Enum\PhoneNumberVerificationStrategy;
...@@ -50,4 +52,26 @@ public function createPhoneNumberVerification( ...@@ -50,4 +52,26 @@ public function createPhoneNumberVerification(
?string $verificationCode = null, ?string $verificationCode = null,
bool $overwriteExisting = false bool $overwriteExisting = false
): PhoneNumberVerification; ): PhoneNumberVerification;
/**
* Create verification for verifying an address via code that is sent via letter.
*
* @param string $identifier - A custom unique identifier that is used to determine identical verifications;
* e.g. an email address for an email address verification process.
* @param Address $address
* @param AddressVerificationHandlerInterface $verificationHandler
* @param array<string|int,mixed> $customData (optional) - Custom data that is stored with the Verification
* @param string|null $verificationCode (optional) - The verification code used. If NULL, generate random code
* @param bool $overwriteExisting (optional) - Overwrite Verification with identical
* identifier and source class [default: false]
* @return AddressVerification
*/
public function createAddressVerification(
string $identifier,
Address $address,
AddressVerificationHandlerInterface $verificationHandler,
array $customData = [],
?string $verificationCode = null,
bool $overwriteExisting = false
): AddressVerification;
} }
...@@ -13,7 +13,7 @@ class Utils ...@@ -13,7 +13,7 @@ class Utils
/** /**
* Verifier site type * Verifier site type
*/ */
const string SITE_TYPE = 'quiqqer/verification:types/verifier'; const SITE_TYPE = 'quiqqer/verification:types/verifier';
/** /**
* Get formatted timestamp for a given UNIX timestamp * Get formatted timestamp for a given UNIX timestamp
......
...@@ -8,10 +8,14 @@ ...@@ -8,10 +8,14 @@
use QUI\Database\Exception; use QUI\Database\Exception;
use QUI\PhoneApi\Entity\PhoneNumber; use QUI\PhoneApi\Entity\PhoneNumber;
use QUI\Verification\Entity\AbstractVerification; use QUI\Verification\Entity\AbstractVerification;
use QUI\Verification\Entity\Address;
use QUI\Verification\Entity\AddressVerification;
use QUI\Verification\Entity\LinkVerification; use QUI\Verification\Entity\LinkVerification;
use QUI\Verification\Entity\PhoneNumberVerification; use QUI\Verification\Entity\PhoneNumberVerification;
use QUI\Verification\Enum\PhoneNumberVerificationStrategy; use QUI\Verification\Enum\PhoneNumberVerificationStrategy;
use QUI\Verification\Enum\VerificationStatus; use QUI\Verification\Enum\VerificationStatus;
use QUI\Verification\Exception\VerificationCooldownActiveException;
use QUI\Verification\Interface\AddressVerificationHandlerInterface;
use QUI\Verification\Interface\LinkVerificationHandlerInterface; use QUI\Verification\Interface\LinkVerificationHandlerInterface;
use QUI\Verification\Interface\PhoneNumberVerificationHandlerInterface; use QUI\Verification\Interface\PhoneNumberVerificationHandlerInterface;
use QUI\Verification\Interface\VerificationCodeFactoryInterface; use QUI\Verification\Interface\VerificationCodeFactoryInterface;
...@@ -19,6 +23,8 @@ ...@@ -19,6 +23,8 @@
use QUI\Verification\Interface\VerificationHandlerInterface; use QUI\Verification\Interface\VerificationHandlerInterface;
use QUI\Verification\Interface\VerificationRepositoryInterface; use QUI\Verification\Interface\VerificationRepositoryInterface;
use function date_create;
use function date_interval_create_from_date_string;
use function is_null; use function is_null;
use function strtotime; use function strtotime;
...@@ -143,6 +149,7 @@ public function createPhoneNumberVerification( ...@@ -143,6 +149,7 @@ public function createPhoneNumberVerification(
]); ]);
} }
$this->checkCooldown($identifier, (int)Settings::get('cooldownPhoneVerification'));
$this->verificationRepository->delete($existingVerification); $this->verificationRepository->delete($existingVerification);
} }
...@@ -171,6 +178,72 @@ public function createPhoneNumberVerification( ...@@ -171,6 +178,72 @@ public function createPhoneNumberVerification(
return $phoneNumberVerification; return $phoneNumberVerification;
} }
/**
* Create verification for verifying an address via code that is sent via letter.
*
* @param string $identifier - A custom unique identifier that is used to determine identical verifications;
* e.g. an email address for an email address verification process.
* @param Address $address
* @param AddressVerificationHandlerInterface $verificationHandler
* @param array<string|int,mixed> $customData (optional) - Custom data that is stored with the Verification
* @param string|null $verificationCode (optional) - The verification code used. If NULL, generate random code
* @param bool $overwriteExisting (optional) - Overwrite Verification with identical
* identifier and source class [default: false]
* @return AddressVerification
*
* @throws DateMalformedStringException
* @throws QUI\Exception
* @throws Exception
*/
public function createAddressVerification(
string $identifier,
Address $address,
AddressVerificationHandlerInterface $verificationHandler,
array $customData = [],
?string $verificationCode = null,
bool $overwriteExisting = false
): AddressVerification {
$existingVerification = $this->verificationRepository->findByIdentifier($identifier);
if (!is_null($existingVerification)) {
if ($overwriteExisting !== true) {
throw new QUI\Verification\Exception([
'quiqqer/verification',
'exception.verifier.verification.already.exists',
[
'identifier' => $existingVerification->identifier
]
]);
}
$this->checkCooldown($identifier, (int)Settings::get('cooldownAddressVerification'));
$this->verificationRepository->delete($existingVerification);
}
$uuid = QUI\Utils\Uuid::get();
if (is_null($verificationCode)) {
$verificationCode = $this->verificationCodeFactory->createRandomDigitsCode();
}
$addressVerification = new AddressVerification(
$uuid,
$identifier,
$verificationCode,
new DateTimeImmutable(),
new DateTimeImmutable(),
0,
$address,
VerificationStatus::PENDING,
$customData
);
$this->setValidUntilDateToVerification($addressVerification, $verificationHandler);
$this->verificationRepository->insert($addressVerification, $verificationHandler);
return $addressVerification;
}
/** /**
* @param AbstractVerification $verification * @param AbstractVerification $verification
* @param VerificationHandlerInterface $handler * @param VerificationHandlerInterface $handler
...@@ -194,7 +267,7 @@ private function setValidUntilDateToVerification( ...@@ -194,7 +267,7 @@ private function setValidUntilDateToVerification(
QUI\System\Log::addError( QUI\System\Log::addError(
"quiqqer/verification :: VerificationFactory -> cannot load Config of quiqqer/verification" "quiqqer/verification :: VerificationFactory -> cannot load Config of quiqqer/verification"
. " for determining default verification validity duration. Falling back to 3 days hardcoded" . " for determining default verification validity duration. Falling back to 3 days hardcoded."
); );
} else { } else {
$validDuration = $Conf->get('settings', 'validDuration'); $validDuration = $Conf->get('settings', 'validDuration');
...@@ -208,4 +281,39 @@ private function setValidUntilDateToVerification( ...@@ -208,4 +281,39 @@ private function setValidUntilDateToVerification(
$verification->validUntilDate = new DateTimeImmutable('@' . $end); $verification->validUntilDate = new DateTimeImmutable('@' . $end);
} }
/**
* Check if a verification with a specific identifier can be recreated.
*
* @param string $identifier
* @param int $cooldownMinutes
* @return void
* @throws VerificationCooldownActiveException
*/
private function checkCooldown(string $identifier, int $cooldownMinutes): void
{
$existingVerification = $this->verificationRepository->findByIdentifier($identifier);
$now = date_create();
$cooldownExpirationDate = clone $existingVerification->createDate;
$cooldownMinutesInterval = date_interval_create_from_date_string($cooldownMinutes . ' minutes');
if ($cooldownMinutesInterval === false) {
QUI\System\Log::addWarning(
"Cannot calculate verification cooldown minutes from value $cooldownMinutes."
);
return;
}
$cooldownExpirationDate = $cooldownExpirationDate->add($cooldownMinutesInterval);
if ($now >= $cooldownExpirationDate) {
return;
}
throw new VerificationCooldownActiveException(
$cooldownExpirationDate,
"Verification with identifier '$identifier' is still on cooldown. Can be recreated at "
. $cooldownExpirationDate->format('Y-m-d H:i:s') . " at the earliest."
);
}
} }
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
class VerificationRepository implements VerificationRepositoryInterface class VerificationRepository implements VerificationRepositoryInterface
{ {
const string TBL_VERIFICATION_PROCESSES = 'quiqqer_verification_processes'; const TBL_VERIFICATION_PROCESSES = 'quiqqer_verification_processes';
public function __construct(private ?Connection $databaseConnection = null) public function __construct(private ?Connection $databaseConnection = null)
{ {
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
use function is_null; use function is_null;
class AbstractPhoneVerifier abstract class AbstractPhoneVerifier
{ {
/** /**
* @param PhoneApiClientInterface|null $phoneApiClient * @param PhoneApiClientInterface|null $phoneApiClient
......
0% Lade oder .
You are about to add 0 people to the discussion. Proceed with caution.
Bearbeitung dieser Nachricht zuerst beenden!
Bitte registrieren oder zum Kommentieren