trying to write Policy for enabling comment of posts in Laravel - php

In laravel I have a Follower table that I use to check if a User is folowing another User and also if he can comment on Posts.
The table is like this:
Schema::create('followers', function (Blueprint $table) {
$table->unsignedInteger('publisher_id')->unsigned();
$table->unsignedInteger('follower_id')->unsigned();
$table->boolean('enable_follow')->default('1');
$table->unique(['publisher_id', 'follower_id']);
$table->timestamps();
$table->foreign('publisher_id')
->references('id')
->on('users')
->onDelete('cascade');
$table->foreign('follower_id')
->references('id')
->on('users')
->onDelete('cascade');
});
and these are the checks that I make to decide if a User can comment a Post:
public function canComment(User $user, Post $post)
{
$following = Follower::where('follower_id', $user->id)->where('publisher_id', $post->user_id)->select('enable_follow')->get();
if (!$following->isEmpty()) {
$enabled = $following[0]['enable_follow'];
if ($enabled != '0') {
return true;
} else {
return false;
}
} else if ($following->isEmpty()) {
return true;
}
}
And this is the controller part for storing, as You can see I'm trying to authorize like this: $this->authorize('canComment', $post[0]);
public function store(Request $request)
{
//on_post, from_user, body
// define rules
$rules = array(
'post_id' => 'required',
'body' => 'required'
);
$validator = Validator::make(Input::all(), $rules);
$post_id = $request->input('post_id');
$post = Post::findOrFail($post_id);
if ($validator->fails()) {
return Response()->json($validator);
} else {
$this->authorize('canComment', $post);
//prepares object to be stored in DB
$comment = new Comment();
$comment['user_id'] = $request->user()->id;
$comment['post_id'] = $post_id;
$comment['body'] = $request->input('body');
$comment->save();
if ($comment) {
$comment['user_name'] = $request->user()->username;
$comment['comment_id'] = $comment->id;
$comment['token'] = $request->input('_token');
}
return Response()->json($comment);
}
}
The problem here is I get a 403 (Forbidden) error in a situation where I have $following empty and where following is enabled. The Policy is not working as expected.
Source code for authorize method in Gate facade:
public function authorize($ability, $arguments = [])
{
$result = $this->raw($ability, $arguments);
if ($result instanceof Response) {
return $result;
}
return $result ? $this->allow() : $this->deny();
}
Maybe I am not correct returing true or false in the policy as this code expect the result to be an instance of Response but so what do you return to grant or deny access??

The problem was putting the policy inside commentPolicy and so it expected to receive a Comment not a Post, moving it to postPolicy solved it.

Related

Laravel authenticate using phone or email and password

My database has below structure
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique()->nullable();
$table->string('phone')->unique()->nullable();
$table->timestamp('email_verified_at')->nullable();
$table->timestamp('phone_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
DB::statement('ALTER TABLE users ADD CONSTRAINT chk_phone_or_email CHECK (email IS NOT NULL OR email IS NOT NULL);');
}
Basically I have two fields phone and email that can be null but both of then cannot be null. A user can pass either their phone or email but this is passed to the backend as username. With this I need to attempt to login with either and if successful, return token
Below is my code
public function login(ServerRequestInterface $request)
{
$usernames = ['phone', 'email'];
for ($i = 0; $i < sizeof($usernames); $i += 1){
$credentials = ['password' => request()->get('password')];
$credentials[$usernames[$i]] = request()->get('username');
if (Auth::guard('web')->attempt($credentials)) {
return $this->withErrorHandling(function () use ($request) {
return $this->convertResponse(
$this->server->respondToAccessTokenRequest($request, new Psr7Response)
);
});
}
}
return response()->json([
'message' => 'Invalid username or password'
], 401);
}
If I login with valid email and password, I am able to login but if I try to login using phone and password, I get unauthorised,
How can I resolve this?
You can check for users with queries and login with Auth
https://laravel.com/docs/8.x/authentication#authenticate-a-user-instance
for example in your controller:
public function login(ServerRequestInterface $request)
{
$username = request()->get('username');
$password = request()->get('password');
$user = User::where('phone', $username)->orWhere('email', $username)->first();
if ($user === null || !Hash::check($password, $user->password)) {
return response()->json([
'message' => 'Invalid username or password'
], 401);
}
Auth::guard('web')->login($user);
// ...
}
I found below, hope it helps someone,
We can override findForPassport function in the users model to achieve this
public function findForPassport($identifier)
{
return $this->orWhere('email', $identifier)
->orWhere('phone', $identifier)
->orWhere('username', $identifier)
->first();
}
}
See Customizing the Username field

Laravel CustomRequest authorize, pass request data to validate the auth user customer id and the model id match

I'm trying to fix an if-else statement in the request for my controller. What I'm trying to do is: if the auth::user-companyID == $request-companyID then true else false; The companyID for the request is in a hidden field on the blade file.
CustomRequest
public function authorize()
{
$user = Auth::user();
if ($user->companyID == $request->companyID) {
return true;
} else {
return false;
}
}
Controller
public function edit(EquipmentRequest $request, $id)
{
$validated = $request->validated();
$user = Auth::user();
$equipment = EquipmentModel::where('id', '=', $id)->first();
$equipment->Year = $request->Year;
$equipment->Make = $request->Make;
$equipment->Model = $request->Model;
$equipment->Type = $request->Type;
$equipment->unitNumber = $request->unitNumber;
$equipment->AnnualInspectionDate = $request->AnnualInspectionDate;
$equipment->userID = $request->userID;
$equipment->companyID = $user->companyID;
$e = $equipment->save();
if ($e) {
$request->session()->flash('success', 'The equipment was successfully updated.');
} else {
$request->session()->flash('error',
'An error occurred while saving. Please refresh your browser and try again.');
}
return redirect()->route('equipmentlist');
}
This form worked before I started messing with it so I know the form is working correctly on the blade file. I'm not sure if you can pass the request data the way I'm doing it or if I have to do a construct to do it this way. I would really appreciate any advice.
use Illuminate\Http\Request;
public function authorize()
{
$user = auth()->user();
return $user->companyID === request()->companyID;
}

capture user id when login and save user id based on operations

I need to capture login user and when i add question i need to save the corresponding user id in the questions table.i'm getting user id when i login but it is not saving in the question table
Controller with store function
public function store(Request $request)
{
//
$last_que = Question::orderBy('question_id', 'desc')->first();
if ($last_que != null) {
$old_queId = $last_que->question_id;
$old_queId = $old_queId + 1;
} else {
$old_queId = 1;
}
$qorder=$request->input('order');
$question=new Question();
$quest=$question->checkDuo($qorder);
if(count($quest)==0)
{
$que=Question::create([
'question'=>$request->input('question'),
'question_id'=>$old_queId,
'question_type'=>$request->input('qtype'),
'question_schedul'=>$request->input('qschedule'),
'created_user_id'=>Session::get('created_id'),
'order_no'=>$request->input('order')
]);
if($que)
{
return redirect()->route('questions.index')->with('success', 'Successfully saved');
}
}
else
{
return redirect()->back()->with('fail', 'Unable to save..! Entry with same order no. already exist');
}
}
in Login index file this is i used capture the user id
<?php
if (!empty($id)) {
Session::put('created_id', $id);
}
?>
Login controller
public function postSignIn(Request $request)
{
if (Auth::attempt(['username' => $request['username'], 'password' => $request['password']])) {
$user = DB::table('users')->where([['username', '=', $request['username']], ['status', '=', '0']])->first();
$user_id = $user->user_id;
return redirect()->route('dashboard', $user_id)->with('message', 'State saved correctly!!!');
} else {
return redirect()->back();
}
}
Get user ID. use something like this.
Auth:user()->id;
Or you can use
Session::getId();
Change this line,
'created_user_id'=>Session::get('created_id'),
To,
'created_user_id'=>Auth::id(),
You used $user_id
return redirect()->route('dashboard', $user_id)->with('message', 'State saved correctly!!!');
Than asking:
if (!empty($id)) {
This $id will be always empty so use:
<?php
if (!empty($user_id)) {
Session::put('created_id', $user_id);
}
?>

Save user data on click button

I'm working on laravel 5.4 and I have this code:
public function apply($id){
$user = User::where('id', $id)->get()->first();
$data = [
'name' => $user->first_name,
'family' => $user->last_name,
'email' => $user->email,
'username' => $user->username,
'gender' => $user->gender,
'birthday' => $user->birthday,
'cv' => $user->cv,
'about' => $user->about,
'education' => $user->education,
'experiences' => $user->experiences,
];
$company = Company::get()->first();
Mail::send('emails.apply', $data, function ($message) use ($company)
{
$message->from('noreply#gmail.com', 'Robert Nicjoo');
$message->subject('New Apply');
$message->to($company->email);
});
Mail::send('emails.uapply', $data, function ($message) use ($user)
{
$message->from('noreply#gmail.com', 'Robert Nicjoo');
$message->subject('You Applied successfully');
$message->to($user->email);
});
Session::flash('success', 'Your application was sent to company.');
return redirect()->back()->with('session', $data);
}
This will send email to company when user click on apply button and send user info to them, now I also want to save data of the user include user_id, ad_id and company_id in another table so both user and company owners can have access to their history of applied ads.
I also have this table to save data on:
public function up()
{
Schema::create('applies', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->integer('ad_id')->unsigned();
$table->integer('company_id')->unsigned();
$table->timestamps();
});
Schema::table('ads', function($table) {
$table->foreign('user_id')->references('id')->on('users');
$table->foreign('ad_id')->references('id')->on('ads');
$table->foreign('company_id')->references('company_id')->on('ads');
});
}
but in my controller (first codes) I need to know how to save those information in new table (second codes)?
Update:
Ad Model >>
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Ad extends Model
{
protected $fillable = [
'company_id', 'title', 'slug', 'image', 'description', 'address', 'job_title', 'salary',
];
public function company(){
return $this->belongsTo(Company::class);
}
public function category(){
return $this->belongsTo(Category::class);
}
public function location(){
return $this->belongsTo(Location::class);
}
public function employment(){
return $this->belongsTo(Employment::class);
}
}
since your blade is like this:
<a class="btn btn-info btn-round" href="{{ route('apply.btn', Auth::user()->id) }}">
your route should look like
Route::get('apply/{id}', 'ApplyController#apply')->name('apply.btn');
why id only ? because in the discussion we had, i found out that ad_id and company_id was taken from the controller .. then in your controller this should work
public function apply($id)
{
$ad = Ad::first();
$company = Company::first();
$apply = new Apply();
$apply->user_id = $id
$apply->ad_id = $ad->id;
$apply->company_id = $company->id;
$apply->save();
// some more codes //
}
to avoid duplicates using user_id .. add a validation function like
function validateApply(array $data)
{
return Validator::make($data, [
'user_id' => 'required|numeric|unique:apply,user_id,NULL,id,ad_id,'.$data->ad_id,
]);
}
unique:apply - it means it will check the apply table the user_id already applied ..
then in the code above just do
$validateApply= $this->validateApply(['user_id'=>$id,'ad_id'=>$ad->id]);
if(!$validateApply->fails())
{
// do the above code here
}
else
{
// duplicate !!! so do your code here
}
then to retrieve the data assuming apply is already belongsTo the user as well the user hasOne apply
Auth::user()->apply->first()->somefield;
// im not sure how the hasOne works but try
Auth::user()->apply->somefield;
Your Route should be:
Route::post('apply/{$user_id}/{company_id}/{ad_id}','ApplyController#apply');
I think you have created model for ads.
So, simply save data like this:
Your function be like
public function apply(Request $request){
// other code
$apply = new Apply();
$apply->user_id = $request->user_id;
$apply->ad_id = $request->ad_id;
$apply->company_id = $request->company_id;
$apply->save();
// other code
}
And one more thing, You should have ad_id in your post request.

Slim post method redirect does not Work with slim Middleware

Hey guys i got some Problems with the Slim Middleware.
I created a Middleware that checks if the user is logged with Facebook and has a specific Email address. So now when i call the url with the PHPStorm RESTful Test tool i should not be able to post data to the server...
But the Redirect does not work so i will be able to send data to the server.
/**
* Admin Middleware
*
* Executed before /admin/ route
*/
$adminPageMiddleware = function ($request, $response, $next) {
FBLoginCtrl::getInstance();
$user = isset($_SESSION['user']) ? $_SESSION['user'] : new User();
if (!($user->getEmail() == ADMIN_USER_EMAIL)) {
$response = $response->withRedirect($this->router->pathFor('login'), 403);
}
$response = $next($request, $response);
return $response;
};
/**
* Milestone POST Method
*
* Create new Milestone
*/
$app->post('/admin/milestone', function (Request $request, Response $response) use ($app) {
$milestones = $request->getParsedBody();
$milestones = isset($milestones[0]) ? $milestones : array($milestones);
foreach ($milestones as $milestone) {
$ms = new Milestone();
$msRepo = new MilestoneRepository($ms);
$msRepo->setJsonData($milestone);
if (!$msRepo->createMilestone()) {
return $response->getBody()->write("Not Okay");
};
}
return $response->getBody()->write("Okay");
})->add($adminPageMiddleware);
So can anyone give me a hint what the problem could be?
I tried to add the same middleware to the get Route ... there it works :/ Strange stuff.
The problem is in your middleware logic.
if (!($user->getEmail() == ADMIN_USER_EMAIL)) {
return $response->withRedirect($this->router->pathFor('login'), 403); //We do not want to continue execution
}
$response = $next($request, $response);
return $response;
So now i ended up with this code:
class AdminRouteMiddleware
{
public function __invoke($request, $response, $next)
{
FBLoginCtrl::getInstance();
$user = isset($_SESSION['user']) ? $_SESSION['user'] : new User();
if (!($user->getEmail() == ADMIN_USER_EMAIL)) {
if ($_SERVER['REQUEST_METHOD'] == "GET") {
$response = $response->withRedirect('/login', 403);//want to use the route name instead of the url
} else {
$response->getBody()->write('{"error":Access Denied"}');
}
} else {
$response = $next($request, $response);
}
return $response;
}
}
/**
* Milestone POST Method
*
* Create new Milestone
*/
$app->post('/admin/milestone', function (Request $request, Response $response) use ($app) {
$milestones = $request->getParsedBody();
$milestones = isset($milestones[0]) ? $milestones : array($milestones);
foreach ($milestones as $milestone) {
$ms = new Milestone();
$msRepo = new MilestoneRepository($ms);
$msRepo->setJsonData($milestone);
if (!$msRepo->createMilestone()) {
return $response->getBody()->write("Not Okay");
};
}
return $response->getBody()->write("Okay");
})->add(new AdminRouteMiddleware());

Categories