1
0
mirror of http://git.k-space.ee/arti/doors.git synced 2024-11-12 15:30:59 +02:00

Add more web

This commit is contained in:
Arti Zirk 2020-09-13 19:36:05 +03:00
parent aa313497cd
commit 1252f7af9e
9 changed files with 319 additions and 35 deletions

1
.gitignore vendored
View File

@ -64,3 +64,4 @@ target/
# Other
venv/
*.sqlite
ad.json

View File

@ -1,14 +1,53 @@
import datetime
import sqlite3
import inspect
import json
from bottle import HTTPError
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:
def __init__(self, dbfile=":memory:"):
self.dbfile = dbfile
self.db = sqlite3.connect(self.dbfile)
self.db.row_factory = sqlite3.Row
def close(self):
self.db.close()
@staticmethod
def create_db(dbfile):
@ -21,7 +60,7 @@ class DB:
create table users (
id integer primary key,
user text,
real_name text,
full_name text,
email text,
disabled integer,
admin integer
@ -32,6 +71,7 @@ class DB:
card_uid blob,
name text,
created text,
disabled integer,
foreign key (user_id)
references users (id)
@ -69,25 +109,69 @@ class DB:
)
db.commit()
def list_users(*, page=0, count=20, query=None):
pass
def add_user(self, user, real_name=None, email=None, disabled=False, admin=False):
def add_user(self, user, full_name=None, email=None, disabled=False, admin=False):
self.add_users([(
user, real_name, email, disabled, admin
user, full_name, email, int(disabled), int(admin)
)])
def add_users(self, users):
self.db.executemany("""
insert into users(user, real_name, email, disabled, admin)
insert into users(user, full_name, email, disabled, admin)
values(?, ?, ?, ?, ?)
""", users)
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__":
#DB.create_db("kdoorweb.sqlite")
db = DB("kdoorweb.sqlite")
db.add_user("juku")
dbfile = "../kdoorweb.sqlite"
import os, sys
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))

View File

@ -1,3 +1,4 @@
% from bottle import request
<!doctype html>
<html lang="en">
<head>
@ -10,14 +11,21 @@
<header>
<h1>K-Space Door system</h1>
<nav>
<a href="/list">Users List</a>
<a href="/log">Door Log</a>
% if request.get("bottle.request.ext.current_user"):
<a href="/list">Users</a>
% if request.current_user["admin"]:
<a href="/doors">Doors</a>
<a href="/log">Log</a>
% end
<a href="/logout">Logout</a>
% end
</nav>
</header>
<hr>
{{!base}}
<hr>
</body>
</html>

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

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

View File

@ -6,15 +6,17 @@
<thead>
<tr>
<th>Name</th>
<th>nr of cards</th>
<th>Last access</th>
<th style="width:4em;">Cards</th>
<th style="width:10em;">Last access</th>
</tr>
</thead>
<tbody>
% for user in users:
<tr>
<td><a href="/info/1">Arti Zirk</a></td>
<td>2</td>
<td><a href="/info/{{user['id']}}">{{user["full_name"]}}</a></td>
<td>-</td>
<td>-</td>
</tr>
% end
</tbody>
</table>

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

View File

@ -1,5 +1,7 @@
% rebase('base.html')
<h3>Login:</h3>
% if error:
<p>Error: {{ error }}</p>
% end

View File

@ -2,22 +2,45 @@ import os
import secrets
from bottle import Bottle, view, TEMPLATE_PATH, static_file, \
request, redirect, response
request, redirect, response, HTTPError
from .db import SQLitePlugin
application = app = Bottle()
# TODO: Could be replaced with importlib.resources
TEMPLATE_PATH.append(os.path.join(os.path.dirname(__file__), "views"))
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 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:
print(f"logged in as {user}")
print(f"logged in as {user['user']}")
print(request.current_user)
return callback(*args, **kwargs)
else:
print("not logged in")
@ -26,6 +49,7 @@ def check_auth(callback):
return wrapper
app.install(SQLitePlugin(SQLITE_PATH))
app.install(check_auth)
@ -35,7 +59,14 @@ def callback(path):
@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")
@ -49,31 +80,71 @@ def login():
@app.post('/login', skip=[check_auth])
def do_login():
print(dict(request.forms))
user = request.forms.get("user")
print(f"user {user}")
def do_login(db):
user_name = request.forms.get("user")
user = db.get_user_by_name(user_name)
if user:
response.set_cookie("user", user, key=COOKIE_KEY)
redirect("/list")
print(f"user {dict(user)}")
login_user(user["id"])
redirect("/")
else:
response.set_cookie("error", "No user")
response.set_cookie("error", "Login Failed")
redirect("/login")
@app.route("/logout")
def logout():
response.set_cookie("user", "", key=COOKIE_KEY)
logout_user()
redirect("/login")
@app.route("/list")
@view("list.html")
def list():
return {}
def user_list(db):
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")
@view("log")
def log():
return {}
@view("log.html")
def log(db):
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":[]}