I'm doing a validation by regex from a request, this validation is to check that the parameter that is sent is an IP.
My rules are as follows:
<?php
namespace App\Http\Requests\usuarios;
use Illuminate\Foundation\Http\FormRequest;
class storeVPN extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'segmentwan' => 'regex:/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/',
'segmentlan' => 'regex:/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/',
];
}
the debugger shows me that the error is in this line
the debugger shows me that the error is in this line
return preg_match($parameters[0], $value) > 0;
y el error completo es el siguiente
* #return bool
*/
public function validateRegex($attribute, $value, $parameters)
{
if (! is_string($value) && ! is_numeric($value)) {
return false;
}
$this->requireParameterCount(1, $parameters, 'regex');
return preg_match($parameters[0], $value) > 0;
}
/**
* Validate that a required attribute exists.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function validateRequired($attribute, $value)
{
if (is_null($value)) {
return false;
} elseif (is_string($value) && trim($value) === '') {
return false;
} elseif ((is_array($value) || $value instanceof Countable) && count($value) < 1) {
return false;
} elseif ($value instanceof File) {
return (string) $value->getPath() !== '';
}
In Laravel you have ip validation rule so you can use:
return [
'segmentwan' => 'ip',
'segmentlan' => 'ip',
];
Also to be honest when I'm looking at your code I don't see the error. Are you sure error is with those 2 regexes?
Change your Validation rules. When you are using regex you need to use Array instead of pipe delimiter.
return [
'segmentwan' => [ 'regex:/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/'],
'segmentlan' => [ 'regex:/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/'],
];
Note: When using the regex pattern, it may be necessary to specify
rules in an array instead of using pipe delimiters, especially if the
regular expression contains a pipe character.
UPDATE
If you are validating IP address than laravel provides three validation options.
ip:
The field under validation must be an IP address.
ipv4:
The field under validation must be an IPv4 address.
ipv6:
The field under validation must be an IPv6 address.
You shouldn't be using regex to validate IPs; you can use filter_var() built-in function instead
It has many flags including FILTER_FLAG_IPV4 for ipv4 & FILTER_FLAG_IPV6 for ipv6 ... Ofcourse you can mix flags so you are able to check both
Related
I have a Symfony (4.3) Form and some validation rules.
In my App\Entity\Objectif class :
/**
* #ORM\Column(type="float", options={"default" : 0})
* #Assert\Type("float")
*/
private $budget;
In my App\Form\ObjectifType class :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('budget')
->add('userQuantity')
/* some other parameters */
;
}
In my App\Controller\ObjectifController class :
public function generateMenu(Request $request)
{
$form = $this->createForm(ObjectifType::class);
$data = json_decode($request->getContent(),true);
$form->submit($data);
if ($form->isValid()) {
/* do some stuff with data */
} else {
return $this->json('some error message');
}
}
My Symfony application is an API, so I receive data formatted in Json from the frontend.
My goal is to ensure that value sended by end-user as $budget is of float type.
Problem : the validation process does not work for value 'true'.
If end-user sends a string as $budget, the process works and the validation fails as it should.
If end-user sends the value 'true' as $budget, that value gets implicitly type-casted as a '1' and so the validation succeed, which souldn't happen.
How do I force PHP or Symfony to not implicitly type-cast 'true' to '1' in that situation ?
Thank you
TESTS (optionnal reading)
For testing purpose, I put a Callbak validator (symfony doc) in my App\Entity\Objectif class, whose only purpose is to output the type of $budget property when form is being validated :
// App\Entity\Objectif.php
/**
* #Assert\Callback
*/
public function validate(ExecutionContextInterface $context, $payload)
{
dump('Actual value is : ' . $this->budget);
if (!is_float($this->budget)) {
dump('Value is NOT FLOAT');
$context->buildViolation('This is not a float type.')
->atPath('budget')
->addViolation();
exit;
} else {
dump('Value is FLOAT');
exit;
}
}
If I send 'true' as the 'budget' key with my API testing software (Insomnia) :
{
"budget": true
}
I always get those outputs :
Objectif.php on line 193:
"Actual value is : 1"
Objectif.php on line 202:
"Value is FLOAT"
I suspect it is a Symfony problem, because when i use PHP CLI :
php > var_dump(is_float(true));
bool(false)
I get correct result.
By the way, 'false' value get autocasted to 'null', which doesn't bother regarding my validation purpose, but I don't find if necesary.
I can't tell you where and why the Form Component changes true to 1 without further investigation but you could use a DataTransferObject and validate against that before submitting the form.
class ObjectifDto
{
/**
* #Assert\Type("float")
* #var float
*/
public $budget;
public static function fromRequestData($data): self
{
$objectifDto= new self();
$objectifDto->budget = $data['budget'] ?? 0;
return $objectifDto;
}
}
In the Controller:
public function generateMenu(Request $request, ValidatorInterface $validator)
{
$data = json_decode($request->getContent(),true);
$dto = ObjectifDto::fromRequestData($data);
$errors = $validator->validate($dto);
//return errors or go on with the form or persist manually
}
So, after some research, I found out that submit() method of classes that implements FormInterface do cast every scalar value (i.e. integer, float, string or boolean) to string (and "false" values to "null") :
// Symfony\Component\Form\Form.php
// l. 531 (Symfony 4.3)
if (false === $submittedData) {
$submittedData = null;
} elseif (is_scalar($submittedData)) {
$submittedData = (string) $submittedData;
}
This has nothing to do with the validation process (which was, in my case, correctly set up).
This explains why I get "true" value casted to "1".
Someone know why symfony's code is designed that way ? I don't get it.
I tried to get rid of submit() method in my controller by using the more conventionnal handleRequest() method. But it does not change anything at all, since handleRequest internally calls submit() (see handleRequest() in HttpFoundationRequestHandler class). So "true" is still casted to "1".
So I ended up using Chris's solution (cf. above). It works perfectly. All credits goes to him :
public function generateMenu(
Request $request,
ValidatorInterface $validator
)
{
$data = json_decode(
strip_tags($request->getContent()),
true);
$dto = ObjectifDto::fromRequestData($data);
$errors = $validator->validate($dto);
if (count($errors) > 0) {
$data = [];
$data['code status'] = 400;
foreach ($errors as $error) {
$data['errors'][$error->getPropertyPath()] = $error->getMessage();
}
return $this->json($data, 400);
}
// everything is fine. keep going
}
I think it is a good habit to not use Symfony's Form validation process, at least when dealing with API and JSON.
I feel like the 'raw' (so to speak) Validator, combined with DataTransfertObject, gives much more control.
Most importantly, it does not autocast your values for whatever reasons (which would totally screw your validation process)...
Using adldap-laravel and Laravel 5.8.
I'm getting permissions based on LDAP groups. I can check if a user is part of a group using: $user->ldap->inGroup('Accounts'); (that returns a bool)
However that method also accepts an array, but seems to be an "AND" search, rather than "ANY".
So I've written this:
/**
* LDAP helper, to see if the user is in an AD group.
* Iterate through and break when a match is found.
*
* #param mixed $input
* #return bool
*/
public function isInGroup($input)
{
if (is_string($input)) {
$input[] = $input;
}
foreach ($input as $group)
if ($this->ldap->inGroup($group)) {
return true;
}
return false;
}
Implemented like this:
$user->isInGroup(['Accounts', 'Sales', 'Marketing']);
However it takes a long time to check.
Does anyone know of an improved way to solve my problem?
Yes can do it via query builder of Adldap.
/**
* LDAP helper, to see if the user is in an AD group.
* Iterate through and break when a match is found.
*
* #param mixed $input
* #return bool
*/
public function isInGroup($input){
if (is_string($input)) {
$input[] = $input;
}
$counter = 0;
$groupQuery = $this->ldap->search()->groups();
foreach ($input as $group) {
if ($counter == 0) {
$groupQuery->where('samaccountname', '=', $group);
} else {
$groupQuery->orWhere('samaccountname', '=', $group);
}
$counter++;
}
return $groupQuery->get()->count();
}
This samaccountname may be different field name for your LDAP provider. I couldn't tested it if has any syntax or method name error anyway you will find this same methods from your Adldap Provider class. The algorithm/process is same.
How can I check if the Ethereum address from Laravel input is valid in terms of format?
Here's a Laravel custom validation rule for validating Ethereum addresses against the EIP 55 specification. For details of how it works, please go through the comments.
<?php
namespace App\Rules;
use kornrunner\Keccak; // composer require greensea/keccak
use Illuminate\Contracts\Validation\Rule;
class ValidEthereumAddress implements Rule
{
/**
* #var Keccak
*/
protected $hasher;
public function __construct(Keccak $hasher)
{
$this->keccak = $hasher;
}
public function passes($attribute, $value)
{
// See: https://github.com/ethereum/web3.js/blob/7935e5f/lib/utils/utils.js#L415
if ($this->matchesPattern($value)) {
return $this->isAllSameCaps($value) ?: $this->isValidChecksum($value);
}
return false;
}
public function message()
{
return 'The :attribute must be a valid Ethereum address.';
}
protected function matchesPattern(string $address): int
{
return preg_match('/^(0x)?[0-9a-f]{40}$/i', $address);
}
protected function isAllSameCaps(string $address): bool
{
return preg_match('/^(0x)?[0-9a-f]{40}$/', $address) || preg_match('/^(0x)?[0-9A-F]{40}$/', $address);
}
protected function isValidChecksum($address)
{
$address = str_replace('0x', '', $address);
$hash = $this->keccak->hash(strtolower($address), 256);
// See: https://github.com/web3j/web3j/pull/134/files#diff-db8702981afff54d3de6a913f13b7be4R42
for ($i = 0; $i < 40; $i++ ) {
if (ctype_alpha($address{$i})) {
// Each uppercase letter should correlate with a first bit of 1 in the hash char with the same index,
// and each lowercase letter with a 0 bit.
$charInt = intval($hash{$i}, 16);
if ((ctype_upper($address{$i}) && $charInt <= 7) || (ctype_lower($address{$i}) && $charInt > 7)) {
return false;
}
}
}
return true;
}
}
Dependencies
To validate checksum addresses, we need a Keccac implementation in place which is not supported by the built-in hash() function. You need to require this pure PHP implementation for the above rule to work.
Any 42 character string starting with 0x, and following with 0-9, A-F, a-f (valid hex characters) represent a valid Ethereum address.
You can find more information about lowercase and partial uppercase (for adding checksum) Ethereum addresses format here.
As of Laravel 9, If you are using a request class for validation, add this to the validation rules:
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'address' => 'regex:/^(0x)?(?i:[0-9a-f]){40}$/'
];
}
Notes:
Here, the input field name is address
I applied case insensitivity to only a part of the regular expression using the format (?i: ... )
I'm using PHP and curl with json to interact with my geth server.
I'm able to do all I want except one thing: checking if user's inputted address is valid according to ethereum wallet format.
I saw a javascript function here, but I'm mostly using PHP, I'm not into JS at all.
Any ideas how to validate ethereum addresses in PHP?
Here's a PHP implementation for Ethereum address validation against the EIP 55 specification. For details of how it works, please go through the comments.
<?php
use kornrunner\Keccak; // composer require greensea/keccak
class EthereumValidator
{
public function isAddress(string $address): bool
{
// See: https://github.com/ethereum/web3.js/blob/7935e5f/lib/utils/utils.js#L415
if ($this->matchesPattern($address)) {
return $this->isAllSameCaps($address) ?: $this->isValidChecksum($address);
}
return false;
}
protected function matchesPattern(string $address): int
{
return preg_match('/^(0x)?[0-9a-f]{40}$/i', $address);
}
protected function isAllSameCaps(string $address): bool
{
return preg_match('/^(0x)?[0-9a-f]{40}$/', $address) || preg_match('/^(0x)?[0-9A-F]{40}$/', $address);
}
protected function isValidChecksum($address)
{
$address = str_replace('0x', '', $address);
$hash = Keccak::hash(strtolower($address), 256);
// See: https://github.com/web3j/web3j/pull/134/files#diff-db8702981afff54d3de6a913f13b7be4R42
for ($i = 0; $i < 40; $i++ ) {
if (ctype_alpha($address{$i})) {
// Each uppercase letter should correlate with a first bit of 1 in the hash char with the same index,
// and each lowercase letter with a 0 bit.
$charInt = intval($hash{$i}, 16);
if ((ctype_upper($address{$i}) && $charInt <= 7) || (ctype_lower($address{$i}) && $charInt > 7)) {
return false;
}
}
}
return true;
}
}
Dependencies
To validate checksum addresses, we need a keccak-256 implementation in place which is not supported by the built-in hash() function. You need to require the greensea/keccak composer package as a dependency.
Kudos to #WebSpanner for pointing out the issue with SHA3 hashing.
Basically, you can convert the javascript entirely to PHP.
Here i have been able to convert and test the code for validating an ethereum address in PHP.
/**
* Checks if the given string is an address
*
* #method isAddress
* #param {String} $address the given HEX adress
* #return {Boolean}
*/
function isAddress($address) {
if (!preg_match('/^(0x)?[0-9a-f]{40}$/i',$address)) {
// check if it has the basic requirements of an address
return false;
} elseif (!preg_match('/^(0x)?[0-9a-f]{40}$/',$address) || preg_match('/^(0x)?[0-9A-F]{40}$/',$address)) {
// If it's all small caps or all all caps, return true
return true;
} else {
// Otherwise check each case
return isChecksumAddress($address);
}
}
/**
* Checks if the given string is a checksummed address
*
* #method isChecksumAddress
* #param {String} $address the given HEX adress
* #return {Boolean}
*/
function isChecksumAddress($address) {
// Check each case
$address = str_replace('0x','',$address);
$addressHash = hash('sha3',strtolower($address));
$addressArray=str_split($address);
$addressHashArray=str_split($addressHash);
for($i = 0; $i < 40; $i++ ) {
// the nth letter should be uppercase if the nth digit of casemap is 1
if ((intval($addressHashArray[$i], 16) > 7 && strtoupper($addressArray[$i]) !== $addressArray[$i]) || (intval($addressHashArray[$i], 16) <= 7 && strtolower($addressArray[$i]) !== $addressArray[$i])) {
return false;
}
}
return true;
}
Meanwhile, for someone looking for a very simple regular expression for checking ethereum address validity (e.g to use is as a pattern attribute of an HTML field), this regular expression may suffice.
^(0x)?[0-9a-fA-F]{40}$
In my model, a Song is linked to a Type. A Type can be Youtube, Soundcloud, Deezer, etc..
When the link value has been validated by my validator, I want to set the style_id value with the correct Type.
What is the best way to do it ?
I think the best way is to perform the check twice:
first time: using the validator, so you know it's one of these video provider and then return the video link (not the id)
second time: redefine the setLink() so it takes the link, extract the id and save both the link and the style_id
How to do that.
Create a custom lib, like lib/videoProvider.class.php. This is kind of prototyped class to valid & retrieve id from a video provider. It, of course, needs improvements.
class videoProvider
{
private $url;
private $providers = array('youtube','deezer','soundcloud');
private $youtubePattern = '%^# Match any youtube URL
(?:https?://)? # Optional scheme. Either http or https
(?:www\.)? # Optional www subdomain
(?: # Group host alternatives
youtu\.be/ # Either youtu.be,
| youtube\.com # or youtube.com
(?: # Group path alternatives
/embed/ # Either /embed/
| /v/ # or /v/
| /watch\?v= # or /watch\?v=
) # End path alternatives.
) # End host alternatives.
([\w-]{10,12}) # Allow 10-12 for 11 char youtube id.
$%x';
private $deezerPattern = '/\d+/';
private $soundcloudPattern = '[\w-]+/[\w-]+$';
public function __construct($url)
{
$this->url = $url;
}
/**
* #return true / false
*/
private function checkYoutube()
{
return preg_match($this->youtubePattern, $this->url) ? true : false;
}
/**
* #return true / false
*/
private function checkDeezer()
{
// A Deezer URL has this format : http://www.deezer.com/track/61340079
return preg_match($this->deezerPattern, $this->url) ? true : false;
}
/**
* #return true / false
*/
private function checkSoundcloud()
{
// A Soundcloud URL has this format : http://soundcloud.com/[A-Z Artist]/[A-Z Title]
return preg_match($this->soundcloudPattern, $this->url) ? true : false;
}
/**
* #return true / false
*/
public function isValid()
{
// check all video provider as you do in your validator
// so it will return true if it find one, otherwise false
foreach ($this->providers as $provider)
{
$function = 'check'.ucfirst($provider);
if (true === $this->$function())
{
return true;
}
}
return false;
}
/**
* #return string
*/
public function getId()
{
if ($this->checkYoutube() && preg_match($this->youtubePattern, $this->url, $matches))
{
return $matches[1];
}
if ($this->checkDeezer() && preg_match($this->deezerPattern, $this->url, $matches))
{
return $matches[1];
}
if ($this->checkSoundcloud() && preg_match($this->deezerPattern, $this->url, $matches))
{
return $matches[1];
}
}
/**
* #return string
*/
public function getProvider()
{
if ($this->checkYoutube())
{
return 'youtube';
}
if ($this->checkDeezer())
{
return 'deezer';
}
if ($this->checkSoundcloud())
{
return 'soundcloud';
}
}
}
Then in the doClean of your validator, you just need to call this class, like that:
$videoProvider = new videoProvider($url);
if (false === $videoProvider->isValid())
{
throw new sfValidatorError($this, 'invalid', array('value' => $url));
}
return $url;
Finally, the setLink in Song.class.php should now be:
public function setLink($value)
{
// only perform this tweak if the value is a http link
if (preg_match('/^http/i', $value))
{
$videoProvider = new videoProvider($value);
// define url id
parent::_set('link', $videoProvider->getId());
// define type id
$provider = $videoProvider->getProvider();
$type = Doctrine_Core::getTable('Type')->findOneByName($provider);
parent::_set('type_id', $type->getId());
}
}
This is a first draft that must be tested and improved (test if getId() returns an id and not false, same for getProvider, etc ...)