CakePHP: Prevent Auth component's "authError" message on homepage - cakephp

I have a CakePHP project where I modified "app/config/routes.php" so that the root points to the "Users" controller's "dashboard" action. In other words, these two URLs go to the same place:
http://example.com/
http://example.com/users/dashboard
I have the "Auth" component set up in my "App" controller like so:
class AppController extends Controller {
var $components = array('Auth', 'Session');
function beforeFilter() {
$this->Auth->authorize = 'controller';
$this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'dashboard');
if ($this->Auth->user()) {
$this->set('logged_in', true);
}
else {
$this->set('logged_in', false);
}
}
}
I want it so that if a non-authenticated user goes straight to http://example.com/users/dashboard , they are taken to the login page with the "Auth" component's "authError" message showing, but if they go to http://example.com/ , they are taken to the login page without the "Auth" component's "authError" message showing. Is this possible?

I resolved this by putting the following code in my "Users" controller's "login" action:
if ($this->Session->read('Auth.redirect') == $this->webroot && $this->Session->read('Message.auth.message') == $this->Auth->authError) {
$this->Session->delete('Message.auth');
}

been looking for somthing like that for a long time! Thank you.
I had to make a little change then $this->webroot is not "/":
if (str_replace("//","/",$this->webroot.$this->Session->read('Auth.redirect')) == $this->webroot && $this->Session->read('Message.auth.message') == $this->Auth->authError) {
$this->Session->delete('Message.auth');
}

Well, I don't understand why sometimes you show the error and why sometimes not.. but you can afford this creating an isAuthorized method and modifying all the logic of the default AuthComponent behavior.
Open your Auth component and check for method "startup()". There, at it's last line, you will se this:
$this->Session->setFlash($this->authError, $this->flashElement, array(), 'auth');
$controller->redirect($controller->referer(), null, true);
This is the part responsible for displaying the error.
Before it, you will se...
if ($this->isAuthorized($type)) {
return true;
}
So you can change your isAuthorized method to change this message when you want.
Is a lot of work for (I think..) nothing.
PS. There may be a simpler way to be ignoring me

If you really wants to prevent authError message on homepage and simple redirect to login page then you have to put false as parameter of authError
class AppController extends Controller {
public function initialize() {
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authError' => false
]);
}
}

Related

Cake: Access the controller in a static method in a component

we have a static method in a Cake component. The task was to redirect the user to the login page, if this component throws a specific error. The current (working) solution is:
class SomeComponent extends Component {
static $controllerObject;
function startup(&$controller) {
SomeComponent::$controllerObject =& $controller;
}
(...)
public static function decodeResponse($response) {
if($response == null || trim($response) == '') {
return null;
}
$result = json_decode($response, true);
if($result == null) {
throw new FatalErrorException('Could not parse api server response: ' . $response);
}
if(isset($result['error'])) {
if ($result['error'] === "IncorrectCredentialsException") {
self::$controllerObject->Session->destroy();
self::$controllerObject->Session->setFlash(__('Your session has ended. Please log in again.', true), 'default', array(), 'error');
self::$controllerObject->redirect(array(
'language' => Configure::read('Config.language'),
'controller' => 'users',
'action' => 'login'
));
}
else { throw new ApiServerException($result); }
}
return $result;
}
However, my team colleague, who is responsible for the software quality, doesn't find this solution satisfying. He says: "pls find a better way to pass the controller to the decode method. Setting the controller as static variable is not the best way".
Is there any better way to do this?
I think the problem is that your method does two different things: decoding and error handling. Instead of handling the IncorrectCredentialsException inside your method, I would move this functionality to where you handle the other exceptions and just throw an IncorrectCredentialsException in your method. With this change you no longer need to access the controller in your static method.

CakePHP Controller Testing: Mocking $this->referer()

I have code in place so that if the "Add User" page is accessed from anywhere in the "Posts" section of the website, the user will be taken to the "Users" index after adding the user. But if the "Add User" page is accessed from any other section of the website, the user will be taken back to where they were after adding the user. I want to test this, but I don't know how. This is what I have so far:
Controller Code
<?php
App::uses('AppController', 'Controller');
class UsersController extends AppController {
public function add() {
if ($this->request->is('post')) {
$this->User->create();
if ($this->User->save($this->request->data)) {
$this->Session->setFlash(__('The user has been saved'));
return $this->redirect($this->request->data['User']['redirect']);
} else {
$this->Session->setFlash(__('The user could not be saved. Please, try again.'));
}
}
else {
if ($this->referer() == '/' || strpos($this->referer(), '/posts') !== false) {
$this->request->data['User']['redirect'] = Router::url(array('action' => 'index'));
}
else {
$this->request->data['User']['redirect'] = $this->referer();
}
}
}
public function index() {
$this->User->recursive = 0;
$this->set('users', $this->paginate());
}
}
Test Code
<?php
App::uses('UsersController', 'Controller');
class UsersControllerTest extends ControllerTestCase {
public function testAdd() {
$this->Controller = $this->generate('Users');
// The following line is my failed attempt at making $this->referer()
// always return "/posts".
$this->Controller->expects($this->any())->method('referer')->will($this->returnValue('/posts'));
$this->testAction('/users/add/', array('method' => 'get'));
$this->assertEquals('/users', $this->Controller->request->data['User']['redirect']);
}
}
What am I doing wrong?
You aren't mocking any methods
This line
$this->Controller = $this->generate('Users');
Only generates a test controller, you aren't specifying any methods to mock. To specify that some controller methods need to be mocked refer to the documentation:
$Posts = $this->generate('Users', array(
'methods' => array(
'referer'
),
...
));
The expectation is never triggered
Before asking this question, you probably had an internal conversation a bit like: "why is it saying that my expectation is never called? I'll just use $this->any() and ignore it.."
Don't use $this->any() unless it really doesn't matter if the mocked method is called at all. Looking at the controller code, you're expecting it to be called exactly once - so instead use $this->once():
public function testAdd() {
...
$this->Controller
->expects($this->once()) # <-
->method('referer')
->will($this->returnValue('/posts'));
...
}
The full list of available matchers is available in PHPUnit's documentation.

Cakephp Auth Component - Home page redirect loop

I want to have a login form in my home page, the registered users should be redirected to users/index
with the below code, my home page is going to redirect loop
can anyone tell me where is the issue ??
Note:- infact it is perfectly working if i change the line to
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
App Controller
public function beforeFilter(){
$this->Auth->autoRedirect = false;
$this->Auth->loginAction = array('controller' => './', 'action' => 'index');
$this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'index');
$this->Auth->logoutRedirect = array('controller' => './', 'action' => './');
$this->Auth->authorize = 'controller';
$this->Auth->authError= 'You need permissions to access this page';
$this->Auth->allow('index');
$this->set('Auth',$this->Auth);
}
UsersController
public function login(){
$id = $this->Auth->user('id');
if(empty($id)){
if($this->request->is('post')){
if($this->Auth->login()){
$this->redirect($this->Auth->redirect());
}else{
$this->Session->setFlash('Invalid Username or password');
}
}
}else{
$this->redirect(array('action'=>'index'));
}
}
Thanks for the help...
You pretty much answered your own question here:
Note:- infact it is perfectly working if i change the line to
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
Indeed that would work and that is what it should look like. Right now, you're telling the auth component your loginAction (the action which holds your login logic) is the index action of the ./ controller (which doesn't even exist). I'm assuming you're confusing it with the loginRedirect variable, which is for setting the page to go to after successful authentication.
If you only want Registered Users to Access Your Site you could have something like this... at least, this is how I implement something similar in my site...
In your app_controller file add the following to the beginning of your beforeFilter() function
function beforeFilter(){
//Check if user was able to log in thru Auth using your form in the homepage
if($this->isLoggedIn() == TRUE){
$this->layout = 'default'
}else{
// You can created this layout with a login form and
// whatever else you need except <?php echo $content_for_layout; ?>
// Any registered user will be allowed to login using the form
// and continue on to your site using the default layout
// But it guarantees no one else can see your default site
$this->layout = "unregistered_user"
}
}
On your App_controller.php you can create this function
function isLoggedIn(){
// You can also use $this->Auth->user directly in your App's beforeFilter()
// But I just like to have functions so I can reuse
if($this->Auth->user()){
$loggedin= TRUE;
}else{
$loggedin= FALSE;
}
return $loggedin;
}
I have something similar of my site but is only used when in maintenance mode. I am still developing my site. The only problem I've seen with this way, which I have not yet have time/need to look at, is that my errors are not sent to the layout I want. Supposed a user types in http://www.mydomain.com/inexistentpage then cake transfers them to my default layout. It might be easy to fix, but I havent got time to do that yet.
NOTE: I quickly did this off the top of my head and because of it, this code is untested. However, if you have any issues please let me know and I will test it and post back.
using $this->requestAction(anotherController/action); in the view might call to another controller->action. you must ensure that the another controller->action has the right permissions. or you'll get redirect loop.
solve it by adding $this->auth->allow('action name'); to the another controller page in the beforeFilter() callback.

CakePHP Auth Deny Admin Routing Pages

I have been reading Stack Overflow questions all afternoon trying to figure this out..
I have a users controller with index/login/logout/register functions but also has admin_index/admin_add/admin_edit/admin_delete etc.
I have Auth component enabled and in my users_controller i am trying to deny access to the admin_* pages if the Auth.User.role != 'admin', when i enable the $this->Auth->authorize = 'controller'; it denies access to the site.com/admin/users/ page and also seems to kill the logout function even tho my account has the role set to admin.
However if i type the url in i get redirected back to the main homepage.
users_controller.php
<?php
class UsersController extends AppController {
var $name = 'Users';
function beforeFilter(){
parent::beforeFilter();
$this->Auth->authorize = 'controller';
$this->Auth->allow('register');
}
function isAuthorized() {
if ($this->Auth->user('role') != 'admin') {
$this->Auth->deny('admin_index','admin_view', 'admin_add', 'admin_edit','admin_delete');
}
}
app_controller.php
<?php
class AppController extends Controller {
var $components = array('Auth', 'Session');
function beforeFilter() {
$this->Auth->loginAction = array('controller'=>'users','action'=>'login', 'admin'=>false);
$this->Auth->logoutRedirect = array('controller'=>'users','action'=>'logout');
$this->Auth->loginRedirect = array('controller'=>'shows', 'action'=>'index');
$this->Auth->autoRedirect = false;
$this->Auth->allow('home');
}
My Second question relates to the way $this->Auth->deny('page'); redirects the user, as far as i can tell it redirects to / but i need it to redirect back to the users controller.
Hope it all makes sense and i have provided enough info..
The root of your problem is probably your isAuthorized() method. This should simply return true or false, and indicates whether an authenticated user is AUTHORIZED to access a particular action.
It's difficult to say why you'd be redirected to the home page instead of the login page. But it's possible that you have other code somewhere that's messing things up.
Try modifying your code as below and see if that doesn't help get things working:
app_controller.php
<?php
class AppController extends Controller {
var $components = array('Session', 'Auth' => array(
'loginAction' => array('controller'=>'users','action'=>'login', 'admin'=>false),
'logoutRedirect' => array('controller'=>'users','action'=>'logout'),
'loginRedirect' => array('controller'=>'shows', 'action'=>'index'),
'autoRedirect' => false,
'authorize' => 'controller'
);
function beforeFilter() {
$this->Auth->allow('home');
}
function isAuthorized() {
if (!empty($this->params['prefix']) && $this->params['prefix'] == 'admin') {
if ($this->Auth->user('role') != 'admin') {
return false;
}
}
return true;
}
?>
users_controller.php
<?php
class UsersController extends AppController {
var $name = 'Users';
function beforeFilter(){
parent::beforeFilter();
$this->Auth->allow('register');
}
?>
I moved all the Auth settings to the declaration in the $components variable because it seems cleaner and to make more sense to declare default values there. But this is more a matter of personal preference and it shouldn't have a real effect on the code's functioning.
Also, note that if you set autoRedirect to false, you'll have to redirect logged-in users manually in your Users::login() action, getting the loginRedirect value with $this->Auth->redirect().
I don't see any reason why you should be sent to / when you're not logged in and you try to access a blocked action, but maybe it will be easier to figure out after you fix the above. **
you should do this like...
function beforeFilter()
{
if($this->Auth->user('role')=='admin'){
$this->Auth->allow('admin_view','admin_controls');//put your all admin actions separated by comma
}
else
{
$this->Auth->allow('home');//put your all non-admin actions separated by comma
}
}
hope it will work... if any problem let me know....

Cakephp - how to make error pages have its own layouts?

I wanna have a different layout for the page not found 404 page. How can i set a different layout for that page?
Savant from the IRC helped me out and he suggest in using beforeRender(){} in the app_controller
// Before Render
function beforeRender() {
if($this->name == 'CakeError') {
//$this->layout = 'error';
}
}
CakeError is a catchAll for errors :D
In CakePHP 2.2.2 I changed the ExceptionRenderer in core.php with my own, like this:
app/Config/core.php:
Configure::write('Exception', array(
'handler' => 'ErrorHandler::handleException',
'renderer' => 'MyExceptionRenderer', // this is ExceptionRenderer by default
'log' => true
));
app/Lib/Error/MyExceptionRenderer.php:
App::uses('ExceptionRenderer', 'Error');
class MyExceptionRenderer extends ExceptionRenderer {
protected function _outputMessage($template) {
$this->controller->layout = 'error';
parent::_outputMessage($template);
}
}
Just you need to make layout changes in your error400.ctp file under /app/View/Errors/error400.ctp
Open that file and set layout by
<?php $this->layout=''; //set your layout here ?>
better to create an error.php file in your app folder
class AppError extends ErrorHandler {
function error404($params) {
$this->controller->layout = 'error';
parent::error404($params);
}
}
so you can avoid the if-testing at EVERY page render that savants' solution introduces
My solution for CakePHP 2.3
Change the ExceptionRenderer in core.php to use your own renderer.
app/Config/core.php:
Configure::write('Exception', array(
'handler' => 'ErrorHandler::handleException',
'renderer' => 'MyExceptionRenderer',
'log' => true
));
app/Lib/Error/MyExceptionRenderer.php:
App::uses('ExceptionRenderer', 'Error');
class MyExceptionRenderer extends ExceptionRenderer
{
/**
* Overrided, to always use a bare controller.
*
* #param Exception $exception The exception to get a controller for.
* #return Controller
*/
protected function _getController($exception) {
if (!$request = Router::getRequest(true)) {
$request = new CakeRequest();
}
$response = new CakeResponse(array('charset' => Configure::read('App.encoding')));
$controller = new Controller($request, $response);
$controller->viewPath = 'Errors';
$controller->layout = 'error';
return $controller;
}
}
The advantage to this approach is that it ensures any exceptions thrown from AppController don't cause an endless loop when rendering the exception. Forces a basic rendering of the exception message every time.
This simplest way I know of is to create this function in your AppController:
function appError($method, $messages)
{
}
You can then do whatever you want with the error, display it however you like, or not display it at all, send an email etc.. (I'm not sure if this method if still valid.)
There is also an option of creating app_error.php in your app root, with class AppError extends ErrorHandler in it, which enables you to override all kinds of errors. But I haven't done this yet, so I can't tell you more about it.
See cake/libs/error.php and cake/libs/object.php and of course The Book for more info.
Edit: Forgot to mention, once you caught the error, there's nothing preventing you to - for example - store the error in session, redirect to your "error handling controller", and then display it in your controller however you want.

Resources