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 werkzeug.utils import secure_filename
|
||||
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 threading
|
||||
import logging, logging.config
|
||||
import subprocess
|
||||
|
||||
import hashlib
|
||||
|
||||
from octoprint.printer import Printer, getConnectionOptions
|
||||
from octoprint.settings import settings, valid_boolean_trues
|
||||
import octoprint.timelapse as timelapse
|
||||
|
@ -109,7 +107,8 @@ def index():
|
|||
webcamStream=settings().get(["webcam", "stream"]),
|
||||
enableTimelapse=(settings().get(["webcam", "snapshot"]) is not None and settings().get(["webcam", "ffmpeg"]) is not None),
|
||||
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
|
||||
|
@ -119,6 +118,7 @@ def connectionOptions():
|
|||
return jsonify(getConnectionOptions())
|
||||
|
||||
@app.route(BASEURL + "control/connect", methods=["POST"])
|
||||
@login_required
|
||||
def connect():
|
||||
port = None
|
||||
baudrate = None
|
||||
|
@ -456,12 +456,36 @@ def login():
|
|||
username = request.values["user"]
|
||||
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):
|
||||
pass
|
||||
@app.route(BASEURL + "logout", methods=["POST"])
|
||||
@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
|
||||
class Server():
|
||||
|
@ -476,6 +500,7 @@ class Server():
|
|||
# Global as I can't work out a way to get it into PrinterStateConnection
|
||||
global printer
|
||||
global gcodeManager
|
||||
global userManager
|
||||
|
||||
from tornado.wsgi import WSGIContainer
|
||||
from tornado.httpserver import HTTPServer
|
||||
|
@ -487,13 +512,25 @@ class Server():
|
|||
|
||||
# then initialize logging
|
||||
self._initLogging(self._debug)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
gcodeManager = gcodefiles.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"
|
||||
login_manager = LoginManager()
|
||||
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)
|
||||
|
||||
if self._host is None:
|
||||
|
@ -501,7 +538,7 @@ class Server():
|
|||
if self._port is None:
|
||||
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
|
||||
|
||||
self._router = tornadio2.TornadioRouter(PrinterStateConnection)
|
||||
|
@ -517,7 +554,7 @@ class Server():
|
|||
s = settings(init=True, basedir=basedir, configfile=configfile)
|
||||
|
||||
def _initLogging(self, debug):
|
||||
self._config = {
|
||||
config = {
|
||||
"version": 1,
|
||||
"formatters": {
|
||||
"simple": {
|
||||
|
@ -556,13 +593,13 @@ class Server():
|
|||
}
|
||||
|
||||
if debug:
|
||||
self._config["loggers"]["SERIAL"] = {
|
||||
config["loggers"]["SERIAL"] = {
|
||||
"level": "DEBUG",
|
||||
"handlers": ["serialFile"],
|
||||
"propagate": False
|
||||
}
|
||||
|
||||
logging.config.dictConfig(self._config)
|
||||
logging.config.dictConfig(config)
|
||||
|
||||
if __name__ == "__main__":
|
||||
octoprint = Server()
|
||||
|
|
|
@ -69,6 +69,11 @@ default_settings = {
|
|||
"controls": [],
|
||||
"system": {
|
||||
"actions": []
|
||||
},
|
||||
"accessControl": {
|
||||
"enabled": False,
|
||||
"userManager": "octoprint.users.FilebasedUserManager",
|
||||
"userfile": None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1618,6 +1618,71 @@ $(function() {
|
|||
//~~ Offline overlay
|
||||
$("#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
|
||||
|
||||
ko.bindingHandlers.popover = {
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if enableAccessControl %}
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="icon-user"></i> <span id="login_dropdown_text">Login</span>
|
||||
|
@ -63,9 +64,10 @@
|
|||
<ul id="login_dropdown_loggedin" class="hide">
|
||||
<li><a href="#">Profile</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#">Logout</a></li>
|
||||
<li><a href="#" id="logout_button">Logout</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -83,7 +83,7 @@ class Timelapse(object):
|
|||
|
||||
with self._captureMutex:
|
||||
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)
|
||||
|
||||
captureThread = threading.Thread(target=self._captureWorker, kwargs={"filename": filename})
|
||||
|
|
|
@ -9,8 +9,8 @@ import yaml
|
|||
|
||||
from octoprint.settings import settings
|
||||
|
||||
class UserManager:
|
||||
valid_roles=["user", "admin"]
|
||||
class UserManager(object):
|
||||
valid_roles = ["user", "admin"]
|
||||
|
||||
@staticmethod
|
||||
def createPasswordHash(password):
|
||||
|
@ -37,9 +37,10 @@ class UserManager:
|
|||
##~~ FilebasedUserManager, takes available users from users.yaml file
|
||||
|
||||
class FilebasedUserManager(UserManager):
|
||||
def __init__(self, userfile=None):
|
||||
def __init__(self):
|
||||
UserManager.__init__(self)
|
||||
|
||||
userfile = settings().get(["accessControl", "userfile"])
|
||||
if userfile is None:
|
||||
userfile = os.path.join(settings().settings_dir, "users.yaml")
|
||||
self._userfile = userfile
|
||||
|
@ -49,7 +50,7 @@ class FilebasedUserManager(UserManager):
|
|||
self._load()
|
||||
|
||||
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):
|
||||
with open(self._userfile, "r") as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
@ -158,4 +159,13 @@ class User(UserMixin):
|
|||
return self.username
|
||||
|
||||
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 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