Skip to content

Commit

Permalink
Authorization checks in server.py (#5386)
Browse files Browse the repository at this point in the history
  • Loading branch information
ndmlny-qs authored Aug 21, 2023
1 parent 68f0d93 commit b6d6b34
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 10 deletions.
26 changes: 26 additions & 0 deletions doc/how_to/authentication/user_info.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,29 @@ The auth template must be a valid Jinja2 template and accepts a number of argume
- `{{ error_type }}`: The type of error.
- `{{ error }}`: A short description of the error.
- `{{ error_msg }}`: A full description of the error.

The `authorization_callback` may also contain a second parameter, which is set by the
requested application path. You can use this extra parameter to check if a user is
authenticated _and_ has access to the application at the given path.

```python
from urllib import parse
import panel as pn

authorized_user_paths = {
"user1": ["/app1", "/app2"],
"user2": ["/app1"],
}

def authorize(user_info, request_path):
current_user = user_info['username']
current_path = parse.urlparse(request_path).path
if current_user not in authorized_user_paths:
return False
current_user_paths = authorized_user_paths[current_user]
if current_path in current_user_paths:
return True
return False

pn.config.authorize_callback = authorize
```
6 changes: 5 additions & 1 deletion panel/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ class _config(_base_config):
is enabled. The callback is given the user information returned
by the configured Auth provider and should return True or False
depending on whether the user is authorized to access the
application.""")
application. The callback may also contain a second parameter,
which is the requested path the user is making. If the user
is authenticated and has explicit access to the path, then
the callback should return True otherwise it should return
False.""")

auth_template = param.Path(default=None, doc="""
A jinja2 template rendered when the authorize_callback determines
Expand Down
39 changes: 30 additions & 9 deletions panel/io/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import gc
import html
import importlib
import inspect
import logging
import os
import pathlib
Expand Down Expand Up @@ -503,7 +504,34 @@ async def get(self, *args, **kwargs):
token = session.token
logger.info(LOG_SESSION_CREATED, id(session.document))
with set_curdoc(session.document):
if config.authorize_callback and not config.authorize_callback(state.user_info):
resources = Resources.from_bokeh(self.application.resources())
auth_cb = config.authorize_callback
if auth_cb:
auth_cb = config.authorize_callback
auth_params = inspect.signature(auth_cb).parameters
auth_args = (state.user_info,)
if len(auth_params) == 2:
auth_args += (self.request.path,)
else:
raise RuntimeError(
'Authorization callback must accept either one or two arguments.'
)
auth_error = f'{state.user} is not authorized to access this application.'
try:
authorized = auth_cb(*auth_args)
auth_error = None
except Exception:
auth_error = f'Authorization callback errored. Could not validate user {state.user}'
else:
authorized = True

if authorized:
page = server_html_page_for_session(
session, resources=resources, title=session.document.title,
token=token, template=session.document.template,
template_variables=session.document.template_variables,
)
else:
if config.auth_template:
with open(config.auth_template) as f:
template = _env.from_string(f.read())
Expand All @@ -514,14 +542,7 @@ async def get(self, *args, **kwargs):
title='Panel: Authorization Error',
error_type='Authorization Error',
error='User is not authorized.',
error_msg=f'{state.user} is not authorized to access this application.'
)
else:
resources = Resources.from_bokeh(self.application.resources())
page = server_html_page_for_session(
session, resources=resources, title=session.document.title,
token=token, template=session.document.template,
template_variables=session.document.template_variables,
error_msg=auth_error
)
self.set_header("Content-Type", 'text/html')
self.write(page)
Expand Down

0 comments on commit b6d6b34

Please sign in to comment.