So at the moment, my CI website is formatted to take part of the URI, let's say an id, and find a result in the database from it to display on the page.
If I enter an id that is not in the database however, naturally I am going to get a database error. Is there a better way of protecting my URI's from direct db access, and, what would be the best method to tell the user that the URL they entered is invalid?
imo you should not only display a error, but also throw a 404 (or 301, depends on your application) for SEO reasons
a simple implementation:
MY_Controller.php
...
protected function error404()
{
set_status_header(404);
$data = array();
$this->load->view('error404_view', $data);
}
examplecontroller.php
...
$data = $this->Some_model->get_by_id($id);
if (empty($data))
{
$this->error404();
}
else
{
// your view here
}
...
http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
Suppose your id is stored in $id. Then you can do something like this.
if() // if $id not found - depends how you are doing check
{
$errorMsg = "Not found";
$this->session->set_flashdata('errorMsg', $errorMsg );
redirect('home'); //Redirect to the page which is always available and display flashdata with error message here
}
Related
I'm working on my cakephp project
and I am currently upgrading from 3.3.16 to 3.4.0
The project uses the cakephp-upload plugin to save an image.
The Upload plugin needs an existing entity to attach a file to it. A modification of the request is done to grab the avatar, before unsetting it to save the user.
I know this is not a good practice to modify a request, but the code was made this way.
With immutable objects in version 3.4.0, it is just not possible anymore. But i dont know how to do it properly.
Here is the error message given by my unit-test,
ran by vendor/bin/phpunit --filter testAdd tests/TestCase/Controller/Api/V1/UsersControllerTest.php:
There was 1 failure:
1) App\Test\TestCase\Controller\Api\V1\UsersControllerTest::testAdd
Failed asserting that file "/home/comptoir/Comptoir-srv/webroot/img/files/Users/photo/5/avatar/correctAvatarLogo.jpg" exists.
/home/comptoir/Comptoir-srv/tests/TestCase/Controller/Api/V1/UsersControllerTest.php:208
Here is the actual code:
public function add()
{
if (!empty($this->request->data)) {
$user = $this->Users->newEntity($this->request->data);
} else {
$user = $this->Users->newEntity();
}
$message = "";
// Get the avatar before unset it to save the user.
// The Upload plugin need an existing entity to attach a file to it.
if ($this->request->is('post')) {
if (isset($this->request->data['photo']) && !$user->errors()) {
$avatar = $this->request->data['photo'];
$this->request->data['photo'] = "";
}
$user = $this->Users->patchEntity($user, $this->request->data);
if ($this->Users->save($user)) {
$user = $this->Users->get($user->id, ['contain' => []]);
isset($avatar) ? $this->request->data['photo'] = $avatar : null;
$user = $this->Users->patchEntity($user, $this->request->data);
if ($this->Users->save($user)) {
$message = "Success";
$this->Flash->success(__d("Forms", "Your are registred on the Comptoir du Libre, welcome !"));
if (!$this->request->is('json')) {
$this->Auth->setUser($this->Auth->identify());
$this->redirect([
"prefix" => false,
"controller" => "Pages",
"language" => $this->request->param("language")
]);
}
} else {
$message = "Error";
}
} else {
$message = "Error";
$this->Flash->error(__d("Forms", "Your registration failed, please follow rules in red."));
}
$message == "Error" ? $this->set('errors', $user->errors()) : null;
}
$this->ValidationRules->config('tableRegistry', "Users");
$rules = $this->ValidationRules->get();
$userTypes = $this->Users->UserTypes->find('list', ['limit' => 200]);
$this->set(compact('user', 'userTypes', 'rules', 'message'));
$this->set('_serialize', ['user', 'userTypes', 'rules', 'message', 'errors']);
}
Does anyone know how to do that respecting the immutable rule ?
Your premise is wrong.
The Upload plugin needs an existing entity to attach a file to it
That's actually not correct, uploading files alongside creating new records works fine. There's no need for this stuff in your controller, it should be possible to handle this with a single basic save, ie you should investigate the problem that you're having with that, and fix it.
However looking at your test, it should fail anyways, because the file data that you're passing is invalid, it's neither an actual uploaded file for which is_uploaded_file() would return true, nor is it acceptable for user data to be able to define the temporary file path, and the error code, ie you're not properly validating the data if that test passes as is. Accepting such data is a security vulnerability, it could allow all sorts of attacks, from path traversal to arbitrary file injections!
Ideally your whole upload validation and writing functionality would support \Psr\Http\Message\UploadedFileInterface objects, that would allow for very simply testing by being able to pass instances of that class into the test data, that might be something worth suggesting for the plugin. Without such functionality, your second best bet would probably be something like modifying the table's validation rules before issuing the test request, so that is_uploaded_file() is being skipped, or you're switching to integration tests over HTTP, instead of the simulation in CakePHP.
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);
}
I am implementing Backbone.js with Codeigniter, and having a hard time receiving a proper response from Codeigniter upon a Ajax call. I was doing a #Create, which led to #save, and then #set, right at there, it breaks and couldn't find the ID in the format I returned the data.
For testing purpose, I'm echo-ing
'[{"id":"100"}]'
right back to the browswer, still it couldn't find it.
Anyone aware of a Backbone/Codeigniter(or similar) RESTful implementation example?
You need to return 200 response code or it would not pass as good response.
I built few apps with Backbone/CI combo and it is much easier if you use Phil Sturgeon's REST implementation for CodeIgniter
Than you controller that is located at url example.com/api/user and directory applications/controllers/api/user.php would look something like this:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
include APPPATH.'core/REST_Controller.php'; // MUST HAVE THIS LINE!!!
class User extends REST_Controller {
// update user
public function index_put() // prefix http verbs with index_
{
$this->load->model('Administration');
if($this->Administration->update_user($this->request->body)){ // MUST USE request->body
$this->response(NULL, 200); // this is how you return response with success code
return;
}
$this->response(NULL, 400); // this is how you return response with error code
}
// create user
public function index_post()
{
$this->load->model('Administration');
$new_id = $this->Administration->add_user($this->request->body);
if($new_id){
$this->response(array('id' => $new_id), 200); // return json to client (you must set json to default response format in app/config/rest.php
return;
}
$this->response(NULL, 400);
}
// deleting user
public function index_delete($id)
{
$this->load->model('Administration');
if($this->Administration->delete_user($id)){
$this->response(NULL, 200);
return;
}
$this->response(NULL, 400);
}
}
It will help you to return proper responses.
TIP: Also whatever you return to client will be set to model attributes. E.g. when creating user if you return only:
'[{"id":"100"}]'
model will be assigned id 100. But if you return:
'[{"id":"100", "date_created":"20-aug-2011", "created_by": "Admin", "random": "lfsdlkfskl"}]'
all this key value pairs will be set to user model (I added this just for clarity, since it confused me in start)
IMPORTANT: this is for CI 2.0+ if you are using 1.7.x REST implementation is a tiny bit different that concerns directory structure
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');
....
}
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);
}
}