How to bind a GET request parameter safely to an Eloquent query - php

The problem is that I need to prevent SQL Injection.
I have the folowing route:
Route::get('/data/{searchRequestId}', [RequestedDataController::class, 'GetData'])->name('data');
and this controller function:
public function GetData(Request $request)
{
$requestedData = RequestedData::
where('SearchRequestId', $request->searchRequestId)
->orderByDesc('SearchRequestId')
->cursorPaginate(8);
if($requestedData->isEmpty()){
return abort(404, "page not found");
}
return view('RequestedData', ['requestedData' => $requestedData]);
}
Now when i call /data/2' (look at the quote), it will crash and the quote is litteraly in the query.
select top 9 * from [RequestedData] where [SearchRequestId] = 2' order by [SearchRequestId] desc
I don't understand why this is not escaped by laravel automaticaly, afterall I am not using raw queries.
The question is, what is the proper way to tackle this problem or pass the request parameter? I am using laravel 9 and sql server as database.
I tried using the query builder (DB::table('RequestedData') etc...), and i tried to pass the parameter with route().
$request->route('searchRequestId'))

Validation is your answer: https://laravel.com/docs/9.x/validation
You can create a FormRequest or do it right in your controller
public function GetData(Request $request)
{
$validated = $request->validate([
'searchRequestId' => 'integer'
]);
$requestedData = RequestedData::
where('SearchRequestId', $validated['searchRequestId'])
->orderByDesc('SearchRequestId')
->cursorPaginate(8);
if($requestedData->isEmpty()){
return abort(404, "page not found");
}
return view('RequestedData', ['requestedData' => $requestedData]);
}
Edit: This won't work ^^.
#aynber's comment would be a better idea.
You could also type hint the controller function:
public function GetData(Request $request, int $searchRequestId)

Related

Method Illuminate\Support\Collection::find does not exist

Edit function:
public function editCheck($id, LanguagesRequest $request)
{
try{
$language = language::select()->find($id);
$language::update($request->except('_token'));
return redirect()->route('admin.languages')->with(['sucess' => 'edit done by sucsses']);
} catch(Exception $ex) {
return redirect()->route('admin.addlanguages');
}
}
and model or select function
public function scopeselect()
{
return DB::table('languages')->select('id', 'name', 'abbr', 'direction', 'locale', 'active')->get();
}
This code is very inefficient, you're selecting every record in the table, then filtering it to find your ID. This will be slow, and is entirely unnecessary. Neither are you using any of the Laravel features specifically designed to make this kind of thing easy.
Assuming you have a model named Language, if you use route model binding, thing are much simpler:
Make sure your route uses the word language as the placeholder, eg maybe your route for this method looks like:
Route::post('/languages/check/{language}', 'LanguagesController#editCheck');
Type hint the language as a parameter in the method:
public function editCheck(Language $language, LanguagesRequest $request) {
Done - $language is now the single model you were afer, you can use it without any selecting, filtering, finding - Laravel has done it all for you.
public function editCheck(Language $language, LanguagesRequest $request) {
// $language is now your model, ready to work with
$language::update($request->except('_token'));
// ... etc
If you can't use route model binding, or don't want to, you can still make this much simpler and more efficient. Again assuming you have a Language model:
public function editCheck($id, LanguagesRequest $request) {
$language = Language::find($id);
$language::update($request->except('_token'));
// ... etc
Delete the scopeselect() method, you should never be selecting every record in your table. Additionally the word select is surely a reserved word, trying to use a function named that is bound to cause problems.
scopeselect() is returning a Collection, which you're then trying to filter with ->find() which is a method on QueryBuilders.
You can instead filter with ->filter() or ->first() as suggested in this answer
$language = language::select()->first(function($item) use ($id) {
return $item->id == $id;
});
That being said, you should really find a different way to do all of this entirely. You should be using $id with Eloquent to get the object you're after in the first instance.

Try to double filter sql query data laravel

guys i trying to filter my data from mysql with where clause but after put secound value laravel give me a blank result? If i try to filtered with first value example like this : http://localhost/transport/1 everything is good but if i try to set from destionation give me a blank result. example with fail : http://localhost/transport/1/Германия
Here is my Controller
class TransportController extends Controller
{
public function filtermethod($method){
$data['ads'] = db::table('ads')->where('method', $method)->get();
return view('transport', $data );
}
public function regionfrom($from){
$data['ads'] = db::table('ads')->where('from', $from)->get();
return view('transport', $data );
}
Here is my routes :
Route::get('transport/{method}', 'TransportController#filtermethod');
Route::get('transport/{method}/{from}', 'TransportController#regionfrom');
Your second route should be giving your controller 2 variables.
public function regionfrom($method, $from)
Is what your route your having problems with is calling, do the logic you like in there.
If you would like to filter twice, try this:
$data = DB::table('ads')-where('method', $method)->where('region', $region)->get();

How can I add function name as route in Laravel 5.7?

I have a controller which returns enums for respective fields. e.g.
// Expected route - /api/getFamilyTypes - only GET method is allowed
public function getFamilyTypes()
{
return [
'Nuclear Family',
'Joint Family'
];
}
I've around 20 functions like this. How can I add this without manually adding an entry per function in routes file?
Thanks in advance.
In your routes file, add something like this,
Route::get('/something/{func}', 'SomeController#functionRoute');
Where something is whatever path you're wanting to use and SomeController is the controller with the 20 functions you're using and functionRoute is the action that we're about to make.
Then in your controller, make a function like this,
public function functionRoute($func)
{
return $this->$func();
}
This will make it so that whenever someone browses to /something/* on your website, it'll execute the function name at the end. So if you navigate to /something/getFamilyTypes it'll run your getFamilyTypes function.
This isn't particularly secure. If you do this, the user will be able to run any of the controller's methods. You could set up a blacklist like this.
public function functionRoute($func)
{
$blacklist = [
'secret',
'stuff',
];
return in_array($func, $blacklist) ? redirect('/') : $this->$func();
}
Or you could set up a whitelist like this,
public function functionRoute($func)
{
$whitelist = [
'getFamilyTypes',
'otherUserFriendlyStuff',
];
return in_array($func, $whitelist) ? $this->$func() : redirect('/');
}
If the responses are always from hard-coded arrays (as opposed to being from a database) then one way might be to have a variable in your route:
Route::get('/api/enum/{field}', 'EnumController#getField');
And then in your controller method, use the variable to get the correct data from a keyed array:
public function getField($field)
{
$fields = [
'family' => [
'Nuclear Family',
'Joint Family'
],
// ...
];
return $fields[$field];
}
If you want to continue using different methods for every field then Michael's answer is the easiest option, with one caveat. Allowing users to call any method by name on your controller is a security risk. To protect yourself, you should validate the method name against a whitelist.

Route binding with composite key

I am trying to bind a model that has composite key. Take a look, at first place I define my route:
Route::get('laptop/{company}/{model}', 'TestController#test');
Now, I define as I want to be resolved:
$router->bind('laptop', function ($company, $model) {
$laptop = ... select laptop where company=$company and ...;
return $laptop;
});
Now, I see how I am injecting the class in order to get the laptop in the controller: function into to test the resolution:
function test(Laptop $laptop){
return 'ok';
}
However, I am receiving the following error:
BindingResolutionException in Container.php line 839:
I assume that the error is caused by $router->bind('laptop' because it should matches a unique placeholder in the url ("company" or "model"). In my case I get lost because I need to matches both at the same time.
Note: I am not using db/eloquent layer. This problem is focused in the way on how to resolve route binding with multiples keys representing an unique object.
I am not sure if is it possible or if am I missing something. Thank you in advance for any suggestion.
Laravel does not support composite key in eloquent query.
You need to use query builder method of laravel to match against both values. ie: DB::select()->where()->where()->get();
Just put select and where conditions in above.
If you bind $router->bind('laptop', ...); then your route parameter should be Route::get('{laptop}', ...);. There is two possibility to query a laptop by model and company as you expected.
The safest way is query laptop on your controller:
Route::get('laptop/{company}/{model}', 'TestController#test');
In you TestController.php
function test(Laptop $laptop, $company, $model){
return $laptop->whereCompany($company)->whereModel($model)->first();
}
Another solution is allow slashes on your route parameter:
Route::get('laptop/{laptop}', 'TestController#test')->where('laptop', , '(.*)?');
and your binding function could be:
$router->bind('laptop', function ($laptop) {
$laptop = explode('/', $laptop);
$company = current($laptop);
$model = end($laptop);
if ((count($laptop) === 2) && ($result = App\Laptop::whereCompany($company)->whereModel($model)->first()) {
return $result;
}
return abort(404);
}

Adding exceptions to laravel orm scopes

I have a table called snippets. It contains in it, amnongst other fields, the following:
ID - autoincrement
TITLE -varchar(60)
PRETTY- varchar(60)
HTML - text
My snippet model is set up as you would expect:
class Snippet extends BaseModel
{
protected $guarded = ['id'];
protected $rules = ['html' => 'required',
'pretty' => 'required|unique'];
public function scopeFromPretty($query,$prettyUrl)
{
return $query->where('pretty', '=', $prettyUrl);
}
}
This works fine if the $prettyUrl exists.
So for instance, the following line works fine, assuming there is a record with 'faq' in the pretty field.
$article = Snippet::fromPretty("faq")->first();
However, if the pretty url field doesn't exist, then $article will be empty.
Now, I could check for this in the controller, but, in my limited laravel experience, this feels wrong. I would instead like to check in the model, but don't see how I would do that.
I have tried the following:
public function scopeFromPretty($query,$prettyUrl)
{
$article = $query->where('pretty', '=', $prettyUrl);
if ($article->count() == 0)
{$article = $query->where('pretty', '=', "lost");}
return $article;
}
The aim here is to check to see if an article was found, and if it wasn't return an article which says "Your link was invalid. Please try again", or something similar.
However, this returns an empty result set (even though the "lost" Url exists in the table. At a guess I would say that the original where query is still in force, but I wouldn't know for sure.
I have a feeling that there must be a straightforward way to do this but that I am missing it, so any pointers would be great.
Thanks
You may want to look at using the repository pattern. It is very popular in the Laravel community. http://vimeo.com/53029232
The aim here is to check to see if an article was found, and if it wasn't return an article which says "Your link was invalid. Please try again", or something similar.
I would also recommend throwing an exception rather than returning a row from the database. You could then catch the exception in your controller and render an alternative view. This is an example of what your code could look like.
SnippetRepositoryInterface.php
interface SnippetRepositoryInterface {
/*
* Gets snippets by pretty url.
*
* #return \Illuminate\Database\Eloquent\Collection
* #throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function getSnippetByPretty();
}
DbSnippetRepository.php
use Illuminate\Database\Eloquent\ModelNotFoundException;
class DbSnippetRepository implements SnippetRepositoryInteface {
/**
* #inheritDoc
*/
public function getSnippetByPretty($url)
{
if (Snippet::fromPretty($url)->count())
{
return Snippet::fromPretty($url)->get();
}
throw new ModelNotFoundException(sprintf('The snippet for "%s" could not be found.', $url));
}
}
It turned out to be straightforward.
When I did my original scope query, the multiple where clauses were effectively being anded together.
Instead, I needed to change the second where clause to orWhere.
public function scopeFromPretty($query,$prettyUrl)
{
$article = $query->where('pretty', '=', $prettyUrl);
if ($article->count() == 0)
{$article = $query->orWhere('pretty', '=', "lost");}
return $article;
}
Note that I couldn't just do the following:
return $query->where('pretty', '=', $prettyUrl);
->orWhere('pretty', '=', "lost");}
If you do that method, and the prettyUrl happens to be returned after the lost url, then you will get the lost url.

Categories