I have several models in a Laravel/Backpack app where write access to certain properties is restricted to certain user roles.
Say I have a model Client with the properties
name
phone
active
viewCount
I want users with regular user privileges to be able to edit the name and phone properties but not active and viewCount. However, administrators should be able to edit active, and super duper administrators should have full access to viewCount.
I have this sorted out on the UI level and now want to implement basic security in the backend.
I would like to silently filter out (unset) properties that the current user is not allowed to edit, but may have maliciously set in the request.
I've been pointed to a custom FormRequest class being the place for this, but am a little lost how to implement it.
Should I take the pre-generated
public function rules()
{
return [
'name' => 'nullable|string',
'address' => 'required|string',
.....
and return a different array of validation rules depending on the current user's permissions? Is there a validation rule that unsets a field?
Or is there a more elegant way to handle this inside the FormRequest class?
You can simply pass in additional rule for different user role, and as long as you use the validated inputs, all should be good and any inputs not specified in the rules will not in the the validated result,
e.i.
for using Form validation
public function rules() {
$rules = [
'name' => 'nullable|string',
'address' => 'required|string',
];
// Additional rule only for administrator or super admin
if ( $this->user()->howEverYouFilterTheRole() )
$rules['active'] = 'nullable';
// additional rule only for superAdmin
if ( $this->user()->howEverYouFilterTheRole() )
$rules['viewCount'] = 'nullable';
return $rules;
}
then just the call in the validated method of the request
public function store( WhatEverFormRequest $request ) {
// Only contains field input specific in the rules;
$validated = $request->validated();
}
OR
apply the rule directly on request
public function store( Request $request ) {
$rules = [
'name' => 'nullable|string',
'address' => 'required|string',
];
// Additional rule only for administrator or super admin
if ( $request->user()->howEverYouFilterTheRole() )
$rules['active'] = 'nullable';
// additional rule only for superAdmin
if ( $request->user()->howEverYouFilterTheRole() )
$rules['viewCount'] = 'nullable';
// Only contains inputs specified in rules
$validated = $request->validate( $rules );
}
Another options you could use would be the rules; prohibited-if, prohibited-unless, exclude-if, exclude-unless
Related
I am using laravel-backpack 4.0. want to add a password change link with a page and all the functionality with validation and all, to a listing of users like edit, in the Action column.
It seems a bit odd to want another page where you can update only the password when the package's built in user crud lets you update that, and all the other user fields. That said, assuming you have your reasons (and that I've understood the usage correctly), one approach would be to use the users addon package suggested but then make a second CRUD controller for the user model that only supports the "update" operation and only allows editing the password.
NOTE
This is untested so there might be some minor issues to iron out, but the approach is sound.
Install and configure the users addon package. Then, create a second controller for users but edit such that only the "update" action is allowed and only the password and password confirmation fields are editable. We'll make the name and email read only so you can see who its for but cant edit those fields. You can make those fields hidden if you want, or remove them, but if you remove them, note that you'll need to create a custom request class and update the rules to not require those fields on submission.
<?php
namespace App\Http\Controllers;
use Backpack\CRUD\app\Http\Controllers\CrudController;
use Backpack\CRUD\app\Http\Requests\CrudRequest;
use EduardoArandaH\UserManager\app\Http\Requests\UserStoreCrudRequest as StoreRequest;
use EduardoArandaH\UserManager\app\Http\Requests\UserUpdateCrudRequest as UpdateRequest;
class UserPasswordCrudController extends CrudController
{
use \Backpack\CRUD\app\Http\Controllers\Operations\UpdateOperation { update as traitUpdate; }
public function setup()
{
$this->crud->setModel(config('backpack.permissionmanager.models.user'));
$this->crud->setEntityNameStrings('User Password', 'User Passwords');
$this->crud->setRoute(backpack_url('userPasswords'));
$this->crud->denyAccess('create');
$this->crud->denyAccess('list');
$this->crud->denyAccess('delete');
$this->crud->denyAccess('reorder');
$this->crud->denyAccess('revisions');
}
public function setupUpdateOperation()
{
$this->addUserFields();
$this->crud->setValidation(UpdateRequest::class);
}
/**
* Update the specified resource in the database.
*
* #return \Illuminate\Http\RedirectResponse
*/
public function update()
{
$this->crud->setRequest($this->crud->validateRequest());
$this->crud->setRequest($this->handlePasswordInput($this->crud->getRequest()));
$this->crud->unsetValidation(); // validation has already been run
return $this->traitUpdate();
}
/**
* Handle password input fields.
*/
protected function handlePasswordInput($request)
{
// Remove fields not present on the user.
$request->request->remove('password_confirmation');
$request->request->remove('roles_show');
$request->request->remove('permissions_show');
// Encrypt password if specified.
if ($request->input('password')) {
$request->request->set('password', Hash::make($request->input('password')));
} else {
$request->request->remove('password');
}
return $request;
}
protected function addUserFields()
{
$this->crud->addFields([
[
'name' => 'name',
'label' => trans('backpack::permissionmanager.name'),
'type' => 'text',
'attributes' => ['readonly' => 'readonly'],
],
[
'name' => 'email',
'label' => trans('backpack::permissionmanager.email'),
'type' => 'email',
'attributes' => ['readonly' => 'readonly'],
],
[
'name' => 'password',
'label' => trans('backpack::permissionmanager.password'),
'type' => 'password',
],
[
'name' => 'password_confirmation',
'label' => trans('backpack::permissionmanager.password_confirmation'),
'type' => 'password',
],
]);
}
}
Load the route for the new controller:
<?php
Route::group([
'namespace' => 'App\Http\Controllers',
'prefix' => config('backpack.base.route_prefix', 'admin'),
'middleware' => ['web', backpack_middleware()],
], function () {
Route::crud('userPasswords', 'UserPasswordCrudController');
});
Create a custom button at resources/views/vendor/backpack/crud/buttons/update_password.blade.php with this content:
#if ($crud->hasAccess('update'))
<!-- Single edit button -->
<i class="la la-edit"></i>Edit Password
#endif
Finally, in your normal user crud controller (or whatever controller you want the button in) add the button to the line stack in your controller's setupListOperation method:
public function setupListOperation()
{
$this->crud->addButtonFromView('line', 'update_password', 'view', 'end');
// ... normal setup code
}
I want to allow a user to create a folder on the local storage disk. So the form that is sent to the server quite is simple and has three attributes:
new-folder-name - that is the name of the folder to be created,
relative-path - a path to the directory inside which the new directory should be created relative to an asset root directory, and
asset_id - the id of an asset, I need this id to get the asset's root directory.
The thing is when I validate these attributes I need to also check if the folder the user is going to create already exists. For this purpose I made a rule called FolderExists. So, before I run FolderExists, I have to be sure all other rules have passed successfully because my custom rule should accept relative-path and asset_id to be able to build the path to check against.
Here is my rules() function, I'm doing validation in custom form request:
public function rules()
{
return [
'asset_id' => ['bail', 'required', 'exists:assets,id'],
'relative-path' => ['bail', 'required', 'string'],
'new-folder-name' => ['bail', 'required', 'string', 'min:3', new FolderName, new FolderExists($this->input('asset_id'), $this->input('relative-path')]
];
}
So my question is:
Is it possible to add FolderExists only if all other validation rules pass?
Or maybe it's possible to stop entire validation when the validator encounters first error?
Both options should be fine here.
Thank you!
I have finally found the solution myself. Here is what I ended up with.
To achieve the desired result I created another validator in withValidator() method of my custom form request, this second validator will handle only the FolderExists rule and only if the previous validation fails.
public function rules()
{
return [
'asset-id' => ['bail', 'required', 'integer', 'exists:assets,id'],
'relative-path' => ['bail', 'required', 'string'],
'new-folder-name' => ['bail', 'required', 'string', 'min:3', 'max:150', new FolderName]
];
}
public function withValidator($validator)
{
if (!$validator->fails())
{
$v = Validator::make($this->input(),[
'new-folder-name' => [new FolderExists($this->input('asset-id'), $this->input('relative-path'))]
]);
$v->validate();
}
}
If our main validator passes, we make another validator and pass only FolderExists rule with its arguments, that have already been validated, and call validate() method. That's it.
How do you validate a field is unique in cakephp 3.0? There doesn't appear to be a validation function listed in the API.
You want to use the rule validateUnique. For example, to check an email address is unique on an UsersTable:-
public function validationDefault(Validator $validator)
{
$validator->add(
'email',
['unique' => [
'rule' => 'validateUnique',
'provider' => 'table',
'message' => 'Not unique']
]
);
return $validator;
}
Details can be found in the API docs.
you have to use the rules from cake's ORM on your table...
add this at the top of your UsersTable after your namespace
use Cake\ORM\Rule\IsUnique;
Then prepare your rule to apply to your field by placing it in a public function
public function buildRules(RulesChecker $rules){
$rules->add($rules->isUnique(['email']));
return $rules;
}
Consult the cakephp documentation for more information about RULES
Validation providers can be objects, or class names. If a class name is used the methods must be static. To use a provider other than ‘default’, be sure to set the provider key in your rule:
// Use a rule from the table provider
$validator->add('title', 'unique', [
'rule' => 'uniqueTitle',
'provider' => 'table'
]);
For more details, look at the Adding Validation Providers section in the CakePHP3 reference book.
Use application rules as described in manual.
Kindly please check this for unique validation in cakephp 3.8
go to site
public function validationDefault(Validator $validator)
{
$validator->requirePresence('login_id');
return $validator;
}
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->isUnique(['login_id'], 'User already exist.'));
return $rules;
}
Using Laravel's localization (http://laravel.com/docs/5.1/localization) I have created some custom validation attributes to provide friendlier validation errors (for instance, 'First Name' instead of first name etc).
I am using form requests (http://laravel.com/docs/5.1/validation#form-request-validation) in order to validate user submissions and there are scenarios where I would like to provide store-specific custom validation attributes (for instance, I may have a 'name' field that is Brand Name in one context, and Product Name in another).
The messages() method allows me to specify validation rule specific message overrides, but that isn't ideal as it's not the validation message as such we need to override, just the attribute name (for example, if we have 5 validation rules for 'email', we have to provide 5 overrides here, rather than one override for, let's say, Customer Email).
Is there a solution to this? I note references to formatValidationErrors() and formatErrors() in the Laravel documentation, but there is not really any information on how to correctly override these, and I've not had much luck in trying.
You can override the attribute names, which is defaulting to whatever the field name is.
With form request
In your form request class override the attributes() method:
public function attributes()
{
return [
'this_is_my_field' => 'Custom Field'
];
}
With controller or custom validation
You can use the 4th argument to override the field names:
$this->validate($request, $rules, $messages, $customAttributes);
or
Validator::make($data, $rules, $messages, $customAttributes);
Simple working example
Route::get('/', function () {
// The data to validate
$data = [
'this_is_my_field' => null
];
// Rules for the validator
$rules = [
'this_is_my_field' => 'required',
];
// Custom error messages
$messages = [
'required' => 'The message for :attribute is overwritten'
];
// Custom field names
$customAttributes = [
'this_is_my_field' => 'Custom Field'
];
$validator = Validator::make($data, $rules, $messages, $customAttributes);
if ($validator->fails()) {
dd($validator->messages());
}
dd('Validation passed!');
});
As detailed in my question, I was looking for a way to provide specific form request stores (http://laravel.com/docs/5.1/validation#form-request-validation) with custom attribute names.
The Laravel documentation only covers two methods for Requests in this context - rules() and authorize(). I was aware there is a messages() method to provide validation specific custom error messages, but it also appears there is an attributes() method, which fits my requirements exactly:
public function attributes()
{
return [
'name' => 'Product Name'
]
}
This overrides the attribute name in the context of my store.
Im new to Cartalyst Sentinel and this concept of ACL. I've managed to create a user, perform activation and login and logout.
I'd like to take my learning to the next level. I would like 2 types of Users on this laravel app. 1 is Administrator another is Subscriber. I'm assuming my account creation method should by default create the user a subscriber.
public function postCreate() {
/* Validation */
$validation = Validator::make(Input::all(), [
'email' => 'required|email|max:50|unique:users',
'username' => 'required|min:3|max:20|unique:users',
'password' => 'required|min:6',
'password_repeat' => 'required|same:password',
]);
if ($validation->fails()) {
return Redirect('login')->withErrors($validation)->withInput();
} else {
$credentials = Input::all();
$user = Sentinel::register($credentials);
$activation = Activation::create($user);
$activation_code = $activation->code;
if ($user) {
Mail::send('emails.auth.activate', ['link' => URL::route('account-activate', [$user->id, $activation_code]), 'username' => $user->username], function($message) use ($user) {
$message->to($user->email, $user->username)->subject('Activate your account');
});
return Redirect::route('home')->with('global', 'Thank you for registering! We have sent you an email to activate your account');
}
}
}
Do i alter the code like so
$user = Sentinel::register($credentials);
$user = Sentinel::findById(1);
$role = Sentinel::findRoleByName('Subscribers');
$role->users()->attach($user);
The thing is i have not even created any roles to begin with. Where do we write that functionality? Right now i have the following Controllers
AccountController - handles activation
AuthController - handles login/logout
RegistrationController - handles registration of user
RolesController - i've not written anything inside here yet. Im a bit lost.
Please guide me. Any help is greatly appreciated.
You do not need to do a search for your user if you already registered them, the register method returns the user.
You can do the following to attach a role to a user:
$user = Sentinel::register($credentials);
$role = Sentinel::findRoleByName('Subscribers');
$role->users()->attach($user);
// OR
$user->roles()->attach($role);
you have both a user and a role object and they have a many to many relation so it doesn't matter which one you use.
You will need to create a db seeder or a method to create your permissions. But to create your Subscribers Role you will need to do the following:
Sentinel::getRoleRepository()->createModel()->create([
'name' => 'Subscribers',
'slug' => 'subscribers',
'permissions' => [
'user.view' => true,
'user.delete' => false,
// any other permissions you want your Subscribers to have
]
]);
A similar call can build your Administrator roles as well.
Your Roles Model and Controller are already built for you, you just need to access them through Sentinel, which you already have Sentinel::findRoleByName('Subscribers'); call.
Cartalyst has some pretty decent documentation about setting up roles and permissions for your users:
https://cartalyst.com/manual/sentinel#roles
https://cartalyst.com/manual/sentinel#permissions
It's just a matter of figuring out what you want each role to do or not do.
Also, you can set specific permissions per user to override the role permissions.