Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DummyProvider for quick password based auth #4684

Merged
merged 7 commits into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added doc/_static/images/basic_auth.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 68 additions & 0 deletions doc/how_to/authentication/basic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Configuring Basic Authentication

For simple uses cases it may be totally sufficient to enable a basic Auth provider, which simply compares the provided login credentials against a master password or credentials stored in a file.

## Setting up basic authentication

Basic authentication can be set up simply by providing the `--basic-auth` commandline argument (or the `PANEL_BASIC_AUTH` environment variable):

```bash
panel serve app.py --basic-auth my_password --cookie-secret my_super_safe_cookie_secret
```

When loading the application you should now see a very simple login form:

![Panel Pyodide App](../../_static/images/basic_auth.png)

In this mode the username is not authenticated and will simply be provided as part of the [user info](user_info.md).

## User credentials

If you want a slightly more complex setup with a number of different users with potentially different access controls you can also provide a path to a file containing user credentials, e.g. let's say we have a file called `credentials.json` containing:

```json
{
"user1": "my_password",
"admin": "my_super_safe_password"
}
```

We can now configure the basic authentication with:

```bash
panel serve app.py --basic-auth credentials.json --cookie-secret my_super_safe_cookie_secret
```

The basic auth provider will now check the provided credentials against the credentials declared in this file.

## Custom templates

If you want to customize the authentication template you can provide a custom template with the `--basic-login-template` CLI argument. The template needs to submit `username` and `password` to the `/login` endpoint of the Panel server, e.g. the form of the default template looks like this:

```html
<form class="login-form" action="/login" method="post">
<div class="form-header">
<h3><img src="https://panel.holoviz.org/_images/logo_stacked.png" width="150" height="120"></h3>
<p> Login to access your application</p>
</div>
<div class="form-group">
<span style="color:rgb(255, 0, 0);font-weight:bold" class="errormessage">{{errormessage}}</span>
</div>
<p></p>
<!--Email Input-->
<div class="form-group">
<input name="username" type="text" class="form-input" autocapitalize="off" autocorrect="off" placeholder="username">
</div>
<!--Password Input-->
<div class="form-group">
<input name="password" type="password" class="form-input" placeholder="password">
</div>
<!--Login Button-->
<div class="form-group">
<button class="form-button" type="submit">Login</button>
</div>
<div><small>
<p><a href="https://panel.holoviz.org/how_to/authentication/index.html">See the documentation</a> for a full discussion.</p>
</small></div>
</form>
```
8 changes: 8 additions & 0 deletions doc/how_to/authentication/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ In other words OAuth outsources authentication to a third party provider, e.g. G
::::{grid} 1 2 2 3
:gutter: 1 1 1 2

:::{grid-item-card} {octicon}`unlock;2.5em;sd-mr-1 sd-animate-grow50` Configuring Basic Authentication
:link: basic
:link-type: doc

Discover how to add basic password based authentication to your application.
:::

:::{grid-item-card} {octicon}`gear;2.5em;sd-mr-1 sd-animate-grow50` Configuring OAuth
:link: configuration
:link-type: doc
Expand Down Expand Up @@ -45,6 +52,7 @@ Note that since Panel is built on Bokeh server and Tornado it is also possible t
:hidden:
:maxdepth: 2

basic
configuration
providers
user_info
Expand Down
97 changes: 97 additions & 0 deletions panel/_templates/basic_login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<!-- Uses the template from https://github.com/bokeh/bokeh/tree/branch-3.2/examples/server/app/server_auth -->
<!DOCTYPE html>
<html>
<head>
<title>Panel App with BasicAuth</title>
<style>
*{
margin:0;
padding: 0;
box-sizing: border-box;
}
html{
height: 100%;
}
body{
font-family: 'Segoe UI', sans-serif;;
font-size: 1rem;
line-height: 1.6;
height: 100%;
}
p{
padding-bottom: 5px;
}
.wrap {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background: #fafafa;
}
.login-form{
width: 350px;
margin: 0 auto;
border: 1px solid #ddd;
padding: 2rem;
background: #ffffff;
}
.form-input{
background: #fafafa;
border: 1px solid #eeeeee;
padding: 12px;
width: 100%;
}
.form-group{
margin-bottom: 1rem;
}
.form-button{
background: #69d2e7;
border: 1px solid #ddd;
color: #ffffff;
padding: 10px;
width: 100%;
text-transform: uppercase;
}
.form-button:hover{
background: #69c8e7;
}
.form-header{
text-align: center;
margin-bottom : 2rem;
}
.form-footer{
text-align: center;
}
</style>
</head>
<body>
<div class="wrap">
<form class="login-form" action="/login" method="post">
<div class="form-header">
<h3><img src="https://panel.holoviz.org/_images/logo_stacked.png" width="150" height="120"></h3>
<p> Login to access your application</p>
</div>
<div class="form-group">
<span style="color:rgb(255, 0, 0);font-weight:bold" class="errormessage">{{errormessage}}</span>
</div>
<p></p>
<!--Email Input-->
<div class="form-group">
<input name="username" type="text" class="form-input" autocapitalize="off" autocorrect="off" placeholder="username">
</div>
<!--Password Input-->
<div class="form-group">
<input name="password" type="password" class="form-input" placeholder="password">
</div>
<!--Login Button-->
<div class="form-group">
<button class="form-button" type="submit">Login</button>
</div>
<div><small>
<p><a href="https://panel.holoviz.org/how_to/authentication/index.html">See the documentation</a> for a full discussion.</p>
</small></div>
</form>
</div>
</body>
</html>
80 changes: 63 additions & 17 deletions panel/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
import urllib.parse as urlparse
import uuid

from typing import List, Tuple, Type

import tornado

from bokeh.server.auth_provider import AuthProvider
Expand All @@ -20,7 +18,7 @@
from .config import config
from .entry_points import entry_points_for
from .io import state
from .io.resources import ERROR_TEMPLATE, _env
from .io.resources import BASIC_LOGIN_TEMPLATE, ERROR_TEMPLATE, _env
from .util import base64url_decode, base64url_encode

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -804,20 +802,6 @@ def get_user(request_handler):
return request_handler.get_secure_cookie("user", max_age_days=config.oauth_expiry)
return get_user

@property
def endpoints(self) -> List[Tuple[str, Type[RequestHandler]]]:
''' URL patterns for login/logout endpoints.

'''
endpoints: List[Tuple[str, Type[RequestHandler]]] = []
if self.login_handler:
assert self.login_url is not None
endpoints.append(('/login', self.login_handler))
if self.logout_handler:
assert self.logout_url is not None
endpoints.append(('/logout', self.logout_handler))
return endpoints

@property
def login_url(self):
if config.oauth_redirect_uri is None:
Expand All @@ -841,6 +825,68 @@ def logout_handler(self):
return LogoutHandler


class BasicLoginHandler(RequestHandler):

def get(self):
try:
errormessage = self.get_argument("error")
except Exception:
errormessage = ""
self.write(self._basic_login_template.render(errormessage=errormessage))

def _validate(self, username, password):
if os.path.isfile(config.basic_auth):
with open(config.basic_auth, encoding='utf-8') as auth_file:
auth_info = json.loads(auth_file.read())
if username not in auth_info:
return False
return password == auth_info[username]
elif password == config.basic_auth:
return True
return False

def post(self):
username = self.get_argument("username", "")
password = self.get_argument("password", "")
auth = self._validate(username, password)
if auth:
self.set_current_user(username)
self.redirect("/")
else:
error_msg = "?error=" + tornado.escape.url_escape("Login incorrect")
self.redirect('/login' + error_msg)

def set_current_user(self, user):
if not user:
self.clear_cookie("user")
return
self.set_secure_cookie("user", user, expires_days=config.oauth_expiry)
id_token = base64url_encode(json.dumps({'user': user}))
if state.encryption:
id_token = state.encryption.encrypt(id_token.encode('utf-8'))
self.set_secure_cookie('id_token', id_token, expires_days=config.oauth_expiry)



class BasicProvider(OAuthProvider):
def __init__(self, basic_login_template=None):
if basic_login_template is None:
self._basic_login_template = BASIC_LOGIN_TEMPLATE
else:
with open(basic_login_template) as f:
self._basic_login_template = _env.from_string(f.read())
super().__init__()

@property
def login_url(self):
return '/login'

@property
def login_handler(self):
BasicLoginHandler._basic_login_template = self._basic_login_template
return BasicLoginHandler


AUTH_PROVIDERS = {
'auth0': Auth0Handler,
'azure': AzureAdLoginHandler,
Expand Down
Loading