Add more web
This commit is contained in:
parent
aa313497cd
commit
1252f7af9e
1
.gitignore
vendored
1
.gitignore
vendored
@ -64,3 +64,4 @@ target/
|
||||
# Other
|
||||
venv/
|
||||
*.sqlite
|
||||
ad.json
|
||||
|
@ -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))
|
||||
|
@ -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>
|
||||
<a href="/logout">Logout</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>
|
||||
|
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>
|
||||
<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>
|
||||
|
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')
|
||||
|
||||
<h3>Login:</h3>
|
||||
|
||||
% if error:
|
||||
<p>Error: {{ error }}</p>
|
||||
% end
|
||||
|
@ -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":[]}
|
||||
|
Loading…
Reference in New Issue
Block a user