Skip to content
Draft
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
67 changes: 46 additions & 21 deletions continuousprint/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,30 @@
from octoprint.server.util.flask import restricted_access
import flask
import json
from collections import namedtuple
from .storage import queries
from .storage.database import DEFAULT_QUEUE
from .storage.database import DEFAULT_QUEUE, Queue, Set, Job
from .driver import Action as DA
from abc import ABC, abstractmethod


Creds = namedtuple("Credentials", ["user", "groups", "admin"])


def get_creds():
# https://flask-login.readthedocs.io/en/latest/#your-user-class
# https://github.com/OctoPrint/OctoPrint/blob/f430257d7072a83692fc2392c683ed8c97ae47b6/src/octoprint/server/api/access.py#L171
from flask_login import current_user
from octoprint.server import userManager

usr = userManager.find_user(current_user.get_name())
return Creds(
usr.get_id(),
[g.get_name() for g in usr.groups],
Permissions.PLUGIN_CONTINUOUSPRINT_QUEUE_ADMIN.can(),
)


class Permission(Enum):
STARTSTOP = (
"Start and Stop Queue",
Expand Down Expand Up @@ -41,6 +59,11 @@ class Permission(Enum):
"Allows for adding/removing queues and rearranging them",
True,
)
QUEUE_ADMIN = (
"Queue administrator",
"Skip user/group permissions checks when performing operations",
True,
)

def __init__(self, longname, desc, dangerous):
self.longname = longname
Expand All @@ -58,12 +81,12 @@ def as_dict(self):
)


def cpq_permission(perm: Permission):
def cpq_permission(perm: Permission, field=None):
def cpq_permission_decorator(func):
def cpq_permission_wrapper(*args, **kwargs):
if not getattr(Permissions, f"PLUGIN_CONTINUOUSPRINT_{perm.name}").can():
return flask.make_response(f"Insufficient Rights ({perm.name})", 403)
return func(*args, **kwargs)
return func(*args, **kwargs, creds=get_creds())

# the BlueprintPlugin decorator used below relies on the original function name
# to map the function to an HTTP handler
Expand Down Expand Up @@ -141,7 +164,7 @@ def get_state(self):
@octoprint.plugin.BlueprintPlugin.route("/set_active", methods=["POST"])
@restricted_access
@cpq_permission(Permission.STARTSTOP)
def set_active(self):
def set_active(self, creds):
self._update(
DA.ACTIVATE if flask.request.form["active"] == "true" else DA.DEACTIVATE
)
Expand All @@ -153,17 +176,17 @@ def set_active(self):
@octoprint.plugin.BlueprintPlugin.route("/set/add", methods=["POST"])
@restricted_access
@cpq_permission(Permission.ADDSET)
def add_set(self):
def add_set(self, creds):
data = self._preprocess_set(dict(**flask.request.form))
return json.dumps(
self._get_queue(DEFAULT_QUEUE).add_set(data.get("job", ""), data)
self._get_queue(DEFAULT_QUEUE).add_set(data.get("job", ""), data, creds)
)

# PRIVATE API METHOD - may change without warning.
@octoprint.plugin.BlueprintPlugin.route("/job/add", methods=["POST"])
@restricted_access
@cpq_permission(Permission.ADDJOB)
def add_job(self):
def add_job(self, creds):
return json.dumps(
self._get_queue(DEFAULT_QUEUE)
.add_job(flask.request.form.get("name"))
Expand All @@ -174,7 +197,7 @@ def add_job(self):
@octoprint.plugin.BlueprintPlugin.route("/set/mv", methods=["POST"])
@restricted_access
@cpq_permission(Permission.EDITJOB)
def mv_set(self):
def mv_set(self, creds):
self._get_queue(DEFAULT_QUEUE).mv_set(
int(flask.request.form["id"]),
int(
Expand All @@ -190,7 +213,7 @@ def mv_set(self):
@octoprint.plugin.BlueprintPlugin.route("/job/mv", methods=["POST"])
@restricted_access
@cpq_permission(Permission.EDITJOB)
def mv_job(self):
def mv_job(self, creds):
self._get_queue(DEFAULT_QUEUE).mv_job(
int(flask.request.form["id"]),
int(
Expand All @@ -203,7 +226,7 @@ def mv_job(self):
@octoprint.plugin.BlueprintPlugin.route("/job/submit", methods=["POST"])
@restricted_access
@cpq_permission(Permission.ADDJOB)
def submit_job(self):
def submit_job(self, creds):
j = queries.getJob(int(flask.request.form["id"]))
# Submit to the queue and remove from its origin
err = self._get_queue(flask.request.form["queue"]).submit_job(j)
Expand All @@ -219,15 +242,17 @@ def submit_job(self):
@octoprint.plugin.BlueprintPlugin.route("/job/edit", methods=["POST"])
@restricted_access
@cpq_permission(Permission.EDITJOB)
def edit_job(self):
def edit_job(self, creds):
data = json.loads(flask.request.form.get("json"))
return json.dumps(self._get_queue(DEFAULT_QUEUE).edit_job(data["id"], data))
return json.dumps(
self._get_queue(DEFAULT_QUEUE).edit_job(data["id"], data, creds)
)

# PRIVATE API METHOD - may change without warning.
@octoprint.plugin.BlueprintPlugin.route("/job/import", methods=["POST"])
@restricted_access
@cpq_permission(Permission.ADDJOB)
def import_job(self):
def import_job(self, creds):
return json.dumps(
self._get_queue(flask.request.form["queue"])
.import_job(flask.request.form["path"])
Expand All @@ -238,7 +263,7 @@ def import_job(self):
@octoprint.plugin.BlueprintPlugin.route("/job/export", methods=["POST"])
@restricted_access
@cpq_permission(Permission.EXPORTJOB)
def export_job(self):
def export_job(self, creds):
job_ids = [int(jid) for jid in flask.request.form.getlist("job_ids[]")]
results = []
root_dir = self._path_on_disk("/")
Expand All @@ -253,7 +278,7 @@ def export_job(self):
@octoprint.plugin.BlueprintPlugin.route("/job/rm", methods=["POST"])
@restricted_access
@cpq_permission(Permission.EDITJOB)
def rm_job(self):
def rm_job(self, creds):
return json.dumps(
self._get_queue(flask.request.form["queue"]).remove_jobs(
flask.request.form.getlist("job_ids[]")
Expand All @@ -264,7 +289,7 @@ def rm_job(self):
@octoprint.plugin.BlueprintPlugin.route("/set/rm", methods=["POST"])
@restricted_access
@cpq_permission(Permission.EDITJOB)
def rm_set(self):
def rm_set(self, creds):
return json.dumps(
self._get_queue(DEFAULT_QUEUE).rm_multi(
set_ids=flask.request.form.getlist("set_ids[]")
Expand All @@ -275,7 +300,7 @@ def rm_set(self):
@octoprint.plugin.BlueprintPlugin.route("/job/reset", methods=["POST"])
@restricted_access
@cpq_permission(Permission.EDITJOB)
def reset_multi(self):
def reset_multi(self, creds):
return json.dumps(
self._get_queue(flask.request.form["queue"]).reset_jobs(
flask.request.form.getlist("job_ids[]")
Expand All @@ -286,29 +311,29 @@ def reset_multi(self):
@octoprint.plugin.BlueprintPlugin.route("/history/get", methods=["GET"])
@restricted_access
@cpq_permission(Permission.GETHISTORY)
def get_history(self):
def get_history(self, creds):
return self._history_json()

# PRIVATE API METHOD - may change without warning.
@octoprint.plugin.BlueprintPlugin.route("/history/reset", methods=["POST"])
@restricted_access
@cpq_permission(Permission.CLEARHISTORY)
def reset_history(self):
def reset_history(self, creds):
queries.resetHistory()
return json.dumps("OK")

# PRIVATE API METHOD - may change without warning.
@octoprint.plugin.BlueprintPlugin.route("/queues/get", methods=["GET"])
@restricted_access
@cpq_permission(Permission.GETQUEUES)
def get_queues(self):
def get_queues(self, creds):
return json.dumps([q.as_dict() for q in queries.getQueues()])

# PRIVATE API METHOD - may change without warning.
@octoprint.plugin.BlueprintPlugin.route("/queues/edit", methods=["POST"])
@restricted_access
@cpq_permission(Permission.EDITQUEUES)
def edit_queues(self):
def edit_queues(self, creds):
queues = json.loads(flask.request.form.get("json"))
(absent_names, added) = queries.assignQueues(queues)
self._commit_queues(added, absent_names)
Expand Down
9 changes: 7 additions & 2 deletions continuousprint/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,17 +266,22 @@ def _state_start_clearing(self, a: Action, p: Printer):
return self._state_clearing

def _state_cooldown(self, a: Action, p: Printer):
clear = False
if self._bed_temp < self.cooldown_threshold:
self._logger.info(
f"Cooldown threshold of {self.cooldown_threshold} has been met"
)
return self._state_clearing
clear = True
elif (time.time() - self.cooldown_start) > (60 * self.cooldown_timeout):
self._logger.info(f"Timeout of {self.cooldown_timeout} minutes exceeded")
return self._state_clearing
clear = True
else:
self._set_status("Cooling down")

if clear:
self._runner.clear_bed()
return self._state_clearing

def _state_clearing(self, a: Action, p: Printer):
if a == Action.SUCCESS:
return self._enter_start_print(a, p)
Expand Down
40 changes: 20 additions & 20 deletions continuousprint/queues/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,44 +89,44 @@ def as_dict(self) -> dict:
)
)

def remove_jobs(self, job_ids):
return self.rm_multi(job_ids=job_ids)
def remove_jobs(self, job_ids, creds):
return self.rm_multi(job_ids=job_ids, creds=creds)

def reset_jobs(self, job_ids):
return self.queries.resetJobs(job_ids)
def reset_jobs(self, job_ids, creds):
return self.queries.resetJobs(job_ids, creds=creds)

# -------------- end AbstractQueue ------------------

# -------------- begin AbstractEditableQueue -----------

def add_job(self, name="") -> JobView:
return self.queries.newEmptyJob(self.ns, name)
def add_job(self, creds, name="") -> JobView:
return self.queries.newEmptyJob(self.ns, name, creds=creds)

def add_set(self, job_id, data) -> SetView:
return self.queries.appendSet(self.ns, job_id, data)
def add_set(self, job_id, data, creds) -> SetView:
return self.queries.appendSet(self.ns, job_id, data, creds=creds)

def mv_set(self, set_id, after_id, dest_job):
return self.queries.moveSet(set_id, after_id, dest_job)
def mv_set(self, set_id, after_id, dest_job, creds):
return self.queries.moveSet(set_id, after_id, dest_job, creds=creds)

def mv_job(self, job_id, after_id):
return self.queries.moveJob(job_id, after_id)
def mv_job(self, job_id, after_id, creds):
return self.queries.moveJob(job_id, after_id, creds=creds)

def edit_job(self, job_id, data):
return self.queries.updateJob(job_id, data)
def edit_job(self, job_id, data, creds):
return self.queries.updateJob(job_id, data, creds=creds)

def rm_multi(self, job_ids=[], set_ids=[]) -> dict:
return self.queries.remove(job_ids=job_ids, set_ids=set_ids)
def rm_multi(self, creds, job_ids=[], set_ids=[]) -> dict:
return self.queries.remove(job_ids=job_ids, set_ids=set_ids, creds=creds)

def import_job(self, gjob_path: str, draft=True) -> dict:
def import_job(self, gjob_path: str, creds, draft=True) -> dict:
out_dir = str(Path(gjob_path).stem)
self._mkdir(out_dir)
manifest, filepaths = unpack_job(
self._path_on_disk(gjob_path), self._path_on_disk(out_dir)
)
return self.queries.importJob(self.ns, manifest, out_dir, draft)
return self.queries.importJob(self.ns, manifest, out_dir, draft, creds=creds)

def export_job(self, job_id: int, dest_dir: str) -> str:
j = self.queries.getJob(job_id)
def export_job(self, job_id: int, dest_dir: str, creds) -> str:
j = self.queries.getJob(job_id, creds=creds)
filepaths = dict([(s.path, self._path_on_disk(s.path)) for s in j.sets])
with tempfile.NamedTemporaryFile(
suffix=".gjob", dir=dest_dir, delete=False
Expand Down
Loading