اعتبار سنجی
اطلاعات ارسال شده از سمت کاربر می بایست قبل از هرگونه عملیات، اعتبار سنجی شوند . بررسی رشته های دریافتی برای اطمینان از عدم وجود کد و یا کد های مخرب برنامه نویسی یکی از مهم ترین کارهایی است که در این بخش باید کنترل شوند .
فقط فیلد هایی که باید دریافت کنید را مشخص کنید تا مابقی فیلد ها ( که ممکن است توسط افراد مخرب در مقدار های ارسالی گنجانده شده اند ) نادیده گرفته شوند . میتوانید نوع و یا مقداری که انتطار دارید توسط کاربر از هر فیلد دریافت کنید را مشخص کنید . اعتبار سنج به صورت خودکار هر مقدار و یا نوعی به غیر از آنچه مشخص شده دریافت کنید ، با پرتاب یک استثناء از جنس packages\base\InputValidationException از ادا مه عملیات جلوگیری خواهد کرد .
برای این موضوع میتوانید از متد checkinputs کلاس packages\base\controller استفاده کنید .
متد checkinputs
آرگومان ورودی این متد آرایه ای میباشد که کلیدهای آن نام فیلد های فرم هستند و هرکدام از کلیدها ارایهای دریافت میکند که نوع، مقدار پیشفرض، مقدار های مورد انتظار، اختیاری بودن و یا مجاز به خالی بودن برای هر فیلد را به صورت جداگانه مشخص میکند. خروجی این متد یک آرایه با همان کلید های داده شده و مقدار های اعتبار سنجی شده است.
نوع داده
با استفاده از کلید type نوع داده دریافتی مشخص میشود.
$this->checkinputs(array(
'id' => array(
'type' => 'number',
),
));
انواع داده های اعتبارسنجی
در فریمورک برای انواع مختلف داده ها کلاس هایی برای اعتبار سنجی تعریف شده است که داده های زیر را بررسی میکنند.
- داده های رشته ای
- داده های عددی
- داده های boolean
- تاریخ
- شماره همراه
- ایمیل
- ip ورژن 4
- آدرس اینترنتی (url)
- فایل
- تصویر
- دادههایی از جنس کلاس های Model
- توابع
اگر داده های مورد اعتبارسجی نوعی بجز موارد بالا باشد برنامه نویس میتواند اعتبارسنج جدید ایجاد نمایید.
برای تعریف اعتبارسنج جدید باید کلاسی تعریف کرد که از اینترفیس packages\base\Validator\IValidator implements شده باشد.
در اینترفیس IValidator متد getTypes() برای مشخص کردن نام اعتبارسنج و متد validate() برای عملیات اعتبارسنجی در نظرگرفته شده است.
برای مشاهده نمونه کدهای اعتبارسنج میتوانید به کلاس های اعتبارسنج تعریف شده فریمورک در این پوشه مراجعه کنید.
استفاده از اعتبارسنج جدید
زمان استفاده از اعتبارسنج جدید با استفاده از متد addValidator() اعتبار سنج جدید معرفی میشود.
این متد باید قبل از فراخوانی متد checkinputs() فراخوانی شود.
همچنین میتوان بجای فراخوانی متد addValidator() در ایندکس type namespace کلاس اعتبارسنج داده شود.
درمثال زیر اعتبارسنج جدید (IBANValidator) معرفی شده است.
مقدار داده شده به ایندکس type نامی است که در متد getTypes() کلاس اعتبارسنج تعیین شده است.
اعتبار سنج جدید نوشته شده
<?php
namespace packages\packagename\Validators;
use packages\packagename\Bank;
use packages\base\{InputValidationException, Validator\IValidator, db\DuplicateRecord};
class IBANValidator implements IValidator {
/**
* Get alias types
*
* @return string[]
*/
public function getTypes(): array {
return ['iban'];
}
/**
* Validate data to be a IBAN code.
*
* @throws packages\base\InputValidationException
* @param string $input
* @param array $rule
* @param mixed $data
* @return array
*/
public function validate(string $input, array $rule, $data): array {
if (!is_array($data)) {
throw new InputValidationException($input);
}
if (!$data) {
if (!isset($rule['empty']) or !$rule['empty']) {
throw new InputValidationException($input);
}
if (isset($rule['default'])) {
return $rule['default'];
}
}
foreach ($data as $key => $value) {
if (!isset($value["id"])) {
throw new InputValidationException($input . "[{$key}][id]");
}
if (!isset($value["account"])) {
throw new InputValidationException($input . "[{$key}][account]");
}
}
foreach ($data as $key => $value) {
foreach ($data as $key2 => $value2) {
if ($key != $key2 and $value["id"] == $value2["id"]) {
throw new DuplicateRecord($input . "[{$key}][id]");
}
}
$bank = Bank::byId($value["id"]);
if (!$bank) {
throw new InputValidationException($input . "[{$key}][id]");
}
$data[$key]["bank"] = $bank;
}
return $data;
}
}
1 مثال :
<?php
namespace packages\packagename\controllers;
use packages\base\{Controller, Response, Validator};
use packages\packagename\{Validators\IBANValidator};
class Banks extends Controller {
public function update(): Response {
Validator::addValidator(IBANValidator::class);
$inputs = $this->checkinputs(array(
"banks" => array(
"type" => 'iban',
),
));
foreach ($inputs["banks"] as $item) {
$item["bank"]->account = $item["account"];
$item["bank"]->save();
}
$this->response->setStatus(true);
return $this->response;
}
}
مثال 2 :
<?php
namespace packages\packagename\controllers;
use packages\base\{Controller, Response};
use packages\packagename\{Validators\IBANValidator};
class Banks extends Controller {
public function update(): Response {
$inputs = $this->checkinputs(array(
"banks" => array(
"type" => IBANValidator::class,
),
));
foreach ($inputs["banks"] as $item) {
$item["bank"]->account = $item["account"];
$item["bank"]->save();
}
$this->response->setStatus(true);
return $this->response;
}
}
خالی بودن
با استفاده از کلید empty مجوز خالی بودن یا نبودن فیلد مشخص میشود. مقدار این کلید true یا false میتواند باشد.
مقدار true برای مجوز خالی بودن است. مقدار پیش فرض این کلید false میباشد.
اختیاری بودن
با استفاده از کلید optional اجازه وجود و یا عدم وجود فیلد ورودی داده میشود .
مقدار پیش فرض این کلید false میباشد .
اگر این کلید false باشد و فیلد ورودی تعریف نشده باشد استثنا inputValidationException پرتاب میشود.
مقدار پیشفرض
با استفاده از کلید default برای فیلد مورد نظر مقدار پیش فرض مشخص میشود.
اگر فیلد مجوز خالی بودن داشته باشد در صورت خالی بودن مقدار پیشفرض به عنوان value در نظر گرفته میشود.
مقادیر ثابت
اگر داده های دریافت شده دارای مقادیر مشخص و ثابتی هستند با استفاده از کلید values میتوان مقادیر را مشخص کرد. این کلید برای input های checkbox, select,... که دارای تعداد value های ثابت هستند کاربرد بیشتری دارد.
داده های رشته ای
برای اعتبارسنجی داده های رشته ای از مقدار string برای کلید type استفاده میشود.
برای دادههایی که از نوع string باشند میتوان کلید های زیر را برای مدیریت اعتبارسنجی آن تعریف کرد.
عبارت منظم
اگر فیلدی که دریافت میکنیم باید از قاعده ی مشخصی پیروی کند با استفاده از کلید regex قاعده آن مشخص میشود.
مثال
<?php
namespace packages\packagename\controllers;
use packages\base\{Controller, Response};
class Order extends Controller {
public function validateDomain(): Response {
$this->checkinputs(array(
'domain' => array(
'type' => 'string',
'regex' => '/^([a-z0-9\\-]+\\.)+[a-z]{2,12}$/i', // Thrown InpuvalidationException in response if given domain is not match to this pattern
),
));
$this->response->setStatus(true);
return $this->response;
}
}
حذف whitespace
کلید trim مانند متد trim() در php عمل میکند. این کلید میتواند مقادیر true یا false داشته باشد.
اگر trim مقدار دهی نشود در فریمورک بطور خودکار whitespace های داده حذف میشوند. برای جلوگیری از حذف whitespace ها باید مقدار trim را برابر با false قرار دهید.
کدها و تگ ها
اگر داده دریافتی کد باشد باید مقدار کلید htmlTags را برابر با true قرار دهید در غیر اینصورت کد ها به صورت اسکی خود تبدیل میشوند.
بطور مثال اگر داده ورودی <h1> salam </h1> باشد و مقدار htmlTags برابر true باشد داده ورودی به <h1>salam</h1> تبدیل میشود.
مقدار پیشفرض این کلید false است.
رشته های چند خطی
اگر داده ورودی چند خطی باشد، اگر بخواهیم \n را از داده حذف کنیم باید به کلید multiLine مقدار false میدهیم. اگر این کلید مقدار دهی نشود داده ها میتوانند چند خطی باشند.
مثال :
<?php
namespace packages\packagename\controllers;
use packages\blog\Comment;
use packages\base\{Controller, Response};
class Blog extends Controller {
public function comment(): Response {
$inputs = $this->checkinputs(array(
'name' => array(
'type' => 'string',
'multiLine' => false,
),
'message' => array(
'type' => 'string',
),
));
$model = new Comment();
$model->name = $inputs["name"];
$model->message = $inputs["message"];
$model->save();
$this->response->setStatus(true);
return $this->response;
}
}
داده های عددی
برای اعتبارسنجی داده های عددی از مقادیر
number, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float برای کلید type استفاده میشود.
هریک از موارد فوق، رنج عدد و همچنین مثبت و منفی بودن آن را مشخص میکند که قوانین آن در جدول زیر قابل مشاهده است.
| نوع | رنج عددی مجاز |
|---|---|
number | تمامی اعداد مثبت و منفی را میپذیرد |
int | تمامی اعداد مثبت و منفی را میپذیرد |
int8 | -128 تا 127 |
int16 | -32768 تا 32767 |
int32 | -2147483648 تا 2147483647 |
int64 | تمامی اعداد مثبت و منفی را میپذیرد |
uint8 | 0 تا 255 |
uint16 | 0 تا 65535 |
uint32 | 0 تا 4294967295 |
uint64 | تمامی اعداد مثبت را میپذیرد |
float | اعداد اعشاری |
علاوه بر قوانینی که برای هریک از مقادیر فوق وجود دارد میتوان بصورت دستی رنج عددی را مشخص کرد. این تنظیمات با استفاده از دو کلید min و max انجام پذیر میباشد.
نوع های float و number مقدار ورودی صفر را نمیپذیرند و استتثنا InputValidationException پرتاب میشود . برای جلوگیری از این استثنا و پذیرش عدد صفر، باید کلید zero با مقدار true تعریف کرد.
نکته 1 : اگر فیلد از نوع عددی باشد و empty آن برابر true باشد زمانی که مقدار این فیلد خالی باشد، مقدار null برای فیلد در نظر گرفته میشود.
نکته 2 : اگر عدد وارد شده خارج از رنج مجاز عددی باشد استثنا از جنس InputValidationException با پیغام min-value یا max-value پرتاب میشود.
نکته 3 : اگر داده وارد شده عدد نباشد استثنا از جنس InputValidationException با پیغام not-a-number پرتاب میشود.
نکته 4 : اگر عدد وارد شده برابر با value تعریف شده نباشد استثنا از جنس InputValidationException با پیغام not-defined-value پرتاب میشود.
مثال 1
<?php
namespace packages\packagename\controllers;
use packages\cronjob\Task;
use packages\base\{Controller, Response};
class Cronjobs extends Controller {
public function store(): Response {
$inputs = $this->checkinputs(array(
'hour' => array(
'type' => 'number',
'values' => range(0, 24),
),
'minuts' => array(
'type' => 'string',
'min' => 0,
'max' => 60,
),
'command' => array(
'type' => 'string',
'htmlTags' => true,
),
'port' => array(
'type' => 'uint16',
'optional' => true,
'default' => 22,
),
));
$model = new Task();
$model->hour = $inputs["hour"];
$model->minuts = $inputs["minuts"];
$model->command = $inputs["command"];
$model->port = $inputs["port"];
$model->save();
$this->response->setStatus(true);
return $this->response;
}
}
مثال 2
<?php
namespace packages\packagename\controllers;
use packages\base\{Controller, Response, Options};
class Chats extends Controller {
public function update(): Response {
$inputs = $this->checkinputs(array(
'prev_messages_count' => array(
'type' => 'number',
'min' => -100,
'max' => 100,
'zero' => true,
),
));
Options::save("packages.packagename.chats.prev_messages_count", $inputs['prev_messages_count']);
$this->response->setStatus(true);
return $this->response;
}
}
**توجه :**در مثال فوق اگر index zero .تعریف نشود عدد صفر را نمیپذیرد.
داده های boolean
برای اعتبارسنجی داده های boolean از کلید bool برای مقدار type استفاده میشود.
مقادیر ورودی میتواند 0، 1، true و یا false باشد.
نکته : اگر مقدار empty برابر true باشد، درصورتی که فیلد خالی باشد مقدار آن برابر false در نظر گرفته میشود.
مثال
<?php
namespace packages\packagename\controllers;
use packages\base\{Controller, Response, Options};
class Chats extends Controller {
public function update(): Response {
$inputs = $this->checkinputs(array(
'status' => array(
'type' => 'bool',
),
));
Options::save("packages.packagename.chats.one-on-one-chats.status", $inputs['status']);
$this->response->setStatus(true);
return $this->response;
}
}
تاریخ
برای اعتبار سنجی تاریخ از مقدار date برای کلید type استفاده میشود. این اعتبارسنج ورودی های تاریخ را به شکل YYYY/MM/DD و یا همراه با زمان بصورت YYYY/MM/DD HH:II:SS میپذیرد.
توجه : سال باید بصورت چهاررقمی وارد شود. روز و ماه میتواند یک یا دو رقمی وارد شود.
برای تاریخ کلید unix تعریف شده است اگر این کلید تعریف نشود و یا مقدار false داشته باشد خروجی اعتبارسنج، تاریخ وارد شده میباشد و اگر مقدار true داشته باشد خروجی آن timestamp تاریخ وارد شده است.
مثال
<?php
namespace packages\packagename\controllers;
use packages\shop\category\Special;
use packages\base\{Controller, Response, Date, Options, InputValidationException};
class Shop extends Controller {
public function updateSpecialSells(): Response {
$inputs = $this->checkinputs(array(
'start_at' => array(
'type' => 'date',
'unix' => true,
'optional' => true,
'default' => Date::time(),
),
'end_at' => array(
'type' => 'date',
'unix' => true,
),
));
if ($inputs["start_at"] < Date::time()) {
throw new InputValidationException("start_at");
}
if ($inputs["end_at"] <= $inputs["start_at"]) {
throw new InputValidationException("end_at");
}
$models = Special::where("status", Special::ACTIVE)->get();
foreach ($models as $model) {
$model->start_at = $inputs["start_at"];
$model->end_at = $inputs["end_at"];
$model->save();
}
$this->response->setStatus(true);
return $this->response;
}
}
شماره همراه
برای اعتبارسنجی شماره موبایل از مقدار cellphone برای کلید type استفاده میشود.
نکته: در حال حاضر فقط اعتبار سنجی شماره های همراه اپراتور های ایران در جالنو به صورت پیشفرض وجود دارد.
همچنین بطور پیش فرض کد ایران 98 تنظیم شده است. برای تغییر آن میتوانید تنظیم با نام packages.base.validators.default_cellphone_country_code در فایل تنظیمات جالنو در مسیر packages/base/libraries/config/config.php را به مقدار مورد نظر تغییر دهید..
اعتبارسنج ورودی ها را با فرمت های 9131101234 , 09131101234 , 989131101234 , 9809131101234 , +989131101234 و 98989131101234 را گرفته و پس از بررسی شماره وارد شده را بصورت 989131234567 برمیگرداند.
مثال
<?php
namespace packages\packagename\controllers;
use packages\packagename\User as Medel;
use packages\base\{Controller, Response, InputValidationException, Password, Session};
class Users extends Controller {
public function login(): Response {
$inputs = $this->checkinputs(array(
'username' => array(
'type' => 'cellphone',
),
'password' => array(),
));
$model = new Medel();
$model->where("username", $inputs["username"]);
$user = $user->getOne();
if (!$user) {
throw new InputValidationException("username");
}
if (!Password::verify($inputs["password"], $user->password)) {
throw new InputValidationException("password");
}
Session::set("loggin", true);
Session::set("userID", $user->id);
$this->response->setStatus(true);
return $this->response;
}
}
ایمیل
برای اعتبارسنجی ایمیل از مقدار email برای کلید type استفاده میشود.
مثال
<?php
namespace packages\packagename\controllers;
use packages\packagename\User as Model;
use packages\base\{Controller, Response, InputValidationException, Session};
class Users extends Controller {
public function login(): Response {
$inputs = $this->checkinputs(array(
'username' => array(
'type' => 'email',
),
'password' => array(),
));
$model = new Model();
$model->where("username", $inputs["username"]);
$user = $user->getOne();
if (!$user) {
throw new InputValidationException("username");
}
if (!Password::verify($inputs["password"], $user->password)) {
throw new InputValidationException("password");
}
Session::set("loggin", true);
Session::set("userID", $user->id);
$this->response->setStatus(true);
return $this->response;
}
}
مثال 2
<?php
namespace packages\packagename\controllers;
use packages\packagename\User as Model;
use packages\base\{Controller, Response, InputValidationException, Session};
class Users extends Controller {
public function login(): Response {
$inputs = $this->checkinputs(array(
'username' => array(
'type' => ['email', 'cellphone'],
),
'password' => array(),
));
$model = new Model();
$model->where("username", $inputs["username"]);
$user = $user->getOne();
if (!$user) {
throw new InputValidationException("username");
}
if (!Password::verify($inputs["password"], $user->password)) {
throw new InputValidationException("password");
}
Session::set("loggin", true);
Session::set("userID", $user->id);
$this->response->setStatus(true);
return $this->response;
}
}
ip ورژن 4
برای اعتبارسنجی ip ورژن چهار از مقدار ip4 برای کلید type استفاده میشود.
مثال 1
<?php
namespace packages\packagename\controllers;
use packages\cronjob\Task;
use packages\base\{Controller, Response};
class Cronjobs extends Controller {
public function store(): Response {
$inputs = $this->checkinputs(array(
'hour' => array(
'type' => 'number',
'values' => range(0, 24),
),
'minuts' => array(
'type' => 'string',
'min' => 0,
'max' => 60,
),
'ip' => array(
'type' => 'ip4',
),
'command' => array(
'type' => 'string',
'htmlTags' => true,
),
'port' => array(
'type' => 'uint16',
'optional' => true,
'default' => 22,
),
));
$model = new Task();
$model->hour = $inputs["hour"];
$model->minuts = $inputs["minuts"];
$model->ip = $inputs["ip"];
$model->command = $inputs["command"];
$model->port = $inputs["port"];
$model->save();
$this->response->setStatus(true);
return $this->response;
}
}
آدرس اینترنتی (url)
برای اعتبارسنجی آدرسهای url از مقدار url برای کلید type استفاده میشود.
اگر در url لازم به نوشتن پروتکل و یا بررسی نوع پروتکل باشیم با تعریف کلید protocols با مقدار نوع پروتکل مورد نظر برای اعتبارسنجی وجود پروتکل را اجباری میکنیم.
برای protocols میتوان آرایه ای ازپروتکل ها را معرفی کرد.
مثال
<?php
namespace packages\packagename\controllers;
use packages\cronjob\Task;
use packages\base\{Controller, Response};
class Cronjobs extends Controller {
public function store(): Response {
$inputs = $this->checkinputs(array(
'hour' => array(
'type' => 'number',
'values' => range(0, 24),
),
'minuts' => array(
'type' => 'string',
'min' => 0,
'max' => 60,
),
'ip' => array(
'type' => 'ip4',
),
'hostname' => array(
'type' => 'url',
'protocols' => 'https',
),
'command' => array(
'type' => 'string',
'htmlTags' => true,
),
'port' => array(
'type' => 'uint16',
'optional' => true,
'default' => 22,
),
));
$model = new Task();
$model->hour = $inputs["hour"];
$model->minuts = $inputs["minuts"];
$model->command = $inputs["command"];
$model->ip = $inputs["ip"];
$model->hostname = $inputs["hostname"];
$model->port = $inputs["port"];
$model->save();
$this->response->setStatus(true);
return $this->response;
}
}
درمثال فوق اگر آدرس فاقد پروتکل یا پروتکل آن برابر https .نباشد استثنا پرتاب میشود.
فایل
برای اعتبارسنجی فایل ها از مقدار file برای کلید type استفاده میشود. میتوان از کلید های زیر برای مدیریت اعتبارسنجی فایل ها استفاده کرد.
نوع فایل
از کلید extension برای مشخص کردن نوع فایل استفاده میشود. مقدار آن میتواند بصورت ارایهای از پسوندهای مجاز باشد.
اگر extension مقداردهی نشود فایل با هر پسوندی پذیرفته میشود.
اندازه فایل
با استفاده از کلیدهای min-size و max-size میتوان برای فایل دریافتی محدودیت اندازه مشخص کرد.
ارسال چند فایل
اگر فایل دریافتی بصورت آرایهای از چند فایل باشد با قرار دادن مقدار true برای کلید multiple به فریمورک اعلام میشود داده دریافتی شامل چند فایل میباشد.
تبدیل فایل دریافتی به شی کلاس File
با تعریف کلید obj با مقدار true فایل دریافتی به یک شی از کلاس local\Tmp تبدیل میشود. اگر این کلید مقدار دهی نشود برابر false در نظر گرفته میشود، در اینصورت خروجی اعتبارسنج همانند خروجی $_FILES میباشد.
برای اطلاعات بیشتر در رابطه با فایل ها به صفحه فایل مراجعه کنید.
توجه : هنگام کار با فایل ها تگ form باید صفت enctype="multipart/form-data" را داشته باشد.
1 مثال
<?php
namespace packages\packagename\controllers;
use packages\base\{Controller, Response, Packages};
class Files extends Controller {
public function upload(): Response {
$inputs = $this->checkInputs(array(
"files" => array(
"type" => "file",
"max-size" => 2097152 \\ Byte
"obj" => true,
"extension" => ["pdf", "word"],
"multiple" => true, // You can false or remove this line if you to accept only one file.
),
));
foreach ($inputs["files"] as $file) {
$name = $file->md5();
$localFile = Packages::package("packagename")->file("storage/private/files/{$name}.{$file->getExtension()}");
if ($localFile->exists()) {
continue;
}
$directory = $localFile->getDirectory();
if (!$directory->exists()) {
$directory->make(true);
}
$file->copyTo($localFile);
}
$this->response->setStatus(true);
return $this->response;
}
}
در مثال فوق فقط فایل های pdf یا word پ ذیرفته میشوند و حداکثر حجم آن 2 مگابایت میتواند باشد. و خروجی اعتبارسنج، شی از کلاس local\Tmp میباشد.
تصاویر
در فریمورک برای اعتبارسنجی تصاویر علاوه بر استفاده از اعتبارسنج فایل ها، بطور اختصاصی اعتبارسنجی برای تصاویر در نظر گرفته شده است که برای استفاده از آن، از مقدار image برای کلید type استفاده میشود.
اعتبارسنج، تصاویر با پسوند های jpeg, jpg, png, gif, webp را میپذیرد.
با استفاده از کلید extension میتوان مشخص کرد تنها تعدادی از پسوند های فوق پذیرفته شود.
برای اعتبارسنجی تصاویر میتوان عواملی مانند سایز تصویر و طول و عرض آن را نیز اعتبارسنجی کرد. از کلید های زیر برای مدیریت اعتبارسنجی تصاویر استفاده میشود.
سایز تصویر
با استفاده از کلیدهای min-size و max-size میتوان برای تصویر دریافتی محدودیت اندازه مشخص کرد.