refactor(router): gate a route with a permission instead of an if around it
A route was declared or not declared depending on who was asking: nine routes.php files took
the current user as an argument and wrapped their staff routes in 'if ($user->rights >= 9)'.
The route is declared once now and says what it needs — ->permission('news.manage') — while
RequirePermissionMiddleware, which the kernel puts in front of any route carrying the
attribute, decides who gets through.
The permission is an attribute of the route rather than a middleware of its own: middlewares
are resolved by class name and live as long as the process, so they cannot take 'which
permission' in a constructor, and one class per permission would be a hundred classes saying
the same thing. Same mechanism Route::module() and Route::withoutCsrf() already use. A group
can name a permission for all of its routes; one naming its own keeps it.
404 becomes 403, on purpose. A route that does not exist and a route that is not yours are
different things, and the second one is debuggable: the addresses of the admin panel are
known anyway. A guest is redirected to sign in instead, since for them it is usually the
session that is missing. The rare route whose very existence is the secret asks for
->permission('...', hidden: true) and answers 404 as before.
The branches that only asked for a session — the file uploads of the forum and the guestbook,
the writing half of the forum — became RequireAuthMiddleware, which takes no parameter and
is therefore an ordinary middleware. The forum ones are listed and given the middleware in a
loop rather than put in a nested group: a nested group compiles after the '/forum/{sectionPath}'
catch-all of its parent and would never be matched.
What this buys beyond uniformity: the route collection no longer depends on the visitor, so
RouteCollectorFactory asks the container for nothing, the collection can be dumped and
cached, and routing no longer has to identify the visitor first. Three new permissions come
with it — admin.system_check, news.manage and collections.manage — plus news.comments.post,
which is what the picture upload of a comment is worth and what a ban takes away.
The signature of routes.php is a breaking change for third-party modules; it lands in the
same 10.0 break as the removal of the number, and docs/ has the section for module authors.
An installation that has already run auth:apply-default-permissions needs it once more with
--force.
the current user as an argument and wrapped their staff routes in 'if ($user->rights >= 9)'.
The route is declared once now and says what it needs — ->permission('news.manage') — while
RequirePermissionMiddleware, which the kernel puts in front of any route carrying the
attribute, decides who gets through.
The permission is an attribute of the route rather than a middleware of its own: middlewares
are resolved by class name and live as long as the process, so they cannot take 'which
permission' in a constructor, and one class per permission would be a hundred classes saying
the same thing. Same mechanism Route::module() and Route::withoutCsrf() already use. A group
can name a permission for all of its routes; one naming its own keeps it.
404 becomes 403, on purpose. A route that does not exist and a route that is not yours are
different things, and the second one is debuggable: the addresses of the admin panel are
known anyway. A guest is redirected to sign in instead, since for them it is usually the
session that is missing. The rare route whose very existence is the secret asks for
->permission('...', hidden: true) and answers 404 as before.
The branches that only asked for a session — the file uploads of the forum and the guestbook,
the writing half of the forum — became RequireAuthMiddleware, which takes no parameter and
is therefore an ordinary middleware. The forum ones are listed and given the middleware in a
loop rather than put in a nested group: a nested group compiles after the '/forum/{sectionPath}'
catch-all of its parent and would never be matched.
What this buys beyond uniformity: the route collection no longer depends on the visitor, so
RouteCollectorFactory asks the container for nothing, the collection can be dumped and
cached, and routing no longer has to identify the visitor first. Three new permissions come
with it — admin.system_check, news.manage and collections.manage — plus news.comments.post,
which is what the picture upload of a comment is worth and what a ban takes away.
The signature of routes.php is a breaking change for third-party modules; it lands in the
same 10.0 break as the removal of the number, and docs/ has the section for module authors.
An installation that has already run auth:apply-default-permissions needs it once more with
--force.