Login and logout working for the first time
parent
150d6cb53d
commit
874a7421e9
|
@ -5,15 +5,13 @@ __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agp
|
||||||
from flask import Flask, request, render_template, jsonify, send_from_directory, abort, url_for
|
from flask import Flask, request, render_template, jsonify, send_from_directory, abort, url_for
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
import tornadio2
|
import tornadio2
|
||||||
from flask.ext.login import LoginManager
|
from flask.ext.login import LoginManager, login_user, logout_user, login_required, current_user, AnonymousUser
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import logging, logging.config
|
import logging, logging.config
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
from octoprint.printer import Printer, getConnectionOptions
|
from octoprint.printer import Printer, getConnectionOptions
|
||||||
from octoprint.settings import settings, valid_boolean_trues
|
from octoprint.settings import settings, valid_boolean_trues
|
||||||
import octoprint.timelapse as timelapse
|
import octoprint.timelapse as timelapse
|
||||||
|
@ -109,7 +107,8 @@ def index():
|
||||||
webcamStream=settings().get(["webcam", "stream"]),
|
webcamStream=settings().get(["webcam", "stream"]),
|
||||||
enableTimelapse=(settings().get(["webcam", "snapshot"]) is not None and settings().get(["webcam", "ffmpeg"]) is not None),
|
enableTimelapse=(settings().get(["webcam", "snapshot"]) is not None and settings().get(["webcam", "ffmpeg"]) is not None),
|
||||||
enableGCodeVisualizer=settings().get(["feature", "gCodeVisualizer"]),
|
enableGCodeVisualizer=settings().get(["feature", "gCodeVisualizer"]),
|
||||||
enableSystemMenu=settings().get(["system"]) is not None and settings().get(["system", "actions"]) is not None and len(settings().get(["system", "actions"])) > 0
|
enableSystemMenu=settings().get(["system"]) is not None and settings().get(["system", "actions"]) is not None and len(settings().get(["system", "actions"])) > 0,
|
||||||
|
enableAccessControl=userManager is not None
|
||||||
)
|
)
|
||||||
|
|
||||||
#~~ Printer control
|
#~~ Printer control
|
||||||
|
@ -119,6 +118,7 @@ def connectionOptions():
|
||||||
return jsonify(getConnectionOptions())
|
return jsonify(getConnectionOptions())
|
||||||
|
|
||||||
@app.route(BASEURL + "control/connect", methods=["POST"])
|
@app.route(BASEURL + "control/connect", methods=["POST"])
|
||||||
|
@login_required
|
||||||
def connect():
|
def connect():
|
||||||
port = None
|
port = None
|
||||||
baudrate = None
|
baudrate = None
|
||||||
|
@ -456,12 +456,36 @@ def login():
|
||||||
username = request.values["user"]
|
username = request.values["user"]
|
||||||
password = request.values["pass"]
|
password = request.values["pass"]
|
||||||
|
|
||||||
passwordHash = users.createPasswordHash(password)
|
if "remember" in request.values.keys() and request.values["remember"]:
|
||||||
|
remember = True
|
||||||
|
else:
|
||||||
|
remember = False
|
||||||
|
|
||||||
pass
|
user = userManager.findUser(username)
|
||||||
|
if user is not None:
|
||||||
|
passwordHash = users.UserManager.createPasswordHash(password)
|
||||||
|
if passwordHash == user.passwordHash:
|
||||||
|
login_user(user, remember=remember)
|
||||||
|
return jsonify({"name": user.username, "roles": user.roles})
|
||||||
|
return app.make_response(("User unknown or password incorrect", 401, []))
|
||||||
|
elif "passive" in request.values.keys():
|
||||||
|
user = current_user
|
||||||
|
if user is not None and not user.is_anonymous():
|
||||||
|
return jsonify({"name": user.username, "roles": user.roles})
|
||||||
|
else:
|
||||||
|
return jsonify(SUCCESS)
|
||||||
|
|
||||||
def load_user(userid):
|
@app.route(BASEURL + "logout", methods=["POST"])
|
||||||
pass
|
@login_required
|
||||||
|
def logout():
|
||||||
|
logout_user()
|
||||||
|
return jsonify(SUCCESS)
|
||||||
|
|
||||||
|
def load_user(id):
|
||||||
|
if userManager is not None:
|
||||||
|
return userManager.findUser(id)
|
||||||
|
else:
|
||||||
|
return users.DummyUser()
|
||||||
|
|
||||||
#~~ startup code
|
#~~ startup code
|
||||||
class Server():
|
class Server():
|
||||||
|
@ -476,6 +500,7 @@ class Server():
|
||||||
# Global as I can't work out a way to get it into PrinterStateConnection
|
# Global as I can't work out a way to get it into PrinterStateConnection
|
||||||
global printer
|
global printer
|
||||||
global gcodeManager
|
global gcodeManager
|
||||||
|
global userManager
|
||||||
|
|
||||||
from tornado.wsgi import WSGIContainer
|
from tornado.wsgi import WSGIContainer
|
||||||
from tornado.httpserver import HTTPServer
|
from tornado.httpserver import HTTPServer
|
||||||
|
@ -487,13 +512,25 @@ class Server():
|
||||||
|
|
||||||
# then initialize logging
|
# then initialize logging
|
||||||
self._initLogging(self._debug)
|
self._initLogging(self._debug)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
gcodeManager = gcodefiles.GcodeManager()
|
gcodeManager = gcodefiles.GcodeManager()
|
||||||
printer = Printer(gcodeManager)
|
printer = Printer(gcodeManager)
|
||||||
|
|
||||||
|
if settings().getBoolean(["accessControl", "enabled"]):
|
||||||
|
userManagerName = settings().get(["accessControl", "userManager"])
|
||||||
|
try:
|
||||||
|
clazz = util.getClass(userManagerName)
|
||||||
|
userManager = clazz()
|
||||||
|
except AttributeError, e:
|
||||||
|
logger.exception("Could not instantiate user manager %s, will run with accessControl disabled!" % userManagerName)
|
||||||
|
|
||||||
app.secret_key = "k3PuVYgtxNm8DXKKTw2nWmFQQun9qceV"
|
app.secret_key = "k3PuVYgtxNm8DXKKTw2nWmFQQun9qceV"
|
||||||
login_manager = LoginManager()
|
login_manager = LoginManager()
|
||||||
login_manager.session_protection = "strong"
|
login_manager.session_protection = "strong"
|
||||||
|
login_manager.user_callback = load_user
|
||||||
|
if userManager is None:
|
||||||
|
login_manager.anonymous_user = users.DummyUser
|
||||||
login_manager.init_app(app)
|
login_manager.init_app(app)
|
||||||
|
|
||||||
if self._host is None:
|
if self._host is None:
|
||||||
|
@ -501,7 +538,7 @@ class Server():
|
||||||
if self._port is None:
|
if self._port is None:
|
||||||
self._port = settings().getInt(["server", "port"])
|
self._port = settings().getInt(["server", "port"])
|
||||||
|
|
||||||
logging.getLogger(__name__).info("Listening on http://%s:%d" % (self._host, self._port))
|
logger.info("Listening on http://%s:%d" % (self._host, self._port))
|
||||||
app.debug = self._debug
|
app.debug = self._debug
|
||||||
|
|
||||||
self._router = tornadio2.TornadioRouter(PrinterStateConnection)
|
self._router = tornadio2.TornadioRouter(PrinterStateConnection)
|
||||||
|
@ -517,7 +554,7 @@ class Server():
|
||||||
s = settings(init=True, basedir=basedir, configfile=configfile)
|
s = settings(init=True, basedir=basedir, configfile=configfile)
|
||||||
|
|
||||||
def _initLogging(self, debug):
|
def _initLogging(self, debug):
|
||||||
self._config = {
|
config = {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"formatters": {
|
"formatters": {
|
||||||
"simple": {
|
"simple": {
|
||||||
|
@ -556,13 +593,13 @@ class Server():
|
||||||
}
|
}
|
||||||
|
|
||||||
if debug:
|
if debug:
|
||||||
self._config["loggers"]["SERIAL"] = {
|
config["loggers"]["SERIAL"] = {
|
||||||
"level": "DEBUG",
|
"level": "DEBUG",
|
||||||
"handlers": ["serialFile"],
|
"handlers": ["serialFile"],
|
||||||
"propagate": False
|
"propagate": False
|
||||||
}
|
}
|
||||||
|
|
||||||
logging.config.dictConfig(self._config)
|
logging.config.dictConfig(config)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
octoprint = Server()
|
octoprint = Server()
|
||||||
|
|
|
@ -69,6 +69,11 @@ default_settings = {
|
||||||
"controls": [],
|
"controls": [],
|
||||||
"system": {
|
"system": {
|
||||||
"actions": []
|
"actions": []
|
||||||
|
},
|
||||||
|
"accessControl": {
|
||||||
|
"enabled": False,
|
||||||
|
"userManager": "octoprint.users.FilebasedUserManager",
|
||||||
|
"userfile": None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1618,6 +1618,71 @@ $(function() {
|
||||||
//~~ Offline overlay
|
//~~ Offline overlay
|
||||||
$("#offline_overlay_reconnect").click(function() {dataUpdater.reconnect()});
|
$("#offline_overlay_reconnect").click(function() {dataUpdater.reconnect()});
|
||||||
|
|
||||||
|
//~~ Alert
|
||||||
|
|
||||||
|
/*
|
||||||
|
function displayAlert(text, timeout, type) {
|
||||||
|
var placeholder = $("#alert_placeholder");
|
||||||
|
|
||||||
|
var alertType = "";
|
||||||
|
if (type == "success" || type == "error" || type == "info") {
|
||||||
|
alertType = " alert-" + type;
|
||||||
|
}
|
||||||
|
|
||||||
|
placeholder.append($("<div id='activeAlert' class='alert " + alertType + " fade in' data-alert='alert'><p>" + text + "</p></div>"));
|
||||||
|
placeholder.fadeIn();
|
||||||
|
$("#activeAlert").delay(timeout).fadeOut("slow", function() {$(this).remove(); $("#alert_placeholder").hide();});
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
//~~ Login/logout
|
||||||
|
|
||||||
|
$("#login_button").click(function() {
|
||||||
|
var username = $("#login_user").val();
|
||||||
|
var password = $("#login_pass").val();
|
||||||
|
var remember = $("#login_remember").is(":checked");
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: AJAX_BASEURL + "login",
|
||||||
|
type: "POST",
|
||||||
|
data: {"user": username, "pass": password, "remember": remember},
|
||||||
|
success: function(response) {
|
||||||
|
$.pnotify({title: "Login successful", text: "You are now logged in", type: "success"});
|
||||||
|
$("#login_dropdown_text").text("\"" + response.name + "\"");
|
||||||
|
$("#login_dropdown_loggedout").removeClass("dropdown-menu").addClass("hide");
|
||||||
|
$("#login_dropdown_loggedin").removeClass("hide").addClass("dropdown-menu");
|
||||||
|
},
|
||||||
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
|
$.pnotify({title: "Login failed", text: "User unknown or wrong password", type: "error"});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
$("#logout_button").click(function(){
|
||||||
|
$.ajax({
|
||||||
|
url: AJAX_BASEURL + "logout",
|
||||||
|
type: "POST",
|
||||||
|
success: function(response) {
|
||||||
|
$.pnotify({title: "Logout successful", text: "You are now logged out", type: "success"});
|
||||||
|
$("#login_dropdown_text").text("Login");
|
||||||
|
$("#login_dropdown_loggedin").removeClass("dropdown-menu").addClass("hide");
|
||||||
|
$("#login_dropdown_loggedout").removeClass("hide").addClass("dropdown-menu");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: AJAX_BASEURL + "login",
|
||||||
|
type: "POST",
|
||||||
|
data: {"passive": true},
|
||||||
|
success: function(response) {
|
||||||
|
if (response["name"]) {
|
||||||
|
$("#login_dropdown_text").text("\"" + response.name + "\"");
|
||||||
|
$("#login_dropdown_loggedout").removeClass("dropdown-menu").addClass("hide");
|
||||||
|
$("#login_dropdown_loggedin").removeClass("hide").addClass("dropdown-menu");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
//~~ knockout.js bindings
|
//~~ knockout.js bindings
|
||||||
|
|
||||||
ko.bindingHandlers.popover = {
|
ko.bindingHandlers.popover = {
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if enableAccessControl %}
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||||
<i class="icon-user"></i> <span id="login_dropdown_text">Login</span>
|
<i class="icon-user"></i> <span id="login_dropdown_text">Login</span>
|
||||||
|
@ -63,9 +64,10 @@
|
||||||
<ul id="login_dropdown_loggedin" class="hide">
|
<ul id="login_dropdown_loggedin" class="hide">
|
||||||
<li><a href="#">Profile</a></li>
|
<li><a href="#">Profile</a></li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li><a href="#">Logout</a></li>
|
<li><a href="#" id="logout_button">Logout</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -83,7 +83,7 @@ class Timelapse(object):
|
||||||
|
|
||||||
with self._captureMutex:
|
with self._captureMutex:
|
||||||
filename = os.path.join(self._captureDir, "tmp_%05d.jpg" % (self._imageNumber))
|
filename = os.path.join(self._captureDir, "tmp_%05d.jpg" % (self._imageNumber))
|
||||||
self._imageNumber += 1;
|
self._imageNumber += 1
|
||||||
self._logger.debug("Capturing image to %s" % filename)
|
self._logger.debug("Capturing image to %s" % filename)
|
||||||
|
|
||||||
captureThread = threading.Thread(target=self._captureWorker, kwargs={"filename": filename})
|
captureThread = threading.Thread(target=self._captureWorker, kwargs={"filename": filename})
|
||||||
|
|
|
@ -9,8 +9,8 @@ import yaml
|
||||||
|
|
||||||
from octoprint.settings import settings
|
from octoprint.settings import settings
|
||||||
|
|
||||||
class UserManager:
|
class UserManager(object):
|
||||||
valid_roles=["user", "admin"]
|
valid_roles = ["user", "admin"]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def createPasswordHash(password):
|
def createPasswordHash(password):
|
||||||
|
@ -37,9 +37,10 @@ class UserManager:
|
||||||
##~~ FilebasedUserManager, takes available users from users.yaml file
|
##~~ FilebasedUserManager, takes available users from users.yaml file
|
||||||
|
|
||||||
class FilebasedUserManager(UserManager):
|
class FilebasedUserManager(UserManager):
|
||||||
def __init__(self, userfile=None):
|
def __init__(self):
|
||||||
UserManager.__init__(self)
|
UserManager.__init__(self)
|
||||||
|
|
||||||
|
userfile = settings().get(["accessControl", "userfile"])
|
||||||
if userfile is None:
|
if userfile is None:
|
||||||
userfile = os.path.join(settings().settings_dir, "users.yaml")
|
userfile = os.path.join(settings().settings_dir, "users.yaml")
|
||||||
self._userfile = userfile
|
self._userfile = userfile
|
||||||
|
@ -49,7 +50,7 @@ class FilebasedUserManager(UserManager):
|
||||||
self._load()
|
self._load()
|
||||||
|
|
||||||
def _load(self):
|
def _load(self):
|
||||||
self._users = {}
|
self._users = {"admin": User("admin", "7557160613d5258f883014a7c3c0428de53040fc152b1791f1cc04a62b428c0c2a9c46ed330cdce9689353ab7a5352ba2b2ceb459b96e9c8ed7d0cb0b2c0c076", True, UserManager.valid_roles)}
|
||||||
if os.path.exists(self._userfile) and os.path.isfile(self._userfile):
|
if os.path.exists(self._userfile) and os.path.isfile(self._userfile):
|
||||||
with open(self._userfile, "r") as f:
|
with open(self._userfile, "r") as f:
|
||||||
data = yaml.safe_load(f)
|
data = yaml.safe_load(f)
|
||||||
|
@ -159,3 +160,12 @@ class User(UserMixin):
|
||||||
|
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
return self.active
|
return self.active
|
||||||
|
|
||||||
|
##~~ DummyUser object to use when accessControl is disabled
|
||||||
|
|
||||||
|
class DummyUser(UserMixin):
|
||||||
|
def __init__(self):
|
||||||
|
self.roles = UserManager.valid_roles
|
||||||
|
|
||||||
|
def get_id(self):
|
||||||
|
return "dummy"
|
|
@ -28,3 +28,14 @@ def getFormattedDateTime(d):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return d.strftime("%Y-%m-%d %H:%M")
|
return d.strftime("%Y-%m-%d %H:%M")
|
||||||
|
|
||||||
|
def getClass(name):
|
||||||
|
"""
|
||||||
|
Taken from http://stackoverflow.com/a/452981/2028598
|
||||||
|
"""
|
||||||
|
parts = name.split(".")
|
||||||
|
module = ".".join(parts[:-1])
|
||||||
|
m = __import__(module)
|
||||||
|
for comp in parts[1:]:
|
||||||
|
m = getattr(m, comp)
|
||||||
|
return m
|
Loading…
Reference in New Issue