diff --git a/octoprint/server.py b/octoprint/server.py
index aab4393c..cdc059a2 100644
--- a/octoprint/server.py
+++ b/octoprint/server.py
@@ -5,25 +5,31 @@ __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
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
import octoprint.gcodefiles as gcodefiles
import octoprint.util as util
+import octoprint.users as users
SUCCESS = {}
BASEURL = "/ajax/"
+
app = Flask("octoprint")
# Only instantiated by the Server().run() method
# In order that threads don't start too early when running as a Daemon
printer = None
gcodeManager = None
+userManager = None
#~~ Printer state
@@ -442,6 +448,21 @@ def performSystemAction():
return app.make_response(("Command failed: %r" % ex, 500, []))
return jsonify(SUCCESS)
+#~~ Login/user handling
+
+@app.route(BASEURL + "login", methods=["POST"])
+def login():
+ if "user" in request.values.keys() and "pass" in request.values.keys():
+ username = request.values["user"]
+ password = request.values["pass"]
+
+ passwordHash = users.createPasswordHash(password)
+
+ pass
+
+def load_user(userid):
+ pass
+
#~~ startup code
class Server():
def __init__(self, configfile=None, basedir=None, host="0.0.0.0", port=5000, debug=False):
@@ -470,6 +491,11 @@ class Server():
gcodeManager = gcodefiles.GcodeManager()
printer = Printer(gcodeManager)
+ app.secret_key = "k3PuVYgtxNm8DXKKTw2nWmFQQun9qceV"
+ login_manager = LoginManager()
+ login_manager.session_protection = "strong"
+ login_manager.init_app(app)
+
if self._host is None:
self._host = settings().get(["server", "host"])
if self._port is None:
diff --git a/octoprint/static/js/ui.js b/octoprint/static/js/ui.js
index 21640066..98358673 100644
--- a/octoprint/static/js/ui.js
+++ b/octoprint/static/js/ui.js
@@ -1680,6 +1680,11 @@ $(function() {
$.pnotify.defaults.history = false;
+ // Fix input element click problem
+ $('.dropdown input, .dropdown label').click(function(e) {
+ e.stopPropagation();
+ });
+
}
);
diff --git a/octoprint/templates/index.html b/octoprint/templates/index.html
index dc370ee3..0a39ae91 100644
--- a/octoprint/templates/index.html
+++ b/octoprint/templates/index.html
@@ -45,6 +45,27 @@
{% endif %}
+
+
+ Login
+
+
+
+
+
diff --git a/octoprint/users.py b/octoprint/users.py
new file mode 100644
index 00000000..1a8ce1c6
--- /dev/null
+++ b/octoprint/users.py
@@ -0,0 +1,161 @@
+# coding=utf-8
+__author__ = "Gina Häußge "
+__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
+
+from flask.ext.login import UserMixin
+import hashlib
+import os
+import yaml
+
+from octoprint.settings import settings
+
+class UserManager:
+ valid_roles=["user", "admin"]
+
+ @staticmethod
+ def createPasswordHash(password):
+ return hashlib.sha512(password + "mvBUTvwzBzD3yPwvnJ4E4tXNf3CGJvvW").hexdigest()
+
+ def addUser(self, username, password):
+ pass
+
+ def addRoleToUser(self, username, role):
+ pass
+
+ def removeRoleFromUser(self, username, role):
+ pass
+
+ def updateUser(self, username, password):
+ pass
+
+ def removeUser(self, username):
+ pass
+
+ def findUser(self, username=None):
+ return None
+
+##~~ FilebasedUserManager, takes available users from users.yaml file
+
+class FilebasedUserManager(UserManager):
+ def __init__(self, userfile=None):
+ UserManager.__init__(self)
+
+ if userfile is None:
+ userfile = os.path.join(settings().settings_dir, "users.yaml")
+ self._userfile = userfile
+ self._users = None
+ self._dirty = False
+
+ self._load()
+
+ def _load(self):
+ self._users = {}
+ if os.path.exists(self._userfile) and os.path.isfile(self._userfile):
+ with open(self._userfile, "r") as f:
+ data = yaml.safe_load(f)
+ for name in data.keys():
+ attributes = data[name]
+ self._users[name] = User(name, attributes.password, attributes.active, attributes.roles)
+
+ def _save(self, force=False):
+ if not self._dirty and not force:
+ return
+
+ data = {}
+ for name in self._users.keys():
+ user = self._users[name]
+ data[name] = {
+ "password": user.passwordHash,
+ "active": user.active,
+ "roles": user.roles
+ }
+
+ with open(self._userfile, "wb") as f:
+ yaml.safe_dump(data, f, default_flow_style=False, indent=" ", allow_unicode=True)
+ self._dirty = False
+ self._load()
+
+ def addUser(self, username, password):
+ if username in self._users.keys():
+ raise UserAlreadyExists(username)
+
+ self._users[username] = User(username, UserManager.createPasswordHash(password), False, ["user"])
+ self._dirty = True
+ self._save()
+
+ def addRoleToUser(self, username, role):
+ if not username in self._users.keys():
+ raise UnknownUser(username)
+
+ user = self._users[username]
+ if not role in user.roles:
+ user.roles.append(role)
+ self._dirty = True
+ self._save()
+
+ def removeRoleFromUser(self, username, role):
+ if not username in self._users.keys():
+ raise UnknownUser(username)
+
+ user = self._users[username]
+ if role in user.roles:
+ user.roles.remove(role)
+ self._dirty = True
+ self._save()
+
+ def updateUser(self, username, password):
+ if not username in self._users.keys():
+ raise UnknownUser(username)
+
+ passwordHash = UserManager.createPasswordHash(password)
+ user = self._users[username]
+ if user.passwordHash != passwordHash:
+ user.passwordHash = passwordHash
+ self._dirty = True
+ self._save()
+
+ def removeUser(self, username):
+ if not username in self._users.keys():
+ raise UnknownUser(username)
+
+ del self._users[username]
+ self._dirty = True
+ self._save()
+
+ def findUser(self, username=None):
+ if username is None:
+ return None
+
+ if username not in self._users.keys():
+ return None
+
+ return self._users[username]
+
+##~~ Exceptions
+
+class UserAlreadyExists(Exception):
+ def __init__(self, username):
+ Exception.__init__(self, "User %s already exists" % username)
+
+class UnknownUser(Exception):
+ def __init__(self, username):
+ Exception.__init__(self, "Unknown user: %s" % username)
+
+class UnknownRole(Exception):
+ def _init_(self, role):
+ Exception.__init__(self, "Unknown role: %s" % role)
+
+##~~ User object
+
+class User(UserMixin):
+ def __init__(self, username, passwordHash, active, roles):
+ self.username = username
+ self.passwordHash = passwordHash
+ self.active = active
+ self.roles = roles
+
+ def get_id(self):
+ return self.username
+
+ def is_active(self):
+ return self.active
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 2599a61c..4114ee78 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,4 +3,5 @@ numpy>=1.6.2
pyserial>=2.6
tornado>=2.4.1
tornadio2>=0.0.4
-PyYAML>=3.10
\ No newline at end of file
+PyYAML>=3.10
+Flask-Login>=0.1.3
\ No newline at end of file