2
votes

I currently have two route groups where one route group has six routes and the other has two routes (that are also in the previous group).

/**
 * Foo Routes for admin
 */
Route::group(['middleware' => 'bar:admin'], function () {
    Route::put('foo/{uuid}/publish', 'FooController@publish');
    Route::put('foo/{uuid}/disable', 'FooController@disable');
    Route::put('foo/{uuid}/enable', 'FooController@enable');
    Route::delete('foo/{uuid}', 'FooController@destroy');
    Route::post('foo', 'FooController@store');
    Route::put('foo/{uuid}', 'FooController@update');
});

/**
 * Foo Routes for creator
 */
Route::group(['middleware' => 'bar:creator'], function () {
    Route::post('foo', 'FooController@store');
    Route::put('foo/{uuid}', 'FooController@update');
});

The reason for this split is because the creator needs access to two of the routes from the admin group, but admin needs permission to all the routes. Access is given via the middleware bar.

However, whenever I am an admin and I try to access one of the two routes available in the second route group, my bar class denies its request. It says that I must be a creator to access the route. Does this mean that routes have a cascading behaviour where the last instance of a route group is the one laravel uses? If it does, how can I format my routes to avoid this issue?

bar code:

public function handle($request, \Closure $next, ...$permissionRules)
{
    .
    .
    .

    $userPermissions = $decodedToken['user']['permissions'];

    // If the user does not have every permission defined via route parameters, deny.
    foreach ($permissionRules as $permissions) {
        if (!in_array($permissions, $userPermissions)) {
            return $this->denyResponse();
        }
    }

    // The user has every permission rule defined via route parameters, so allow.
    return $next($request);
}
1
"Does this mean that routes have a cascading behaviour where the last instance of a route group is the one laravel uses?" -- Yep, although slight correction: the last instance of an individual route will always take precedence.Aken Roberts

1 Answers

2
votes

The proper way to do this would be to customize the middleware you are using (bar) to accept multiple permissions/roles.

An easy way to do this would be to pass a comma-delimited list of acceptable permissions, convert it to an array in then check to see if the Auth user has the passed permissions.

To use the code you gave us originally, here is a way to implement:

First, create a new Route Group for the group of permissions:

/**
 * Foo Routes for admin
 */
Route::group(['middleware' => 'bar:admin'], function () {
    Route::put('foo/{uuid}/publish', 'FooController@publish');
    Route::put('foo/{uuid}/disable', 'FooController@disable');
    Route::put('foo/{uuid}/enable', 'FooController@enable');
    Route::delete('foo/{uuid}', 'FooController@destroy');
});

/**
 * Foo Routes for creator
 */
Route::group(['middleware' => 'bar:creator'], function () {
    // Other Routes available only to Creator permission users
});

/**
 * Foo Routes for creator & admin
 */
Route::group(['middleware' => 'bar:creator,admin'], function () {
    Route::post('foo', 'FooController@store');
    Route::put('foo/{uuid}', 'FooController@update');
});

Second, update bar middleware to convert the comma-delimited string to an array

public function handle($request, \Closure $next, ...$permissionRules)
{
    .
    .
    .

    $permissionRules = explode(',', $permissionRules);

    $userPermissions = $decodedToken['user']['permissions']; //Assuming this is an array of the Auth'ed user permissions. 

    // If the user does not have every permission defined via route 
parameters, deny.
    foreach ($permissionRules as $permission) {
        if (in_array($permission, $userPermissions)) {
            // Change this to see if the permission is in the array, opposed to NOT in the array
            return $next($request);
        }
    }

    // Made it so that if the permission is NOT found in the array then Deny
    return $this->denyResponse();
}

This should be all you need. Hope this helps!