From 1252f7af9ed2f760010a0a6699846ad7141bb611 Mon Sep 17 00:00:00 2001 From: Arti Zirk Date: Sun, 13 Sep 2020 19:36:05 +0300 Subject: [PATCH] Add more web --- .gitignore | 1 + kdoorweb/kdoorweb/db.py | 104 +++++++++++++++++++++++++--- kdoorweb/kdoorweb/views/base.html | 14 +++- kdoorweb/kdoorweb/views/doors.html | 36 ++++++++++ kdoorweb/kdoorweb/views/info.html | 58 ++++++++++++++++ kdoorweb/kdoorweb/views/list.html | 10 +-- kdoorweb/kdoorweb/views/log.html | 22 ++++++ kdoorweb/kdoorweb/views/login.html | 2 + kdoorweb/kdoorweb/web.py | 107 ++++++++++++++++++++++++----- 9 files changed, 319 insertions(+), 35 deletions(-) create mode 100644 kdoorweb/kdoorweb/views/doors.html create mode 100644 kdoorweb/kdoorweb/views/info.html create mode 100644 kdoorweb/kdoorweb/views/log.html diff --git a/.gitignore b/.gitignore index 5c6f97b..32b5193 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,4 @@ target/ # Other venv/ *.sqlite +ad.json diff --git a/kdoorweb/kdoorweb/db.py b/kdoorweb/kdoorweb/db.py index 9359198..7901935 100644 --- a/kdoorweb/kdoorweb/db.py +++ b/kdoorweb/kdoorweb/db.py @@ -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)) diff --git a/kdoorweb/kdoorweb/views/base.html b/kdoorweb/kdoorweb/views/base.html index be03984..1d95458 100644 --- a/kdoorweb/kdoorweb/views/base.html +++ b/kdoorweb/kdoorweb/views/base.html @@ -1,3 +1,4 @@ +% from bottle import request @@ -10,14 +11,21 @@

K-Space Door system


{{!base}} +
+ diff --git a/kdoorweb/kdoorweb/views/doors.html b/kdoorweb/kdoorweb/views/doors.html new file mode 100644 index 0000000..4e8aee7 --- /dev/null +++ b/kdoorweb/kdoorweb/views/doors.html @@ -0,0 +1,36 @@ +% rebase('base.html') + +

Doors List

+ +
+ + +
+ + + + + + + + + + + % for door in doors: + + + + + + + + + % end + +
NameStateAction
{{door["name"]}}{{door["state"]}} +
+ + + +
+
{{door["note"]}}
diff --git a/kdoorweb/kdoorweb/views/info.html b/kdoorweb/kdoorweb/views/info.html new file mode 100644 index 0000000..77af65b --- /dev/null +++ b/kdoorweb/kdoorweb/views/info.html @@ -0,0 +1,58 @@ +% rebase('base.html') + +

Info {{full_name}}

+ +
+
Disabled
+
{{disabled}}
+
Admin
+
{{admin}}
+
+ +
+
User
+
{{user}}
+
Full Name
+
{{full_name}}
+
Email
+
{{email}}
+
+ +

Keycards

+ + + + + + + + + + + + % for keycard in keycards: + + + + + + + % end + +
NameCreatedDisabledAction
{{keycard["name"]}}{{keycard["created"]}}{{keycard["disabled"]}} +
+ + + +
+
+ +

Add new keycard

+
+ + + + + + +
diff --git a/kdoorweb/kdoorweb/views/list.html b/kdoorweb/kdoorweb/views/list.html index b7e0db0..fef248a 100644 --- a/kdoorweb/kdoorweb/views/list.html +++ b/kdoorweb/kdoorweb/views/list.html @@ -6,15 +6,17 @@ Name - nr of cards - Last access + Cards + Last access + % for user in users: - Arti Zirk - 2 + {{user["full_name"]}} + - - + % end diff --git a/kdoorweb/kdoorweb/views/log.html b/kdoorweb/kdoorweb/views/log.html new file mode 100644 index 0000000..50a2496 --- /dev/null +++ b/kdoorweb/kdoorweb/views/log.html @@ -0,0 +1,22 @@ +% rebase('base.html') + +

Access log

+ + + + + + + + + + + % for event in events: + + + + + + % end + +
TimeDoorUser
{{event["timestamp"]}}{{event["door"]}}{{event["user_name"]}}
diff --git a/kdoorweb/kdoorweb/views/login.html b/kdoorweb/kdoorweb/views/login.html index 14034f2..e472622 100644 --- a/kdoorweb/kdoorweb/views/login.html +++ b/kdoorweb/kdoorweb/views/login.html @@ -1,5 +1,7 @@ % rebase('base.html') +

Login:

+ % if error:

Error: {{ error }}

% end diff --git a/kdoorweb/kdoorweb/web.py b/kdoorweb/kdoorweb/web.py index 5d5b9e6..6df9204 100644 --- a/kdoorweb/kdoorweb/web.py +++ b/kdoorweb/kdoorweb/web.py @@ -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/") +@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":[]}