How to assert the full response, not just the"view" part using Zend_Test_PHPUnit? - zend-framework

I want to test that my /login page is working well and rejecting invalid credentials i.e. not redirecting to the user's dashboard and showing an aller message identified here with the .alert HTML class. So I've created a test like this:
class AuthControllerTest extends Zend_Test_PHPUnit_ControllerTestCase {
...
public function testUserFailingLogin() {
$this->request->setPost(array(
'email' => 'wrong#email.com',
'password' => 'wrongpassword',
));
$this->request->setMethod('POST');
$this->dispatch('/login');
$this->assertQuery('.alert');
}
}
My problem is that the assertQuery() method is running against the render of login.phtml view file and is not including my Zend_Layout set up (that's where the .alert's are shown) and thereof, the assertQuery() assertion fails always.
Is there any way to get assert*Query*() assertions evaluating the full response ("layout" + "view"), instead of just the "view" part?

You(I) should use the undocumented outputBody() method.

Related

Redirect if request authorization is failed in Laravel 5.5

I am trying to redirect request if authorization is failed for it. I have following code:
class ValidateRequest extends Request{
public function authorize(){
// some logic here...
return false;
}
public function rules(){ /* ... */}
public function failedAuthorization() {
return redirect('safepage');
}
}
By default I am redirected to the 403 error page, but I would like to specify some specific route. I noticed that method failedAuthorization() is run, but redirect() method does not work...
Previously this code worked well with Laravel 5.1 but I used forbiddenResponse() method to redirect wrong request. How can I fix it with new LTS version?
Looks like it is impossible to redirect() directly from the custom ValidateRequest class. The only solution that I found is create custom exception and than handle it in the Handler class. So, now it works with following code:
Update: The method redirectTo() was updated to make solution work on Laravel 6.x and higher
app/Requests/ValidateRequest.php
class ValidateRequest extends Request{
public function authorize(){
// some logic here...
return false;
}
public function rules(){
return [];
}
public function failedAuthorization() {
$exception = new NotAuthorizedException('This action is unauthorized.', 403);
throw $exception->redirectTo("safepage");
}
}
app/Exceptions/NotAuthorizedException.php
<?php
namespace App\Exceptions;
use Exception;
class NotAuthorizedException extends Exception
{
protected $route;
public function redirectTo($route) {
$this->route = $route;
abort(Redirect::to($route));
}
public function route() {
return $this->route;
}
}
and app/Exceptions/Handler.php
...
public function render($request, Exception $exception){
...
if($exception instanceof NotAuthorizedException){
return redirect($exception->route());
}
...
}
So, it works, but much more slower than I expected... Simple measuring shows that handling and redirecting take 2.1 s, but with Laravel 5.1 the same action (and the same code) takes only 0.3 s
Adding NotAuthorizedException::class to the $dontReport property does not help at all...
Update
It runs much more faster with php 7.2, it takes 0.7 s
If you are revisiting this thread because in 2021 you are looking to redirect after failed authorization here's what you can do:
You cannot redirect from the failedAuthorization() method because it is expected to throw an exception (check the method in the base FormRequest class that you extend), the side effect of changing the return type is the $request hitting the controller instead of being handled on FormRequest authorization level.
You do not need to create a custom exception class, neither meddle with the Laravel core files like editing the render() of app/Exceptions/Handler.php, which will pick up the exception you threw and by default render the bland 403 page.
All you need to do is throw new HttpResponseException()
In the Laravel reference API we can see its job is to "Create a new HTTP response exception instance." and that is exactly what we want, right?
So we need to pass this Exception a $response. We can pass a redirect or JSON response!
Redirecting:
protected function failedAuthorization()
{
throw new HttpResponseException(response()->redirectToRoute('postOverview')
->with(['error' => 'This action is not authorized!']));
}
So we are creating a new instance of the HttpResponseException and we use the response() helper, which has this super helpful redirectToRoute('routeName') method, which we can further chain with the well known with() method and pass an error message to display on the front-end.
JSON:
Inspired by this topic
throw new HttpResponseException(response()->json(['error' => 'Unauthorized action!'], 403));
Thats'it.
You don't have to make ANY checks for validation or authorization in your controller, everything is done in the background before it hits the controller. You can test this by putting a dd('reached controller'); at the top of your controller method and it shouln't get trigered.
This way you keep your controller thin and separate concerns :)
Sidenote:
forbiddenResponse() has been replaced after lara 5.4 with failedAuthorization()
You can do through a middleware/policy i think. I don't know if you can do it from the validation.
You can override the function from FormRequest like this below:
/**
* Handle a failed authorization attempt.
*
* #return void
*
* #throws \Illuminate\Auth\Access\AuthorizationException
*/
protected function failedAuthorization()
{
throw new AuthorizationException('This action is unauthorized.');
}
And redirect where you want.

How to make morre readable URIs using unique-id-plus-redundant-information (UPRI) using PHP with Laravel

I would like to know the best (and most consistent) way to add redundant bits to my restful uris so that they are more readable while remaining unchanging when some things such as username change.
I read of the concept at this excellent blog post and it is something like what stack overflow does /users/3836923/inkalimeva where the last segment of the URI is redundant and may change but makes the URI more readable and SEO friendly.
Currently I am using Laravel's Route::resource() but that creates routes with only the id segment.
You can use eloquent-sluggable to create slugs for your users. That way the slug will change when they update their username. You can also simply call their username in the url method, though this will result in uglier urls.
This method still requires that you drop Route::resource() and write your routes explicitly.
Here is the code, tested and working:
ROUTES.PHP (don't mind the route details)
Route::get('route-name/{id}/{slugOrUsernameAsYouPlease}', [
'as' => 'admin-confirm-detach-admin',
'uses' => 'AdminController#confirmDetachAdmin'
]);
IN YOUR VIEW
Click me!
OR
Click me!
URL RESULT (My users name here is Fnup. Just for testing)
With Username: http://website.local/route-name/8/Fnup
With Slug: http://website.local/route-name/8/fnup
A quick final note
I just changed fnup's username to fnupper and here is the result:
http://website.local/route-name/8/Fnupper
However the slug didn't change automatically. You have to add that code yourself to the user update method. Otherwise the slug stays as what it was the first time the resource was made. Here is my code when using eloquent-sluggable
public function update(UpdateUserRequest $request)
{
$user = \Auth::user();
$user->name = $request->name;
$user->email = $request->email;
$user->resluggify();
$user->save();
session()->flash('message', 'Din profil er opdateret!');
return redirect()->route('user-show');
}
Which result in: http://website.local/route-name/8/fnupper
New edit per request: Controller method example
Here is my confirmDetachAdmin() method in AdminController.php. Just to clarify, the methods job is to show a "confirm" view before modifying a users status. Just like edit/update & create/store, I made up confirm to accompany destroy (since I'd like a javascript free confirmation option should javascript be disabled).
public function confirmAttachAdmin($id)
{
$user = User::findOrFail($id);
/* Prevent error if user already has role */
if ( $user->hasRole('admin')) {
return redirect()->back();
}
return view('admin.confirmAttachAdmin', compact('user'));
}
You can add your slug/username as a second parameter if you want to, but I don't see a reason, as you can access it from $user when you find them by id.
As opposed to #MartinJH's answer, I don't think you should store your slugs in database if you don't rely only on them in your URIs. A simple link() method on your model, and an explicit route is enough.
App\User
class User extends \Illuminate\Database\Eloquent\Model {
public function link()
{
return route('user-profile', [ $this->id, Str::slug($this->username) ]);
}
}
routes.php
Route::get('{id}/{username}', [ 'as' => 'user-profile', 'uses' => 'UserController#profile' ])
->where('id', '\d+')
->where('username', '[a-zA-Z0-9\-\_]+');
App\Http\Controllers\UserController
...
public function profile($id, $username)
{
$user = \App\User::findOrFail($id);
return view('profile')->with('user', $user);
}
...

Laravel REST redirect from GET to POST method in SAME controller not working

I am trying to support the use of EITHER GET or POST methods in my REST controller in laravel.
So, I would like to redirect ANY get requests sent to our REST controller to the POST method in the SAME controller instead.
I have tried many things, and now have returned back to basics as follows:
routes.php
Route::resource('user', 'userController');
userController.php
class userController extends \BaseController {
public function index() {
return Redirect::action('userController#store');
}
public function store() {
echo 'yeeha!';
}
}
Performing a POST on the page works and outputs:
yeeha!
Performing a GET on the page produces:
Could not get any response
This seems to be like an error connecting to https://www.test.com/user. The response status was 0.
Check out the W3C XMLHttpRequest Level 2 spec for more details about when this happens.
I have tried many different redirects and none are successful.
The correct way is to do it is to use the routes file and just define it;
Routes.php
Route::get('/user', array ('as' => 'user.index', 'uses' => userController#store))
Route::post('/user', array ('as' => 'user.create', 'uses' => userController#store))
Controller
class userController extends \BaseController {
public function store() {
echo 'yeeha!';
}
}

Lithium forward request

The controller redirect() method in Lithium does an actual HTTP redirect. But is there a method to simply forward a request to another controller/action without an HTTP redirect?
For example, say I want to add an authentication layer and rather than redirecting the user to a "/auth/login" page, the login layout and template get rendered rather than the content for the page they requested. Then, when they submit the form and they authenticate, they're already on the page they requested. Zend framework has something similar with a _forward() method.
Thanks!
There's no method, mostly because you can do it in about two lines of code:
<?php
namespace my_app\controllers;
use lithium\core\Libraries;
class PostsController extends \lithium\action\Controller {
public function index() {
$forward = Libraries::instance("controllers", "Auth", [
'request' => $this->request
]);
return $forward($this->request, ['action' => 'login']);
}
}
?>

Symfony 1.4: Custom error message for CSRF in forms

Can anyone tell me where/how to customise the CSRF token error message for forms in Symfony 1.4. I'm using sfDoctrineGuard for logins and in this form particularly, whenever a session runs out and you still have the page open, it throws a very user-unfriendly error: "CSRF attack detected". Something like "This session has expired. Please return to the home page and try again" sounds better.
What's the right way to do this in the form class?
Thanks.
The only way seems to be to overwrite sfForm::addCSRFProtection().
In /lib/form/BaseForm.class.php you can add this piece of code:
class BaseForm extends sfFormSymfony
{
public function addCSRFProtection($secret = null)
{
parent::addCSRFProtection($secret);
if (array_key_exists(self::$CSRFFieldName, $this->getValidatorSchema())) {
$this->getValidator(self::$CSRFFieldName)->setMessage('csrf_attack', 'This session has expired. Please return to the home page and try again.');
}
}
}
After calling the parent method, you retrieve the validator associated with the CSRF field and change the message for the code csrf_attack.
Edit: You also need to check whether or not the validator exists. Some forms might have their CSRF protection disabled!
Hope this helps!
None of these answers explain how to remove the "CSRF token:" label that prefixes the error message in a non-hackish way (e.g. changing the token name is a bad idea!).
The only sound way of removing the label is to extend the CSRF validator to throw a global error. While we do this, we can also change the error message.
class myValidatorCSRFToken extends sfValidatorCSRFToken
{
protected function configure($options = array(), $messages = array())
{
parent::configure($options, $messages);
$this->addMessage('csrf_attack', 'Your session has expired. Please return to the home page and try again.');
}
protected function doClean($value)
{
try {
return parent::doClean($value);
} catch (sfValidatorError $e) {
throw new sfValidatorErrorSchema($this, array($e));
}
}
}
Now, let's set our forms to use this validator by overriding sfForm::addCSRFProtection in BaseForm:
public function addCSRFProtection($secret = null)
{
parent::addCSRFProtection($secret);
if (isset($this->validatorSchema[self::$CSRFFieldName])) //addCSRFProtection doesn't always add a validator
{
$this->validatorSchema[self::$CSRFFieldName] = new myValidatorCSRFToken(array(
'token' => $this->validatorSchema[self::$CSRFFieldName]->getOption('token')
));
}
}
In 1.4.4 I had to modify naag's code like so...
public function addCSRFProtection($secret = null)
{
parent::addCSRFProtection($secret);
if (isset($this->validatorSchema[self::$CSRFFieldName])) {
$this->validatorSchema[self::$CSRFFieldName]->setMessage('csrf_attack', 'This session has expired. Please refresh and try again.');
}
}
This got it working, the 'csrf token:' bit still appears in the error message though.
Improving on previous answers, here is the code I use:
public function addCSRFProtection($secret = null)
{
parent::addCSRFProtection($secret);
if (isset($this->validatorSchema[self::$CSRFFieldName])) {
$this->validatorSchema[self::$CSRFFieldName]->setMessage('csrf_attack', 'This session has expired. Please refresh and try again.');
$this->getWidgetSchema()->getFormFormatter()->setNamedErrorRowFormatInARow(" <li>%error%</li>\n");
}
}
The default value for the NamedErrorRowFormatInARow is "<li>%name%: %error%</li>\n" adding the name and the colon.
Be careful because it changes the value for all forms and all global errors.
You can also change the field by creating a custom form formatter and using it in the forms you want. You can go look at the documentation here for more info on this.
Use event dispatcher. Check this out http://bluehorn.co.nz/2010/07/15/how-to-change-csrf-attack-message-in-symfony-1-2/
I wrote it for Symfony 1.2, but using event dispatcher, so it still might work for Symfony 1.4.
I suppose the "csrf token:" prefix can be removed or customized by setting the label of the CSRF token field, globally of course.