forked from arti/doors
Add more web
This commit is contained in:
parent
aa313497cd
commit
1252f7af9e
1
.gitignore
vendored
1
.gitignore
vendored
@ -64,3 +64,4 @@ target/
|
|||||||
# Other
|
# Other
|
||||||
venv/
|
venv/
|
||||||
*.sqlite
|
*.sqlite
|
||||||
|
ad.json
|
||||||
|
@ -1,14 +1,53 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import inspect
|
||||||
|
import json
|
||||||
|
from bottle import HTTPError
|
||||||
|
|
||||||
SCHEMA_VERSION = 1
|
SCHEMA_VERSION = 1
|
||||||
|
|
||||||
|
|
||||||
|
class SQLitePlugin:
|
||||||
|
|
||||||
|
name = "sqlite"
|
||||||
|
api = 2
|
||||||
|
|
||||||
|
def __init__(self, dbfile):
|
||||||
|
self.dbfile = dbfile
|
||||||
|
|
||||||
|
def apply(self, callback, context):
|
||||||
|
# Test if the original callback accepts a 'db' keyword.
|
||||||
|
# Ignore it if it does not need a database handle.
|
||||||
|
args = inspect.signature(context.callback).parameters
|
||||||
|
if "db" not in args:
|
||||||
|
return callback
|
||||||
|
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
db = DB(self.dbfile)
|
||||||
|
# Add the connection handle as a keyword argument.
|
||||||
|
kwargs["db"] = db
|
||||||
|
try:
|
||||||
|
rv = callback(*args, **kwargs)
|
||||||
|
except sqlite3.IntegrityError as e:
|
||||||
|
db.db.rollback()
|
||||||
|
raise HTTPError(500, "Database Error", e)
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
return rv
|
||||||
|
|
||||||
|
# Replace the route callback with the wrapped one.
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
class DB:
|
class DB:
|
||||||
|
|
||||||
def __init__(self, dbfile=":memory:"):
|
def __init__(self, dbfile=":memory:"):
|
||||||
self.dbfile = dbfile
|
self.dbfile = dbfile
|
||||||
self.db = sqlite3.connect(self.dbfile)
|
self.db = sqlite3.connect(self.dbfile)
|
||||||
|
self.db.row_factory = sqlite3.Row
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.db.close()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_db(dbfile):
|
def create_db(dbfile):
|
||||||
@ -21,7 +60,7 @@ class DB:
|
|||||||
create table users (
|
create table users (
|
||||||
id integer primary key,
|
id integer primary key,
|
||||||
user text,
|
user text,
|
||||||
real_name text,
|
full_name text,
|
||||||
email text,
|
email text,
|
||||||
disabled integer,
|
disabled integer,
|
||||||
admin integer
|
admin integer
|
||||||
@ -32,6 +71,7 @@ class DB:
|
|||||||
card_uid blob,
|
card_uid blob,
|
||||||
name text,
|
name text,
|
||||||
created text,
|
created text,
|
||||||
|
disabled integer,
|
||||||
|
|
||||||
foreign key (user_id)
|
foreign key (user_id)
|
||||||
references users (id)
|
references users (id)
|
||||||
@ -69,25 +109,69 @@ class DB:
|
|||||||
)
|
)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
def list_users(*, page=0, count=20, query=None):
|
def add_user(self, user, full_name=None, email=None, disabled=False, admin=False):
|
||||||
pass
|
|
||||||
|
|
||||||
def add_user(self, user, real_name=None, email=None, disabled=False, admin=False):
|
|
||||||
self.add_users([(
|
self.add_users([(
|
||||||
user, real_name, email, disabled, admin
|
user, full_name, email, int(disabled), int(admin)
|
||||||
)])
|
)])
|
||||||
|
|
||||||
def add_users(self, users):
|
def add_users(self, users):
|
||||||
self.db.executemany("""
|
self.db.executemany("""
|
||||||
insert into users(user, real_name, email, disabled, admin)
|
insert into users(user, full_name, email, disabled, admin)
|
||||||
values(?, ?, ?, ?, ?)
|
values(?, ?, ?, ?, ?)
|
||||||
""", users)
|
""", users)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
|
def list_users(self):
|
||||||
|
cur = self.db.execute(
|
||||||
|
"select id, user, full_name, email from users"
|
||||||
|
)
|
||||||
|
return cur.fetchall()
|
||||||
|
|
||||||
|
def get_user(self, user_id):
|
||||||
|
cur = self.db.execute(
|
||||||
|
"select * from users where id = ?",
|
||||||
|
(user_id, )
|
||||||
|
)
|
||||||
|
return cur.fetchone()
|
||||||
|
|
||||||
|
def get_user_by_name(self, user_name):
|
||||||
|
cur = self.db.execute(
|
||||||
|
"select * from users where user = ?",
|
||||||
|
(user_name, )
|
||||||
|
)
|
||||||
|
return cur.fetchone()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def import_ad(json_file):
|
||||||
|
with open(json_file) as fp:
|
||||||
|
json_data = json.load(fp)
|
||||||
|
for user, fields in json_data.items():
|
||||||
|
if fields.get("considered_active", False) and \
|
||||||
|
fields.get("groups", {}).get("floor_access", False):
|
||||||
|
yield (
|
||||||
|
user,
|
||||||
|
fields.get("full_name"),
|
||||||
|
fields.get("personal_mail"),
|
||||||
|
0,
|
||||||
|
int(fields.get("groups",{}).get("onboarding", False))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
#DB.create_db("kdoorweb.sqlite")
|
dbfile = "../kdoorweb.sqlite"
|
||||||
db = DB("kdoorweb.sqlite")
|
import os, sys
|
||||||
db.add_user("juku")
|
from pprint import pprint
|
||||||
|
try:
|
||||||
|
os.unlink(dbfile)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
DB.create_db(dbfile)
|
||||||
|
db = DB(dbfile)
|
||||||
|
db.add_users(db.import_ad("../ad.json"))
|
||||||
|
users = db.list_users()
|
||||||
|
for user in users:
|
||||||
|
print(dict(user))
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
% from bottle import request
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@ -10,14 +11,21 @@
|
|||||||
<header>
|
<header>
|
||||||
<h1>K-Space Door system</h1>
|
<h1>K-Space Door system</h1>
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/list">Users List</a>
|
% if request.get("bottle.request.ext.current_user"):
|
||||||
<a href="/log">Door Log</a>
|
<a href="/list">Users</a>
|
||||||
<a href="/logout">Logout</a>
|
% if request.current_user["admin"]:
|
||||||
|
<a href="/doors">Doors</a>
|
||||||
|
<a href="/log">Log</a>
|
||||||
|
% end
|
||||||
|
<a href="/logout">Logout</a>
|
||||||
|
% end
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
{{!base}}
|
{{!base}}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
36
kdoorweb/kdoorweb/views/doors.html
Normal file
36
kdoorweb/kdoorweb/views/doors.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
% rebase('base.html')
|
||||||
|
|
||||||
|
<h3>Doors List</h3>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
<input name="action" value="Lock All" type="submit">
|
||||||
|
<input name="action" value="Unlock All" type="submit">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<table class="double">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>State</th>
|
||||||
|
<th style="width:10em;">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
% for door in doors:
|
||||||
|
<tr>
|
||||||
|
<td>{{door["name"]}}</td>
|
||||||
|
<td>{{door["state"]}}</td>
|
||||||
|
<td>
|
||||||
|
<form method="post">
|
||||||
|
<input name="door_id" value="door['id']" type="hidden">
|
||||||
|
<input name="action" value="Lock" type="submit">
|
||||||
|
<input name="action" value="Unlock" type="submit">
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3">{{door["note"]}}</td>
|
||||||
|
</tr>
|
||||||
|
% end
|
||||||
|
</tbody>
|
||||||
|
</table>
|
58
kdoorweb/kdoorweb/views/info.html
Normal file
58
kdoorweb/kdoorweb/views/info.html
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
% rebase('base.html')
|
||||||
|
|
||||||
|
<h3>Info {{full_name}}</h3>
|
||||||
|
|
||||||
|
<dl style="float:right;">
|
||||||
|
<dt>Disabled</dt>
|
||||||
|
<dd>{{disabled}}</dd>
|
||||||
|
<dt><abbr title="User is in Onboarding AD group">Admin</abbr></dt>
|
||||||
|
<dd>{{admin}}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<dl>
|
||||||
|
<dt>User</dt>
|
||||||
|
<dd>{{user}}</dd>
|
||||||
|
<dt>Full Name</dt>
|
||||||
|
<dd>{{full_name}}</dd>
|
||||||
|
<dt>Email</dt>
|
||||||
|
<dd>{{email}}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<h3>Keycards</h3>
|
||||||
|
|
||||||
|
<table class="even">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Disabled</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
% for keycard in keycards:
|
||||||
|
<tr>
|
||||||
|
<td>{{keycard["name"]}}</td>
|
||||||
|
<td>{{keycard["created"]}}</td>
|
||||||
|
<td>{{keycard["disabled"]}}</td>
|
||||||
|
<td>
|
||||||
|
<form method="post">
|
||||||
|
<input name="keycard_id" value="keycard['id']" type="hidden">
|
||||||
|
<input name="action" value="disable" type="hidden">
|
||||||
|
<input value="Disable" type="submit">
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
% end
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h4>Add new keycard</h4>
|
||||||
|
<form method="post" class="inline">
|
||||||
|
<label for="name">Name: </label>
|
||||||
|
<input id="name" name="name" type="text">
|
||||||
|
<label for="uid">UID: </label>
|
||||||
|
<input id="uid" name="uid" type="text">
|
||||||
|
<input name="action" value="create" type="hidden">
|
||||||
|
<input value="Add" type="submit">
|
||||||
|
</form>
|
@ -6,15 +6,17 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>nr of cards</th>
|
<th style="width:4em;">Cards</th>
|
||||||
<th>Last access</th>
|
<th style="width:10em;">Last access</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
% for user in users:
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/info/1">Arti Zirk</a></td>
|
<td><a href="/info/{{user['id']}}">{{user["full_name"]}}</a></td>
|
||||||
<td>2</td>
|
<td>-</td>
|
||||||
<td>-</td>
|
<td>-</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
% end
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
22
kdoorweb/kdoorweb/views/log.html
Normal file
22
kdoorweb/kdoorweb/views/log.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
% rebase('base.html')
|
||||||
|
|
||||||
|
<h3>Access log</h3>
|
||||||
|
|
||||||
|
<table class="even">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Time</th>
|
||||||
|
<th>Door</th>
|
||||||
|
<th>User</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
% for event in events:
|
||||||
|
<tr>
|
||||||
|
<td>{{event["timestamp"]}}</td>
|
||||||
|
<td>{{event["door"]}}</td>
|
||||||
|
<td><a href="/info/{{event['user_id']}}">{{event["user_name"]}}</a></td>
|
||||||
|
</tr>
|
||||||
|
% end
|
||||||
|
</tbody>
|
||||||
|
</table>
|
@ -1,5 +1,7 @@
|
|||||||
% rebase('base.html')
|
% rebase('base.html')
|
||||||
|
|
||||||
|
<h3>Login:</h3>
|
||||||
|
|
||||||
% if error:
|
% if error:
|
||||||
<p>Error: {{ error }}</p>
|
<p>Error: {{ error }}</p>
|
||||||
% end
|
% end
|
||||||
|
@ -2,22 +2,45 @@ import os
|
|||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
from bottle import Bottle, view, TEMPLATE_PATH, static_file, \
|
from bottle import Bottle, view, TEMPLATE_PATH, static_file, \
|
||||||
request, redirect, response
|
request, redirect, response, HTTPError
|
||||||
|
|
||||||
|
from .db import SQLitePlugin
|
||||||
|
|
||||||
application = app = Bottle()
|
application = app = Bottle()
|
||||||
|
|
||||||
# TODO: Could be replaced with importlib.resources
|
# TODO: Could be replaced with importlib.resources
|
||||||
TEMPLATE_PATH.append(os.path.join(os.path.dirname(__file__), "views"))
|
TEMPLATE_PATH.append(os.path.join(os.path.dirname(__file__), "views"))
|
||||||
STATIC_PATH = os.path.join(os.path.dirname(__file__), "static")
|
STATIC_PATH = os.path.join(os.path.dirname(__file__), "static")
|
||||||
|
SQLITE_PATH = os.path.join(os.path.dirname(__file__), "..", "kdoorweb.sqlite")
|
||||||
|
|
||||||
COOKIE_KEY = secrets.token_bytes(32)
|
COOKIE_KEY = b"1234"#secrets.token_bytes(32)
|
||||||
|
|
||||||
|
|
||||||
|
def current_user(db):
|
||||||
|
user_id = request.get_cookie("uid", secret=COOKIE_KEY)
|
||||||
|
if not user_id:
|
||||||
|
return
|
||||||
|
return db.get_user(user_id)
|
||||||
|
|
||||||
|
|
||||||
|
def login_user(uid):
|
||||||
|
response.set_cookie("uid", uid, secret=COOKIE_KEY)
|
||||||
|
|
||||||
|
|
||||||
|
def logout_user():
|
||||||
|
response.set_cookie("uid", "", secret=COOKIE_KEY)
|
||||||
|
|
||||||
|
|
||||||
def check_auth(callback):
|
def check_auth(callback):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
user = request.get_cookie("user", key=COOKIE_KEY)
|
if "db" not in kwargs:
|
||||||
|
request.current_user = None
|
||||||
|
return callback(*args, **kwargs)
|
||||||
|
user = current_user(kwargs["db"])
|
||||||
|
request.current_user = user
|
||||||
if user:
|
if user:
|
||||||
print(f"logged in as {user}")
|
print(f"logged in as {user['user']}")
|
||||||
|
print(request.current_user)
|
||||||
return callback(*args, **kwargs)
|
return callback(*args, **kwargs)
|
||||||
else:
|
else:
|
||||||
print("not logged in")
|
print("not logged in")
|
||||||
@ -26,6 +49,7 @@ def check_auth(callback):
|
|||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
app.install(SQLitePlugin(SQLITE_PATH))
|
||||||
app.install(check_auth)
|
app.install(check_auth)
|
||||||
|
|
||||||
|
|
||||||
@ -35,7 +59,14 @@ def callback(path):
|
|||||||
|
|
||||||
|
|
||||||
@app.route("/", skip=[check_auth])
|
@app.route("/", skip=[check_auth])
|
||||||
def index():
|
def index(db):
|
||||||
|
user = current_user(db)
|
||||||
|
if user:
|
||||||
|
if user["admin"]:
|
||||||
|
redirect("/list")
|
||||||
|
else:
|
||||||
|
redirect(f"/info/{user['id']}")
|
||||||
|
|
||||||
redirect("/login")
|
redirect("/login")
|
||||||
|
|
||||||
|
|
||||||
@ -49,31 +80,71 @@ def login():
|
|||||||
|
|
||||||
|
|
||||||
@app.post('/login', skip=[check_auth])
|
@app.post('/login', skip=[check_auth])
|
||||||
def do_login():
|
def do_login(db):
|
||||||
print(dict(request.forms))
|
user_name = request.forms.get("user")
|
||||||
user = request.forms.get("user")
|
user = db.get_user_by_name(user_name)
|
||||||
print(f"user {user}")
|
|
||||||
if user:
|
if user:
|
||||||
response.set_cookie("user", user, key=COOKIE_KEY)
|
print(f"user {dict(user)}")
|
||||||
redirect("/list")
|
login_user(user["id"])
|
||||||
|
redirect("/")
|
||||||
else:
|
else:
|
||||||
response.set_cookie("error", "No user")
|
response.set_cookie("error", "Login Failed")
|
||||||
redirect("/login")
|
redirect("/login")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/logout")
|
@app.route("/logout")
|
||||||
def logout():
|
def logout():
|
||||||
response.set_cookie("user", "", key=COOKIE_KEY)
|
logout_user()
|
||||||
redirect("/login")
|
redirect("/login")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/list")
|
@app.route("/list")
|
||||||
@view("list.html")
|
@view("list.html")
|
||||||
def list():
|
def user_list(db):
|
||||||
return {}
|
user = request.current_user
|
||||||
|
if user and not user["admin"]:
|
||||||
|
users = [user]
|
||||||
|
else:
|
||||||
|
users = db.list_users()
|
||||||
|
return {"users": users}
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/info")
|
||||||
|
def user_info(db):
|
||||||
|
user = request.current_user
|
||||||
|
redirect(f"/info/{user['id']}")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/info/<user_id>")
|
||||||
|
@view("info.html")
|
||||||
|
def info(db, user_id):
|
||||||
|
user_id = int(user_id)
|
||||||
|
c_user = request.current_user
|
||||||
|
if not c_user["admin"] and c_user["id"] != user_id:
|
||||||
|
raise HTTPError(403, "Logged in user is not admin")
|
||||||
|
user = db.get_user(user_id)
|
||||||
|
if not user:
|
||||||
|
raise HTTPError(404, "User does not exist")
|
||||||
|
return {**user, "keycards": []}
|
||||||
|
|
||||||
|
|
||||||
@app.route("/log")
|
@app.route("/log")
|
||||||
@view("log")
|
@view("log.html")
|
||||||
def log():
|
def log(db):
|
||||||
return {}
|
return {
|
||||||
|
"events":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"timestamp": 0,
|
||||||
|
"door": "Back Door",
|
||||||
|
"user_id":1,
|
||||||
|
"user_name": "Arti Zirk"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/doors")
|
||||||
|
@view("doors.html")
|
||||||
|
def doors(db):
|
||||||
|
return {"doors":[]}
|
||||||
|
Loading…
Reference in New Issue
Block a user