Add more web
This commit is contained in:
		
							
								
								
									
										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":[]} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user