Cakephp 3 - CRUD plugin - Use id from auth component - 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);
}

Related

Trying to get user id with Auth::id() but it doesn't work in my Model? [duplicate]

When I am tring to get loggedin user details using auth:api middleware, it returns user object with details in my controller function.
api.php (with auth:api middleware returns User object)
Route::group(['middleware' => 'auth:api'], function() {
Route::get('users/mentor_details/{uuid}','UserController#getMentorProfileDetails');
});
But when I am trying to get loggedin user details outside this auth:api middleware, it returns null.
api.php (without auth:api middleware return null)
Route::get('users/mentor_details/{uuid}','UserController#getMentorProfileDetails');
When the auth middleware is not provided, or is provided without specifying the guard, the default guard is used to determine the user. Unless you have changed this in your config/auth.php file, the default guard is the web guard.
So, when you go to a route that is not protected by a specific auth middleware, the user that is loaded is the one provided by the web guard.
Therefore, even though you may be sending the bearer token to use a specific user, the web guard doesn't know anything about that, and since you have no user logged in via the web guard, you are getting a null user.
You've got four options:
Make sure the route is protected by the auth:api middleware, which specifies the api guard. This, however, will not allow guests to access the url.
Change your default guard to api in your config/auth.php file. This is probably not what you want to do, especially if you do have normal web users.
Tell the request you want the user from the api guard. The $request->user() method takes a guard as an argument, so if you do $request->user('api'), it will retrieve the user using the api guard.
Get the user from the api guard directly: auth()->guard('api')->user().
The auth middleware is the one returning the user. auth:api just indicates to use the API guard. In the source code of laravel, the file vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php line 62, the function shouldUse is the one setting the Auth::user() object. Check out also vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php shouldUse function
override createToken() method app\Models\User.php like below.
public function createToken($user_id,$name, array $scopes = [])
{
return Container::getInstance()->make(PersonalAccessTokenFactory::class)
->make(
$user_id, $name, $scopes
);
}
And create token as given below wherever you want and explicitly pass $user_id.
$user= new user();
$accessToken = $user->createToken($user_id,'authToken')->accessToken;
Now you will get $request->user()
override register() method app\Exceptions\Handler.php like below-
public function register() {
$this->renderable(function (\Illuminate\Auth\AuthenticationException $e, $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Not authenticated'
], 401);
}
});
}

How can CakePHP Authorization plugin authorize access to indexes?

I'm converting my app to CakePHP 3.6, and working now on using the new Authorization plugin. I'm not sure how to check authorization for things like indexes or other reports, where there is no "resource" to pass to the can() or authorize() functions.
For now, I've built a ControllerResolver, loosely copied from the ORMResolver, which accepts controller objects and finds policies based on the singularized controller name, so that they're named the same as the Entity policies I'm building. (That is, my UserPolicy can have canIndex and canEdit functions, the former found via the controller and the latter via the entity.)
This works fine in controller actions where I can call $this->Authorize->authorize($this);, but it doesn't work in views, where I'd like to be able to do things like:
if ($this->Identity->can('index', *something*)) {
echo $this->Html->link('List', ['action' => 'index']);
}
so as to only show links to people who are allowed to run those actions.
Anyone know if there's a reason why the system implicitly requires that the "resource" passed into authorization functions be an object? (For example, the plugin component calls get_class($resource) in the case of a failed authorization, without first checking that the provided resource is in fact an object.) Allowing a string (e.g. \App\Controller\UsersController::class) would make my life easy. Very happy to put together a PR for this if it's just an oversight.
But authorizing indexes seems like a pretty obvious function, so I wonder if I've missed something. Maybe I'm supposed to pass the table object, and split the authorization between an entity policy and a table policy? But using table objects in views just for this purpose seems like a violation of separation of concerns. Maybe uses of the plugin to date have been things where indexes are always public?
to do this you can use the authorizeModel, as stated in the documentation https://github.com/cakephp/authorization/blob/master/docs/Component.md#automatic-authorization-checks. Basically is adding the auhtorizeModel parameters when you load the component at AppController.php
$this->loadComponent('Authorization.Authorization', [
'skipAuthorization' => ['login','token'],
'authorizeModel' => ['index','add'],
]);
When you configure an action to be authorized by model the authorization service uses the TablePolicy, so if you want to authorize the index action for Books you need to create the BooksTablePolicy and implement the method
<?php
namespace App\Policy;
use App\Model\Table\BooksTable;
use Authorization\IdentityInterface;
/**
* Books policy
*/
class BooksTablePolicy
{
public function scopeIndex($user, $query)
{
return $query->where(['Books.user_id' => $user->id]);
}
public function canIndex(IdentityInterface $identity)
{
// here you can resolve true or false depending of the identity required characteristics
$identity['can_index']=true;
return $identity['can_index'];
}
}
This will be validated before the request reaches your controller so you do not need to authorize anything there. Nevertheless if you want to apply an scope policy as you can see in this example:
public function index()
{
$user = $this->request->getAttribute('identity');
$query = $user->applyScope('index', $this->Books->find()->contain('Users'));
$this->set('books', $this->paginate($query));
}

CakePHP default layout, can't pass values to default.ctp

I have one page website, on homepage(Layout/default.ctp) I have 2 forms, subscribe and contact form that are being controlled over contact controller. With $this->set('some_val', 'test'); I can set value from AppController, but not from contact controller, how can I set values from contact controller to be available in default.ctp except with sessions?
public function beforeFilter() {
parent::beforeFilter();
//pr('beforeFilter'); // i was testing is this happening or not
//exit();
$tester = 'test';
$this->set(compact('tester'));
}
and in default.ctp I just pr($this->viewVars); to make sure that I have tester value, but it is always empty.
Is this right approach how to implement several controllers in one page design?
Another question is there a place/function where I could check is current request post or not, I would like to check for each request what is it?
Thank you.
Not sure if I understand correctly, but it sounds like you might need multiple layouts:
class CarsController extends AppController
{
public function index($page)
{
/* Your logic */
if ( $page == 'other' ) {
$this->render('view', 'layout');
} else {
$this->render('view-other', 'layout-other');
}
}
}
For more information i'd suggest looking at: http://api20.cakephp.org/class/controller#method-Controllerrender
try an echo $tester; in your default, it should be available.
If the request is post, you would have data in $this->data.

Cakephp html2pdf auth problem

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');
....
}

Can I make CakePHP return a suitable status code based on certain conditions?

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);
}
}

Resources