First work on login and user management

This commit is contained in:
Gina Häußge 2013-03-17 22:28:08 +01:00
parent 34a860efda
commit 150d6cb53d
5 changed files with 215 additions and 1 deletions

View file

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

View file

@ -1680,6 +1680,11 @@ $(function() {
$.pnotify.defaults.history = false;
// Fix input element click problem
$('.dropdown input, .dropdown label').click(function(e) {
e.stopPropagation();
});
}
);

View file

@ -45,6 +45,27 @@
</ul>
</li>
{% endif %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="icon-user"></i> <span id="login_dropdown_text">Login</span>
<b class="caret"></b>
</a>
<div id="login_dropdown_loggedout" class="dropdown-menu" style="padding: 15px">
<label for="login_user">Username</label>
<input type="text" id="login_user" placeholder="Username">
<label for="login_pass">Password</label>
<input type="password" id="login_pass" placeholder="Password">
<label class="checkbox">
<input type="checkbox" id="login_remember"> Remember me
</label>
<button class="btn btn-block btn-primary" id="login_button">Login</button>
</div>
<ul id="login_dropdown_loggedin" class="hide">
<li><a href="#">Profile</a></li>
<li class="divider"></li>
<li><a href="#">Logout</a></li>
</ul>
</li>
</ul>
</div>
</div>

161
octoprint/users.py Normal file
View file

@ -0,0 +1,161 @@
# coding=utf-8
__author__ = "Gina Häußge <osd@foosel.net>"
__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

View file

@ -3,4 +3,5 @@ numpy>=1.6.2
pyserial>=2.6
tornado>=2.4.1
tornadio2>=0.0.4
PyYAML>=3.10
PyYAML>=3.10
Flask-Login>=0.1.3