Welcome Guest, Not a member yet? Register   Sign In
AdminController - how to redirect in __construct or initController function?
#1

I'm migrating an extensive site to CI4. I organize my controllers thusly:
* public ones that just extend BaseController
* ones for that require authenticated users which extend UserController
* admin-only pages that extend AdminController, which extends UserController and does extra permission checks on a db table.

The old CI3 redirect() function was great because you could call it anywhere. Sadly, the new approach does not work in either my __construct function or in initController function:

PHP Code:
class AdminController extends BaseController
{
    function __construct()
    {
        
// CI4's controller class doesn't have a constructor, but ours currently
        // has a trivial one as a stub
        
parent::__construct();
    }

    public function 
initController(\CodeIgniter\HTTP\RequestInterface $request, \CodeIgniter\HTTP\ResponseInterface $response, \Psr\Log\LoggerInterface $logger)
    {
        
// Do Not Edit This Line
        
parent::initController($request$response$logger);
        
        
// if the user is not logged in, remember what page they requested and redirect them to the login page
        // once they log in, they will be redirected back to the requested url
        
if (!$this->user_logged_in()) {
            
$_SESSION[self::SESSION_KEY_LOGIN_REDIRECT_URL] = $_SERVER["REQUEST_URI"];

            
// THIS DOES NOT WORK
            
return redirect()->to("/login");
        }

        
// this code never even runs if the user isn't logged in
        
if (!$this->has_permission("VIEW_ADMIN_MENU")) {
            
show_error("You are not permitted to view this page"403);
        }
        
    
// initController()
    
// class AdminController 

What is the recommended best practice if I want to redirect from inside a __construct or initController function? Someone suggested setting up filters or some kind but that isn't going to work for me.
Reply
#2

(This post was last modified: 01-21-2021, 08:14 PM by iRedds.)

Even if there is a return operator in the class constructor, it will not return anything.
No return is expected when the initController method is called.

In my humble opinion you need to use filters. If you cannot use the code that you use in the controller in filters, then your application has design problems.
But if you really need to redirect from the class constructor, you can throw an exception

PHP Code:
throw new RedirectException('urlToRedirect'); 
Reply
#3

(01-21-2021, 08:13 PM)iRedds Wrote: Even if there is a return operator in the class constructor, it will not return anything.
No return is expected when the initController method is called.

In my humble opinion you need to use filters. If you cannot use the code that you use in the controller in filters, then your application has design problems.
But if you really need to redirect from the class constructor, you can throw an exception

PHP Code:
throw new RedirectException('urlToRedirect'); 
Thank you for the RedirectException suggestion. Sadly, that doesn't seem to allow one to specify an HTTP response code. It defaults to 302.


Are filters powerful? YES.

Are filters a potent, flexible tool to filter requests and responses? YES.

Are filters "clean" as the kilishan suggests? NO.

I'll elaborate:

That a filter is even running is in no way evident in a controller's source code. Filters might do anything at all and you'd have no idea one would run by looking at the controller. You'd have to examine the app/config/filters.php file, mentally parse its contents, and examine any relevant filter files. You can't shift-click or hit F3 in the controller and instantly navigate to any filters applied to it.

One of the very best features of CodeIgniter is its intuitive mapping between website url paths and the file path of the controller PHP file. Looking at some site url, you can usually find the controller that handles it quickly just by looking in the app\Controllers directory. I'm not a fan of frameworks where one must define routes for every controller or url. I vaguely recall Laravel having this problem. Such centralized files cause trouble when you have multiple developers working. Most of your merge conflicts appear in such files. The filter config file has the same problem. If any developer wants to even apply an existing filter to a new controller, they may have to make changes to the site-wide filter file. These files become bottlenecks.

The structure of the filter config file is peculiar and not intuitive and the requirement that you define aliases and assign them through fairly messy array notation means you have a bunch of string literals, which are not error-checked by your IDE and which are vulnerable to typos. E.g., I might accidentally type 'honyepot' and the IDE wouldn't blink an eye. I might mistype the url to which a filter applies. Or I might fail to notice an 'except' rule which overrides my filter rules. Either of these mistakes could expose some vital controller method to mischief.

Is there some order of precedence that applies? Presumably $globals is processed first, then $methods, then $filters. If I somehow specify a filter in two of these arrays, it can be applied more than once, introducing performance issues and also possibly data corruption if values get translated then translated again.

Additionally, one must also maintain mental strong focus to make sure the rules do what you want them to do. Are my wildcards correct? Did I accidentally overrule my first filter with an except somewhere? What about the return values of the filters themselves? If I return a nonzero value in any of them then all the later filters will not be executed. UNLESS the value returned is either a Response or Request object. The way filters allow return values of empty/non empty/Response/Request is certainly permitted in reflective programming, but IMHO it's rather an abuse of PHP's flexibility and runs counter to the trend toward type hinting and stronger typing.

There are also security concerns with filters. If, for example I establish some vital adminRole alias in my filters and apply it to one controller method like so:
PHP Code:
public $filters = [
    
'foo' => ['before' => ['mycontroller/foo-bar']],
]; 
this definitely runs the filter if I visit this url:
/mycontroller/foo-bar

but the filter doesn't run if i visit these:
/mycontroller/foo-bar/parameter
/mycontroller/foo_bar

I can apply the filter to both of those latter urls with a different rule:
PHP Code:
public $filters = [
    
'foo' => ['before' => ['jtest/foo.bar']],
]; 
however, this complication is not mentioned at all in the documentation, and is likely to result in security vulnerabilities for inexperienced users, much like SQL injection has been a problem for decades.

Given all these considerations, especiall the last one regarding pattern matching, I don't think filters should be considered a 'best practice' for authentication. IMHO, extending the BaseController with some role-checking and simple logic to prevent code form executing has numerous advantages. I also think it's entirely reasonable that a Controller's __construct or initController might want to redirect the request -- they are controllers, after all.

I can easily add a redirect function to my own BaseController, but I think it would be really nice to augment the CodeIgniter\Controller to include a protected method redirect that mimics the old CI3 redirect functionality. I can easily do this myself in my own BaseController, but I think it'd be a convenient feature all around and would ease CI3-to-CI4 migrations like the one I'm currently working on.

I welcome any thoughts others may have.
Reply
#4

(01-29-2021, 05:57 PM)sneakyimp Wrote: Thank you for the RedirectException suggestion. Sadly, that doesn't seem to allow one to specify an HTTP response code. It defaults to 302.
Two ways:
1. Passing the second parameter 
PHP Code:
throw new RedirectException('path'301); 

2. You can create your own class extending RedirectException and define a property with HTTP code
PHP Code:
class MyRedirectException extends RedirectException
{
    protected $code 301;
}

throw new 
MyRedirectException('path'); 


(01-29-2021, 05:57 PM)sneakyimp Wrote: Are filters powerful? YES.

Are filters a potent, flexible tool to filter requests and responses? YES.

Are filters "clean" as the kilishan suggests? NO.

I'll elaborate:

.... 

Thanks. It was interesting to read your opinion.
I didn't work in a team and I can understand your sadness with merge problems.

For example, I'm glad they can manually define routes without autodiscover.
But the framework still has a controller autodetection function like CI3.

If you do not like the current implementation of the filters, you can propose a concept of the idea of their work or the final implementation.

At least I'll be interested to see. Perhaps the maintainers will appreciate your idea.
Reply




Theme © iAndrew 2016 - Forum software by © MyBB