CakePHP: calling function in the AppController but not effect some exception - php

Normally, in cakephp there is Auth component to help user login and there is function Auth->Allow() to make the guests users still can access to some pages like Index. But now i want that only Activated account can access almost every function of the web, but still except some normal pages like index, view etc.
I have a fucntion in Appcontroller
public function is_activated(){
$userId = $this->Auth->user('id');
$user = $this->Users->find('all', [
'conditions' => ['id' => $userId],
'fields' => ['id', 'email', 'activated']
])->first();
$activated = $user->activated;
if($activated !== 1){
$this->Flash->error(__('Your account is not yet activated'));
return $this->redirect('/users/activate');
}
}
I call it in BeforeFilter along with Auth->allow() in ProjectsController:
public function beforeFilter(Event $event) {
parent::beforeFilter($event);
$this->Auth->allow(['index', 'getMyProjects']);
$this->is_activated();
}
But in this way, every pages are affected and Auth->allow() not working anymore. Can anybody show me a better way for my is_activated() function, i guess that this way i redirect the web is not a good way.

What you are looking for is isAuthorized() function:
public function isAuthorized($user){
if($user->activated){
return true;
}
return false;
}
Put it in your AppController, you can also override it in your other controllers. If present, it will be automatically called.
Further reading:
https://book.cakephp.org/3.0/en/tutorials-and-examples/blog-auth-example/auth.html#authorization-who-s-allowed-to-access-what
https://book.cakephp.org/3.0/en/controllers/components/authentication.html#authorization

Related

Get request for the policies in controller constructor

I'm setting up policies for my laravel application, and I'm stuck with a problem. I have to put the policy in the constructor of my controller this way:
public function __construct()
{
$this->middleware(['can:viewAny,App\Models\Photo'], ['only' => ['index']]);
$this->middleware(['can:view,photo'], ['only' => ['show']]);
}
Problem is, for the store action, I have to check one of the params sent in the request to check if the user is allowed to post on the related parent. According to the documentation, I could make my Policy this way:
public function store(User $user, int $parentId)
{
$parent = Parent::find($parentId);
return $user->id === $parent->user_id
}
And in the controller:
public function store(Request $request)
{
$this->authorize('store', [$request->parent]);
// The current user can store the photo...
}
But in the example, the authorization is put in the function, and there are no example with the usage of the request when treating the policy as a middleware. Is it even possible? I would have crafted something like:
$this->middleware(['can:store,App\Models\Photo,request->parent'], ['only' => ['store']]);
But that won't work. Thanks a lot if you can help me on this one!
I found how to do it, I forgot about the request() helper. Thus, I can access everything put in the request, and I can call the helper directly inside the policy.
So I can do this in the contructor:
$this->middleware(['can:store,App\Models\Photo'], ['only' => ['store']]);
And in the PhotoPolicy:
public function store(User $user)
{
$input = request()->input();
$parent = Parent::find($input['parent_id']);
return $user->id === $parent->user_id
}

how to block a user for some blade unsing laravel 6?

I created a small site by laravel 6, with the four blade index, create, edit, show and an authentication system, I want everyone to see the blades index and show, and the blades create and edit prohibit that if user authenticate.
TinghirsController.php
public function __construct() {
$this->middleware('auth');
}
public function index()
{
$tinghirs=Tinghir::orderBy('created_at','desc')->paginate(30);
return view('tinghirs.index', ['tinghirs' => $tinghirs]);
}
public function create()
{
return view('tinghirs.create');
}
public function show($id){
$tinghirs = Tinghir::where('id',$id)->firstOrfail();
return view('tinghirs.show', ['tinghirs' => $tinghirs]);
}
public function edit($id) {
$tinghir = Tinghir::find($id);
return view('tinghirs.edit', ['tinghir' => $tinghir]);
}
Route/web.php
Route::resource('tinghirs','TinghirsController');
As per the documentation, you can specify which controller methods you want to apply a piece of middleware to. In you're case you want to apply the auth middleware to all methods except index and show.
To achieve change the middleware call in your __constructor method to be:
$this->middleware('auth')->except('index', 'show');

Laravel resource policy always false

I'm trying to allow user to view their own profile in Laravel 5.4.
UserPolicy.php
public function view(User $authUser, $user)
{
return true;
}
registered policy in AuthServiceProvider.php
protected $policies = [
App\Task::class => App\Policies\TaskPolicy::class,
App\User::class => App\Policies\UserPolicy::class
];
Routes
Route::group(['middleware' => 'auth'], function() {
Route::resource('user', 'UserController');
} );
Blade template
#can ( 'view', $user )
// yes
#else
// no
#endcan
UserController.php
public function profile()
{
return $this->show(Auth::user()->id);
}
public function show($id)
{
$user = User::find($id);
return view('user.show', array( 'user'=>$user,'data'=>$this->data ) );
}
The return is always 'false'. Same for calling policy form the controller. Where do I go wrong?
Answering my own question feels weird, but I hate it when I come across questions without followups.
So after double checking It turned out that if I remove authorizeResource from the constructor:
public function __construct()
{
$this->authorizeResource(User::class);
}
and check for authorization in the controller function:
$this->authorize('view',$user);
everything works.
I must've missed this part when I added $user as a parameter in the policy function. So the user to be viewed is never passed in the authorizeResource method.
Thanks everyone for taking your time to help me.
When you add
public function __construct()
{
$this->authorizeResource(User::class);
}
to your Controller, you have to edit all your function signatures to match it to the class e.g. your show signature has to change from public function show($id)
to public function show(User $user)
After that it should work
Just a different approach here to users viewing their own profile.
First, I will create a route for that
Route::group(['middleware' => 'auth'], function() {
Route::get('profile', 'UserController#profile');
});
Then in the profile function I do
public function profile()
{
$user = Auth::user();
return view('profile', compact('user'));
}
This way, user automatically only views their own profile.
Now, if you want to allow some users to view others' profiles, then you can use Policy. Why? Because I think user should ALWAYS be able to view their own profile. But not all users should view other users profiles.
Solution:
Change the second parameter from #can( 'view', $user ) to #can( 'view', $subject ) and it will work find.
Why:
Because you're doing it the wrong way.
public function view(User $user, $subject){
return true;
}
Just look carefully the policy view method, first parameter is authenticated user or current user and second parameter is $subject, Since policies organize authorization logic around models.
Policies are classes that organize authorization logic around a
particular model or resource. For example, if your application is a
blog, you may have a Post model and a corresponding PostPolicy to
authorize user actions such as creating or updating posts.
if you want to go further deep inside it.
https://github.com/laravel/framework/blob/5.3/src/Illuminate/Auth/Access/Gate.php#L353
/**
* Resolve the callback for a policy check.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param string $ability
* #param array $arguments
* #return callable
*/
protected function resolvePolicyCallback($user, $ability, array $arguments)
{
return function () use ($user, $ability, $arguments) {
$instance = $this->getPolicyFor($arguments[0]);
// If we receive a non-null result from the before method, we will return it
// as the final result. This will allow developers to override the checks
// in the policy to return a result for all rules defined in the class.
if (method_exists($instance, 'before')) {
if (! is_null($result = $instance->before($user, $ability, ...$arguments))) {
return $result;
}
}
if (strpos($ability, '-') !== false) {
$ability = Str::camel($ability);
}
// If the first argument is a string, that means they are passing a class name
// to the policy. We will remove the first argument from this argument list
// because the policy already knows what type of models it can authorize.
if (isset($arguments[0]) && is_string($arguments[0])) {
array_shift($arguments);
}
if (! is_callable([$instance, $ability])) {
return false;
}
return $instance->{$ability}($user, ...$arguments);
};
}
See the last line where it is calling the method with $user and $argument( in our case Model ) is passed.
Laravel Docs for Authorization/Policies
It's possible to escape one or more policies methods using options parameter at authorizeResource with except:
public function __construct()
{
$this->authorizeResource(User::class, 'user', ['except' => ['view']]);
}
This should be on Laravel's documentation, but it isn't. I discovered this just guessing. I think this way it is a better approach thus, by removing authorizeResource method in the construct, it would be necessary to implement the authorization method for each resource action in order to protect the controller.

How to access one controller action inside another controller action?

I am using cakephp-2.x. I have one function name user_info() in the UsersController.php i want to access this in another controller name MessagesController.php
Code -
UsersController.php
public function user_info(){
$user_id=$this->Session->read('Auth.User.id');
$data=$this->User->findById($user_id);
$this->set('user_info',$data);
}
MessagesController.php
public function index(){
//$userInfo=new UsersController();
//$userInfo->user_info();
$this->user_info();
pr($data);
}
Error Message-
Fatal Error
Error: Call to undefined method MessagesController::user_info()
File: E:\xampp\htdocs\2014\myshowcam\msc\app\Controller\MessagesController.php
Line: 18
Notice: If you want to customize this error message, create app\View\Errors\fatal_error.ctp
Typically if you're trying to access a function in one controller from another controller you have a fundamental flaw in your project's logic.
But in general object usage is thus:
$otherController = new whateverMyControllerNameIs();
$otherController->functionName();
However I'm not familiar enough with cake to tell you the pitfalls of doing such a thing. For example I have no idea what this would do to routes or what other variables/objects are required to initialize a controller correctly.
EDIT:
Ref: CakePHP 2.3.8: Calling Another Controller function in CronController.php
App::import('Controller', 'Products'); // mention at top
// Instantiation // mention within cron function
$Products = new ProductsController;
// Call a method from
$Products->ControllerFunction();
Try requestAction function of cakephp
$result = $this->requestAction(array('controller' => 'users', 'action' => 'user_info'));
Why would a simple, When can complicated?
All the information for a registered user of User model is accessible in the following manner:
AppController.php
public $user_info; /* global scope */
public function beforeFilter(){
$this->user_info = $this->Auth->user(); // for access user data in any controller
$this->set('user_info_view',$this->Auth->user()); // for access user data in any view or layout
}
MessagesController.php
public function index(){
debug($this->user_info);
$my_messages = $this->Message->find('all',
array('conditions' => array('Message.user_id' => $this->user_info['id']))
}
....
layout or view.ctp
<?php echo $user_info_view['name']; ?> // email, etc
Why not take advantage of the way CakePHP handles relationships? There's a very easy way to achieve what you're trying to do without extending controllers or loading in additional controllers which seems excessive for your example.
Inside AppController's beforeFilter()
Configure::write('UserId', $this->Session->read('Auth.User.id'));
This will allow you to access the UserID from your models
Inside your User's model, create the following function
/**
* Sample query which can be expanded upon, adding fields or contains.
*
* #return array The user data if found
*/
public function findByUserId() {
$user = $this->find('first', array(
'conditions' => array(
'User.id' => Configure::read('UserId')
)
));
return $user;
}
Inside your Users controller (Minimal is better, no?)
public function user_info() {
$this->set('user', $this->User->findByUserId());
}
Inside your Messages controller
public function index() {
$this->set('user', $this->Message->User->findByUserId());
// --- Some more stuff here ---
}
And that's it, no need to be extending controllers, just make sure your Message and User model are related to each other, failing that you can bindModel or use ClassRegistry::init('User')-> for example.

CakePHP - BaseAuthenticate altering loginRedirect

I have a number of authentication components that extend the BaseAuthenticate class. These are setup in the AppController in the normal way.
Is it possible for an authentication component to alter the AuthComponent's loginRedirect variable?
To clarify the situation, one of my authentication components looks at a certain subset of users. It checks to see if the credentials are valid before checking to see if that person has any outstanding invoices. Depending on the outstanding value, I'd like to redirect the user to a given page or block them out altogether.
Thanks
Yes it's possible. The AuthComponent's redirect location is just a session variable (so technically it can be set anywhere).
To change the redirect location, you can set it manually:
$this->Session->write('Auth.redirect', 'http://example.com');
On the next request, they will be redirected by the AuthComponent.
Or, have your component redirect them then and there:
$this->_Collection->getController()->redirect('http://example.com');
A big thanks to #jerermyharris for pushing me in the right direction. Here goes with what I ended up doing.
1. Extended the AuthComponent
App::uses('AuthComponent', 'Controller/Component');
class MyAuthComponent extends AuthComponent {
var $components = array('Session');
public function identify(CakeRequest $request, CakeResponse $response) {
if (empty($this->_authenticateObjects)) {
$this->constructAuthenticate();
}
foreach ($this->_authenticateObjects as $auth) {
$result = $auth->authenticate($request, $response);
if (!empty($result) && is_array($result)) {
if(isset($result['Redirect']))
{
$this->Session->write('Auth.redirect', $result['Redirect']);
}
return $result;
}
}
return false;
}
}
2. Add this the AppController components
public $components = array(
'Auth' => array(
'className' => 'MyAuth',
)
);
Add this bit around whatever other definitions you have for your AuthComponent.
3. Return a redirect from your authentication component
App::uses('BaseAuthenticate', 'Controller/Component/Auth');
class TutorAuthenticate extends BaseAuthenticate {
public function authenticate(CakeRequest $request, CakeResponse $response) {
$user = ...... // However you authenticate your user
$user['Redirect'] = "http://example.com";
return $user;
}
}
So now if you want to redirect based on the user you can just add it in, if you don't then cake will obey the directives you set up in AppController.
Wow, that seems like I've had to do loads extra, but it's the right thing to do.

Categories