I am building a website using PHP Yii2 Framework and dektrium\yii2-user extension for user authentication.
I want to ask the user for a captcha if the number of unsuccessful logins is over three, but by default the extension doesn't support this.
Now, I have overridden the extension's User and LoginForm model, and added the fields and checks required. However, I cannot figure out how to add a rule to make the captcha required only from the fourth attempt.
Is it possible to add rules dynamically? I have shown a simplified code view below and my comments where I need help. I will write the functions, just need help with the commented part.
<?php
namespace app\models\dektrium\user;
class LoginForm extends \dektrium\user\models\LoginForm
{
public $captcha;
public $need_captcha;
public function rules() {
$rules = parent::rules();
//This is how you'd normally add a rule, but this will require it for every login
//The following rule should be added from the login()
$rules[] = ['captcha', 'captcha', 'message' => 'Too many attempts. Captcha required.'];
$rules[] = ['need_captcha', 'boolean'];
return $rules;
}
public function login() {
$success = false;
$requireCaptcha = false;
if ($this->validate() && $this->user) {
if ($this->user->login_attempts > 3) {
//add rule here to require captcha
$requireCaptcha = true;
}
$success = !$requireCaptcha && $this->validateCaptcha() && $this->validateLogin();
if ($success) {
$this->user->updateAttributes(['last_login_at' => time()]);
}
}
return $success;
}
}
?>
EDIT:
If there is an 'optional' parameter converse to 'required', that'd suffice to. I can check for the captcha within my login().
EDIT 2:
I tried to use scenarios as follows, but the model is not loading the captcha value in the controller action, before doing the validation.
<?php
namespace app\models\dektrium\user;
class LoginForm extends \dektrium\user\models\LoginForm
{
public $captcha;
public $need_captcha;
public $login_count;
public function rules() {
$rules = parent::rules();
$rules[] = ['captcha', 'captcha', 'message' => 'Too many attempts. Captcha required.', 'on' => ['required_captcha']];
$rules[] = ['need_captcha', 'boolean'];
$rules[] = ['login_count', 'integer'];
return $rules;
}
public function login() {
$this->user = $this->finder->findUserByUsernameOrEmail(trim($this->login));
if($this->user && $this->user->login_count > 3) {
$this->scenario = 'required_captcha';
$this->need_captcha = true;
}
$success = parent::login();
if ($success) {
$this->user->login_count = 0;
$this->user->save();
} else {
$this->login_count++;
if ($this->user) {
$this->user->login_count++;
$this->user->save();
}
}
if ($this->login_count > 3) {
$this->scenario = 'required_captcha';
$this->need_captcha = true;
}
return $success;
}
}
Use
['field', 'required', 'when' => function($model) { return your_true_condition; } when setting up validation rules.
Condition can be something from session/cookie, like an integer that you will increment each time
So here's how I ended up doing this...
I used a scenario based rule and in the controller action, I set the scenario when the condition for which I needed captcha was true. In reality, the controller was also of any extension, so I had to do some supported controller mapping and set the scenario via an event.
My first attempts at doing this failed since I was setting the scenario during the validation function, but should have probably set it prior to doing so, where it worked.
<?php
namespace app\models\dektrium\user;
class LoginForm extends \dektrium\user\models\LoginForm
{
public $captcha;
public $need_captcha;
public $login_count;
public function rules() {
$rules = parent::rules();
$rules[] = ['captcha', 'required', 'on' => ['use_captcha']];
$rules[] = ['captcha', 'captcha', 'on' => ['use_captcha']];
$rules[] = ['need_captcha', 'boolean'];
$rules[] = ['login_count', 'integer'];
return $rules;
}
public function login() {
$success = true;
$this->user = $this->finder->findUserByUsernameOrEmail(trim($this->login));
if(!$this->need_captcha && $this->user && $this->user->login_count > 3) {
$this->need_captcha = true;
$success = false;
}
$success = $success && parent::login();
if ($success) {
$this->user->login_count = 0;
$this->user->save();
} else {
$this->login_count++;
if ($this->user) {
$this->user->login_count++;
$this->user->save();
}
if ($this->login_count > 2)
$this->need_captcha = true;
}
return $success;
}
}
Related
I'm trying to validate four forms in one controller in laravel, how do i pass the requests coming from different forms to my custom requests depends on the form.
if ( file_exists(base_path().'/App/'.$request->program_type.'.php')) {
$formRequest = 'App\Http\Requests\\'.$request>program_type.'FormRequest';
$validated = $formRequest::validate($request);
}
}
I would recommend Form Request Validation.
You can specify different rules depending on the parameter sent in the form.
For example:
public function rules()
{
$formType = $this->get('formType');
if ($formType == 1) {
$rules = $this->formOneRules();
} elseif ($formType == 2) {
$rules = $this->formTwoRules();
} elseif ($formType == 2) {
$rules = $this->formThreeRules();
} else {
$rules = $this->formFourRules();
}
return $rules;
}
private function formOneRules()
{
return [
//some rules
];
}
private function formTwoRules()
{
return [
//some rules
];
}
private function formThreeRules()
{
return [
//some rules
];
}
private function formFourRules()
{
return [
//some rules
];
}
So in your form, send some kind of parameter like formType.
Of course, instead of if-else you can use a switch statement.
I have this formrequest that contains rules and a withValidator as a second layer of validation.
Note: I am aware that having it unique on the rules would supress the need for this example, but I'll need to do further validations here.
public function rules(Request $request) {
return [
"name" => "required|max:191",
"begin_date" => "required|after_or_equal:today|date_format:d-m-Y",
"end_date" => "required|after:begin_date|date_format:d-m-Y",
];
}
public function withValidator($factory) {
$result = User::where('name', $this->name)->get();
if (!$result->isEmpty()) {
$factory->errors()->add('User', 'Something wrong with this guy');
}
return $factory;
}
I am positive that it enters the if as I've placed a dd previously it to check if it's going inside. However, it proceeds to this method on the Controller and I don't want it to.
public function justATest(UserRequest $request) {
dd("HI");
}
I'm an idiot and didn't read the full doc.
It needs to specify with an after function,like this:
public function withValidator($factory) {
$result = User::where('name', $this->name)->get();
$factory->after(function ($factory) use ($result) {
if (!$result->isEmpty()) {
$factory->errors()->add('User', 'Something wrong with this guy');
}
});
return $factory;
}
I was facing this problem too.
I changed my withValidator to this:
public function withValidator($validator)
{
if (!$validator->fails()) {
$validator->after(function ($validator) {
if (Cache::has($this->mobile)) {
if (Cache::get($this->mobile) != $this->code) {
$validator->errors()->add('code', 'code is incorrect!');
} else {
$this->user = User::where('mobile', $this->mobile)->first();
}
} else {
$validator->errors()->add('code', 'code not found!');
}
});
}
Hi I take user data to two models If the user clicks the checkbox (company) it show him the additional data that needs to complete. I needs to work on scenario if checbox = 1 the data fields of the form must be passed. It is my action from the controller:
public function actionCreate() {
$model = new UrUserForm();
$userDate = new UserDataForm();
$model->scenario = 'create';
if (($userDate->load(Yii::$app->request->post()) && $userDate->validate() && $model->load(Yii::$app->request->post()) && $model->validate()) || $model->load(Yii::$app->request->post()) && $model->validate()) {
if ($userDate->IsCompany()) {
$userDate->scenario = 'setFirm';
} else {
$userDate->scenario = 'notFirm';
$userDate->clearData();
}
var_dump($userDate->scenario);
exit();
$userDate->saveOptionalData();
$model->RoyalUserData=$userDate->data['Id'];
$model->saveUser();
Yii::$app->session->setFlash('success', 'Użytkownik został dodany');
return $this->redirect(['index']);
} else {
return $this->render('create', [
'model' => $model,
'userDate' => $userDate
]);
}
}
An my model:
<?php
namespace backend\modules\users\models;
use common\models\UserData;
use frontend\modules\settings\models\Profile;
use yii\base\Model;
use Yii;
class UserDataForm extends Model
{
public $Address;
public $NIP;
public $CompanyName;
public $Website;
public $Phone;
public $IsCompany;
public $IsPhoneConfirmed;
public $CreatedAt;
public $UpdateAt;
public $Rel_State;
public $Rel_Currency;
public $IsDeleted;
public $data;
public function rules()
{
return [
[['Address', 'Phone', 'Rel_State', 'Rel_Currency','IsCompany'], 'safe', 'on' => 'notFirm'],
[['Address', 'Phone', 'Rel_State', 'Rel_Currency','IsCompany'], 'required', 'on' => 'setFirm'],
[['NIP','IsCompany', 'Phone', 'IsPhoneConfirmed', 'CreatedAt', 'UpdateAt', 'Rel_State', 'Rel_Currency', 'IsDeleted'], 'integer'],
[['Address', 'CompanyName', 'Website'], 'string', 'max' => 45],
[['Phone'], 'common\components\validators\PhoneValidator'],
[['NIP'], 'common\components\validators\NipValidator'],
['IsCompany', 'safe']
];
}
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios['setFirm'] = ['Address', 'Phone', 'Rel_State', 'Rel_Currency','IsCompany'];
$scenarios['notFirm'] = ['Address', 'Phone', 'Rel_State', 'Rel_Currency','IsCompany'];
return $scenarios;
}
public function saveOptionalData() {
$model = new UserData();
$model->Address=$this->Address;
$model->Phone=$this->Phone;
$model->Rel_State=$this->Rel_State;
$model->Rel_Currency= $this->Rel_Currency;
$model->NIP=$this->NIP;
$model->IsCompany = $this->IsCompany;
$model->IsPhoneConfirmed = $this->IsPhoneConfirmed;
$model->CompanyName = $this->CompanyName;
$model->Website = $this->Website;
$this->data=$model;
if ($model->validate() && $model->save()) {
return $model;
}
return false;
}
public function clearData() {
$this->Address = NULL;
$this->Phone = NULL;
$this->Rel_State = NULL;
$this->Rel_Currency = NULL;
$this->NIP = NULL;
$this->IsCompany = NULL;
$this->IsPhoneConfirmed = NULL;
$this->CompanyName = NULL;
$this->Website = NULL;
}
public function IsCompany() {
if ($this->IsCompany == 1) {
return true;
}
return false;
}
}
I read the documentation but it does not help me. In the create action I created
var_dump($userDate->scenario);
exit();
which indicates that there is everything okay because when checkobox is off vardump spits: string (7) "notFirm" and when he's on spits: string (7) "setFirm." I do not know where the fault but each time validation is safe, that should work that if checkbox is on data from rules(addres, phone) should be required. Anyone see my bad and can help me?
I hope you have found an answer, but in case you haven't here's one. You're setting the scenario after you validate the data. Scenarios must be set before you have run the validation in order to use different validation rules.
In your code you have
if ($userDate->IsCompany()) {
$userDate->scenario = 'setFirm';
} else {
$userDate->scenario = 'notFirm';
$userDate->clearData();
}
But in the first if in your code you have already validated
if (($userDate->load(Yii::$app->request->post()) && $userDate->validate() ...
In order to use a scenario I suggest the following:
$userDate->load(Yii::$app->request->post();//load data into model
if ($userDate->IsCompany()) {//check if company was set and is equal to 1
$userDate->scenario = 'setFirm';
} else {
$userDate->scenario = 'notFirm';
}
if($userDate->validate()...)//Validation code according to the scenario
need help updating a unique rule in my validation rules. I have a abstract validator that will validate a rules before storing into my database and in the rules array I set the email to be unique when creating or registering a user but when updating the user the enique email should not validate if the email is owned by the user.
abstract class Validator
abstract class Validator {
protected $errors;
protected $attributes;
public function __construct($attributes = null)
{
$this->attributes = $attributes ?: \Input::all();
}
public function passes()
{
$validation = \Validator::make($this->attributes, $this->rules());
if ($validation->passes()) return true;
$this->errors = $validation->messages();
return false;
}
public function getErrors()
{
return $this->errors;
}
}
Validation Rules(UserRule.php)
use MyCustomValidatorNamespaceHere....
class UserRules extends Validator
public function rules()
{
return [
'email' => 'required|email|unique:users,email,id',
...
];
}
and in my UserController I injected the UserRule in the constractor. (UserRule $userRule). Here is the code in the update method.
public function update($id)
{
$if ($this->userRule->passes())
{
$this->user->find($id)->update(Input::all());
return .........
}
}
But the validation always fail and displaying the error that the email is already taken. Please help guys.
The problem is your rule. When you update, you need to use unique that doesn't check record you update. So you should have:
unique:users,email,id
but for example:
unique:users,email,10
if you edit record with id 10.
What you could do is to define this rule:
'email' => 'required|email|unique:users,email,{id}',
and your passes method:
public function passes($id = null)
{
$rules = $this->rules();
$rules['email'] = str_replace('{id}', $id, $rules['email']);
$validation = \Validator::make($this->attributes, $rules);
if ($validation->passes()) return true;
$this->errors = $validation->messages();
return false;
}
and now in update rule use:
if ($this->userRule->passes($id))
By the way you have error in $if ($this->userRule->passes()) - it should be if and not $if
You can use the route method inside your request class to except an id from the validation
public function rules()
{
return [
'email' => 'required|email|unique:users,email,'.$this->route('user'),
...
];
}
I had a problem like that before and it was difficult to find an answer. Here is what I did.
class UserRules extends Validator {
public function __construct($input = NULL) {
$this->input = $input ?: \Input::all();
if(isset($this->input['id'])):
self::$rules['username'] = 'required|unique:users,username,'.$this->input['id'];
self::$rules['email'] = 'required|email|unique:users,email,'.$this->input['id'];
else:
self::$rules['username'] = 'required|unique:users';
self::$rules['email'] = 'required|email|unique:users';
endif;
}
public static $rules = array(
'company_id' => 'required',
'role' => 'required',
'password' => 'sometimes|required|confirmed'
);
}
You need to use self:: because $rules is static.
I have moved this function:
public function passes($id)
{
$rules = static::$rules;
$rules['username'] = str_replace('{id}', $id, $rules['username']);
$rules['email'] = str_replace('{id}', $id, $rules['email']);
$validation = \Validator::make($this->attributes, $rules);
if($validation->passes()) return true;
$this->errors = $validation->messages();
return false;
}
into UserRule.php and commented the same function in abstract class Validator
Now updating is working.
I solved this problem here on stackoverflow in a generic way. It will automatically adapt your rules if you do an update and add exceptions to your :unique if necessary.
I have created a sign in form with a remember me checkbox. I want to know how can i allow user to keep sign in when the browser is closed or sign out person when they close the browser. A sample code would be nice thank you.
here is my code
class HomeController extends BaseController {
public function getIndex()
{
if(Auth::check())
{
return Redirect::to('profile');
}
return View::make('index');
}
public function postRegister()
{
//gets array of the register form input values
$value = Input::all();
// create a new instance of the User model
$user = new User;
$validate = $user->userValidate($value);
//checks if the validation for the field fails
if($validate->fails())
{
/* $message = $validation->messages();
return $message; */
return Redirect::back()->withInput()->withErrors($validate);
}
//adds the users input to speicific field in the users table
$user->user_name = $value['username'];
$user->email = $value['email'];
$user->password = Hash::make($value['password']);
//save the inputs to the users table
$user->save();
return 'information has been stored';
}
public function getRegister()
{
$title = 'Register';
return View::make('register')->with('title',$title);
}
public function getSignIn()
{
$title = 'Signup';
return View::make('signup')->with('title',$title);
}
public function postSignIn()
{
//user's information
$credentials = array('email' => Input::get('email'),'password'=>Input::get('password'));
//logs this user in and checked if they are registered already in
if(Auth::attempt($credentials,false))
{
return Redirect::to('profile');
}
return Redirect::back()->withInput();
}
}
You just have to turn it on in your login method:
if (Auth::attempt(array('email' => $email, 'password' => $password), true))
{
// The user will now be logged in and remembered
}
else
{
// Raise a login error
}
This "true" parameter is to remember your user.
Here is the Laravel Auth::attempt() method declaration:
public function attempt(array $credentials = array(), $remember = false, $login = true)
{
...
}
You could set a cookie on the users browser (make sure you tell them if you are) to identify them. But beware that this could be modified by a malicious user.
PHP Cookies Documentation