'CakePHP 4 RequestAuthorizationMiddleware - how to dynamically redirect unauthorized users

I have implemented the new Authentication, Authorization and RequestAuthorization middlewares in CakePHP 4.0. I would like to redirect the unauthorized requests (from the authenticated users) to various pages depending on several conditions like their role, status etc. Currently, all the unauthorized requests are redirected to my unauthorized redirect url - Pages/Permission page. Is there a way to dynamically change the unauthorized redirect url from the RequestPolicy/canAccess function?

My RequestPolicy/canAccess function look like this(Edited):

public function canAccess($identity, ServerRequest $request)
{
    $unauthenticatedActions = $request->getAttribute('authentication')->getConfig('unauthenticatedActions');
    if (in_array($request->getParam('action'), $unauthenticatedActions, true)) { 
        return true;
    }
    else if(!empty($identity)){
        //check based on actions and user roles

        $userRole = $identity->role;
        $userStatus = $identity->status;
        $accountType = $identity->accountsubType;
        $action = $request->getParam('action');
        $controller = $request->getParam('controller');

        switch ($controller) {

            case 'Pages':
                return true;
                break;

            case 'Users':
                    if($userRole === 'ADMIN' && $userStatus === 'ACTIVE'){
                        return true;
                    }elseif($action === 'aaa'){
                        if($userRole === 'xxx' && $userStatus === 'ACTIVE'){
                            return true;
                        }
                        elseif($userRole === 'xxx' && $userStatus === 'EXPIRED'){
                            //redirect to custom 'suspended access' page
                            return false;
                        }
                        elseif($userRole === 'yyy' && $accountType === 'c'){
                            //redirect to custom 'restricted access' page 
                            return false;
                        }
                        else{
                            return false;
                        }
                    }
                    ...
                    return false;
            case: "Sales":   
            ...
            default:
                //none of the Controller names matches
                return false; //redirects to the default unauthorized redirect url (pages/permission)
                //here if I return true, I get the MissingControllerException, MissingActionException etc as expected
    }else{
        //force them back to the login page
        $request =  $request
                    ->withParam('controller', 'users')
                    ->withParam('action', 'login');
        return true; 
    }
}

src/Application/middleware()

$middlewareQueue
            ....
            ->add(new AuthenticationMiddleware($this))
            ->add(new AuthorizationMiddleware($this, [
                    'requireAuthorizationCheck' => true,
                    'unauthorizedHandler' => [
                        'className' => 'CustomRedirect',
                        'url' => '/pages/permission',
                        'exceptions' => [
                            'MissingIdentityException' => 'Authorization\Exception\MissingIdentityException',
                            'ForbiddenException' => 'Authorization\Exception\ForbiddenException'
                        ],
                    ],
                ]))
            ->add(new RequestAuthorizationMiddleware());


Solution 1:[1]

No, a Policy cannot do that, that's not its concern, all it is supposed to do, is to answer the question whether the given identity is allowed to access the given resource.

If you want custom redirects, then you should look into implementing a custom "unauthorized handler", where you can check the identity and figure the redirect URL. For example extend the built-in redirect handler, and overwrite its URL generation method, something along the lines of this:

// in src/Middleware/UnauthorizedHandler/IdentityRedirectHandler.php

namespace App\Middleware\UnauthorizedHandler;

use Authorization\Middleware\UnauthorizedHandler\RedirectHandler;
use Psr\Http\Message\ServerRequestInterface;

class IdentityRedirectHandler extends RedirectHandler
{
    protected function getUrl(ServerRequestInterface $request, array $options): string
    {
        $identity = $request->getAttribute('identity');
        
        $role = $identity->get('role');
        $status = $identity->get('status');
        
        if ($role === 'xxx' && $status === 'b') {
            $url = '/suspended';
        } elseif ($role === 'yyy' && $status === 'c') {
            $url = '/restricted';
        } else {
            // default URL from middleware config
            $url = $options['url'];
        }

        $options['url'] = $url;
        
        return parent::getUrl($request, $options);
    }
}
// in src/Applicatiom.php

new AuthorizationMiddleware($this, [
    'unauthorizedHandler' => [
        'className' => 'IdentityRedirect',
        'url' => '/default-redirect-url',
        // ...
    ],
])

See also

Solution 2:[2]

You can try enumerating the x and then use the index in a format string, check the code below.

df['sequence'] = (df.groupby(["event_name", "batfast_id", "session_no", "overs"])["length/type"]
                   .apply(lambda x: ','.join(f'{val}_{i}' for i, val in enumerate(x)))
               )

Solution 3:[3]

Use enumerate:

out = df.groupby(level=df.index.names[:-1])['length/type'] \
        .apply(lambda x: ','.join(f"{v}{i}" for i, v in enumerate(x.tolist(), 1)))
print(out)

# Output:
                                                        length/type
event_name batfast_id session_no overs deliveries_faced            
fulham     bfs1       1          0     0                    ES_LS_Y
                                       1                    ES_LS_Y
                                       2                      S_S_Y
                                       3                    ES_OS_Y
                                       4                    ES_LS_Y
                                       5                    ES_LS_Y

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 ndm
Solution 2 Siddhartha Pramanik
Solution 3 Corralien