diff --git a/src/vCaptcha/Classes/Security.php b/src/vCaptcha/Classes/Security.php new file mode 100644 index 0000000..a018387 --- /dev/null +++ b/src/vCaptcha/Classes/Security.php @@ -0,0 +1,41 @@ + 120) + { + return false; + } + + if(strlen($input) < 3) + { + return false; + } + + if(preg_match("/^[a-zA-Z0-9 ]*$/", $input)) + { + return true; + } + + return false; + } + } \ No newline at end of file diff --git a/src/vCaptcha/Exceptions/CaptchaInstanceNotFoundException.php b/src/vCaptcha/Exceptions/CaptchaInstanceNotFoundException.php new file mode 100644 index 0000000..d616f53 --- /dev/null +++ b/src/vCaptcha/Exceptions/CaptchaInstanceNotFoundException.php @@ -0,0 +1,19 @@ +message = $message; + $this->query = $query; + $this->code = $code; + } + + /** + * @return string + */ + public function getQuery(): string + { + return $this->query; + } + } \ No newline at end of file diff --git a/src/vCaptcha/Exceptions/InvalidCaptchaNameException.php b/src/vCaptcha/Exceptions/InvalidCaptchaNameException.php new file mode 100644 index 0000000..7ac34ad --- /dev/null +++ b/src/vCaptcha/Exceptions/InvalidCaptchaNameException.php @@ -0,0 +1,20 @@ +message = $message; + $this->code = $code; + } + } \ No newline at end of file diff --git a/src/vCaptcha/Managers/CaptchaInstanceManager.php b/src/vCaptcha/Managers/CaptchaInstanceManager.php index 56ed47d..e3a766d 100644 --- a/src/vCaptcha/Managers/CaptchaInstanceManager.php +++ b/src/vCaptcha/Managers/CaptchaInstanceManager.php @@ -6,8 +6,14 @@ use msqg\QueryBuilder; use Symfony\Component\Uid\Uuid; + use vCaptcha\Classes\Security; + use vCaptcha\Classes\Validate; + use vCaptcha\Exceptions\CaptchaInstanceNotFoundException; + use vCaptcha\Exceptions\DatabaseException; + use vCaptcha\Exceptions\InvalidCaptchaNameException; use vCaptcha\Objects\CaptchaInstance; use vCaptcha\Objects\CaptchaInstance\FirewallOptions; + use vCaptcha\Objects\SampleCaptchaInstance; use vCaptcha\vCaptcha; use ZiProto\ZiProto; @@ -23,10 +29,25 @@ $this->vcaptcha = $vcaptcha; } - public function createCaptcha(string $captcha_type, string $owner_id): CaptchaInstance + /** + * Creates a new Captcha instance + * + * @param string $captcha_type + * @param string $owner_id + * @param string $name + * @return CaptchaInstance + * @throws DatabaseException + * @throws InvalidCaptchaNameException + */ + public function createInstance(string $captcha_type, string $owner_id, string $name): CaptchaInstance { + if(Validate::captchaInstanceName($name) == false) + throw new InvalidCaptchaNameException('The given name must not be greater than 120 characters or less than 3 and must be alphanumeric.'); + $CaptchaInstance = new CaptchaInstance(); $CaptchaInstance->ID = Uuid::v4()->toRfc4122(); + $CaptchaInstance->Name = $name; + $CaptchaInstance->SecretKey = Security::generateCaptchaInstanceSecret($CaptchaInstance->ID, $owner_id); $CaptchaInstance->CaptchaType = $captcha_type; $CaptchaInstance->OwnerID = $owner_id; $CaptchaInstance->Enabled = true; @@ -34,14 +55,127 @@ $CaptchaInstance->CreatedTimestamp = time(); $CaptchaInstance->LastUpdatedTimestamp = time(); - $Query = QUeryBuilder::insert_into('instances', [ + $Query = QueryBuilder::insert_into('instances', [ 'id' => $this->vcaptcha->getDatabase()->real_escape_string($CaptchaInstance->ID), + 'name' => $this->vcaptcha->getDatabase()->real_escape_string(urlencode($CaptchaInstance->Name)), 'captcha_type' => $this->vcaptcha->getDatabase()->real_escape_string($CaptchaInstance->CaptchaType), 'owner_id' => $this->vcaptcha->getDatabase()->real_escape_string($CaptchaInstance->OwnerID), + 'secret_key' => $this->vcaptcha->getDatabase()->real_escape_string($CaptchaInstance->SecretKey), 'enabled' => (int)$CaptchaInstance->Enabled, 'firewall_options' => $this->vcaptcha->getDatabase()->real_escape_string(ZiProto::encode($CaptchaInstance->FirewallOptions->toArray())), 'created_timestamp' => $this->vcaptcha->getDatabase()->real_escape_string($CaptchaInstance->CreatedTimestamp), 'last_updated_timestamp' => $this->vcaptcha->getDatabase()->real_escape_string($CaptchaInstance->LastUpdatedTimestamp) ]); + $QueryResults = $this->vcaptcha->getDatabase()->query($Query); + + if($QueryResults == false) + throw new DatabaseException($this->vcaptcha->getDatabase()->error, $Query, $this->vcaptcha->getDatabase()->errno); + + return $CaptchaInstance; + } + + /** + * Returns an existing instance from the database + * + * @param string $id + * @return CaptchaInstance + * @throws CaptchaInstanceNotFoundException + * @throws DatabaseException + */ + public function getInstance(string $id): CaptchaInstance + { + $Query = QueryBuilder::select('instances', [ + 'id', + 'name', + 'captcha_type', + 'owner_id', + 'secret_key', + 'enabled', + 'firewall_options', + 'created_timestamp', + 'last_updated_timestamp' + ], 'id', $this->vcaptcha->getDatabase()->real_escape_string($id), null, null, 1); + + $QueryResults = $this->vcaptcha->getDatabase()->query($Query); + + if($QueryResults == false) + throw new DatabaseException($this->vcaptcha->getDatabase()->error, $Query, $this->vcaptcha->getDatabase()->errno); + + if($QueryResults->num_rows == 0) + throw new CaptchaInstanceNotFoundException(); + + $Row = $QueryResults->fetch_array(MYSQLI_ASSOC); + $Row['firewall_options'] = ZiProto::decode($Row['firewall_options']); + $Row['name'] = urldecode($Row['name']); + + return CaptchaInstance::fromArray($Row); + } + + /** + * Returns an array of captcha sample instance objects associated with the owner ID + * + * @param string $owner_id + * @return SampleCaptchaInstance[] + * @throws DatabaseException + */ + public function getInstances(string $owner_id): array + { + $Query = QueryBuilder::select('instances', [ + 'id', + 'captcha_type', + 'name' + ], 'owner_id', $this->vcaptcha->getDatabase()->real_escape_string($owner_id)); + + $QueryResults = $this->vcaptcha->getDatabase()->query($Query); + + if($QueryResults == false) + throw new DatabaseException($this->vcaptcha->getDatabase()->error, $Query, $this->vcaptcha->getDatabase()->errno); + + if($QueryResults->num_rows == 0) + return []; + + $ResultsArray = []; + + while($Row = $QueryResults->fetch_assoc()) + { + $Row['name'] = urldecode($Row['name']); + $ResultsArray[] = SampleCaptchaInstance::fromArray($Row); + } + + return $ResultsArray; + } + + /** + * Updates an existing captcha instance in the database + * + * @param CaptchaInstance $captchaInstance + * @return CaptchaInstance + * @throws DatabaseException + * @throws InvalidCaptchaNameException + * @noinspection PhpCastIsUnnecessaryInspection + */ + public function updateInstance(CaptchaInstance $captchaInstance): CaptchaInstance + { + if(Validate::captchaInstanceName($captchaInstance->Name) == false) + throw new InvalidCaptchaNameException('The given name must not be greater than 120 characters or less than 3 and must be alphanumeric.'); + + $captchaInstance->LastUpdatedTimestamp = time(); + $firewall_options = $captchaInstance->FirewallOptions->toArray()['firewall_options']; + + $Query = QueryBuilder::update('instances', [ + 'name' => $this->vcaptcha->getDatabase()->real_escape_string(urlencode($captchaInstance->Name)), + 'secret_key' => $this->vcaptcha->getDatabase()->real_escape_string($captchaInstance->SecretKey), + 'captcha_type' => $this->vcaptcha->getDatabase()->real_escape_string($captchaInstance->CaptchaType), + 'enabled' => (int)$captchaInstance->Enabled, + 'firewall_options' => $this->vcaptcha->getDatabase()->real_escape_string(ZiProto::encode($firewall_options)), + 'last_updated_timestamp' => (int)$captchaInstance->LastUpdatedTimestamp + ]); + + $QueryResults = $this->vcaptcha->getDatabase()->query($Query); + + if($QueryResults == false) + throw new DatabaseException($this->vcaptcha->getDatabase()->error, $Query, $this->vcaptcha->getDatabase()->errno); + + return $captchaInstance; } } \ No newline at end of file diff --git a/src/vCaptcha/Objects/CaptchaInstance.php b/src/vCaptcha/Objects/CaptchaInstance.php index d434fd2..82dc5ba 100644 --- a/src/vCaptcha/Objects/CaptchaInstance.php +++ b/src/vCaptcha/Objects/CaptchaInstance.php @@ -23,6 +23,13 @@ */ public $OwnerID; + /** + * The name of the captcha instance + * + * @var string + */ + public $Name; + /** * The secret key used for creating and validating * @@ -104,7 +111,7 @@ $CaptchaInstanceObject->SecretKey = $data['secret_key']; if(isset($data['enabled'])) - $CaptchaInstanceObject->Enabled = $data['enabled']; + $CaptchaInstanceObject->Enabled = (bool)$data['enabled']; if(isset($data['captcha_type'])) $CaptchaInstanceObject->CaptchaType = $data['captcha_type']; @@ -113,10 +120,10 @@ $CaptchaInstanceObject->FirewallOptions = FirewallOptions::fromArray($data['firewall_options']); if(isset($data['last_updated_timestamp'])) - $CaptchaInstanceObject->LastUpdatedTimestamp = $data['last_updated_timestamp']; + $CaptchaInstanceObject->LastUpdatedTimestamp = (int)$data['last_updated_timestamp']; if(isset($data['created_timestamp'])) - $CaptchaInstanceObject->CreatedTimestamp = $data['created_timestamp']; + $CaptchaInstanceObject->CreatedTimestamp = (int)$data['created_timestamp']; return $CaptchaInstanceObject; } diff --git a/src/vCaptcha/Objects/SampleCaptchaInstance.php b/src/vCaptcha/Objects/SampleCaptchaInstance.php new file mode 100644 index 0000000..bc67a4c --- /dev/null +++ b/src/vCaptcha/Objects/SampleCaptchaInstance.php @@ -0,0 +1,74 @@ + $this->ID, + 'name' => $this->Name, + 'captcha_type' => $this->CaptchaType, + 'enabled' => $this->Enabled + ]; + } + + /** + * Constructs object from an array representation + * + * @param array $data + * @return SampleCaptchaInstance + */ + public static function fromArray(array $data): SampleCaptchaInstance + { + $SampleCaptchaInstance = new SampleCaptchaInstance(); + + if(isset($data['id'])) + $SampleCaptchaInstance->ID = $data['id']; + + if(isset($data['name'])) + $SampleCaptchaInstance->Name = $data['name']; + + if(isset($data['captcha_type'])) + $SampleCaptchaInstance->CaptchaType = $data['captcha_type']; + + if(isset($data['enabled'])) + $SampleCaptchaInstance->Enabled = (bool)$data['enabled']; + + return $SampleCaptchaInstance; + } + } \ No newline at end of file diff --git a/src/vCaptcha/package.json b/src/vCaptcha/package.json index e969a8c..2038db0 100644 --- a/src/vCaptcha/package.json +++ b/src/vCaptcha/package.json @@ -49,10 +49,34 @@ "required": true, "file": "Abstracts/CaptchaType.php" }, + { + "required": true, + "file": "Classes/Validate.php" + }, + { + "required": true, + "file": "Classes/Security.php" + }, { "required": true, "file": "vCaptcha.php" }, + { + "required": true, + "file": "Exceptions/DatabaseException.php" + }, + { + "required": true, + "file": "Exceptions/CaptchaInstanceNotFoundException.php" + }, + { + "required": true, + "file": "Exceptions/InvalidCaptchaNameException.php" + }, + { + "required": true, + "file": "Objects/SampleCaptchaInstance.php" + }, { "required": true, "file": "Objects/CaptchaInstance.php"