L4.2 Event: know where event got fired from? - laravel

I have a question regarding Events with Laravel 4.2...
I currently have an event listener on "auth.login"... some code lines are executed when user logins on web version... however I would like to execute a different action if the user logged via the API controller, example: ApiController#postLogin (my mobile version).
Code in my home controller:
if (Auth::attempt(['email' => Input::get('login'), 'password' => Input::get('password')]) OR Auth::attempt(['username' => Input::get('login'), 'password' => Input::get('password')]))
{
return Redirect::intended(URL::route('dashboard.index'));
}
else
{
return Redirect::action('HomeController#getIndex')->with('poplogin', true)->with('badcredentials',true)->withInput();
}
Code in global.php (event listener)
Event::listen('auth.login', function($user)
{
//Put Login_attemp in Database for Last activity, etc
$user->login_attemp()->create(['login_ip'=>$_SERVER['REMOTE_ADDR'],'login_time'=> date('Y-m-d H:i:s',time())]);
$user->last_logged = date('Y-m-d H:i:s',time());
$user->save();
Session::flash('justlogged',true);
//other code that I didnt include..........
});
Code in my ApiController
public function getRefreshData() {
//check the token
$token = Input::get('token');
$username = Input::get('username');
$user = User::where('api_token', $token)
->where('username', $username)
->first();
if(!$user || !$token) {
return Response::json([
'error' => true,
'message' => 'Invalid Token, please re login',
'code' => 401],
401
);
}
Auth::login($user);
//5 last Timesheets + tslines, for pre-load at log-in in phone memory
//Not inserting possible creation dates between, to keep phone app 100% independent
$timesheets = $user->timesheets()->orderBy('startdate', 'DESC')->take(10)->with('tslines')->get();
//Other code that I didnt include
);
return $response;
}
I cannot control the execution of the event "auth.login" myself.. firing it manually with parameter would just double-fire the event (i think?)
Is there a way to detect where the event got fired from in the Event:listen and do not insert a "log-in attemp" (my code in event listener) each time I use the getRefreshData() function in my API? Yes, I need to log the user in my API function (for other code that isn't included)

Edit: It seems to me that the most straightforward way to handle this is to check for the token in the Event listener.
Event::listen('auth.login', function($user)
{
if (Input::has('token') && Input::has('username')) {
//Put Login_attemp in Database for Last activity, etc
$user->login_attemp()->create(['login_ip'=>$_SERVER['REMOTE_ADDR'],'login_time'=> date('Y-m-d H:i:s',time())]);
$user->last_logged = date('Y-m-d H:i:s',time());
$user->save();
Session::flash('justlogged',true);
//other code that I didnt include..........
}
});
I really would suggest, long term, looking at using the functionality demonstrated in the docs under Accessing the Logged In User, it's just going to make life easier.
Original response: It might be helpful if you posted more code, because I feel like maybe this is an instance where if we zoom out a little bit maybe there is a better way to deal with this situation. Possibly you need multiple actions, different listeners, etc.
For solving this issue though, it's easy, just pass in whatever additional data you need to via a parameter:
$response = Event::fire('auth.login', array($user, 'source' => 'ApiController#postLogin', 'mobile' => true));
Then you can set those parameters to the $event object that is passed to your listener.
Let me know if you have any further questions!

After some research, I found how I could 'bypass' the execution of the event listener when the event is fired from the ApiController, using the Request::is() function
From L4.2 Docs: http://laravel.com/docs/4.2/requests#request-information )..
My routes.php file is like so:
Route::controller('api/v1', 'ApiV1Controller');
And in my global.php (where I declare my event listener)
Event::listen('auth.login', function($user)
{
if (!Request::is('api/*'))
{
//Code that is always executed at firing of event, except when from my API controllers
//Put Login_attemp in Database for Last activity, etc
$user->login_attemp()->create(['login_ip'=>$_SERVER['REMOTE_ADDR'],'login_time'=> date('Y-m-d H:i:s',time())]);
$user->last_logged = date('Y-m-d H:i:s',time());
$user->save();
}
}

Related

Laravel Return control to caller and continue working

I have two APIs that should communicate with each other. The first one sends the second files, which must be processed. However the processing time is unknown and may take a while.
A solution I thought of is the following - when the processing is ready, the second API sends a request to the first one with the result.
The problem is that in all that time, the first API's request is waiting for a response, because I haven't returned yet the control, which leads into a deadlock.
My question is how can I return a result to the first API (something like received: true/false) as soon as possible, continue with the processing and send the result (from the processing) in a separate request.
I am using Laravel 5.2 and Guzzle for the requests.
Firstly, this is proof of concept and not working code and may require some rework.
From your first api, you should be making a call to the second api telling it to process something.
The second api should then respond once it has received the data (not once processed).
First API
use GuzzleHttp\Client;
public function callApiTwo(Request $request)
{
$client = new Client()
$data = [];
$res = $client->post('http://apitwoexample.com', [
'body' => json_encode($data)
]);
if ($res->getStatus() !== 203) {
// error in processing
}
$resData = json_decode($res->getBody()->getContents());
$token = $resData['token'];
return response()->json([
'success' => 'Data is being processed',
'token' => $token
], 202);
}
Second API
The second api should then receive data (files links, images, data etc) and dispatch a job (as mentioned by #jonathon mentioned).
You then need some sort of identifier for the process/job/record. A id or token of some sort. This is sent back to the first api in its response.
public function executeProcess(Request $request)
{
// Perform logic
$token = str_rand(10);
Job::dispatch('/job/which/needs/time/to/run', [
'data' => $data,
'token' => $token
]);
return response()->json([
'success' => 'Data is being processed',
'token' => $token
], 202);
}
public function progressStatus(Request $request)
{
$model = Model::whereToken($request->token)->first();
return response()->json([
'status' => $model->status
], 200);
}
You'll notice the progressStatus method, which i've put as an example to check the progess of a process/job/record by some identifier.
From the first api, this can be fired off within an artisan command or a job which is re-queued to fire again (after a delay) based on whether the second api has confirmed the process completed.
Let me know if this requires more clarity.

how to generate error message in laravel

Hello stackoverflow geeks, I'm in my final stages of the laravel learning curve all thanks to you guys.
However, i need to generate a warning message like "You cannot delete a role assigned to a user" every time a user tries to delete a role assigned to a user.
instead it loads a page with an sql error. how to i do it?
And how do i avoid a password that has been already been stored from being hashed again. eg:- $2y$10$p8JwI5P4yE2UFo2.vHP99.0dP2jU7ll/9w73IzUa9/yegKOSTHJWq is always hashed every time i edit a user's information.
Thanks you all who've made learning laravel easy for me by answering in time
code
public function destroy(Request $request,$id)
{
// delete
// $role = Role::find($id);
//$role->delete();
$role = Role::find ($id);
if ($role->users() !=null) {
return redirect()->back()->withInput(['warning' => 'Not allowed']);
}
$role->delete();
// redirect
Session::flash('message', 'Record successfully deleted!');
Session::flash('alert-type', 'success');
return Redirect::to('role');
}
This highly depends on how you want to handle the errors. You can either catch the sql exception and display your custom error OR what is probably better for you is to handle the incoming request, validate it and return an error if validation fails.
Here are the validation docs : https://laravel.com/docs/5.3/validation
You have multiple options on how to validate a request. Simple example to validate a title is unique in the table posts and is maximum 255 chars long:
$this->validate($request, [
'title' => 'required|unique:posts|max:255'
]);
If you cannot find a rule that is helping you simply define your own validation rule https://laravel.com/docs/5.3/validation#custom-validation-rules
Ofcourse you can also do the validation manually. In your request or in your controller (depends on your setup) just check for it
// assuming you want to delete an entry
public function delete(Request $request, $id)
{
$role = App\Role::findOrFail($id);
if ($role->users() != null) {
return redirect()->back()->withInput(['message' => 'Not allowed']);
// now you can output $message
}
$role->delete();
return ...
}

Laravel 4 - ignore after filter

Say I have the following route:
Route::any('/door1/{options?}', array(
'as' => 'path',
'after' => 'somefilter',
'uses' => 'DoorController#path'
))
where my after filter has the job to increment some value on a DB Table. The filter would look something like this:
Route::filter('somefilter', function($route, $request, $response, $action)
{
try
{
//...
IncrementDBTable();
//...
}
catch(Exception $e)
{
return Redirect::home();
}
});
As for the controller,
if the options parameter is valid then all is fine and a specific view is called, otherwise the options are "corrected" and a redirect to the same route is performed:
public function path($options='option1,option2')
{
if( !this->isValidOptions($options) ) {
$options = this->correctOptions($options);
return Redirect::route( 'path', array(
'options' => $options
) );
}
// everything fine:
return View::make( 'door' );
}
My problem is, and for the case the options are wrong, the after filter is called twice, meaning the increment on the DB table is performed also twice. How can I "ignore" the after filter for the case the options are wrong? One possible solution of course is to also validate the options inside the filter function, but I wouldn't like to replicate code (this is just a simple example but things would get ugly if the validate options function would involve a lot more of code and possibly a bunch of db queries).
Looking at your code, you can skip the Redirect after correcting the options and just continue down the function instead. I don't see a reason for redirecting after correcting the options. That will also solve your problem about the after filter being called twice.

Before method with multiple role restrictions

Im curious to know if it is possible to prevent users who don't have a role of owner or administrator from accessing certain controllers in a laravel application?
Yes you can. You can do this with a route filter.
routes.php
Route::group(['prefix' => 'admin', 'before' => 'auth.admin'), function()
{
// Your routes
}
]);
and in filters.php
Route::filter('auth.admin', function()
{
// logic to set $isAdmin to true or false
if(!$isAdmin)
{
return Redirect::to('login')->with('flash_message', 'Please Login with your admin credentials');
}
});
Route filters have already been proposed but since your filter should be Controller specific you might want to try controller filters.
First off, lets add this your controller(s)
public function __construct()
{
$this->beforeFilter(function()
{
// check permissions
});
}
This function gets called before a controller action is executed.
In there it depends on you what you want to do. I'm just guessing now, because I don't know your exact architecture but I suppose you want to do something like this:
$user = Auth::user();
$role = $user->role->identifier;
if($role !== 'admin' && $role !== 'other-role-that-has-access'){
App::abort(401); // Throw an unauthorized error
}
Instead of throwing an error you could also make a redirect, render a view or do basically whatever you want. Just do something that stops further execution so your controller action doesn't get called.
Edit
Instead of using Closure function, you can use predefined filters (from the routes.php or filters.php)
$this->beforeFilter('filter-name', array('only' => array('fooAction', 'barAction')));
For more information, check out the documentation

Display message after logout via Silex SecurityServiceProvider

I am using the SecurityServiceProvider to secure my Silex application and would like to display a message after the user has logged out by navigating to the logout_path route.
The message should be stored in the sessions flash bag so that my template can automatically display it after.
I have tried adding an application middleware, but where not able to hook my code in. The before hook doesn't seem to work, because it happens after security and thus after the security's redirected back to my home page.
The before hook with the Application::EARLY_EVENT seems to be to early because as far as I know does the Security provider destroy the session after logout.
Before I keep trying to find a sort of working but probably dirty solution I would like to ask what the best/cleanest solution for this case would be?
UPDATE: After npms hint for a logout event handler I found this article on Google, which describes how to tackle the problem in Symfony very well.
In Silex things are slightly different though and after reading the source of the SecurityServiceProvider I came up with this solution.
$app['security.authentication.logout_handler._proto'] = $app->protect(function ($name, $options) use ($app) {
return $app->share(function () use ($name, $options, $app) {
return new CustomLogoutSuccessHandler(
$app['security.http_utils'],
isset($options['target_url']) ? $options['target_url'] : '/'
);
});
});
class CustomLogoutSuccessHanler extends DefaultLogoutSuccessHandler {
public function onLogoutSuccess(Request $request)
{
$request->getSession()->getFlashBag()->add('info', "Logout success!");
return $this->httpUtils->createRedirectResponse($request, $this->targetUrl);
}
}
The problem however is, that the flashbag message doesn't exist anymore after the redirect. So it seems that the session is being destroyed after the logout success handler is executed... or am I missing something? Is this even the right way to do it?
UPDATE: Still haven't found a proper solution yet. But this works.
I have added a parameter to the target url of the logout and use it to detect if a logout was made.
$app->register( new SecurityServiceProvider(), array(
'security.firewalls' => array(
'default' => array(
'pattern'=> '/user',
'logout' => array(
'logout_path' => '/user/logout',
'target_url' => '/?logout'
),
)
)
));
I had the same problem and your thoughts leaded me to a solution, thank you!
First define logout in the security.firewall:
$app->register(new Silex\Provider\SecurityServiceProvider(), array(
'security.firewalls' => array(
'general' => array(
'logout' => array(
'logout_path' => '/admin/logout',
'target_url' => '/goodbye'
)
)
),
));
Create a CustomLogoutSuccessHandler which handles the needed GET parameters for the logout, in this case redirect, message and pid:
class CustomLogoutSuccessHandler extends DefaultLogoutSuccessHandler
{
public function onLogoutSuccess(Request $request)
{
// use another target?
$target = $request->query->get('redirect', $this->targetUrl);
$parameter = array();
if (null != ($pid = $request->query->get('pid'))) {
$parameter['pid'] = $pid;
}
if (null != ($message = $request->query->get('message'))) {
$parameter['message'] = $message;
}
$parameter_str = !empty($parameter) ? '?'.http_build_query($parameter) : '';
return $this->httpUtils->createRedirectResponse($request, $target.$parameter_str);
}
}
Register the handler:
$app['security.authentication.logout_handler.general'] = $app->share(function () use ($app) {
return new CustomLogoutSuccessHandler(
$app['security.http_utils'], '/goodbye');
});
The trick to make this working as expected is to use another route to logout:
$app->get('/logout', function() use($app) {
$pid = $app['request']->query->get('pid');
$message = $app['request']->query->get('message');
$redirect = $app['request']->query->get('redirect');
return $app->redirect(FRAMEWORK_URL."/admin/logout?pid=$pid&message=$message&redirect=$redirect");
});
/logout set the needed parameters and execute the regular logout /admin/logout
Now you can use
/logout?redirect=anywhere
to redirect to any other route after logout or
/logout?message=xyz
(encoded) to prompt any messages in the /goodbye dialog.

Resources