Get PrusaSlicer working; fix some auth problems
This commit is contained in:
parent
ab0924d803
commit
d40a20138b
6 changed files with 86 additions and 29 deletions
|
@ -51,8 +51,20 @@ def create_j2_request_global(app):
|
||||||
|
|
||||||
|
|
||||||
def user_session():
|
def user_session():
|
||||||
if (session_id := request.cookies.get("sid", "")) and (
|
if (
|
||||||
uid := ctx.db.try_key(session_id)
|
(
|
||||||
|
(session_id := request.cookies.get("sid", ""))
|
||||||
|
and (uid := ctx.db.try_key(session_id))
|
||||||
|
)
|
||||||
|
or (
|
||||||
|
request.authorization
|
||||||
|
and request.authorization.token
|
||||||
|
and (uid := ctx.db.try_key(request.authorization.token))
|
||||||
|
)
|
||||||
|
or (
|
||||||
|
(api_key := request.headers.get("x-api-key"))
|
||||||
|
and (uid := ctx.db.try_key(api_key))
|
||||||
|
)
|
||||||
):
|
):
|
||||||
ctx.sid = session_id
|
ctx.sid = session_id
|
||||||
ctx.uid = uid
|
ctx.uid = uid
|
||||||
|
|
|
@ -7,29 +7,47 @@ import os
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from flask import Blueprint, current_app, request
|
from flask import Blueprint, current_app, request
|
||||||
from tentacles.blueprints.util import (
|
|
||||||
requires_admin,
|
|
||||||
requires_auth,
|
|
||||||
)
|
|
||||||
from tentacles.globals import ctx
|
from tentacles.globals import ctx
|
||||||
|
|
||||||
|
|
||||||
BLUEPRINT = Blueprint("api", __name__, url_prefix="/api")
|
BLUEPRINT = Blueprint("api", __name__, url_prefix="/api")
|
||||||
|
|
||||||
|
|
||||||
|
def requires_admin(f):
|
||||||
|
def _helper(*args, **kwargs):
|
||||||
|
if not ctx.is_admin:
|
||||||
|
return {"error": "unauthorized"}, 401
|
||||||
|
else:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
_helper.__name__ = f.__name__
|
||||||
|
return _helper
|
||||||
|
|
||||||
|
|
||||||
|
def requires_auth(f):
|
||||||
|
def _helper(*args, **kwargs):
|
||||||
|
if not ctx.uid:
|
||||||
|
return {"error": "unauthorized"}, 401
|
||||||
|
else:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
_helper.__name__ = f.__name__
|
||||||
|
return _helper
|
||||||
|
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
# Printers
|
# Printers
|
||||||
#
|
#
|
||||||
# The trick here is handling multipart uploads.
|
# The trick here is handling multipart uploads.
|
||||||
@requires_admin
|
|
||||||
@BLUEPRINT.route("/printers", methods=["POST"])
|
@BLUEPRINT.route("/printers", methods=["POST"])
|
||||||
|
@requires_admin
|
||||||
def create_printer():
|
def create_printer():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@requires_auth
|
|
||||||
@BLUEPRINT.route("/printers", methods=["GET"])
|
@BLUEPRINT.route("/printers", methods=["GET"])
|
||||||
@BLUEPRINT.route("/printers/", methods=["GET"])
|
@BLUEPRINT.route("/printers/", methods=["GET"])
|
||||||
|
@requires_auth
|
||||||
def list_printers():
|
def list_printers():
|
||||||
return {
|
return {
|
||||||
"printers": [
|
"printers": [
|
||||||
|
@ -38,8 +56,8 @@ def list_printers():
|
||||||
}, 200
|
}, 200
|
||||||
|
|
||||||
|
|
||||||
@requires_admin
|
|
||||||
@BLUEPRINT.route("/printers", methods=["DELETE"])
|
@BLUEPRINT.route("/printers", methods=["DELETE"])
|
||||||
|
@requires_admin
|
||||||
def delete_printer():
|
def delete_printer():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -48,9 +66,9 @@ def delete_printer():
|
||||||
# Files
|
# Files
|
||||||
#
|
#
|
||||||
# The trick here is handling multipart uploads.
|
# The trick here is handling multipart uploads.
|
||||||
@requires_auth
|
|
||||||
@BLUEPRINT.route("/files", methods=["POST"])
|
@BLUEPRINT.route("/files", methods=["POST"])
|
||||||
@BLUEPRINT.route("/files/<location>", methods=["POST"])
|
@BLUEPRINT.route("/files/<location>", methods=["POST"])
|
||||||
|
@requires_auth
|
||||||
def create_file(location: Optional[str] = None):
|
def create_file(location: Optional[str] = None):
|
||||||
# This is the important one, because it's the one that PrusaSlicer et all use to upload jobs.
|
# This is the important one, because it's the one that PrusaSlicer et all use to upload jobs.
|
||||||
|
|
||||||
|
@ -68,20 +86,29 @@ def create_file(location: Optional[str] = None):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
digest = sha3_256()
|
digest = sha3_256()
|
||||||
|
digest.update(
|
||||||
|
f"$USER${ctx.uid}$".encode()
|
||||||
|
) # Salt filenames per-user to avoid collisions
|
||||||
digest.update(file.filename.encode())
|
digest.update(file.filename.encode())
|
||||||
sanitized_filename = digest.hexdigest() + ".gcode"
|
sanitized_filename = digest.hexdigest() + ".gcode"
|
||||||
sanitized_path = os.path.join(
|
sanitized_path = os.path.join(
|
||||||
current_app.config["UPLOAD_FOLDER"], sanitized_filename
|
current_app.config["UPLOAD_FOLDER"], sanitized_filename
|
||||||
)
|
)
|
||||||
|
if os.path.exists(sanitized_path):
|
||||||
|
return {"error": "file exists already"}, 409
|
||||||
|
|
||||||
file.save(sanitized_path)
|
file.save(sanitized_path)
|
||||||
ctx.db.create_file(ctx.uid, file.filename, sanitized_path)
|
fid = ctx.db.create_file(ctx.uid, file.filename, sanitized_path)
|
||||||
|
|
||||||
|
if request.form.get("print", "").lower() == "true":
|
||||||
|
ctx.db.create_job(ctx.uid, fid)
|
||||||
|
|
||||||
return {"status": "ok"}, 202
|
return {"status": "ok"}, 202
|
||||||
|
|
||||||
|
|
||||||
@requires_auth
|
|
||||||
@BLUEPRINT.route("/files", methods=["GET"])
|
@BLUEPRINT.route("/files", methods=["GET"])
|
||||||
@BLUEPRINT.route("/files/", methods=["GET"])
|
@BLUEPRINT.route("/files/", methods=["GET"])
|
||||||
|
@requires_auth
|
||||||
def get_files():
|
def get_files():
|
||||||
return {
|
return {
|
||||||
"files": [
|
"files": [
|
||||||
|
@ -97,22 +124,22 @@ def get_files():
|
||||||
}, 200
|
}, 200
|
||||||
|
|
||||||
|
|
||||||
@requires_auth
|
|
||||||
@BLUEPRINT.route("/files", methods=["DELETE"])
|
@BLUEPRINT.route("/files", methods=["DELETE"])
|
||||||
|
@requires_auth
|
||||||
def delete_file():
|
def delete_file():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
# Jobs
|
# Jobs
|
||||||
@requires_auth
|
|
||||||
@BLUEPRINT.route("/jobs", methods=["POST"])
|
@BLUEPRINT.route("/jobs", methods=["POST"])
|
||||||
|
@requires_auth
|
||||||
def create_job():
|
def create_job():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@requires_auth
|
|
||||||
@BLUEPRINT.route("/jobs", methods=["GET"])
|
@BLUEPRINT.route("/jobs", methods=["GET"])
|
||||||
|
@requires_auth
|
||||||
def get_jobs():
|
def get_jobs():
|
||||||
return {
|
return {
|
||||||
"jobs": [
|
"jobs": [
|
||||||
|
@ -128,15 +155,27 @@ def get_jobs():
|
||||||
}, 200
|
}, 200
|
||||||
|
|
||||||
|
|
||||||
@requires_auth
|
|
||||||
@BLUEPRINT.route("/jobs", methods=["DELETE"])
|
@BLUEPRINT.route("/jobs", methods=["DELETE"])
|
||||||
|
@requires_auth
|
||||||
def delete_job():
|
def delete_job():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
# API tokens
|
# API tokens
|
||||||
@requires_auth
|
|
||||||
@BLUEPRINT.route("/tokens", methods=["GET"])
|
@BLUEPRINT.route("/tokens", methods=["GET"])
|
||||||
|
@requires_auth
|
||||||
def get_tokens():
|
def get_tokens():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@BLUEPRINT.route("/version", methods=["GET"])
|
||||||
|
@requires_auth
|
||||||
|
def get_version():
|
||||||
|
"""OctoPrint compatability endpoint."""
|
||||||
|
|
||||||
|
return {
|
||||||
|
"api": "0.1",
|
||||||
|
"server": "1.3.10",
|
||||||
|
"text": "OctoPrint 1.3.10 (Tentacles)",
|
||||||
|
}, 200
|
||||||
|
|
|
@ -20,8 +20,8 @@ log = logging.getLogger(__name__)
|
||||||
BLUEPRINT = Blueprint("files", __name__)
|
BLUEPRINT = Blueprint("files", __name__)
|
||||||
|
|
||||||
|
|
||||||
@requires_auth
|
|
||||||
@BLUEPRINT.route("/files", methods=["GET"])
|
@BLUEPRINT.route("/files", methods=["GET"])
|
||||||
|
@requires_auth
|
||||||
def list_files():
|
def list_files():
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
flash("Not supported yet", category="warning")
|
flash("Not supported yet", category="warning")
|
||||||
|
@ -29,8 +29,8 @@ def list_files():
|
||||||
return render_template("files.html.j2")
|
return render_template("files.html.j2")
|
||||||
|
|
||||||
|
|
||||||
@requires_auth
|
|
||||||
@BLUEPRINT.route("/files", methods=["POST"])
|
@BLUEPRINT.route("/files", methods=["POST"])
|
||||||
|
@requires_auth
|
||||||
def manipulate_files():
|
def manipulate_files():
|
||||||
match request.form.get("action"):
|
match request.form.get("action"):
|
||||||
case "upload":
|
case "upload":
|
||||||
|
@ -43,11 +43,15 @@ def manipulate_files():
|
||||||
|
|
||||||
case "delete":
|
case "delete":
|
||||||
file = ctx.db.fetch_file(ctx.uid, int(request.form.get("file_id")))
|
file = ctx.db.fetch_file(ctx.uid, int(request.form.get("file_id")))
|
||||||
if any(job.finished_at is None for job in ctx.db.list_jobs_by_file(file.id)):
|
if any(
|
||||||
|
job.finished_at is None for job in ctx.db.list_jobs_by_file(file.id)
|
||||||
|
):
|
||||||
flash("File is in use", category="error")
|
flash("File is in use", category="error")
|
||||||
return render_template("files.html.j2"), 400
|
return render_template("files.html.j2"), 400
|
||||||
|
|
||||||
|
if os.path.exists(file.path):
|
||||||
os.unlink(file.path)
|
os.unlink(file.path)
|
||||||
|
|
||||||
ctx.db.delete_file(ctx.uid, file.id)
|
ctx.db.delete_file(ctx.uid, file.id)
|
||||||
flash("File deleted", category="info")
|
flash("File deleted", category="info")
|
||||||
|
|
||||||
|
|
|
@ -18,14 +18,14 @@ log = logging.getLogger(__name__)
|
||||||
BLUEPRINT = Blueprint("jobs", __name__)
|
BLUEPRINT = Blueprint("jobs", __name__)
|
||||||
|
|
||||||
|
|
||||||
@requires_auth
|
|
||||||
@BLUEPRINT.route("/jobs", methods=["GET"])
|
@BLUEPRINT.route("/jobs", methods=["GET"])
|
||||||
|
@requires_auth
|
||||||
def list_jobs():
|
def list_jobs():
|
||||||
return render_template("jobs.html.j2")
|
return render_template("jobs.html.j2")
|
||||||
|
|
||||||
|
|
||||||
@requires_auth
|
|
||||||
@BLUEPRINT.route("/jobs", methods=["POST"])
|
@BLUEPRINT.route("/jobs", methods=["POST"])
|
||||||
|
@requires_auth
|
||||||
def manipulate_jobs():
|
def manipulate_jobs():
|
||||||
match request.form.get("action"):
|
match request.form.get("action"):
|
||||||
case "enqueue":
|
case "enqueue":
|
||||||
|
|
|
@ -18,14 +18,14 @@ log = logging.getLogger(__name__)
|
||||||
BLUEPRINT = Blueprint("printer", __name__)
|
BLUEPRINT = Blueprint("printer", __name__)
|
||||||
|
|
||||||
|
|
||||||
@requires_admin
|
|
||||||
@BLUEPRINT.route("/printers")
|
@BLUEPRINT.route("/printers")
|
||||||
|
@requires_admin
|
||||||
def printers():
|
def printers():
|
||||||
return render_template("printers.html.j2")
|
return render_template("printers.html.j2")
|
||||||
|
|
||||||
|
|
||||||
@requires_admin
|
|
||||||
@BLUEPRINT.route("/printers/add", methods=["get", "post"])
|
@BLUEPRINT.route("/printers/add", methods=["get", "post"])
|
||||||
|
@requires_admin
|
||||||
def add_printer():
|
def add_printer():
|
||||||
if not is_logged_in():
|
if not is_logged_in():
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
@ -50,7 +50,7 @@ def add_printer():
|
||||||
return render_template("add_printer.html.j2")
|
return render_template("add_printer.html.j2")
|
||||||
|
|
||||||
|
|
||||||
@requires_admin
|
|
||||||
@BLUEPRINT.route("/printers/delete")
|
@BLUEPRINT.route("/printers/delete")
|
||||||
|
@requires_admin
|
||||||
def delete_printer():
|
def delete_printer():
|
||||||
return render_template("delete_printer.html.j2")
|
return render_template("delete_printer.html.j2")
|
||||||
|
|
|
@ -21,10 +21,11 @@ def requires_admin(f):
|
||||||
def _helper(*args, **kwargs):
|
def _helper(*args, **kwargs):
|
||||||
if not ctx.is_admin:
|
if not ctx.is_admin:
|
||||||
flash("Sorry, admins only", category="error")
|
flash("Sorry, admins only", category="error")
|
||||||
redirect("/")
|
return redirect("/")
|
||||||
else:
|
else:
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
_helper.__name__ = f.__name__
|
||||||
return _helper
|
return _helper
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,8 +33,9 @@ def requires_auth(f):
|
||||||
def _helper(*args, **kwargs):
|
def _helper(*args, **kwargs):
|
||||||
if not ctx.uid:
|
if not ctx.uid:
|
||||||
flash("Please log in first", category="error")
|
flash("Please log in first", category="error")
|
||||||
redirect("/")
|
return redirect("/")
|
||||||
else:
|
else:
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
_helper.__name__ = f.__name__
|
||||||
return _helper
|
return _helper
|
||||||
|
|
Loading…
Reference in a new issue