Middleware support in nested URLRouter or consumers
sourabhv opened this issue · comments
While working on auth with django channels today, I stumbled upon a problem where there is no straight forward way of adding auth for nested router or per-consumer level router
Django rest framework provides a way to do this via a class member of the targetter class/view/consumer in this case. So after reading a few SO questions online and a few parts of codebase, I wrote a middleware something like this, which allows a global and per-consumer level auth classes.
class MultiAuthMiddleware:
def __init__(
self,
app,
*,
default_auth_handler: Callable[[dict], bool] = None
):
self.app = app
self.default_auth_handler = default_auth_handler
def _get_matching_callback(self, routes, path):
path = path.lstrip("/")
for route in routes:
match = route_pattern_match(route, path)
if not match:
continue
if isinstance(route.callback, URLRouter):
new_path, args, kwargs = match
new_routes = route.callback.routes
return self._get_matching_callback(new_routes, new_path)
else:
return route.callback
def _get_auth_handler_from_callback(self, callback):
if callback is None:
return self.default_auth_handler
return getattr(
callback.consumer_class,
'auth_handler',
self.default_auth_handler
)
async def __call__(self, scope, receive, send):
path = scope['path']
callback = self._get_matching_callback(self.app.routes, path)
auth_handler = self._get_auth_handler_from_callback(callback)
if auth_handler is None:
# No auth required, so just pass through
await self.app(scope, receive, send)
is_authenticated, scope = await auth_handler.authenticate(scope)
if not is_authenticated:
# auth failed, deny the connection
denier = WebsocketDenier()
return await denier(scope, receive, send)
# auth succeeded, pass through
return await self.app(scope, receive, send)
Now this is clearly something I wrote with what I could understand would be the right pattern but I can immediately identify some anti-patterns when compared to rest of your codebase. But this allowed me to solve my problem
So my proposal is to either (in order of preference)
- Somehow allow adding middlewares inside each consumer or in any top level or nested URLRouter. Now what I have used as
auth_handler
is not exactly a middleware (it does not get app instance in init and does not return a new asgi app as callable) but that should be doable - Add this functionality somehow to URLRouter (not sure how that'll work with existing Auth stack middleware)
- Add a middleware in package which supports this
Also, if there are some immediate bugs or improvements anyone can see in the above code, do let me know