Login and logout working for the first time

master
Gina Häußge 2013-03-18 22:27:23 +01:00
parent 150d6cb53d
commit 874a7421e9
7 changed files with 149 additions and 19 deletions

View File

@ -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()

View File

@ -69,6 +69,11 @@ default_settings = {
"controls": [],
"system": {
"actions": []
},
"accessControl": {
"enabled": False,
"userManager": "octoprint.users.FilebasedUserManager",
"userfile": None
}
}

View File

@ -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 = {

View File

@ -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>

View File

@ -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})

View File

@ -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"

View File

@ -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