Laravel - How to pass variable to reset password template? - php

I have implemented reset password functionality with Laravel 5 and getting email. Now how to pass some variable data to my email template to display more information about user.
/**
* Send a reset link to the given user.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function postEmail(Request $request)
{
//echo Input::get('ID'); die;
$this->validate($request, ['ID' => 'required|email']);
$UserProduct = "Sample 1"; // I want to pass this variable to my password.blade.php
$response = Password::sendResetLink($request->only('ID'), function (Message $message) {
$message->subject($this->getEmailSubject());
});
switch ($response) {
case Password::RESET_LINK_SENT:
return redirect()->back()->with('status', trans($response));
case Password::INVALID_USER:
return redirect()->back()->withErrors(['ID' => trans($response)]);
}
}
I want to print $UserProduct = "Sample 1"; to my email template but don't know how to pass to the password.blade page.
Any idea?
Thanks.

The sendResetLink doesn't have a proper way to send more data like a regular email in laravel.
You can kinda hack around this using a view composer, something like this:
$UserProduct = "Sample 1";
$infoArray = [1,2,3,4];
view()->composer('emails.auth.password', function($view) use ($UserProduct, $infoArray) {
$view->with([
'UserProduct' => $UserProduct,
'info' => $infoArray,
'more' => 'Even more info',
]);
});
$response = Password::sendResetLink($request->only('ID'), function (Message $message) {
$message->subject($this->getEmailSubject());
});

Related

Symfony 5.4 - how to filter/sanitize/validate request parameter in Rest API action

I am fairly new to Symfony 5.4 and recently created my first API using that version
For my specific API endpoint one of the parameters is an array of IDs.
I need to validate this array in the following way:
make sure that this IS an array;
make sure that IDs in the array actually refer to database records;
I implemented it in a straightforward way where I check the array before persisting the entity using typecasting and existing Repository:
$parentPropertyIds = (array)$request->request->get('parent_property_ids');
if ($parentPropertyIds) {
$parentCount = $doctrine->getRepository(Property::class)->countByIds($parentPropertyIds);
if ($parentCount !== count($parentPropertyIds)) {
return $this->json([
'status' => 'error',
'message' => 'parent_property_id_invalid'
], 422);
}
foreach ($parentPropertyIds as $parentPropertyId) {
$parentProperty = $doctrine->getRepository(Property::class)->find($parentPropertyId);
$property->addParent($parentProperty);
}
}
However, this makes my controller action become too "body-positive" and also feels like something that could be implemented in a more elegant way.
I was unable to find anything in Symfony 5.4 docs.
At the moment I am wondering if:
there is a way to filter/sanitize request parameter available in Symfony;
there is an elegant built-in way to apply custom validator constraint to a request param (similar to well-documented entity field validation);
Full endpoint code:
/**
* #Route("/property", name="property_new", methods={"POST"})
*/
public function create(ManagerRegistry $doctrine, Request $request, ValidatorInterface $validator): Response
{
$entityManager = $doctrine->getManager();
$property = new Property();
$property->setName($request->request->get('name'));
$property->setCanBeShared((bool)$request->request->get('can_be_shared'));
$parentPropertyIds = (array)$request->request->get('parent_property_ids');
if ($parentPropertyIds) {
$parentCount = $doctrine
->getRepository(Property::class)
->countByIds($parentPropertyIds);
if ($parentCount !== count($parentPropertyIds)) {
return $this->json([
'status' => 'error',
'message' => 'parent_property_id_invalid'
], 422);
}
foreach ($parentPropertyIds as $parentPropertyId) {
$parentProperty = $doctrine->getRepository(Property::class)->find($parentPropertyId);
$property->addParent($parentProperty);
}
}
$errors = $validator->validate($property);
if (count($errors) > 0) {
$messages = [];
foreach ($errors as $violation) {
$messages[$violation->getPropertyPath()][] = $violation->getMessage();
}
return $this->json([
'status' => 'error',
'messages' => $messages
], 422);
}
$entityManager->persist($property);
$entityManager->flush();
return $this->json([
'status' => 'ok',
'id' => $property->getId()
]);
}
You could use a combination of Data Transfer Object (DTO) with Validation service. There is a number of predefined constraints or you could create a custom one.
For expamle, how to use simple constraint as an annotation:
class PropertyDTO {
/**
* #Assert\NotBlank
*/
public string $name = "";
public bool $shared = false;
}
Then assign data to DTO:
$propertyData = new PropertyDTO();
$propertyData->name = $request->request->get('name');
...
In some cases it is a good idea to define a constructor in the DTO, then get all data from the request and pass it to DTO at once:
$data = $request->getContent(); // or $request->getArray(); depends on your content type
$propertyData = new PropertyDTO($data);
Then validate it:
$errors = $validator->validate($propertyData);
if (count($errors) > 0) {
/*
* Uses a __toString method on the $errors variable which is a
* ConstraintViolationList object. This gives us a nice string
* for debugging.
*/
$errorsString = (string) $errors;
return $this->json([
'status' => 'error',
'message' => 'parent_property_id_invalid'
], 422);
}
//...

laravel formrequest before middleware

I know, this is a complex case but maybe one of you might have an idea on how to do this.
Concept
I have the following process in my API:
Process query string parameters (FormRequest)
Replace key aliases by preferred keys
Map string parameters to arrays if an array ist expected
Set defaults (including Auth::user() for id-based parameters)
etc.
Check if the user is allowed to do the request (Middleware)
Using processed (validated and sanitized) query params
→ otherwise I had to do exceptions for every possible alias and mapping as well as checking if the paramter is checked and that doesn't seem reasonable to me.
Problem
Nevertheless, if you just assign the middleware via ->middleware('middlewareName') to the route and the FormRequest via dependency injection to the controller method, first the middleware is called and after that the FormRequest. As described above, that's not what I need.
Solution approach
I first tried dependency injection at the middleware but it didn't work.
My solution was to assign the middleware in the controller constructor. Dependency injection works here, but suddenly Auth::user() returns null.
Then, I came across the FormRequest::createFrom($request) method in \Illuminate\Foundation\Providers\FormRequestServiceProvider.php:34 and the possibility to pass the $request object to the middleware's handle() method. The result looks like this:
public function __construct(Request $request)
{
$middleware = new MyMiddleware();
$request = MyRequest::createFrom($request);
$middleware->handle($request, function() {})
}
But now the request is not validated yet. Just calling $request->validated() returns nothing. So I digged a little deeper and found that $resolved->validateResolved(); is done in \Illuminate\Foundation\Providers\FormRequestServiceProvider.php:30 but that doesn't seem to trigger the validation since it throws an exception saying that this method cannot be called on null but $request isn't null:
Call to a member function validated() on null
Now, I'm completely stumped. Does anyone know how to solve this or am I just doing it wrong?
Thanks in advance!
I guess, I figured out a better way to do this.
My misconception
While middleware is doing authentication, I was doing authorization there and therefore I have to use a Gate
Resulting code
Controller
...
public function getData(MyRequest $request)
{
$filters = $request->query();
// execute queries
}
...
FormRequest
class MyRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return Gate::allows('get-data', $this);
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
// ...
];
}
/**
* Prepare the data for validation.
*
* #return void
*/
protected function prepareForValidation()
{
$this->replace($this->cleanQueryParameters($this->query()));
}
private function cleanQueryParameters($queryParams): array
{
$queryParams = array_filter($queryParams, function($param) {
return is_array($param) ? count($param) : strlen($param);
});
$defaultStartDate = (new \DateTime())->modify('monday next week');
$defaultEndDate = (new \DateTime())->modify('friday next week');
$defaults = [
'article.created_by_id' => self::getDefaultEmployeeIds(),
'date_from' => $defaultStartDate->format('Y-m-d'),
'date_to' => $defaultEndDate->format('Y-m-d')
];
$aliases = [
// ...
];
$mapper = [
// ...
];
foreach($aliases as $alias => $key) {
if (array_key_exists($alias, $queryParams)) {
$queryParams[$key] = $queryParams[$alias];
unset($queryParams[$alias]);
}
}
foreach($mapper as $key => $fn) {
if (array_key_exists($key, $queryParams)) {
$fn($queryParams, $key);
}
}
$allowedFilters = array_merge(
Ticket::$allowedApiParameters,
array_map(function(string $param) {
return 'article.'.$param;
}, TicketArticle::$allowedApiParameters)
);
$arrayProps = [
// ..
];
foreach($queryParams as $param => $value) {
if (!in_array($param, $allowedFilters) && !in_array($param, ['date_from', 'date_to'])) {
abort(400, 'Filter "'.$param.'" not found');
}
if (in_array($param, $arrayProps)) {
$queryParams[$param] = guarantee('array', $value);
}
}
return array_merge($defaults, $queryParams);
}
}
Gate
class MyGate
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Auth\Access\Response|Void
* #throws \Symfony\Component\HttpKernel\Exception\HttpException
*/
public function authorizeGetDataCall(User $user, MyRequest $request): Response
{
Log::info('[MyGate] Checking permissions …');
if (in_array(LDAPGroups::Admin, session('PermissionGroups', []))) {
// no further checks needed
Log::info('[MyGate] User is administrator. No further checks needed');
return Response::allow();
}
if (
($request->has('group') && !in_array(Group::toLDAPGroup($request->get('group')), session('PermissionGroups', []))) ||
$request->has('owner.department') && !in_array(Department::toLDAPGroup($request->query('owner.department')), session('PermissionGroups', [])) ||
$request->has('creator.department') && !in_array(Department::toLDAPGroup($request->query('creator.department')), session('PermissionGroups', []))
) {
Log::warning('[MyGate] Access denied due to insufficient group/deparment membership', [ 'group/department' =>
$request->has('group') ?
Group::toLDAPGroup($request->get('group')) :
($request->has('owner.department') ?
Department::toLDAPGroup($request->query('owner.department')) :
($request->has('creator.department') ?
Department::toLDAPGroup($request->query('creator.department')) :
null))
]);
return Response::deny('Access denied');
}
if ($request->has('customer_id') || $request->has('article.created_by_id')) {
$ids = [];
if ($request->has('customer_id')) {
$ids = array_merge($ids, $request->query('customer_id'));
}
if ($request->has('article.created_by_id')) {
$ids = array_merge($ids, $request->query('article.created_by_id'));
}
$users = User::find($ids);
$hasOtherLDAPGroup = !$users->every(function($user) {
return in_array(Department::toLDAPGroup($user->department), session('PermissionGroups', []));
});
if ($hasOtherLDAPGroup) {
Log::warning('[MyGate] Access denied due to insufficient permissions to see specific other user\'s data', [ 'ids' => $ids ]);
return Response::deny('Access denied');;
}
}
if ($request->has('owner.login') || $request->has('creator.login')) {
$logins = [];
if ($request->has('owner.login')) {
$logins = array_merge(
$logins,
guarantee('array', $request->query('owner.login'))
);
}
if ($request->has('creator.login')) {
$logins = array_merge(
$logins,
guarantee('array', $request->query('creator.login'))
);
}
$users = User::where([ 'samaccountname' => $logins ])->get();
$hasOtherLDAPGroup = !$users->every(function($user) {
return in_array(Department::toLDAPGroup($user->department), session('PermissionGroups', []));
});
if ($hasOtherLDAPGroup) {
Log::warning('[MyGate] Access denied due to insufficient permissions to see specific other user\'s data', [ 'logins' => $logins ]);
return Response::deny('Access denied');
}
}
Log::info('[MyGate] Permission checks passed');
return Response::allow();
}
}

What parameters do I pass to doExpressCheckouPaymentt()?

I have this controller that is supposed to perform PayPal payments. The payment function is working well but on getting to success function I am getting an error Illegal string offset 'total' . I am passing $this->productData($request) as suggested in this question. I tried creating a variable $total = $response['AMT'] which is the response from setCheckoutDetails but I still got the same error. How do I go about it?
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Srmklive\PayPal\Services\ExpressCheckout;
class PayPalController extends Controller
{
private function projectData(Request $request){
// dd($request->all());
$item = [];
$datat = array_map(function($item){
return [
'name'=>$request->project_id,
'price'=>$request->budget,
'desc'=>'Deposit',
'qty'=>1
];
}, $item);
$data = [
'items'=>$datat,
'invoice_id' => uniqid(),
'invoice_description' => "Payment for Project No.".$request->project_id." Amount ".$request->budget,
'return_url' => route('payment.success'),
'cancel_url' => route('payment.cancel'),
'total'=>$request->budget
];
// dd($data);
return $data;
}
/**
* Responds with a welcome message with instructions
*
* #return \Illuminate\Http\Response
*/
public function payment(Request $request) {
$data = $this->projectData($request);
$provider = new ExpressCheckout;
$response = $provider->setExpressCheckout($data);
// dd($response);
// $response = $provider->setExpressCheckout($data, true);
return redirect($response['paypal_link']);
}
/**
* Responds with a welcome message with instructions
*
* #return \Illuminate\Http\Response
*/
public function cancel()
{
dd('Your payment is canceled. You can create cancel page here.');
}
/**
* Responds with a welcome message with instructions
*
* #return \Illuminate\Http\Response
*/
public function success(Request $request)
{
$provider = new ExpressCheckout;
$response = $provider->getExpressCheckoutDetails($request->token);
$token = $response['TOKEN'];
$payerId = $response['PAYERID'];
$total = $response['AMT'];
// dd($response);
if (in_array(strtoupper($response['ACK']), ['SUCCESS', 'SUCCESSWITHWARNING'])) {
// dd('Payment successful');
//Performing transaction
$payment_status = $provider->doExpressCheckoutPayment($token, $payerId, $this->projectData($request));
dd($payment_status);
}
dd('Something is wrong.');
}
}
You have to pass three parameters
data, token, PAYERID
Data can service information like
$data = array(
'total' => Total amount,
'invoice_id' => Invoicen number,
'invoice_description' => invoice descrption
);
And items as well which will contain name, price, desc and qty

Laravel - Authorization to patch function

i have this PATCH function but i need to add some form of authorization to ensure you can only edit/update a film that is associated with the current user, can i get some help on how to add this
controller function:
public function update(string $id)
{
$this->user = Auth::user();
$this->film = film:findOrFail($id);
return $this->film->toJson();
}
I've looked at the laravel docs at the validation section and seen this example
$validatedData = $request->validate([
'title' => 'required|unque:posts|max:255',
'body' => 'required',
]);
i then added my own validation at the top of the file
protected $validation = [
'name' => 'string',
'description' => 'new description'
];
im a little lost on how i implement authorization to ensure only a current user can update a film?
What you're looking for is not a form validation, but a User Authorization (as in the comments). So you should have a look at the official documentation. In your case you should write a FilmPolicy that may look like to this (I will skip the registration part... It can be easily understood from the docs):
class FilmPolicy {
/**
* Determine if the given film can be updated by the user.
*
* #param \App\User $user
* #param \App\Post $post
* #return bool
*/
public function update(User $user, Film $film)
{
return $user->id === $film->user_id; // Or whatever is your foreign key
}
}
Then you should update your controller in order to handle the authorization as follow:
public function update(string $id)
{
$this->film = film::findOrFail($id);
$this->authorize('update', $this->film);
return $this->film->toJson();
}
Since this method simply throws an exception, you can have a more elaborate response as explained in the docs
Ok basically, to enable what you need in a simple way, what you can do is this;
First pass the 'user_id' to the controller.
public function update(string $id, $userid)
{
$user = Auth::user();
$id = $user->id;
if($id == $userid)
{
$this->user = Auth::user();
$this->film = film::findOrFail($id);
return $this->film->toJson();
}else{
return "Not Authorized";
}
}
If im not misunderstanding your question, this basically allows only the user who is logged in to update his film. if he goes into any other profile, the id's would mismatch and thus return a not authorized prompt.

Set authenticate stuff manually in laravel 5.7

I'm working on a laravel 5.7 project and I want to have my own authentication scenario.
Well, I'll give a mobile from my user and send her a one time pass to her phone and then check if she is giving me the correct code.
So, I do not use laravel authentication system at this point at all.
My Controller is something like this :
/*
* Show Login Form
*/
public function showLoginForm()
{
return view('auth.custom.login');
}
/*
* Login
*/
public function login(Request $request)
{
$mobile = $request->mobile;
$this->validate($request, [
'mobile' => 'iran_mobile|required'
]);
$check = User::where('mobile', $mobile)->first();
if( $check === null )
{
Session::flash('toasterr', 'is not registered yet');
Session::put('mobile', $mobile);
return redirect(route('register'));
}
else
{
$singleTimePass = Str::random(4);
sendSms($mobile, 'your code:' . PHP_EOL . $singleTimePass . PHP_EOL . 'Insert that bla bla');
Session::put('singleTimePass', $singleTimePass);
Session::put('mobile', $mobile);
return redirect(route('check_pass'));
}
dd($check);
}
/*
* Show Check Pass page
*/
public function showCheckPass()
{
return view('auth.custom.pass');
}
/*
* Check Pass For Login
*/
public function checkPassForLogin(Request $request)
{
$this->validate($request, [
'pass' => 'required|regex:/^[\w-]*$/'
]);
if( $request->pass == Session::get('singleTimePass'))
{
$user = User::where('mobile', Session::get('mobile'))->first();
// dd($user->id);
Auth::login($user->id);
return redirect(route('game'));
}
else
{
Session::flash('toasterr', 'pass is wrong');
return redirect(route('check_pass'));
}
}
/*
* Show Register Form
*/
public function showRegisterForm()
{
return view('auth.custom.register');
}
/*
* Register
*/
public function register(Request $request)
{
$this->validate($request, [
'name' => 'persian_alpha|required',
'family' => 'persian_alpha|required',
'username' => 'required|min:4|max:255|string',
'mobile' => 'iran_mobile|required',
]);
return $request->all();
}
Ok! Every thing seems to be good but now, I expect laravel that give me abilities like Auth::check() or Auth::user() and...
So I know that I have an error at this line: Auth::login($user->id); and I want to know how can I do something like this manually for mentioned goal.
May be it is because of my poor knowledge about laravel authentication architecture but it would be appreciate if you let me know how do that because googled this for a while and there's not direct answer to this question-or I did not searched enough-.
Based on the documentation the login method expects a User object to log you in. So you can either try
Auth::login($user);
// or
Auth::loginUsingId($user->id);

Categories