My problem is fairly basic: whenever an action in CakePHP is blackholed, I want to display a custom error page; the default behaviour of Cake to display a "File not found" message confuses the hell out of users (not to mention developers). So I came up with this, by searching the docs and StackOverflow:
class TestsController extends AppController
{
public $components = array ('Security');
public function beforeFilter ()
{
parent::beforeFilter ();
$this->Security->blackHoleCallback = 'blackhole';
$this->Security->csrfExpires = '+5 seconds'; // for testing
}
public function index ()
{
}
public function doit ()
{
$this->log ('Performing request; entry = ' . $this->data['foo'], 'tests');
$this->set ('foo', $this->data['foo']);
}
public function blackhole ($type)
{
$this->log ('Request has been blackholed: ' . $type, 'tests');
$this->render ('/Errors/blackhole');
$this->response->send ();
exit ();
}
}
In index.ctp there is a simple form with a single textbox, that commits to doit (excluded for brevity). This works, but I have one major issue: the exit() in the blackhole() function. The problem is, if I do not exit here doit() is still called, even if the request is blackholed, as evidenced by the log:
2013-01-30 15:37:21 Tests: Request has been blackholed: csrf
2013-01-30 15:37:21 Tests: Performing request; entry = kfkfkfkf
This is clearly not what you expect. The Cake documentation hints at using (custom) exceptions to stop processing in blackhole(), but that:
completely beats the purpose of using a custom handler;
adds another layer of complexity to something that should be simple.
My question is: is there a proper way to do an "exit" from blackhole() so that Cake does all the rendering/cleanup/etc. that it usually does; I already had to add $this->response->send() to force output to the browser. Or, althernatively, a way to tell Cake to skip calling doit() after blackhole().
My suggestion would be to redirect in your blackhole.
This is done e.g. here in the cookbook http://book.cakephp.org/2.0/en/core-libraries/components/security-component.html#usage
$this->redirect(array('controller' => 'test', 'action' => 'index'));
A redirect will issue an exit (http://book.cakephp.org/2.0/en/controllers.html#flow-control).
You can also send something nice to the user if you want, before the redirect:
$this->Session->setFlash('What are you doing!?');
In your blackhole callback you could just throw an exception with required message. That would render the proper error page and get logged too (assuming you have turned on logging for errors in core.php).
Related
Currently, I'm using the CRUD v4 plugin for Cakephp 3. For the edit function in my user controller it is important that only a user itself can alter his or her credentials. I want to make this possible by inserting the user id from the authentication component. The following controller method:
public function edit($id = null){
$this->Crud->on('beforeSave', function(\Cake\Event\Event $event) {
$event->subject()->entity->id = $this->Auth->user('id');
});
return $this->Crud->execute();
}
How can I make sure I don't need to give the id through the url? The standard implementation requires the url give like this: http://domain.com/api/users/edit/1.json through PUT request. What I want to do is that a user can just fill in http://domain.com/api/users/edit.json and send a JSON body with it.
I already tried several things under which:
$id = null when the parameter is given, like in the example above. Without giving any id in the url this will throw a 404 error which is caused by the _notFound method in the FindMethodTrait.php
Use beforeFind instead of beforeSave. This doesn't work either since this isn't the appropriate method for the edit function.
Give just a random id which doesn't exist in the database. This will through a 404 error. I think this is the most significant sign (combined with point 1) that there is something wrong. Since I try to overwrite this value, the CRUD plugin doesn't allow me to do that in a way that my inserting value is just totally ignored (overwriting the $event->subject()->entity->id).
Try to access the method with PUT through http://domain.com/api/users.json. This will try to route the action to the index method.
Just a few checks: the controllerTrait is used in my AppController and the crud edit function is not disabled.
Does anyone know what I'm doing wrong here? Is this a bug?
I personally would use the controller authorize in the Auth component to prevent anyone from updating someone else's information. That way you do not have to change up the crud code. Something like this...
Add this line to config of the Auth component (which is probably in your AppController):
'authorize' => ['Controller']
Then, inside the app controller create a function called isAuthorized:
public function isAuthorized($user) {
return true;
}
Then, inside your UsersController you can override the isAuthorized function:
public function isAuthorized($user) {
// The owner of an article can edit and delete it
if (in_array($this->request->action, ['edit'])) {
$userId = (int)$this->request->params['pass'][0];
if ($user['id'] !== $userId) {
return false;
}
}
return parent::isAuthorized($user);
}
In my CakePHP 2 application i have a problem with beforeFilter.
In this thread it worked well. Because of old version of CakePHP.
In my code, If user is not authorized, I want to show him "anotherview.ctp".
I don't want to redirect visitor to another page. (because of adsense issues)
When i use "this->render" in beforeFilter, the code in my "index" action is also run.
I want to stop the execution after the last line of "beforeFilter".
When I add "exit()" to beforeFilter, it broke my code.
How can I stop execution in beforeFilter without breaking code?
class MyController extends AppController {
function beforeFilter() {
if ( $authorization == false ) {
$this->render('anotherview');
//exit();
}
}
}
function index() {
// show authorized staff
}
}
Try:
$this->response->send();
$this->_stop();
I stumbled across this thread while trying to do the same thing. The accepted answer works but omits an important detail. I'm trying to render a layout (not a view) and I had to add an additional line to prevent the original request's view from throwing errors.
Inside AppController::beforeFilter():
$this->render(FALSE, 'maintenance'); //I needed to add this
$this->response->send();
$this->_stop();
or alternatively - redirect to another view:
if ( $authorization == false ) {
$this->redirect('/users/not_authorized');
}
For CakePHP 3.5 this is what worked for me:
$event->setResult($this->render('anotherview'));
This will also enable you to use the debugger. CakePHP Debugger stopped working when I used the exit; statement.
i am new with cake but i´ve somehow managed to get through so far. After i´ve figured out that html2pdf is a convienient way to produce pdf documents out of Cakephp, i´ve installed html2ps/pdf and after some minor problems it worked. So now i am coming now to the point that if i don´t modify my controllers beforeRender function like:
function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('download','view');
}
i just see my loginpage in the pdf i´ve created. Setting within my beforeRender function the $this->Auth->allow value opens obviously erveryone the way to get a perfect pdf without being authorized. The whole controller looks like this:
<?php
class DashboardController extends AppController {
var $name = 'Dashboard';
var $uses = array('Aircrafts','Trainingplans',
'Fstds','Flights','Properties','Person');
function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('download','view');
}
function view() {
/* set layout for print */
$this->layout = 'pdf';
/* change layout for browser */
if> (!isset($this->params['named']['print']))
$this->layout = 'dashboard';
/* aircrafts */
$this->Aircrafts->recursive = 0;
$aircrafts =$this->Aircrafts->find('all');
$this->set('aircrafts',$aircrafts);
.... and so on....
$this->set('person_properties',$person_properties);
}
function download($id = null) {
$download_link = 'dashboard/view/print:1';
// Include Component
App::import('Component', 'Pdf');
// Make instance
$Pdf = new PdfComponent();
// Invoice name (output name)
$Pdf->filename = 'dashboard-' . date("M");
// You can use download or browser here
$Pdf->output = 'download';
$Pdf->init();
// Render the view
$Pdf->process(Router::url('/', true) . $download_link);
$this->render(false);
}
}
?>
So in my opinion the $Pdf->process call get´s the data by calling more or less the view, but this process is not logged in, or in other words not authorized to get the data i want to render into the pdf. So the question is now how to get it done by not opening my application to everyone.
Best regards, cdjw
Edit:
You could do something like this:
if($this->Session->check('Auth.User')) {
// do your stuff
} else {
// do something else
}
You could check for 2 things before rendering /view:
a valid session (a user is logged in)
a valid security token that you pass from your download action as a named parameter
For the security token, just make up a long random string.
As the PDF is rendered on the same server, the token will never be known in the open and provide sufficient security.
Hope this is a working idea for you.
I had this similar issue, and this is how I handled it...
I first noticed that the process call of the PdfComponent was doing a request from the same server, so I tricked CakePHP on allowing the view only for requests being made from the server itself.. like this:
public function beforeFilter() {
if ($this->request->params['action']=='view'&&$_SERVER['SERVER_ADDR']==$_SERVER['REMOTE_ADDR']) { // for PDF access
$this->Auth->allow('view');
}
}
You should put
$this->Auth->allow('download','view');
inside AppController. rather than place where are you using now.
function beforeFilter() {
$this->Auth->allow('download','view');
....
}
I am a 3+ years old in cakephp and facing a somewhat strange issue
with submitting a form to plugin controller's action (i am using
plugin first time). After trying different known things i am posting
this one.
Going straight into the matter here is the form in my "forum" plugin's search_controller.php's "index" view:
echo $form->create("Search", array('url'=>array('controller' =>
'search', 'action' => 'index','plugin'=>'forum'),
'id'=>'searchFormMain'));
<input type="text" name="data[Search][keyword]" style="width:357px; margin-left:9px;"><p><span id="searchButton"><input
type="image" src="/img/button_search.jpg" style="height:40px;width:
136px;border:0;" class="handcursor"></span></p>
</form>
As i am submitting this form to "index" action of search controller of
forum plugin, the following code does print nothing:
public function index($type='') {
if(!empty($this->data)) {
pr($this->data);
die;
}
}
While if i try the same code within beforeFilter of the same
controller i.e. search_controller.php it works well and prints as
follows:
Array
(
[Search] => Array
(
[keyword] => Hello Forum
)
)
And finally here is the beforeFilter code (of search_controller.php):
public function beforeFilter() {
parent::beforeFilter();
if(!empty($this->data)) {
pr($this->data);
}
}
Fyi, it does not matter if i comment out "parent::beforeFilter();" or
even disable $uses of my controller (if they look doubtful to you)
the result is same i.e. the control is not going in to "index" action
in the case of form submit while is working fine in the case of page
call. The url/action to page is http://localhost.rfdf.org/forum/search/index.
If i call the url directly it loads the form fine but when i submit it, it
never gets into the "index" action of the controller thus no view
rendered.
If i try the same set of code out of "forum" plugin environment i.e. in normal application it works just fine
I have been trying to find a way out of this for last 3+ hours now but
no success. I would appreciate any help in solving this puzzle.
I got it, finally!
It was Securty compontent dropping the request into the blackHole whenever it failed to find a security token with the form data. I learned that "Security" component "interferes" with $form->create() method and places a token as a hidden field with each $form->create() call. On the form submit, just after beforeFilter and right before getting into the controller "action" it checks for this token and simply dies everything on a validation failure. Unfortunately there is no error message or entry to cake log.
In my case i had been creating my own custom tag and not with the help of $form->create method so no token was being generated which lead to all the pain.
I resolved it by placing
$this->Security->validatePost = false;
at the end of beforeFilter.
Thanks everyone!
Have you tried putting an else into that if(!empty($this->data)) and doing a pr() as it could be that your post is not empty.
Either that or the format of your url array is not correct.
From ln759, http://api.cakephp.org/view_source/router/#line-757
$defaults = $params = array('plugin' => null, 'controller' => null, 'action' => 'index');
So I guess you need plugin first?
Are you using ACL or any of the like? In the beforeFilter, do a pr of the request. See which action is being requested to make sure that the request is correct
This question is slightly related to my old post Dealing with Alias URLs in CakePHP
After much thought, I am exploring the option of having a custom 404 script in my Cake App, that is reached when a URL does not map to any controllers/actions. This script would check $this->here and look it up in a database of redirects. If a match is found it would track a specific 'promo' code and redirect.
I'm thinking status codes. Can I make my script return a suitable status code based on certain conditions? For example:
URL matches a redirect - return a 301
URL really does not have a destination - return a 404.
Can I do this?
EDIT:
What about this? Anyone see any problems with it? I put it in app_controller.
function appError($method, $params) {
//do clever stuff here
}
This should work. Assuming you redirect 404's at a LegacyUrls::map() controller action. The code needs to be stored in app/app_error.php:
<?php
class AppError extends ErrorHandler{
function error404($params) {
$Dispatcher = new Dispatcher();
$Dispatcher->dispatch('/legacy_urls/map', array('broken-url' => '/'.$params['url']));
exit;
}
function missingController($params) {
$Dispatcher = new Dispatcher();
$Dispatcher->dispatch('/legacy_urls/map', array('broken-url' => '/'.$params['url']));
exit;
}
}
?>
Good luck!
I've always created app\views\errors\missing_action.ctp and app\views\errors\missing_controller.ctp
Cake will automatically display one of those views when a url does not map out to a controller or its methods.
Unless there is a certain need for the error codes that you did not give, this would work perfectly!
I'd like to augment felixge's answer.
This version outputs a 404 error to the browser:
class AppError extends ErrorHandler
{
function _outputMessage($template)
{
if ($template === 'error404') {
$Dispatcher = new Dispatcher();
$Dispatcher->dispatch('legacy_urls/map', array('broken-url' => '/'.$params['url']));
return;
}
parent::_outputMessage($template);
}
}