feat: HTTP basic authentication
This is performed via two configuration settings. Note however that we don't yet support automatic reloading of credentials. This is unfortunate because there is no way to reload tutor Deck...
This commit is contained in:
parent
b60655ed54
commit
8002da7a66
@ -73,6 +73,11 @@ Restart platform via GUI to apply changes:
|
|||||||
.. image:: https://github.com/overhangio/tutor-deck/raw/release/images/apply.png
|
.. image:: https://github.com/overhangio/tutor-deck/raw/release/images/apply.png
|
||||||
:alt: Apply Image
|
:alt: Apply Image
|
||||||
|
|
||||||
|
You may add HTTP basic authentication by defining the following Tutor settings::
|
||||||
|
|
||||||
|
tutor config save --set DECK_AUTH_USERNAME=myusername \
|
||||||
|
--set DECK_AUTH_PASSWORD=s3cr3tpassw0rd
|
||||||
|
|
||||||
Troubleshooting
|
Troubleshooting
|
||||||
***************
|
***************
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,8 @@ from .server import app
|
|||||||
hooks.Filters.CONFIG_DEFAULTS.add_items(
|
hooks.Filters.CONFIG_DEFAULTS.add_items(
|
||||||
[
|
[
|
||||||
("DECK_VERSION", __version__),
|
("DECK_VERSION", __version__),
|
||||||
|
("DECK_AUTH_USERNAME", None),
|
||||||
|
("DECK_AUTH_PASSWORD", None),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -46,12 +46,66 @@ def run(root: str, **app_kwargs: t.Any) -> None:
|
|||||||
tutorclient.logger.addHandler(handler)
|
tutorclient.logger.addHandler(handler)
|
||||||
tutorclient.logger.setLevel(logging.INFO)
|
tutorclient.logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# Configure authentication
|
||||||
|
HttpAuthCredentials.load_credentials()
|
||||||
|
|
||||||
# TODO app.run() should be called only in development
|
# TODO app.run() should be called only in development
|
||||||
app.run(**app_kwargs)
|
app.run(**app_kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class HttpAuthCredentials:
|
||||||
|
USERNAME: str = ""
|
||||||
|
PASSWORD: str = ""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_credentials(cls) -> None:
|
||||||
|
"""
|
||||||
|
Note that credentials will not be automatically reloaded on configuration change.
|
||||||
|
|
||||||
|
TODO reload credentials automatically when needed.
|
||||||
|
"""
|
||||||
|
config = tutorclient.Project.get_config()
|
||||||
|
cls.USERNAME = t.cast(str, config.get("DECK_AUTH_USERNAME", ""))
|
||||||
|
cls.PASSWORD = t.cast(str, config.get("DECK_AUTH_PASSWORD", ""))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_auth_success(cls) -> bool:
|
||||||
|
"""
|
||||||
|
Returns True if the current request has the right HTTP basic auth credentials.
|
||||||
|
"""
|
||||||
|
if not cls.USERNAME or not cls.PASSWORD:
|
||||||
|
# No credential required
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not request.authorization:
|
||||||
|
# No credential was provided
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check provided credentials
|
||||||
|
username = request.authorization.parameters.get("username")
|
||||||
|
password = request.authorization.parameters.get("password")
|
||||||
|
return username == cls.USERNAME and password == cls.PASSWORD
|
||||||
|
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def http_basic_auth() -> None | tuple[str, int, dict[str, str]]:
|
||||||
|
"""
|
||||||
|
Check authentication headers if necessary.
|
||||||
|
"""
|
||||||
|
if not HttpAuthCredentials.is_auth_success():
|
||||||
|
# https://quart.palletsprojects.com/en/latest/reference/response_values/#tuple-str-int-dict-str-str
|
||||||
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/401
|
||||||
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Authentication#authentication_schemes
|
||||||
|
return "", 401, {"WWW-Authenticate": "basic"}
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
async def before_request() -> None:
|
async def before_request() -> None:
|
||||||
|
"""
|
||||||
|
Store installed and enabled plugins as global attributes.
|
||||||
|
"""
|
||||||
# Shared views and template context
|
# Shared views and template context
|
||||||
g.installed_plugins = tutorclient.Client.installed_plugins()
|
g.installed_plugins = tutorclient.Client.installed_plugins()
|
||||||
g.enabled_plugins = tutorclient.Client.enabled_plugins()
|
g.enabled_plugins = tutorclient.Client.enabled_plugins()
|
||||||
@ -222,7 +276,7 @@ async def plugin_upgrade(name: str) -> BaseResponse:
|
|||||||
|
|
||||||
|
|
||||||
@app.post("/plugins/update")
|
@app.post("/plugins/update")
|
||||||
async def plugins_update() -> WerkzeugResponse:
|
async def plugins_update() -> BaseResponse:
|
||||||
tutorclient.CliPool.run_sequential(["plugins", "update"])
|
tutorclient.CliPool.run_sequential(["plugins", "update"])
|
||||||
return redirect(url_for("plugin_store"))
|
return redirect(url_for("plugin_store"))
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user