First work on login and user management
This commit is contained in:
parent
34a860efda
commit
150d6cb53d
5 changed files with 215 additions and 1 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -1680,6 +1680,11 @@ $(function() {
|
|||
|
||||
$.pnotify.defaults.history = false;
|
||||
|
||||
// Fix input element click problem
|
||||
$('.dropdown input, .dropdown label').click(function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
161
octoprint/users.py
Normal 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
|
||||
|
|
@ -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
|
||||
Loading…
Reference in a new issue